From: Ronald S. Bultje Date: Fri, 21 Nov 2003 21:34:27 +0000 (+0000) Subject: Add C-based local matroska/EBML plugin, remove libmatroska/libebml c++ one X-Git-Tag: CAPS-ROOT~32 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=29e9ebcfda9cce9d72099d098d60bc7da0ea601a;p=platform%2Fupstream%2Fgst-plugins-good.git Add C-based local matroska/EBML plugin, remove libmatroska/libebml c++ one Original commit message from CVS: Add C-based local matroska/EBML plugin, remove libmatroska/libebml c++ one --- diff --git a/configure.ac b/configure.ac index 37773bc..0b6e01f 100644 --- a/configure.ac +++ b/configure.ac @@ -287,6 +287,7 @@ GST_PLUGINS_ALL="\ intfloat \ law \ level \ + matroska \ median \ mixmatrix \ mpeg1sys \ @@ -956,15 +957,6 @@ GST_CHECK_FEATURE(MAD, [mad mp3 decoder], mad, [ ]) AC_SUBST(MAD_LIBS) -dnl *** matroska *** -translit(dnm, m, l) AM_CONDITIONAL(USE_MATROSKA, true) -GST_CHECK_FEATURE(MATROSKA, [matroska muxer/demuxer], matroska muxer/demuxer, [ - PATH_EBML([HAVE_MATROSKA=yes], [HAVE_MATROSKA=no]) - if test x$HAVE_MATROSKA = xyes; then - PATH_MATROSKA(0.4.4, [HAVE_MATROSKA=yes], [HAVE_MATROSKA=no]) - fi -]) - dnl *** mikmod *** translit(dnm, m, l) AM_CONDITIONAL(USE_MIKMOD, true) GST_CHECK_FEATURE(MIKMOD, [mikmod plug-in], mikmod, [ @@ -1316,6 +1308,7 @@ gst/id3/Makefile gst/intfloat/Makefile gst/law/Makefile gst/level/Makefile +gst/matroska/Makefile gst/median/Makefile gst/mixmatrix/Makefile gst/mpeg1sys/Makefile @@ -1401,7 +1394,6 @@ ext/libfame/Makefile ext/libpng/Makefile ext/mad/Makefile ext/mas/Makefile -ext/matroska/Makefile ext/mikmod/Makefile ext/mpeg2dec/Makefile ext/mplex/Makefile diff --git a/ext/Makefile.am b/ext/Makefile.am index d00a8c1..ce03626 100644 --- a/ext/Makefile.am +++ b/ext/Makefile.am @@ -172,12 +172,6 @@ else MAD_DIR= endif -if USE_MATROSKA -MATROSKA_DIR=matroska -else -MATROSKA_DIR= -endif - if USE_MIKMOD MIKMOD_DIR=mikmod else @@ -309,7 +303,6 @@ SUBDIRS=\ $(LIBPNG_DIR) \ $(MAD_DIR) \ $(MAS_DIR) \ - $(MATROSKA_DIR) \ $(MIKMOD_DIR) \ $(MPEG2DEC_DIR) \ $(MPLEX_DIR) \ @@ -357,7 +350,6 @@ DIST_SUBDIRS=\ libfame \ libpng \ mad \ - matroska \ mikmod \ mpeg2dec \ mplex \ diff --git a/gst/matroska/Makefile.am b/gst/matroska/Makefile.am new file mode 100644 index 0000000..b0235d8 --- /dev/null +++ b/gst/matroska/Makefile.am @@ -0,0 +1,16 @@ +plugin_LTLIBRARIES = libgstmatroska.la + +libgstmatroska_la_SOURCES = \ + ebml-read.c \ + matroska.c \ + matroska-demux.c + +noinst_HEADERS = \ + ebml-ids.h \ + ebml-read.h \ + matroska-demux.h \ + matroska-ids.h + +libgstmatroska_la_CFLAGS = $(GST_CFLAGS) +libgstmatroska_la_LIBADD = +libgstmatroska_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) diff --git a/gst/matroska/ebml-ids.h b/gst/matroska/ebml-ids.h new file mode 100644 index 0000000..329f036 --- /dev/null +++ b/gst/matroska/ebml-ids.h @@ -0,0 +1,47 @@ +/* GStreamer EBML I/O + * (c) 2003 Ronald Bultje + * + * ebml-ids.h: definition of EBML data IDs + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_EBML_IDS_H__ +#define __GST_EBML_IDS_H__ + +G_BEGIN_DECLS + +/* EBML version supported */ +#define GST_EBML_VERSION 1 + +/* top-level master-IDs */ +#define GST_EBML_ID_HEADER 0x1A45DFA3 + +/* IDs in the HEADER master */ +#define GST_EBML_ID_EBMLVERSION 0x4286 +#define GST_EBML_ID_EBMLREADVERSION 0x42F7 +#define GST_EBML_ID_EBMLMAXIDLENGTH 0x42F2 +#define GST_EBML_ID_EBMLMAXSIZELENGTH 0x42F3 +#define GST_EBML_ID_DOCTYPE 0x4282 +#define GST_EBML_ID_DOCTYPEVERSION 0x4287 +#define GST_EBML_ID_DOCTYPEREADVERSION 0x4285 + +/* general EBML types */ +#define GST_EBML_ID_VOID 0xEC + +G_END_DECLS + +#endif /* __GST_EBML_IDS_H__ */ diff --git a/gst/matroska/ebml-read.c b/gst/matroska/ebml-read.c new file mode 100644 index 0000000..a6b9225 --- /dev/null +++ b/gst/matroska/ebml-read.c @@ -0,0 +1,702 @@ +/* GStreamer EBML I/O + * (c) 2003 Ronald Bultje + * + * ebml-read.c: read EBML data from file/stream + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "ebml-read.h" +#include "ebml-ids.h" + +enum { + /* FILL ME */ + LAST_SIGNAL +}; + +static void gst_ebml_read_class_init (GstEbmlReadClass *klass); +static void gst_ebml_read_init (GstEbmlRead *ebml); +static GstElementStateReturn + gst_ebml_read_change_state (GstElement *element); + +static GstElementClass *parent_class = NULL; + +GType +gst_ebml_read_get_type (void) +{ + static GType gst_ebml_read_type = 0; + + if (!gst_ebml_read_type) { + static const GTypeInfo gst_ebml_read_info = { + sizeof (GstEbmlReadClass), + NULL, + NULL, + (GClassInitFunc) gst_ebml_read_class_init, + NULL, + NULL, + sizeof (GstEbmlRead), + 0, + (GInstanceInitFunc) gst_ebml_read_init, + }; + + gst_ebml_read_type = + g_type_register_static (GST_TYPE_ELEMENT, "GstEbmlRead", + &gst_ebml_read_info, 0); + } + + return gst_ebml_read_type; +} + +static void +gst_ebml_read_class_init (GstEbmlReadClass *klass) +{ + GstElementClass *gstelement_class = (GstElementClass *) klass; + + parent_class = g_type_class_ref (GST_TYPE_ELEMENT); + + gstelement_class->change_state = gst_ebml_read_change_state; +} + +static void +gst_ebml_read_init (GstEbmlRead *ebml) +{ + ebml->sinkpad = NULL; + ebml->bs = NULL; + ebml->level = NULL; +} + +static GstElementStateReturn +gst_ebml_read_change_state (GstElement *element) +{ + GstEbmlRead *ebml = GST_EBML_READ (element); + + switch (GST_STATE_TRANSITION (element)) { + case GST_STATE_READY_TO_PAUSED: + if (!ebml->sinkpad) + return GST_STATE_FAILURE; + ebml->bs = gst_bytestream_new (ebml->sinkpad); + break; + case GST_STATE_PAUSED_TO_READY: + gst_bytestream_destroy (ebml->bs); + while (ebml->level) { + GstEbmlLevel *level = ebml->level->data; + + ebml->level = g_list_remove (ebml->level, level); + g_free (level); + } + break; + default: + break; + } + + if (GST_ELEMENT_CLASS (parent_class)->change_state) + return GST_ELEMENT_CLASS (parent_class)->change_state (element); + + return GST_STATE_SUCCESS; +} + +/* + * Return: the amount of levels in the hierarchy that the + * current element lies higher than the previous one. + * The opposite isn't done - that's auto-done using master + * element reading. + */ + +static guint +gst_ebml_read_element_level_up (GstEbmlRead *ebml) +{ + guint num = 0; + guint64 pos = gst_bytestream_tell (ebml->bs); + + while (ebml->level != NULL) { + GList *last = g_list_last (ebml->level); + GstEbmlLevel *level = last->data; + + if (pos >= level->start + level->length) { + ebml->level = g_list_remove (ebml->level, level); + g_free (level); + num++; + } else + break; + } + + return num; +} + +/* + * Read: the element content data ID. + * Return: the number of bytes read or -1 on error. + */ + +static gint +gst_ebml_read_element_id (GstEbmlRead *ebml, + guint32 *id, + guint *level_up) +{ + guint8 *data; + gint len_mask = 0x80, read = 1, n = 1; + guint32 total; + + if (gst_bytestream_peek_bytes (ebml->bs, &data, 1) != 1) { + /*gst_element_error (GST_ELEMENT (ebml), "Read error");*/ + return -1; + } + total = data[0]; + while (read <= 4 && !(total & len_mask)) { + read++; + len_mask >>= 1; + } + if (read > 4) { + gst_element_error (GST_ELEMENT (ebml), + "Invalid EBML ID size tag (0x%x)", data[0]); + return -1; + } + + if (gst_bytestream_peek_bytes (ebml->bs, &data, read) != read) { + /*gst_element_error (GST_ELEMENT (ebml), "Read error");*/ + return -1; + } + while (n < read) + total = (total << 8) | data[n++]; + + *id = total; + + /* level */ + if (level_up) + *level_up = gst_ebml_read_element_level_up (ebml); + + return read; +} + +/* + * Read: element content length. + * Return: the number of bytes read or -1 on error. + */ + +static gint +gst_ebml_read_element_length (GstEbmlRead *ebml, + guint64 *length) +{ + guint8 *data; + gint len_mask = 0x80, read = 1, n = 1, num_ffs = 0; + guint64 total; + + if (gst_bytestream_peek_bytes (ebml->bs, &data, 1) != 1) { + /*gst_element_error (GST_ELEMENT (ebml), "Read error");*/ + return -1; + } + total = data[0]; + while (read <= 8 && !(total & len_mask)) { + read++; + len_mask >>= 1; + } + if (read > 8) { + gst_element_error (GST_ELEMENT (ebml), + "Invalid EBML length size tag (0x%x)", data[0]); + return -1; + } + + if ((total &= (len_mask - 1)) == len_mask - 1) + num_ffs++; + if (gst_bytestream_peek_bytes (ebml->bs, &data, read) != read) { + /*gst_element_error (GST_ELEMENT (ebml), "Read error");*/ + return -1; + } + while (n < read) { + if (data[n] == 0xff) + num_ffs++; + total = (total << 8) | data[n]; + n++; + } + + if (!total) { + gst_element_error (GST_ELEMENT (ebml), + "Invalid length 0"); + return -1; + } + + if (read == num_ffs) + *length = G_MAXUINT64; + else + *length = total; + + return read; +} + +/* + * Read: the actual data. + * Return: the data, as a GstBuffer. + */ + +static GstBuffer * +gst_ebml_read_element_data (GstEbmlRead *ebml, + guint64 length) +{ + GstBuffer *buf = NULL; + + if (gst_bytestream_peek (ebml->bs, &buf, length) != length) { + /*gst_element_error (GST_ELEMENT (ebml), "Read error");*/ + if (buf) + gst_buffer_unref (buf); + return NULL; + } + + gst_bytestream_flush_fast (ebml->bs, length); + + return buf; +} + +/* + * Return: the ID of the next element. + * Level_up contains the amount of levels that this + * next element lies higher than the previous one. + */ + +guint32 +gst_ebml_peek_id (GstEbmlRead *ebml, + guint *level_up) +{ + guint32 id; + guint my_level_up; + + g_return_val_if_fail (level_up != NULL, 0); + + if (gst_ebml_read_element_id (ebml, &id, &my_level_up) < 0) + return 0; + + if (level_up) + *level_up = my_level_up; + + return id; +} + +/* + * Seek to a given offset. + */ + +void +gst_ebml_read_seek (GstEbmlRead *ebml, + guint64 offset) +{ + gst_bytestream_seek (ebml->bs, offset, GST_SEEK_METHOD_SET); +} + +/* + * Skip the next element. + */ + +gboolean +gst_ebml_read_skip (GstEbmlRead *ebml) +{ + gint bytes; + guint32 id; + guint64 length; + + if ((bytes = gst_ebml_read_element_id (ebml, &id, NULL)) < 0) + return FALSE; + gst_bytestream_flush_fast (ebml->bs, bytes); + + if ((bytes = gst_ebml_read_element_length (ebml, &length)) < 0) + return FALSE; + gst_bytestream_flush_fast (ebml->bs, bytes); + + return gst_bytestream_flush (ebml->bs, length); +} + +/* + * Read the next element as a GstBuffer (binary). + */ + +gboolean +gst_ebml_read_buffer (GstEbmlRead *ebml, + guint32 *id, + GstBuffer **buf) +{ + gint bytes; + guint64 length; + + if ((bytes = gst_ebml_read_element_id (ebml, id, NULL)) < 0) + return FALSE; + gst_bytestream_flush_fast (ebml->bs, bytes); + + if ((bytes = gst_ebml_read_element_length (ebml, &length)) < 0) + return FALSE; + gst_bytestream_flush_fast (ebml->bs, bytes); + + return ((*buf = gst_ebml_read_element_data (ebml, length)) != NULL); +} + +/* + * Read the next element as an unsigned int. + */ + +gboolean +gst_ebml_read_uint (GstEbmlRead *ebml, + guint32 *id, + guint64 *num) +{ + GstBuffer *buf; + guint8 *data; + guint size; + + if (!gst_ebml_read_buffer (ebml, id, &buf)) + return FALSE; + + data = GST_BUFFER_DATA (buf); + size = GST_BUFFER_SIZE (buf); + if (size < 1 || size > 8) { + gst_element_error (GST_ELEMENT (ebml), + "Invalid integer element size %d", size); + gst_buffer_unref (buf); + return FALSE; + } + *num = 0; + while (size > 0) { + *num = (*num << 8) | data[GST_BUFFER_SIZE (buf) - size]; + size--; + } + + gst_buffer_unref (buf); + + return TRUE; +} + +/* + * Read the next element as a signed int. + */ + +gboolean +gst_ebml_read_sint (GstEbmlRead *ebml, + guint32 *id, + gint64 *num) +{ + GstBuffer *buf; + guint8 *data; + guint size; + + if (!gst_ebml_read_buffer (ebml, id, &buf)) + return FALSE; + + data = GST_BUFFER_DATA (buf); + size = GST_BUFFER_SIZE (buf); + if (size < 1 || size > 8) { + gst_element_error (GST_ELEMENT (ebml), + "Invalid integer element size %d", size); + gst_buffer_unref (buf); + return FALSE; + } + *num = 0; + while (size > 0) { + *num = (*num << 8) | data[GST_BUFFER_SIZE (buf) - size]; + size--; + } + + /* make signed */ + *num -= (1LL << ((8 * GST_BUFFER_SIZE (buf)) - 1)); + + gst_buffer_unref (buf); + + return TRUE; +} + +/* + * Read the next element as a float. + */ + +gboolean +gst_ebml_read_float (GstEbmlRead *ebml, + guint32 *id, + gdouble *num) +{ + GstBuffer *buf; + guint8 *data; + guint size; + + if (!gst_ebml_read_buffer (ebml, id, &buf)) + return FALSE; + + data = GST_BUFFER_DATA (buf); + size = GST_BUFFER_SIZE (buf); + + if (size != 4 && size != 8 && size != 10) { + gst_element_error (GST_ELEMENT (ebml), + "Invalid float element size %d", size); + gst_buffer_unref (buf); + return FALSE; + } + + if (size == 10) { + gst_element_error (GST_ELEMENT (ebml), + "FIXME! 10-byte floats unimplemented"); + gst_buffer_unref (buf); + return FALSE; + } + + if (size == 4) { + gfloat f; + +#if (G_BYTE_ORDER == G_BIG_ENDIAN) + f = * (gfloat *) data; +#else + while (size > 0) { + ((guint8 *) &f)[size - 1] = data[4 - size]; + size--; + } +#endif + + *num = f; + } else { + gdouble d; + +#if (G_BYTE_ORDER == G_BIG_ENDIAN) + d = * (gdouble *) data; +#else + while (size > 0) { + ((guint8 *) &d)[size - 1] = data[8 - size]; + size--; + } +#endif + + *num = d; + } + + gst_buffer_unref (buf); + + return TRUE; +} + +/* + * Read the next element as an ASCII string. + */ + +gboolean +gst_ebml_read_ascii (GstEbmlRead *ebml, + guint32 *id, + gchar **str) +{ + GstBuffer *buf; + + if (!gst_ebml_read_buffer (ebml, id, &buf)) + return FALSE; + + *str = g_malloc (GST_BUFFER_SIZE (buf) + 1); + memcpy (*str, GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf)); + (*str)[GST_BUFFER_SIZE (buf)] = '\0'; + + gst_buffer_unref (buf); + + return TRUE; +} + +/* + * Read the next element as a UTF-8 string. + */ + +gboolean +gst_ebml_read_utf8 (GstEbmlRead *ebml, + guint32 *id, + gchar **str) +{ + return gst_ebml_read_ascii (ebml, id, str); +} + +/* + * Read the next element as a date (nanoseconds since 1/1/2000). + */ + +gboolean +gst_ebml_read_date (GstEbmlRead *ebml, + guint32 *id, + gint64 *date) +{ + return gst_ebml_read_sint (ebml, id, date); +} + +/* + * Read the next element, but only the header. The contents + * are supposed to be sub-elements which can be read separately. + */ + +gboolean +gst_ebml_read_master (GstEbmlRead *ebml, + guint32 *id) +{ + gint bytes; + guint64 length; + GstEbmlLevel *level; + + if ((bytes = gst_ebml_read_element_id (ebml, id, NULL)) < 0) + return FALSE; + gst_bytestream_flush_fast (ebml->bs, bytes); + + if ((bytes = gst_ebml_read_element_length (ebml, &length)) < 0) + return FALSE; + gst_bytestream_flush_fast (ebml->bs, bytes); + + /* remember level */ + level = g_new (GstEbmlLevel, 1); + level->start = gst_bytestream_tell (ebml->bs); + level->length = length; + ebml->level = g_list_append (ebml->level, level); + + return TRUE; +} + +/* + * Read the next element as binary data. + */ + +gboolean +gst_ebml_read_binary (GstEbmlRead *ebml, + guint32 *id, + guint8 **binary, + guint64 *length) +{ + GstBuffer *buf; + + if (!gst_ebml_read_buffer (ebml, id, &buf)) + return FALSE; + + *length = GST_BUFFER_SIZE (buf); + *binary = g_memdup (GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf)); + + gst_buffer_unref (buf); + + return TRUE; +} + +/* + * Read an EBML header. + */ + +gboolean +gst_ebml_read_header (GstEbmlRead *ebml, + gchar **doctype, + guint *version) +{ + /* this function is the first to be called */ + guint32 id; + guint level_up; + + /* default init */ + if (doctype) + *doctype = NULL; + if (version) + *version = 1; + + if (!(id = gst_ebml_peek_id (ebml, &level_up))) + return FALSE; + if (level_up != 0 || id != GST_EBML_ID_HEADER) { + gst_element_error (GST_ELEMENT (ebml), "Not a EBML file"); + return FALSE; + } + if (!gst_ebml_read_master (ebml, &id)) + return FALSE; + g_assert (id == GST_EBML_ID_HEADER); + + while (TRUE) { + if (!(id = gst_ebml_peek_id (ebml, &level_up))) + return FALSE; + + /* end-of-header */ + if (level_up) + break; + + switch (id) { + /* is our read version uptodate? */ + case GST_EBML_ID_EBMLREADVERSION: { + guint64 num; + + if (!gst_ebml_read_uint (ebml, &id, &num)) + return FALSE; + g_assert (id == GST_EBML_ID_EBMLREADVERSION); + if (num != GST_EBML_VERSION) + return FALSE; + break; + } + + /* we only handle 8 byte lengths at max */ + case GST_EBML_ID_EBMLMAXSIZELENGTH: { + guint64 num; + + if (!gst_ebml_read_uint (ebml, &id, &num)) + return FALSE; + g_assert (id == GST_EBML_ID_EBMLMAXSIZELENGTH); + if (num != sizeof (guint64)) + return FALSE; + break; + } + + /* we handle 4 byte IDs at max */ + case GST_EBML_ID_EBMLMAXIDLENGTH: { + guint64 num; + + if (!gst_ebml_read_uint (ebml, &id, &num)) + return FALSE; + g_assert (id == GST_EBML_ID_EBMLMAXIDLENGTH); + if (num != sizeof (guint32)) + return FALSE; + break; + } + + case GST_EBML_ID_DOCTYPE: { + gchar *text; + + if (!gst_ebml_read_ascii (ebml, &id, &text)) + return FALSE; + g_assert (id == GST_EBML_ID_DOCTYPE); + if (doctype) { + if (doctype) + g_free (*doctype); + *doctype = text; + } else + g_free (text); + break; + } + + case GST_EBML_ID_DOCTYPEREADVERSION: { + guint64 num; + + if (!gst_ebml_read_uint (ebml, &id, &num)) + return FALSE; + g_assert (id == GST_EBML_ID_DOCTYPEREADVERSION); + if (version) + *version = num; + break; + } + + default: + GST_WARNING ("Unknown data type 0x%x in EBML header (ignored)", id); + /* pass-through */ + + /* we ignore these two, as they don't tell us anything we care about */ + case GST_EBML_ID_VOID: + case GST_EBML_ID_EBMLVERSION: + case GST_EBML_ID_DOCTYPEVERSION: + if (!gst_ebml_read_skip (ebml)) + return FALSE; + break; + } + } + + return TRUE; +} diff --git a/gst/matroska/ebml-read.h b/gst/matroska/ebml-read.h new file mode 100644 index 0000000..52e4e14 --- /dev/null +++ b/gst/matroska/ebml-read.h @@ -0,0 +1,102 @@ +/* GStreamer EBML I/O + * (c) 2003 Ronald Bultje + * + * ebml-read.c: read EBML data from file/stream + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_EBML_READ_H__ +#define __GST_EBML_READ_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_EBML_READ \ + (gst_ebml_read_get_type ()) +#define GST_EBML_READ(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_EBML_READ, GstEbmlRead)) +#define GST_EBML_READ_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_EBML_READ, GstEbmlReadClass)) +#define GST_IS_EBML_READ(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_EBML_READ)) +#define GST_IS_EBML_READ_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_EBML_READ)) +#define GST_EBML_READ_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_EBML_READ, GstEbmlReadClass)) + +typedef struct _GstEbmlLevel { + guint64 start, + length; +} GstEbmlLevel; + +typedef struct _GstEbmlRead { + GstElement parent; + + GstPad *sinkpad; + GstByteStream *bs; + + GList *level; +} GstEbmlRead; + +typedef struct _GstEbmlReadClass { + GstElementClass parent; +} GstEbmlReadClass; + +GType gst_ebml_read_get_type (void); + +guint32 gst_ebml_peek_id (GstEbmlRead *ebml, + guint *level_up); +void gst_ebml_read_seek (GstEbmlRead *ebml, + guint64 offset); +gboolean gst_ebml_read_skip (GstEbmlRead *ebml); +gboolean gst_ebml_read_buffer (GstEbmlRead *ebml, + guint32 *id, + GstBuffer **buf); +gboolean gst_ebml_read_uint (GstEbmlRead *ebml, + guint32 *id, + guint64 *num); +gboolean gst_ebml_read_sint (GstEbmlRead *ebml, + guint32 *id, + gint64 *num); +gboolean gst_ebml_read_float (GstEbmlRead *ebml, + guint32 *id, + gdouble *num); +gboolean gst_ebml_read_ascii (GstEbmlRead *ebml, + guint32 *id, + gchar **str); +gboolean gst_ebml_read_utf8 (GstEbmlRead *ebml, + guint32 *id, + gchar **str); +gboolean gst_ebml_read_date (GstEbmlRead *ebml, + guint32 *id, + gint64 *date); +gboolean gst_ebml_read_master (GstEbmlRead *ebml, + guint32 *id); +gboolean gst_ebml_read_binary (GstEbmlRead *ebml, + guint32 *id, + guchar **binary, + guint64 *length); +gboolean gst_ebml_read_header (GstEbmlRead *read, + gchar **doctype, + guint *version); + +G_END_DECLS + +#endif /* __GST_EBML_READ_H__ */ diff --git a/gst/matroska/matroska-demux.c b/gst/matroska/matroska-demux.c new file mode 100644 index 0000000..c0bd51c --- /dev/null +++ b/gst/matroska/matroska-demux.c @@ -0,0 +1,2746 @@ +/* GStreamer Matroska muxer/demuxer + * (c) 2003 Ronald Bultje + * + * matroska-demux.c: matroska file/stream demuxer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +/* For AVI compatibility mode... Who did that? */ +#include + +#include "matroska-demux.h" +#include "matroska-ids.h" + +enum { + /* FILL ME */ + LAST_SIGNAL +}; + +enum { + ARG_0, + ARG_METADATA, + ARG_STREAMINFO, + /* FILL ME */ +}; + +GST_PAD_TEMPLATE_FACTORY (sink_templ, + "sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_CAPS_NEW ( + "matroskademux_sink", + "video/x-matroska", + NULL + ) +) + +/* gobject magic foo */ +static void gst_matroska_demux_base_init (GstMatroskaDemuxClass *klass); +static void gst_matroska_demux_class_init (GstMatroskaDemuxClass *klass); +static void gst_matroska_demux_init (GstMatroskaDemux *demux); + +/* element functions */ +static void gst_matroska_demux_loop (GstElement *element); +static gboolean gst_matroska_demux_send_event (GstElement *element, + GstEvent *event); + +/* pad functions */ +static const GstEventMask * + gst_matroska_demux_get_event_mask (GstPad *pad); +static gboolean gst_matroska_demux_handle_src_event (GstPad *pad, + GstEvent *event); +static const GstFormat * + gst_matroska_demux_get_src_formats (GstPad *pad); +static const GstQueryType* + gst_matroska_demux_get_src_query_types(GstPad *pad); +static gboolean gst_matroska_demux_handle_src_query (GstPad *pad, + GstQueryType type, + GstFormat *format, + gint64 *value); + +/* gst internal change state handler */ +static GstElementStateReturn + gst_matroska_demux_change_state (GstElement *element); + +/* gobject bla bla */ +static void gst_matroska_demux_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +/* caps functions */ +static GstCaps *gst_matroska_demux_video_caps (GstMatroskaTrackVideoContext + *videocontext, + const gchar *codec_id, + gpointer data, + guint size); +static GstCaps *gst_matroska_demux_audio_caps (GstMatroskaTrackAudioContext + *audiocontext, + const gchar *codec_id, + gpointer data, + guint size); +static GstCaps *gst_matroska_demux_complex_caps (GstMatroskaTrackComplexContext + *complexcontext, + const gchar *codec_id, + gpointer data, + guint size); +static GstCaps *gst_matroska_demux_subtitle_caps (GstMatroskaTrackSubtitleContext + *subtitlecontext, + const gchar *codec_id, + gpointer data, + guint size); + +/* stream methods */ +static void gst_matroska_demux_reset (GstElement *element); + +static GstEbmlReadClass *parent_class = NULL; +static GstPadTemplate *videosrctempl, *audiosrctempl, *subtitlesrctempl; +/*static guint gst_matroska_demux_signals[LAST_SIGNAL] = { 0 };*/ + +GType +gst_matroska_demux_get_type (void) +{ + static GType gst_matroska_demux_type = 0; + + if (!gst_matroska_demux_type) { + static const GTypeInfo gst_matroska_demux_info = { + sizeof (GstMatroskaDemuxClass), + (GBaseInitFunc) gst_matroska_demux_base_init, + NULL, + (GClassInitFunc) gst_matroska_demux_class_init, + NULL, + NULL, + sizeof (GstMatroskaDemux), + 0, + (GInstanceInitFunc) gst_matroska_demux_init, + }; + + gst_matroska_demux_type = + g_type_register_static (GST_TYPE_EBML_READ, + "GstMatroskaDemux", + &gst_matroska_demux_info, 0); + } + + return gst_matroska_demux_type; +} + +static void +gst_matroska_demux_base_init (GstMatroskaDemuxClass *klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + static GstElementDetails gst_matroska_demux_details = { + "Matroska demuxer", + "Codec/Demuxer", + "Demuxes a Matroska Stream into video/audio/subtitles", + "Ronald Bultje " + }; + + gst_element_class_add_pad_template (element_class, videosrctempl); + gst_element_class_add_pad_template (element_class, audiosrctempl); + gst_element_class_add_pad_template (element_class, subtitlesrctempl); + gst_element_class_add_pad_template (element_class, + GST_PAD_TEMPLATE_GET (sink_templ)); + gst_element_class_set_details (element_class, + &gst_matroska_demux_details); +} + +static void +gst_matroska_demux_class_init (GstMatroskaDemuxClass *klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + g_object_class_install_property (gobject_class, ARG_METADATA, + g_param_spec_boxed ("metadata", "Metadata", "Metadata", + GST_TYPE_CAPS, G_PARAM_READABLE)); + g_object_class_install_property (gobject_class, ARG_STREAMINFO, + g_param_spec_boxed ("streaminfo", "Streaminfo", "Streaminfo", + GST_TYPE_CAPS, G_PARAM_READABLE)); + + parent_class = g_type_class_ref (GST_TYPE_EBML_READ); + + gobject_class->get_property = gst_matroska_demux_get_property; + + gstelement_class->change_state = gst_matroska_demux_change_state; + gstelement_class->send_event = gst_matroska_demux_send_event; +} + +static void +gst_matroska_demux_init (GstMatroskaDemux *demux) +{ + GstElementClass *klass = GST_ELEMENT_GET_CLASS (demux); + gint i; + + GST_FLAG_SET (GST_OBJECT (demux), GST_ELEMENT_EVENT_AWARE); + + demux->sinkpad = gst_pad_new_from_template ( + gst_element_class_get_pad_template (klass, "sink"), "sink"); + gst_element_add_pad (GST_ELEMENT (demux), demux->sinkpad); + GST_EBML_READ (demux)->sinkpad = demux->sinkpad; + + gst_element_set_loop_function (GST_ELEMENT (demux), + gst_matroska_demux_loop); + + /* initial stream no. */ + for (i = 0; i < GST_MATROSKA_DEMUX_MAX_STREAMS; i++) { + demux->src[i] = NULL; + } + demux->streaminfo = demux->metadata = NULL; + demux->writing_app = demux->muxing_app = NULL; + demux->index = NULL; + + /* finish off */ + gst_matroska_demux_reset (GST_ELEMENT (demux)); +} + +static void +gst_matroska_demux_reset (GstElement *element) +{ + GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (element); + guint i; + + /* reset input */ + demux->state = GST_MATROSKA_DEMUX_STATE_START; + + /* clean up existing streams */ + for (i = 0; i < GST_MATROSKA_DEMUX_MAX_STREAMS; i++) { + if (demux->src[i] != NULL) { + if (demux->src[i]->pad != NULL) { + gst_element_remove_pad (GST_ELEMENT (demux), demux->src[i]->pad); + } + g_free (demux->src[i]->codec_id); + g_free (demux->src[i]->codec_name); + g_free (demux->src[i]->name); + g_free (demux->src[i]->language); + g_free (demux->src[i]->codec_priv); + g_free (demux->src[i]); + demux->src[i] = NULL; + } + } + demux->num_streams = 0; + demux->num_a_streams = 0; + demux->num_t_streams = 0; + demux->num_v_streams = 0; + + /* reset media info */ + gst_caps_replace (&demux->metadata, NULL); + gst_caps_replace (&demux->streaminfo, NULL); + + g_free (demux->writing_app); + demux->writing_app = NULL; + g_free (demux->muxing_app); + demux->muxing_app = NULL; + + /* reset indexes */ + demux->num_indexes = 0; + g_free (demux->index); + demux->index = NULL; + + /* reset timers */ + demux->time_scale = 1000000; + demux->duration = 0; + demux->pos = 0; + demux->created = G_MININT64; + demux->seek_pending = GST_CLOCK_TIME_NONE; +} + +static gint +gst_matroska_demux_stream_from_num (GstMatroskaDemux *demux, + guint track_num) +{ + guint n; + + for (n = 0; n < demux->num_streams; n++) { + if (demux->src[n] != NULL && + demux->src[n]->num == track_num) { + return n; + } + } + + if (n == demux->num_streams) { + GST_WARNING ("Failed to find corresponding pad for tracknum %d", + track_num); + } + + return -1; +} + +static gboolean +gst_matroska_demux_add_stream (GstMatroskaDemux *demux) +{ + GstElementClass *klass = GST_ELEMENT_GET_CLASS (demux); + GstMatroskaTrackContext *context; + GstPadTemplate *templ = NULL; + GstCaps *caps = NULL; + gchar *padname = NULL; + gboolean res = TRUE; + guint32 id; + + if (demux->num_streams >= GST_MATROSKA_DEMUX_MAX_STREAMS) { + GST_WARNING ("Maximum number of streams (%d) exceeded, skipping", + GST_MATROSKA_DEMUX_MAX_STREAMS); + return gst_ebml_read_skip (GST_EBML_READ (demux)); /* skip-and-continue */ + } + + /* allocate generic... if we know the type, we'll g_renew() + * with the precise type */ + context = g_new0 (GstMatroskaTrackContext, 1); + demux->src[demux->num_streams] = context; + context->index = demux->num_streams; + context->type = 0; /* no type yet */ + demux->num_streams++; + + /* start with the master */ + if (!gst_ebml_read_master (GST_EBML_READ (demux), &id)) + return FALSE; + + /* try reading the trackentry headers */ + while (res) { + if (!(id = gst_ebml_peek_id (GST_EBML_READ (demux), &demux->level_up))) { + res = FALSE; + break; + } else if (demux->level_up > 0) { + demux->level_up--; + break; + } + + switch (id) { + /* track number (unique stream ID) */ + case GST_MATROSKA_ID_TRACKNUMBER: { + guint64 num; + if (!gst_ebml_read_uint (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + context->num = num; + break; + } + + /* track UID (unique identifier) */ + case GST_MATROSKA_ID_TRACKUID: { + guint64 num; + if (!gst_ebml_read_uint (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + context->uid = num; + break; + } + + /* track type (video, audio, combined, subtitle, etc.) */ + case GST_MATROSKA_ID_TRACKTYPE: { + guint64 num; + if (context->type != 0) { + GST_WARNING ("More than one tracktype defined in a trackentry - skipping"); + break; + } + if (!gst_ebml_read_uint (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + context->type = num; + + /* ok, so we're actually going to reallocate this thing */ + switch (context->type) { + case GST_MATROSKA_TRACK_TYPE_VIDEO: + context = (GstMatroskaTrackContext *) + g_renew (GstMatroskaTrackVideoContext, context, 1); + break; + case GST_MATROSKA_TRACK_TYPE_AUDIO: + context = (GstMatroskaTrackContext *) + g_renew (GstMatroskaTrackAudioContext, context, 1); + /* defaults */ + ((GstMatroskaTrackAudioContext *) context)->channels = 1; + ((GstMatroskaTrackAudioContext *) context)->samplerate = 8000; + break; + case GST_MATROSKA_TRACK_TYPE_COMPLEX: + context = (GstMatroskaTrackContext *) + g_renew (GstMatroskaTrackComplexContext, context, 1); + break; + case GST_MATROSKA_TRACK_TYPE_SUBTITLE: + context = (GstMatroskaTrackContext *) + g_renew (GstMatroskaTrackSubtitleContext, context, 1); + break; + case GST_MATROSKA_TRACK_TYPE_LOGO: + case GST_MATROSKA_TRACK_TYPE_CONTROL: + default: + GST_WARNING ("Unknown or unsupported track type 0x%x", + context->type); + context->type = 0; + break; + } + demux->src[demux->num_streams-1] = context; + break; + } + + /* tracktype specific stuff for video */ + case GST_MATROSKA_ID_TRACKVIDEO: { + GstMatroskaTrackVideoContext *videocontext; + if (context->type != GST_MATROSKA_TRACK_TYPE_VIDEO) { + GST_WARNING ("trackvideo EBML entry in non-video track - ignoring track"); + res = FALSE; + break; + } else if (!gst_ebml_read_master (GST_EBML_READ (demux), &id)) { + res = FALSE; + break; + } + videocontext = (GstMatroskaTrackVideoContext *) context; + + while (res) { + if (!(id = gst_ebml_peek_id (GST_EBML_READ (demux), &demux->level_up))) { + res = FALSE; + break; + } else if (demux->level_up > 0) { + demux->level_up--; + break; + } + + switch (id) { + /* fixme, this should be one-up, but I get it here (?) */ + case GST_MATROSKA_ID_TRACKDEFAULTDURATION: { + guint64 num; + if (!gst_ebml_read_uint (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + context->default_duration = num; + break; + } + + /* video framerate */ + case GST_MATROSKA_ID_VIDEOFRAMERATE: { + gdouble num; + if (!gst_ebml_read_float (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + context->default_duration = GST_SECOND * (1. / num); + break; + } + + /* width of the size to display the video at */ + case GST_MATROSKA_ID_VIDEODISPLAYWIDTH: { + guint64 num; + if (!gst_ebml_read_uint (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + videocontext->display_width = num; + break; + } + + /* height of the size to display the video at */ + case GST_MATROSKA_ID_VIDEODISPLAYHEIGHT: { + guint64 num; + if (!gst_ebml_read_uint (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + videocontext->display_height = num; + break; + } + + /* width of the video in the file */ + case GST_MATROSKA_ID_VIDEOPIXELWIDTH: { + guint64 num; + if (!gst_ebml_read_uint (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + videocontext->pixel_width = num; + break; + } + + /* height of the video in the file */ + case GST_MATROSKA_ID_VIDEOPIXELHEIGHT: { + guint64 num; + if (!gst_ebml_read_uint (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + videocontext->pixel_height = num; + break; + } + + /* whether the video is interlaced */ + case GST_MATROSKA_ID_VIDEOFLAGINTERLACED: { + guint64 num; + if (!gst_ebml_read_uint (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + if (num) + context->flags |= GST_MATROSKA_VIDEOTRACK_INTERLACED; + else + context->flags &= ~GST_MATROSKA_VIDEOTRACK_INTERLACED; + break; + } + + /* stereo mode (whether the video has two streams, where + * one is for the left eye and the other for the right eye, + * which creates a 3D-like effect) */ + case GST_MATROSKA_ID_VIDEOSTEREOMODE: { + guint64 num; + if (!gst_ebml_read_uint (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + if (num != GST_MATROSKA_EYE_MODE_MONO && + num != GST_MATROSKA_EYE_MODE_LEFT && + num != GST_MATROSKA_EYE_MODE_RIGHT && + num != GST_MATROSKA_EYE_MODE_BOTH) { + GST_WARNING ("Unknown eye mode 0x%x - ignoring", + (guint) num); + break; + } + videocontext->eye_mode = num; + break; + } + + /* aspect ratio behaviour */ + case GST_MATROSKA_ID_VIDEOASPECTRATIO: { + guint64 num; + if (!gst_ebml_read_uint (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + if (num != GST_MATROSKA_ASPECT_RATIO_MODE_FREE && + num != GST_MATROSKA_ASPECT_RATIO_MODE_KEEP && + num != GST_MATROSKA_ASPECT_RATIO_MODE_FIXED) { + GST_WARNING ("Unknown aspect ratio mode 0x%x - ignoring", + (guint) num); + break; + } + videocontext->asr_mode = num; + break; + } + + /* colourspace (only matters for raw video) fourcc */ + case GST_MATROSKA_ID_VIDEOCOLOURSPACE: { + guint64 num; + if (!gst_ebml_read_uint (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + videocontext->fourcc = num; + break; + } + + default: + GST_WARNING ("Unknown video track header entry 0x%x - ignoring", + id); + /* pass-through */ + + case GST_EBML_ID_VOID: + if (!gst_ebml_read_skip (GST_EBML_READ (demux))) + res = FALSE; + break; + } + + if (demux->level_up) { + demux->level_up--; + break; + } + } + break; + } + + /* tracktype specific stuff for audio */ + case GST_MATROSKA_ID_TRACKAUDIO: { + GstMatroskaTrackAudioContext *audiocontext; + if (context->type != GST_MATROSKA_TRACK_TYPE_AUDIO) { + GST_WARNING ("trackaudio EBML entry in non-audio track - ignoring track"); + res = FALSE; + break; + } else if (!gst_ebml_read_master (GST_EBML_READ (demux), &id)) { + res = FALSE; + break; + } + audiocontext = (GstMatroskaTrackAudioContext *) context; + + while (res) { + if (!(id = gst_ebml_peek_id (GST_EBML_READ (demux), &demux->level_up))) { + res = FALSE; + break; + } else if (demux->level_up > 0) { + demux->level_up--; + break; + } + + switch (id) { + /* samplerate */ + case GST_MATROSKA_ID_AUDIOSAMPLINGFREQ: { + gdouble num; + if (!gst_ebml_read_float (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + audiocontext->samplerate = num; + break; + } + + /* bitdepth */ + case GST_MATROSKA_ID_AUDIOBITDEPTH: { + guint64 num; + if (!gst_ebml_read_uint (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + audiocontext->bitdepth = num; + break; + } + + /* channels */ + case GST_MATROSKA_ID_AUDIOCHANNELS: { + guint64 num; + if (!gst_ebml_read_uint (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + audiocontext->channels = num; + break; + } + + default: + GST_WARNING ("Unknown audio track header entry 0x%x - ignoring", + id); + /* pass-through */ + + case GST_EBML_ID_VOID: + if (!gst_ebml_read_skip (GST_EBML_READ (demux))) + res = FALSE; + break; + } + + if (demux->level_up) { + demux->level_up--; + break; + } + } + break; + } + + /* codec identifier */ + case GST_MATROSKA_ID_CODECID: { + gchar *text; + if (!gst_ebml_read_ascii (GST_EBML_READ (demux), &id, &text)) { + res = FALSE; + break; + } + context->codec_id = text; + break; + } + + /* codec private data */ + case GST_MATROSKA_ID_CODECPRIVATE: { + guint8 *data; + guint64 size; + if (!gst_ebml_read_binary (GST_EBML_READ (demux), &id, &data, &size)) { + res = FALSE; + break; + } + context->codec_priv = data; + context->codec_priv_size = size; + break; + } + + /* name of the codec */ + case GST_MATROSKA_ID_CODECNAME: { + gchar *text; + if (!gst_ebml_read_utf8 (GST_EBML_READ (demux), &id, &text)) { + res = FALSE; + break; + } + context->codec_name = text; + break; + } + + /* name of this track */ + case GST_MATROSKA_ID_TRACKNAME: { + gchar *text; + if (!gst_ebml_read_utf8 (GST_EBML_READ (demux), &id, &text)) { + res = FALSE; + break; + } + context->name = text; + break; + } + + /* language (matters for audio/subtitles, mostly) */ + case GST_MATROSKA_ID_TRACKLANGUAGE: { + gchar *text; + if (!gst_ebml_read_utf8 (GST_EBML_READ (demux), &id, &text)) { + res = FALSE; + break; + } + context->language = text; + break; + } + + /* whether this is actually used */ + case GST_MATROSKA_ID_TRACKFLAGENABLED: { + guint64 num; + if (!gst_ebml_read_uint (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + if (num) + context->flags |= GST_MATROSKA_TRACK_ENABLED; + else + context->flags &= ~GST_MATROSKA_TRACK_ENABLED; + break; + } + + /* whether it's the default for this track type */ + case GST_MATROSKA_ID_TRACKFLAGDEFAULT: { + guint64 num; + if (!gst_ebml_read_uint (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + if (num) + context->flags |= GST_MATROSKA_TRACK_DEFAULT; + else + context->flags &= ~GST_MATROSKA_TRACK_DEFAULT; + break; + } + + /* lacing (like MPEG, where blocks don't end/start on frame + * boundaries) */ + case GST_MATROSKA_ID_TRACKFLAGLACING: { + guint64 num; + if (!gst_ebml_read_uint (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + if (num) + context->flags |= GST_MATROSKA_TRACK_LACING; + else + context->flags &= ~GST_MATROSKA_TRACK_LACING; + break; + } + + /* default length (in time) of one data block in this track */ + case GST_MATROSKA_ID_TRACKDEFAULTDURATION: { + guint64 num; + if (!gst_ebml_read_uint (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + context->default_duration = num; + break; + } + + default: + GST_WARNING ("Unknown track header entry 0x%x - ignoring", id); + /* pass-through */ + + /* we ignore these because they're nothing useful (i.e. crap). */ + case GST_MATROSKA_ID_CODECINFOURL: + case GST_MATROSKA_ID_CODECDOWNLOADURL: + case GST_MATROSKA_ID_TRACKMINCACHE: + case GST_MATROSKA_ID_TRACKMAXCACHE: + case GST_EBML_ID_VOID: + if (!gst_ebml_read_skip (GST_EBML_READ (demux))) + res = FALSE; + break; + } + + if (demux->level_up) { + demux->level_up--; + break; + } + } + + if (context->type == 0 || context->codec_id == NULL || !res) { + if (res) + GST_WARNING ("Unknown stream/codec in track entry header"); + + demux->num_streams--; + demux->src[demux->num_streams] = NULL; + if (context) { + g_free (context->codec_id); + g_free (context->codec_name); + g_free (context->name); + g_free (context->language); + g_free (context->codec_priv); + g_free (context); + } + + return res; + } + + /* now create the GStreamer connectivity */ + switch (context->type) { + case GST_MATROSKA_TRACK_TYPE_VIDEO: { + GstMatroskaTrackVideoContext *videocontext = + (GstMatroskaTrackVideoContext *) context; + padname = g_strdup_printf ("video_%02d", demux->num_v_streams); + templ = gst_element_class_get_pad_template (klass, "video_%02d"); + caps = gst_matroska_demux_video_caps (videocontext, + context->codec_id, + context->codec_priv, + context->codec_priv_size); + break; + } + + case GST_MATROSKA_TRACK_TYPE_AUDIO: { + GstMatroskaTrackAudioContext *audiocontext = + (GstMatroskaTrackAudioContext *) context; + padname = g_strdup_printf ("audio_%02d", demux->num_a_streams); + templ = gst_element_class_get_pad_template (klass, "audio_%02d"); + caps = gst_matroska_demux_audio_caps (audiocontext, + context->codec_id, + context->codec_priv, + context->codec_priv_size); + break; + } + + case GST_MATROSKA_TRACK_TYPE_COMPLEX: { + GstMatroskaTrackComplexContext *complexcontext = + (GstMatroskaTrackComplexContext *) context; + padname = g_strdup_printf ("video_%02d", demux->num_v_streams); + templ = gst_element_class_get_pad_template (klass, "video_%02d"); + caps = gst_matroska_demux_complex_caps (complexcontext, + context->codec_id, + context->codec_priv, + context->codec_priv_size); + break; + } + + case GST_MATROSKA_TRACK_TYPE_SUBTITLE: { + GstMatroskaTrackSubtitleContext *subtitlecontext = + (GstMatroskaTrackSubtitleContext *) context; + padname = g_strdup_printf ("subtitle_%02d", demux->num_t_streams); + templ = gst_element_class_get_pad_template (klass, "subtitle_%02d"); + caps = gst_matroska_demux_subtitle_caps (subtitlecontext, + context->codec_id, + context->codec_priv, + context->codec_priv_size); + break; + } + + case GST_MATROSKA_TRACK_TYPE_LOGO: + case GST_MATROSKA_TRACK_TYPE_CONTROL: + default: + /* we should already have quit by now */ + g_assert (0); + } + + /* the pad in here */ + context->pad = gst_pad_new_from_template (templ, padname); + + if (caps != NULL) { + if (gst_pad_try_set_caps (context->pad, caps) <= 0) { + GST_WARNING ("Failed to set caps on next element for %s", + padname); + } + } + g_free (padname); + + /* set some functions */ + gst_pad_set_formats_function (context->pad, + gst_matroska_demux_get_src_formats); + gst_pad_set_event_mask_function (context->pad, + gst_matroska_demux_get_event_mask); + gst_pad_set_event_function (context->pad, + gst_matroska_demux_handle_src_event); + gst_pad_set_query_type_function (context->pad, + gst_matroska_demux_get_src_query_types); + gst_pad_set_query_function (context->pad, + gst_matroska_demux_handle_src_query); + + gst_element_add_pad (GST_ELEMENT (demux), context->pad); + + /* tadaah! */ + return TRUE; +} + +static const GstFormat * +gst_matroska_demux_get_src_formats (GstPad *pad) +{ + /*GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (gst_pad_get_parent (pad));*/ + + /* we could try to look for units (i.e. samples) in audio streams + * or video streams, but both samplerate and framerate are not + * always constant, and since we only have a time indication, we + * cannot guarantee anything here based purely on index. So, we + * only support time for now. */ + static const GstFormat src_formats[] = { + GST_FORMAT_TIME, + (GstFormat) 0 + }; + + return src_formats; +} + +static const GstQueryType * +gst_matroska_demux_get_src_query_types (GstPad *pad) +{ + static const GstQueryType src_types[] = { + GST_QUERY_TOTAL, + GST_QUERY_POSITION, + (GstQueryType) 0 + }; + + return src_types; +} + +static gboolean +gst_matroska_demux_handle_src_query (GstPad *pad, + GstQueryType type, + GstFormat *format, + gint64 *value) +{ + gboolean res = TRUE; + GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (gst_pad_get_parent (pad)); + + switch (type) { + case GST_QUERY_TOTAL: + switch (*format) { + case GST_FORMAT_DEFAULT: + *format = GST_FORMAT_TIME; + /* fall through */ + case GST_FORMAT_TIME: + *value = demux->duration; + break; + default: + res = FALSE; + break; + } + break; + + case GST_QUERY_POSITION: + switch (*format) { + case GST_FORMAT_DEFAULT: + *format = GST_FORMAT_TIME; + /* fall through */ + case GST_FORMAT_TIME: + *value = demux->pos; + break; + default: + res = FALSE; + break; + } + break; + + default: + res = FALSE; + break; + } + + return res; +} + +static GstMatroskaIndex * +gst_matroskademux_seek (GstMatroskaDemux *demux) +{ + guint entry = (guint) -1; + guint64 offset = demux->seek_pending; + guint n; + + /* make sure we don't seek twice */ + demux->seek_pending = GST_CLOCK_TIME_NONE; + + for (n = 0; n < demux->num_indexes; n++) { + if (entry == (guint) -1) { + entry = n; + } else { + gfloat diff_old = fabs (1. * (demux->index[entry].time - offset)), + diff_new = fabs (1. * (demux->index[n].time - offset)); + + if (diff_new < diff_old) { + entry = n; + } + } + } + + if (entry != (guint) -1) { + return &demux->index[entry]; + } + + return NULL; +} + +static gboolean +gst_matroska_demux_send_event (GstElement *element, + GstEvent *event) +{ + GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (element); + gboolean res = TRUE; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + switch (GST_EVENT_SEEK_FORMAT (event)) { + case GST_FORMAT_TIME: + demux->seek_pending = GST_EVENT_SEEK_OFFSET (event); + break; + + default: + GST_WARNING ("Only time seek is supported"); + res = FALSE; + break; + } + break; + + default: + GST_WARNING ("Unhandled event of type %d", + GST_EVENT_TYPE (event)); + res = FALSE; + break; + } + + gst_event_unref (event); + + return res; +} + +static const GstEventMask * +gst_matroska_demux_get_event_mask (GstPad *pad) +{ + static const GstEventMask masks[] = { + { GST_EVENT_SEEK, (GstEventFlag) ((gint) GST_SEEK_METHOD_SET | + (gint) GST_SEEK_FLAG_KEY_UNIT) }, + { GST_EVENT_SEEK_SEGMENT, (GstEventFlag) ((gint) GST_SEEK_METHOD_SET | + (gint) GST_SEEK_FLAG_KEY_UNIT) }, + { (GstEventType) 0, (GstEventFlag) 0 } + }; + + return masks; +} + +static gboolean +gst_matroska_demux_handle_src_event (GstPad *pad, + GstEvent *event) +{ + GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (gst_pad_get_parent (pad)); + gboolean res = TRUE; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK_SEGMENT: + case GST_EVENT_SEEK: + return gst_matroska_demux_send_event (GST_ELEMENT (demux), event); + + default: + GST_WARNING ("Unhandled event of type %d", + GST_EVENT_TYPE (event)); + res = FALSE; + break; + } + + gst_event_unref (event); + + return res; +} + +static gboolean +gst_matroska_demux_handle_sink_event (GstMatroskaDemux *demux, + GstEvent *event) +{ + guint i; + + /* forward to all src pads */ + for (i = 0; i < demux->num_streams; i++) { + if (GST_PAD_IS_USABLE (demux->src[i]->pad)) { + gst_event_ref (event); + gst_pad_push (demux->src[i]->pad, GST_DATA (event)); + } + } + + if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) { + gst_element_set_eos (GST_ELEMENT (demux)); + } + + gst_event_unref (event); + + return TRUE; +} + +static gboolean +gst_matroska_demux_init_stream (GstMatroskaDemux *demux) +{ + guint32 id; + gchar *doctype; + guint version; + + if (!gst_ebml_read_header (GST_EBML_READ (demux), &doctype, &version)) + return FALSE; + + if (!doctype || strcmp (doctype, "matroska") != 0) { + gst_element_error (GST_ELEMENT (demux), + "Input is not a matroska stream (doctype=%s)", + doctype ? doctype : "none"); + g_free (doctype); + return FALSE; + } + g_free (doctype); + if (version > 1) { + gst_element_error (GST_ELEMENT (demux), + "Demuxer version (1) is too old to read stream version %d", + version); + return FALSE; + } + + /* find segment, must be the next element */ + while (1) { + guint last_level; + + if (!(id = gst_ebml_peek_id (GST_EBML_READ (demux), &last_level))) + return FALSE; + + if (id == GST_MATROSKA_ID_SEGMENT) + break; + + /* oi! */ + GST_WARNING ("Expected a Segment ID (0x%x), but received 0x%x!", + GST_MATROSKA_ID_SEGMENT, id); + if (!gst_ebml_read_skip (GST_EBML_READ (demux))) + return FALSE; + } + + /* we now have a EBML segment */ + return gst_ebml_read_master (GST_EBML_READ (demux), &id); +} + +static gboolean +gst_matroska_demux_parse_tracks (GstMatroskaDemux *demux) +{ + gboolean res = TRUE; + guint32 id; + + while (res) { + if (!(id = gst_ebml_peek_id (GST_EBML_READ (demux), &demux->level_up))) { + res = FALSE; + break; + } else if (demux->level_up) { + demux->level_up--; + break; + } + + switch (id) { + /* one track within the "all-tracks" header */ + case GST_MATROSKA_ID_TRACKENTRY: + if (!gst_matroska_demux_add_stream (demux)) + res = FALSE; + break; + + default: + GST_WARNING ("Unknown entry 0x%x in track header", id); + /* fall-through */ + + case GST_EBML_ID_VOID: + if (!gst_ebml_read_skip (GST_EBML_READ (demux))) + res = FALSE; + break; + } + + if (demux->level_up) { + demux->level_up--; + break; + } + } + + return res; +} + +static gboolean +gst_matroska_demux_parse_index (GstMatroskaDemux *demux) +{ + gboolean res = TRUE; + guint32 id; + GstMatroskaIndex idx; + + while (res) { + if (!(id = gst_ebml_peek_id (GST_EBML_READ (demux), &demux->level_up))) { + res = FALSE; + break; + } else if (demux->level_up) { + demux->level_up--; + break; + } + + switch (id) { + /* one single index entry ('point') */ + case GST_MATROSKA_ID_POINTENTRY: + if (!gst_ebml_read_master (GST_EBML_READ (demux), &id)) { + res = FALSE; + break; + } + + /* in the end, we hope to fill one entry with a + * timestamp, a file position and a tracknum */ + idx.pos = (guint64) -1; + idx.time = (guint64) -1; + idx.track = (guint16) -1; + + while (res) { + if (!(id = gst_ebml_peek_id (GST_EBML_READ (demux), &demux->level_up))) { + res = FALSE; + break; + } else if (demux->level_up) { + demux->level_up--; + break; + } + + switch (id) { + /* one single index entry ('point') */ + case GST_MATROSKA_ID_CUETIME: { + gint64 time; + if (!gst_ebml_read_date (GST_EBML_READ (demux), &id, &time)) { + res = FALSE; + break; + } + idx.time = time; + break; + } + + /* position in the file + track to which it belongs */ + case GST_MATROSKA_ID_CUETRACKPOSITION: + if (!gst_ebml_read_master (GST_EBML_READ (demux), &id)) { + res = FALSE; + break; + } + + while (res) { + if (!(id = gst_ebml_peek_id (GST_EBML_READ (demux), + &demux->level_up))) { + res = FALSE; + break; + } else if (demux->level_up) { + demux->level_up--; + break; + } + + switch (id) { + /* track number */ + case GST_MATROSKA_ID_CUETRACK: { + guint64 num; + if (!gst_ebml_read_uint (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + idx.track = num; + break; + } + + /* position in file */ + case GST_MATROSKA_ID_CUECLUSTERPOSITION: { + guint64 num; + if (!gst_ebml_read_uint (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + idx.pos = num; + break; + } + + default: + GST_WARNING ("Unknown entry 0x%x in CuesTrackPositions", id); + /* fall-through */ + + case GST_EBML_ID_VOID: + if (!gst_ebml_read_skip (GST_EBML_READ (demux))) + res = FALSE; + break; + } + + if (demux->level_up) { + demux->level_up--; + break; + } + } + + break; + + default: + GST_WARNING ("Unknown entry 0x%x in cuespoint index", id); + /* fall-through */ + + case GST_EBML_ID_VOID: + if (!gst_ebml_read_skip (GST_EBML_READ (demux))) + res = FALSE; + break; + } + + if (demux->level_up) { + demux->level_up--; + break; + } + } + + /* so let's see if we got what we wanted */ + if (idx.pos != (guint64) -1 && + idx.time != (guint64) -1 && + idx.track != (guint16) -1) { + if (demux->num_indexes % 32 == 0) { + /* re-allocate bigger index */ + demux->index = g_renew (GstMatroskaIndex, demux->index, + demux->num_indexes + 32); + } + demux->index[demux->num_indexes].pos = idx.pos; + demux->index[demux->num_indexes].time = idx.time; + demux->index[demux->num_indexes].track = idx.track; + demux->num_indexes++; + } + + break; + + default: + GST_WARNING ("Unknown entry 0x%x in cues header", id); + /* fall-through */ + + case GST_EBML_ID_VOID: + if (!gst_ebml_read_skip (GST_EBML_READ (demux))) + res = FALSE; + break; + } + + if (demux->level_up) { + demux->level_up--; + break; + } + } + + return res; +} + +static gboolean +gst_matroska_demux_parse_info (GstMatroskaDemux *demux) +{ + gboolean res = TRUE; + guint32 id; + + while (res) { + if (!(id = gst_ebml_peek_id (GST_EBML_READ (demux), &demux->level_up))) { + res = FALSE; + break; + } else if (demux->level_up) { + demux->level_up--; + break; + } + + switch (id) { + /* cluster timecode */ + case GST_MATROSKA_ID_TIMECODESCALE: { + guint64 num; + if (!gst_ebml_read_uint (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + demux->time_scale = num; + break; + } + + case GST_MATROSKA_ID_DURATION: { + gdouble num; + if (!gst_ebml_read_float (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + demux->duration = num * demux->time_scale; + break; + } + + case GST_MATROSKA_ID_WRITINGAPP: { + gchar *text; + if (!gst_ebml_read_utf8 (GST_EBML_READ (demux), &id, &text)) { + res = FALSE; + break; + } + demux->writing_app = text; + break; + } + + case GST_MATROSKA_ID_MUXINGAPP: { + gchar *text; + if (!gst_ebml_read_utf8 (GST_EBML_READ (demux), &id, &text)) { + res = FALSE; + break; + } + demux->muxing_app = text; + break; + } + + case GST_MATROSKA_ID_DATEUTC: { + gint64 time; + if (!gst_ebml_read_date (GST_EBML_READ (demux), &id, &time)) { + res = FALSE; + break; + } + demux->created = time; + break; + } + + default: + GST_WARNING ("Unknown entry 0x%x in info header", id); + /* fall-through */ + + case GST_EBML_ID_VOID: + if (!gst_ebml_read_skip (GST_EBML_READ (demux))) + res = FALSE; + break; + } + + if (demux->level_up) { + demux->level_up--; + break; + } + } + + return res; +} + +static gboolean +gst_matroska_demux_parse_metadata (GstMatroskaDemux *demux) +{ + gboolean res = TRUE; + guint32 id; + + while (res) { + if (!(id = gst_ebml_peek_id (GST_EBML_READ (demux), &demux->level_up))) { + res = FALSE; + break; + } else if (demux->level_up) { + demux->level_up--; + break; + } + + switch (id) { + default: + GST_WARNING ("metadata unimplemented"); + /* fall-through */ + + case GST_EBML_ID_VOID: + if (!gst_ebml_read_skip (GST_EBML_READ (demux))) + res = FALSE; + break; + } + + if (demux->level_up) { + demux->level_up--; + break; + } + } + + return res; +} + +/* + * Read signed/unsigned "EBML" numbers. + * Return: number of bytes processed. + */ + +static gint +gst_matroska_ebmlnum_uint (guint8 *data, + guint size, + guint64 *num) +{ + gint len_mask = 0x80, read = 1, n = 1, num_ffs = 0; + guint64 total; + + if (size <= 0) { + return -1; + } + + total = data[0]; + while (read <= 8 && !(total & len_mask)) { + read++; + len_mask >>= 1; + } + if (read > 8) + return -1; + + if ((total &= (len_mask - 1)) == len_mask - 1) + num_ffs++; + if (size < read) + return -1; + while (n < read) { + if (data[n] == 0xff) + num_ffs++; + total = (total << 8) | data[n]; + n++; + } + + if (!total) + return -1; + + if (read == num_ffs) + *num = G_MAXUINT64; + else + *num = total; + + return read; +} + +static gint +gst_matroska_ebmlnum_sint (guint8 *data, + guint size, + gint64 *num) +{ + guint64 unum; + gint res; + + /* read as unsigned number first */ + if ((res = gst_matroska_ebmlnum_uint (data, size, &unum)) < 0) + return -1; + + /* make signed */ + if (unum == G_MAXUINT64) + *num = G_MAXINT64; + else + *num = unum - ((1 << ((7 * res) - 1)) - 1); + + return res; +} + +static gboolean +gst_matroska_demux_parse_blockgroup (GstMatroskaDemux *demux, + guint64 cluster_time) +{ + gboolean res = TRUE; + guint32 id; + + while (res) { + if (!(id = gst_ebml_peek_id (GST_EBML_READ (demux), &demux->level_up))) { + res = FALSE; + break; + } else if (demux->level_up) { + demux->level_up--; + break; + } + + switch (id) { + /* one block inside the group. Note, block parsing is one + * of the harder things, so this code is a bit complicated. + * See http://www.matroska.org/ for documentation. */ + case GST_MATROSKA_ID_BLOCK: { + GstBuffer *buf; + guint8 *data; + gint16 time; + guint size, *lace_size = NULL; + gint n, stream, flags, laces = 0; + guint64 num; + + if (!gst_ebml_read_buffer (GST_EBML_READ (demux), &id, &buf)) { + res = FALSE; + break; + } + data = GST_BUFFER_DATA (buf); + size = GST_BUFFER_SIZE (buf); + + /* first byte(s): blocknum */ + if ((n = gst_matroska_ebmlnum_uint (data, size, &num)) < 0) { + gst_element_error (GST_ELEMENT (demux), "Data error"); + gst_buffer_unref (buf); + res = FALSE; + break; + } + data += n; size -= n; + + /* fetch stream from num */ + stream = gst_matroska_demux_stream_from_num (demux, num); + if (size <= 3 || stream < 0 || stream >= demux->num_streams || + !GST_PAD_IS_USABLE (demux->src[stream]->pad)) { + gst_buffer_unref (buf); + res = FALSE; + break; + } + + /* time (relative to cluster time) */ + time = (* (gint16 *) data) * demux->time_scale; + time = GINT16_FROM_BE (time); + data += 2; size -= 2; + flags = * (guint8 *) data; + data += 1; size -= 1; + + switch ((flags & 0x06) >> 1) { + case 0x0: /* no lacing */ + laces = 1; + lace_size = g_new (gint, 1); + lace_size[0] = GST_BUFFER_SIZE (buf) - size; + break; + + case 0x1: /* xiph lacing */ + case 0x2: /* fixed-size lacing */ + case 0x3: /* EBML lacing */ + if (size == 0) { + res = FALSE; + break; + } + laces = (* (guint8 *) data) + 1; + data += 1; size -= 1; + lace_size = g_new (gint, laces); + + switch ((flags & 0x06) >> 1) { + case 0x1: /* xiph lacing */ { + guint total = 0, temp; + for (n = 0; res && n < laces - 1; n++) { + while (1) { + if (size == 0) { + res = FALSE; + break; + } + temp = * (guint8 *) data; + lace_size[n] += temp; + data += 1; size -= 1; + if (temp != 0xff) + break; + } + total += lace_size[n]; + } + lace_size[n] = size - total; + break; + } + + case 0x2: /* fixed-size lacing */ + for (n = 0; n < laces; n++) + lace_size[n] = (GST_BUFFER_SIZE (buf) - size) / laces; + break; + + case 0x3: /* EBML lacing */ { + guint total; + if ((n = gst_matroska_ebmlnum_uint (data, size, &num)) < 0) { + gst_element_error (GST_ELEMENT (demux), "Data error"); + res = FALSE; + break; + } + data += n; size -= n; + total = lace_size[0] = num; + for (n = 1; res && n < laces - 1; n++) { + gint64 snum; + gint r; + if ((r = gst_matroska_ebmlnum_sint (data, size, &snum)) < 0) { + gst_element_error (GST_ELEMENT (demux), "Data error"); + res = FALSE; + break; + } + data += r; size -= r; + lace_size[n] = lace_size[0] + snum; + total += lace_size[n]; + } + lace_size[n] = size - total; + break; + } + } + break; + } + + if (res) { + for (n = 0; n < laces; n++) { + GstBuffer *sub = gst_buffer_create_sub (buf, + GST_BUFFER_SIZE (buf) - size, lace_size[n]); + + if (cluster_time != GST_CLOCK_TIME_NONE) + GST_BUFFER_TIMESTAMP (sub) = cluster_time + time; + + /* FIXME: duration */ + + gst_pad_push (demux->src[stream]->pad, GST_DATA (sub)); + } + } + + g_free (lace_size); + gst_buffer_unref (buf); + break; + } + + case GST_MATROSKA_ID_BLOCKDURATION: { + guint64 num; + if (!gst_ebml_read_uint (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + GST_DEBUG ("FIXME: implement support for BlockDuration"); + break; + } + + default: + GST_WARNING ("Unknown entry 0x%x in blockgroup data", id); + /* fall-through */ + + case GST_EBML_ID_VOID: + if (!gst_ebml_read_skip (GST_EBML_READ (demux))) + res = FALSE; + break; + } + + if (demux->level_up) { + demux->level_up--; + break; + } + } + + return res; +} + +static gboolean +gst_matroska_demux_parse_cluster (GstMatroskaDemux *demux) +{ + gboolean res = TRUE; + guint32 id; + guint64 cluster_time = GST_CLOCK_TIME_NONE; + + /* Not intending to look like a moron, but we only seek when + * we've parsed the headers (for indexes etc.) - so we do that + * here... Yes, this is ugly. Thanks for noticing. */ + if (demux->seek_pending != GST_CLOCK_TIME_NONE) { + GstMatroskaIndex *entry = gst_matroskademux_seek (demux); + if (entry != NULL) { + GstEvent *discont = gst_event_new_discontinuous (FALSE, GST_FORMAT_TIME, + entry->time); + gst_ebml_read_seek (GST_EBML_READ (demux), entry->pos); + gst_matroska_demux_handle_sink_event (demux, discont); + } + } + + while (res) { + if (!(id = gst_ebml_peek_id (GST_EBML_READ (demux), &demux->level_up))) { + res = FALSE; + break; + } else if (demux->level_up) { + demux->level_up--; + break; + } + + switch (id) { + /* cluster timecode */ + case GST_MATROSKA_ID_CLUSTERTIMECODE: { + guint64 num; + if (!gst_ebml_read_uint (GST_EBML_READ (demux), &id, &num)) { + res = FALSE; + break; + } + cluster_time = num * demux->time_scale; + break; + } + + /* a group of blocks inside a cluster */ + case GST_MATROSKA_ID_BLOCKGROUP: + if (!gst_ebml_read_master (GST_EBML_READ (demux), &id)) { + res = FALSE; + break; + } + res = gst_matroska_demux_parse_blockgroup (demux, cluster_time); + break; + + default: + GST_WARNING ("Unknown entry 0x%x in cluster data", id); + /* fall-through */ + + case GST_EBML_ID_VOID: + if (!gst_ebml_read_skip (GST_EBML_READ (demux))) + res = FALSE; + break; + } + + if (demux->level_up) { + demux->level_up--; + break; + } + } + + return res; +} + +static gboolean +gst_matroska_demux_parse_contents (GstMatroskaDemux *demux) +{ + gboolean res = TRUE; + guint32 id; + + while (res) { + if (!(id = gst_ebml_peek_id (GST_EBML_READ (demux), &demux->level_up))) { + res = FALSE; + break; + } else if (demux->level_up) { + demux->level_up--; + break; + } + + switch (id) { + default: + GST_WARNING ("seekhead unimplemented"); + /* fall-through */ + + case GST_EBML_ID_VOID: + if (!gst_ebml_read_skip (GST_EBML_READ (demux))) + res = FALSE; + break; + } + + if (demux->level_up) { + demux->level_up--; + break; + } + } + + return res; +} + +static gboolean +gst_matroska_demux_loop_stream (GstMatroskaDemux *demux) +{ + GstEvent *status; + gboolean res = TRUE; + guint32 id, remain; + + /* we've found our segment, start reading the different contents in here */ + while (res) { + if (!(id = gst_ebml_peek_id (GST_EBML_READ (demux), &demux->level_up))) { + res = FALSE; + break; + } else if (demux->level_up) { + /* end of segment */ + demux->level_up--; + gst_matroska_demux_handle_sink_event (demux, gst_event_new (GST_EVENT_EOS)); + break; + } + + switch (id) { + /* stream info */ + case GST_MATROSKA_ID_INFO: { + if (!gst_ebml_read_master (GST_EBML_READ (demux), &id)) { + res = FALSE; + break; + } + res = gst_matroska_demux_parse_info (demux); + break; + } + + /* track info headers */ + case GST_MATROSKA_ID_TRACKS: { + if (!gst_ebml_read_master (GST_EBML_READ (demux), &id)) { + res = FALSE; + break; + } + res = gst_matroska_demux_parse_tracks (demux); + break; + } + + /* stream index */ + case GST_MATROSKA_ID_CUES: { + if (!gst_ebml_read_master (GST_EBML_READ (demux), &id)) { + res = FALSE; + break; + } + res = gst_matroska_demux_parse_index (demux); + break; + } + + /* metadata */ + case GST_MATROSKA_ID_TAGS: { + if (!gst_ebml_read_master (GST_EBML_READ (demux), &id)) { + res = FALSE; + break; + } + res = gst_matroska_demux_parse_metadata (demux); + break; + } + + /* file index (if seekable, seek to Cues/Tags to parse it) */ + case GST_MATROSKA_ID_SEEKHEAD: { + if (!gst_ebml_read_master (GST_EBML_READ (demux), &id)) { + res = FALSE; + break; + } + res = gst_matroska_demux_parse_contents (demux); + break; + } + + case GST_MATROSKA_ID_CLUSTER: { + if (!gst_ebml_read_master (GST_EBML_READ (demux), &id)) { + res = FALSE; + break; + } + /* The idea is that we parse one cluster per loop and + * then break out of the loop here. In the next call + * of the loopfunc, we will get back here with the + * next cluster. If an error occurs, we didn't + * actually push a buffer, but we still want to break + * out of the loop to handle a possible error. We'll + * get back here if it's recoverable. */ + gst_matroska_demux_parse_cluster (demux); + demux->state = GST_MATROSKA_DEMUX_STATE_DATA; + res = FALSE; + break; + } + + default: + GST_WARNING ("Unknown matroska file header ID 0x%x", id); + /* fall-through */ + + case GST_EBML_ID_VOID: + if (!gst_ebml_read_skip (GST_EBML_READ (demux))) + res = FALSE; + break; + } + + /* check BS for EOS */ + gst_bytestream_get_status (GST_EBML_READ (demux)->bs, &remain, &status); + if (demux->level_up || (status && GST_EVENT_TYPE (status) == GST_EVENT_EOS)) { + /* end of segment */ + if (status) + gst_event_unref (status); + demux->level_up--; + gst_matroska_demux_handle_sink_event (demux, gst_event_new (GST_EVENT_EOS)); + break; + } + } + + return res; +} + +static void +gst_matroska_demux_loop (GstElement *element) +{ + GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (element); + + /* first, if we're to start, let's actually get starting */ + if (demux->state == GST_MATROSKA_DEMUX_STATE_START) { + if (!gst_matroska_demux_init_stream (demux)) { + return; + } + demux->state = GST_MATROSKA_DEMUX_STATE_HEADER; + } + + gst_matroska_demux_loop_stream (demux); +} + +static GstCaps * +gst_matroska_demux_vfw_caps (guint32 codec_fcc, + gst_riff_strf_vids *vids) +{ + GstCaps *caps = NULL; + + switch (codec_fcc) { + case GST_MAKE_FOURCC('I','4','2','0'): + case GST_MAKE_FOURCC('Y','U','Y','2'): + caps = GST_CAPS_NEW ( + "matroskademux_vfw_video_src_raw", + "video/x-raw-yuv", + "format", GST_PROPS_FOURCC (codec_fcc) + ); + break; + + case GST_MAKE_FOURCC('M','J','P','G'): /* YUY2 MJPEG */ + case GST_MAKE_FOURCC('J','P','E','G'): /* generic (mostly RGB) MJPEG */ + case GST_MAKE_FOURCC('P','I','X','L'): /* Miro/Pinnacle fourccs */ + case GST_MAKE_FOURCC('V','I','X','L'): /* Miro/Pinnacle fourccs */ + caps = GST_CAPS_NEW ( + "matroskademux_vfw_video_src_jpeg", + "video/x-jpeg", + NULL + ); + break; + + case GST_MAKE_FOURCC('H','F','Y','U'): + caps = GST_CAPS_NEW ( + "matroskademux_vfw_video_src_hfyu", + "video/x-huffyuv", + NULL + ); + break; + + case GST_MAKE_FOURCC('M','P','E','G'): + case GST_MAKE_FOURCC('M','P','G','I'): + caps = GST_CAPS_NEW ( + "matroskademux_vfw_video_src_mpeg", + "video/mpeg", + "systemstream", GST_PROPS_BOOLEAN (FALSE), + "mpegversion", GST_PROPS_BOOLEAN (1) + ); + break; + + case GST_MAKE_FOURCC('H','2','6','3'): + case GST_MAKE_FOURCC('i','2','6','3'): + case GST_MAKE_FOURCC('L','2','6','3'): + case GST_MAKE_FOURCC('M','2','6','3'): + case GST_MAKE_FOURCC('V','D','O','W'): + case GST_MAKE_FOURCC('V','I','V','O'): + case GST_MAKE_FOURCC('x','2','6','3'): + caps = GST_CAPS_NEW ( + "matroskademux_vfw_video_src_263", + "video/x-h263", + NULL + ); + break; + + case GST_MAKE_FOURCC('D','I','V','3'): + case GST_MAKE_FOURCC('D','I','V','4'): + case GST_MAKE_FOURCC('D','I','V','5'): + caps = GST_CAPS_NEW ( + "matroskademux_vfw_video_src_divx3", + "video/x-divx", + "divxversion", GST_PROPS_INT(3) + ); + break; + + case GST_MAKE_FOURCC('d','i','v','x'): + case GST_MAKE_FOURCC('D','I','V','X'): + case GST_MAKE_FOURCC('D','X','5','0'): + caps = GST_CAPS_NEW ( + "matroskademux_vfw_video_src_divx5", + "video/x-divx", + "divxversion", GST_PROPS_INT(5) + ); + break; + + case GST_MAKE_FOURCC('X','V','I','D'): + case GST_MAKE_FOURCC('x','v','i','d'): + caps = GST_CAPS_NEW ( + "matroskademux_vfw_video_src", + "video/x-xvid", + NULL + ); + break; + + case GST_MAKE_FOURCC('M','P','G','4'): + caps = GST_CAPS_NEW ( + "matroskademux_vfw_video_src", + "video/x-msmpeg", + "msmpegversion", GST_PROPS_INT (41) + ); + break; + + case GST_MAKE_FOURCC('M','P','4','2'): + caps = GST_CAPS_NEW ( + "matroskademux_vfw_video_src", + "video/x-msmpeg", + "msmpegversion", GST_PROPS_INT (42) + ); + break; + + case GST_MAKE_FOURCC('M','P','4','3'): + caps = GST_CAPS_NEW ( + "matroskademux_vfw_video_src", + "video/x-msmpeg", + "msmpegversion", GST_PROPS_INT (43) + ); + break; + + case GST_MAKE_FOURCC('3','I','V','1'): + case GST_MAKE_FOURCC('3','I','V','2'): + caps = GST_CAPS_NEW ( + "matroskademux_vfw_video_src_3ivx", + "video/x-3ivx", + NULL + ); + break; + + case GST_MAKE_FOURCC('D','V','S','D'): + case GST_MAKE_FOURCC('d','v','s','d'): + caps = GST_CAPS_NEW ( + "matroskademux_vfw_video_src", + "video/x-dv", + "systemstream", GST_PROPS_BOOLEAN (FALSE) + ); + break; + + case GST_MAKE_FOURCC('W','M','V','1'): + caps = GST_CAPS_NEW ( + "matroskademux_vfw_video_src_wmv1", + "video/x-wmv", + "wmvversion", GST_PROPS_INT (1) + ); + break; + + case GST_MAKE_FOURCC('W','M','V','2'): + caps = GST_CAPS_NEW ( + "matroskademux_vfw_video_src_wmv2", + "video/x-wmv", + "wmvversion", GST_PROPS_INT (2) + ); + break; + + default: + GST_WARNING ("matroskademux: unkown VFW video format " GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (codec_fcc)); + break; + } + + return caps; +} + +static GstCaps * +gst_matroska_demux_video_caps (GstMatroskaTrackVideoContext *videocontext, + const gchar *codec_id, + gpointer data, + guint size) +{ + GstMatroskaTrackContext *context = + (GstMatroskaTrackContext *) videocontext; + GstCaps *caps = NULL; + + if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_VFW_FOURCC)) { + gst_riff_strf_vids *vids = NULL; + + if (data) { + vids = (gst_riff_strf_vids *) data; + + /* assure size is big enough */ + if (size < 24) { + GST_WARNING ("Too small BITMAPINFOHEADER (%d bytes)", size); + return NULL; + } + if (size < sizeof (gst_riff_strf_vids)) { + vids = (gst_riff_strf_vids *) g_realloc (vids, sizeof (gst_riff_strf_vids)); + } + + /* little-endian -> byte-order */ + vids->size = GUINT32_FROM_LE (vids->size); + vids->width = GUINT32_FROM_LE (vids->width); + vids->height = GUINT32_FROM_LE (vids->height); + vids->planes = GUINT16_FROM_LE (vids->planes); + vids->bit_cnt = GUINT16_FROM_LE (vids->bit_cnt); + vids->compression = GUINT32_FROM_LE (vids->compression); + vids->image_size = GUINT32_FROM_LE (vids->image_size); + vids->xpels_meter = GUINT32_FROM_LE (vids->xpels_meter); + vids->ypels_meter = GUINT32_FROM_LE (vids->ypels_meter); + vids->num_colors = GUINT32_FROM_LE (vids->num_colors); + vids->imp_colors = GUINT32_FROM_LE (vids->imp_colors); + } + + caps = gst_matroska_demux_vfw_caps (vids->compression, vids); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_UNCOMPRESSED)) { + /* how nice, this is undocumented... */ + if (videocontext != NULL) { + guint32 fourcc = 0; + + switch (videocontext->fourcc) { + case GST_MAKE_FOURCC ('I','4','2','0'): + case GST_MAKE_FOURCC ('Y','U','Y','2'): + fourcc = videocontext->fourcc; + break; + + default: + GST_DEBUG ("Unknown fourcc " GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (videocontext->fourcc)); + return NULL; + } + + caps = GST_CAPS_NEW ("matroskademux_src_uncompressed", + "video/x-raw-yuv", + "format", GST_PROPS_FOURCC (fourcc)); + } else { + caps = GST_CAPS_NEW ("matroskademux_src_uncompressed", + "video/x-raw-yuv", + "format", GST_PROPS_LIST ( + GST_PROPS_FOURCC (GST_MAKE_FOURCC ('I','4','2','0')), + GST_PROPS_FOURCC (GST_MAKE_FOURCC ('Y','U','Y','2')), + GST_PROPS_FOURCC (GST_MAKE_FOURCC ('Y','V','1','2')) + ) + ); + } + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_SP)) { + caps = GST_CAPS_NEW ("matroskademux_src_divx4", + "video/x-divx", + "divxversion", GST_PROPS_INT (4)); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP) || + !strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AP)) { + caps = GST_CAPS_NEW ("matroskademux_src_divx5", + "video/x-divx", + "divxversion", GST_PROPS_INT (5)); + caps = gst_caps_append (caps, + GST_CAPS_NEW ("matroskademux_src_xvid", + "video/x-xvid", + NULL)); + caps = gst_caps_append (caps, + GST_CAPS_NEW ("matroskademux_src_mpeg4asp/ap", + "video/mpeg", + "mpegversion", GST_PROPS_INT (4), + "systemstream", GST_PROPS_BOOLEAN (FALSE))); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MSMPEG4V3)) { + caps = GST_CAPS_NEW ("matroskademux_src_msmpeg4v3", + "video/x-divx", + "divxversion", GST_PROPS_INT (3)); + caps = gst_caps_append (caps, + GST_CAPS_NEW ("matroskademux_src_divx3", + "video/x-msmpeg", + "msmpegversion", GST_PROPS_INT (43))); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG1) || + !strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG2)) { + gint mpegversion = -1; + + if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG1)) + mpegversion = 1; + else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG2)) + mpegversion = 2; + else + g_assert (0); + + caps = GST_CAPS_NEW ("matroska_demux_mpeg1", + "video/mpeg", + "systemstream", GST_PROPS_BOOLEAN (FALSE), + "mpegversion", GST_PROPS_INT (mpegversion)); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MJPEG)) { + caps = GST_CAPS_NEW ("matroska_demux_mjpeg", + "video/x-jpeg", + NULL); + } else { + GST_WARNING ("Unknown codec '%s', cannot build Caps", + codec_id); + } + + if (caps != NULL) { + GstCaps *one; + GstPropsEntry *fps = NULL; + GstPropsEntry *width = NULL, *height = NULL; + GstPropsEntry *pixel_width = NULL, *pixel_height = NULL; + + for (one = caps; one != NULL; one = one->next) { + if (videocontext != NULL) { + if (videocontext->pixel_width > 0 && + videocontext->pixel_height > 0) { + gint w = videocontext->pixel_width; + gint h = videocontext->pixel_height; + + width = gst_props_entry_new ("width", + GST_PROPS_INT (w)); + height = gst_props_entry_new ("height", + GST_PROPS_INT (h)); + } + if (videocontext->display_width > 0 && + videocontext->display_height > 0) { + gint w = 100 * videocontext->display_width / videocontext->pixel_width; + gint h = 100 * videocontext->display_height / videocontext->pixel_height; + + pixel_width = gst_props_entry_new ("pixel_width", + GST_PROPS_INT (w)); + pixel_height = gst_props_entry_new ("pixel_height", + GST_PROPS_INT (h)); + } + if (context->default_duration > 0) { + gfloat framerate = 1. * GST_SECOND / context->default_duration; + + fps = gst_props_entry_new ("framerate", + GST_PROPS_FLOAT (framerate)); + } else { + /* sort of a hack to get most codecs to support, + * even if the default_duration is missing */ + fps = gst_props_entry_new ("framerate", GST_PROPS_FLOAT (25.)); + } + } else { + width = gst_props_entry_new ("width", + GST_PROPS_INT_RANGE (16, 4096)); + height = gst_props_entry_new ("height", + GST_PROPS_INT_RANGE (16, 4096)); + + pixel_width = gst_props_entry_new ("pixel_width", + GST_PROPS_INT_RANGE (0, 255)); + pixel_height = gst_props_entry_new ("pixel_height", + GST_PROPS_INT_RANGE (0, 255)); + + fps = gst_props_entry_new ("framerate", + GST_PROPS_FLOAT_RANGE (0, G_MAXFLOAT)); + } + + if (one->properties == NULL) { + one->properties = gst_props_empty_new (); + } + + if (width != NULL && height != NULL) { + gst_props_add_entry (one->properties, width); + gst_props_add_entry (one->properties, height); + } + + if (pixel_width != NULL && pixel_height != NULL) { + gst_props_add_entry (one->properties, pixel_width); + gst_props_add_entry (one->properties, pixel_height); + } + + if (fps != NULL) { + gst_props_add_entry (one->properties, fps); + } + } + } + + return caps; +} + +static GstCaps * +gst_matroskademux_acm_caps (guint16 codec_id, + gst_riff_strf_auds *auds) +{ + GstCaps *caps = NULL; + + switch (codec_id) { + case GST_RIFF_WAVE_FORMAT_MPEGL3: /* mp3 */ + caps = GST_CAPS_NEW ("matroskademux_acm_audio_src_mp3", + "audio/mpeg", + "layer", GST_PROPS_INT (3)); + break; + + case GST_RIFF_WAVE_FORMAT_MPEGL12: /* mp1 or mp2 */ + caps = GST_CAPS_NEW ("matroskademux_acm_audio_src_mp12", + "audio/mpeg", + "layer", GST_PROPS_INT (2)); + break; + + case GST_RIFF_WAVE_FORMAT_PCM: /* PCM/wav */ { + GstPropsEntry *width = NULL, *depth = NULL, *signedness = NULL; + + if (auds != NULL) { + gint ba = GUINT16_FROM_LE (auds->blockalign); + gint ch = GUINT16_FROM_LE (auds->channels); + gint ws = GUINT16_FROM_LE (auds->size); + + width = gst_props_entry_new ("width", + GST_PROPS_INT (ba * 8 / ch)); + depth = gst_props_entry_new ("depth", + GST_PROPS_INT (ws)); + signedness = gst_props_entry_new ("signed", + GST_PROPS_BOOLEAN (ws != 8)); + } else { + signedness = gst_props_entry_new ("signed", + GST_PROPS_LIST ( + GST_PROPS_BOOLEAN (TRUE), + GST_PROPS_BOOLEAN (FALSE))); + width = gst_props_entry_new ("width", + GST_PROPS_LIST ( + GST_PROPS_INT (8), + GST_PROPS_INT (16))); + depth = gst_props_entry_new ("depth", + GST_PROPS_LIST ( + GST_PROPS_INT (8), + GST_PROPS_INT (16))); + } + + caps = GST_CAPS_NEW ("matroskademux_acm_audio_src_pcm", + "audio/x-raw-int", + "endianness", + GST_PROPS_INT (G_LITTLE_ENDIAN)); + gst_props_add_entry (caps->properties, width); + gst_props_add_entry (caps->properties, depth); + gst_props_add_entry (caps->properties, signedness); + } + break; + + case GST_RIFF_WAVE_FORMAT_MULAW: + if (auds != NULL && auds->size != 8) { + g_warning ("invalid depth (%d) of mulaw audio, overwriting.", + auds->size); + } + caps = GST_CAPS_NEW ("matroskademux_acm_audio_src", + "audio/x-mulaw", + NULL); + break; + + case GST_RIFF_WAVE_FORMAT_ALAW: + if (auds != NULL && auds->size != 8) { + g_warning ("invalid depth (%d) of alaw audio, overwriting.", + auds->size); + } + caps = GST_CAPS_NEW ("matroskademux_acm_audio_src", + "audio/x-alaw", + NULL); + break; + + case GST_RIFF_WAVE_FORMAT_VORBIS1: /* ogg/vorbis mode 1 */ + case GST_RIFF_WAVE_FORMAT_VORBIS2: /* ogg/vorbis mode 2 */ + case GST_RIFF_WAVE_FORMAT_VORBIS3: /* ogg/vorbis mode 3 */ + case GST_RIFF_WAVE_FORMAT_VORBIS1PLUS: /* ogg/vorbis mode 1+ */ + case GST_RIFF_WAVE_FORMAT_VORBIS2PLUS: /* ogg/vorbis mode 2+ */ + case GST_RIFF_WAVE_FORMAT_VORBIS3PLUS: /* ogg/vorbis mode 3+ */ + caps = GST_CAPS_NEW ("matroskademux_acm_audio_src_vorbis", + "audio/x-vorbis", + NULL); + break; + + case GST_RIFF_WAVE_FORMAT_A52: + caps = GST_CAPS_NEW ("matroskademux_acm_audio_src_ac3", + "audio/x-ac3", + NULL); + break; + + default: + GST_WARNING ("matroskademux: unkown ACM audio format 0x%04x", + codec_id); + break; + } + + return caps; +} + +static GstCaps * +gst_matroska_demux_audio_caps (GstMatroskaTrackAudioContext *audiocontext, + const gchar *codec_id, + gpointer data, + guint size) +{ + GstMatroskaTrackContext *context = + (GstMatroskaTrackContext *) audiocontext; + GstCaps *caps = NULL; + + if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L1) || + !strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L2) || + !strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L3)) { + gint layer = -1; + + if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L1)) + layer = 1; + else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L2)) + layer = 2; + else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L3)) + layer = 3; + else + g_assert (0); + + caps = GST_CAPS_NEW ("matroskademux_mpeg1-l1", + "audio/mpeg", + "mpegversion", GST_PROPS_INT (1), + "systemstream", GST_PROPS_BOOLEAN (FALSE), + "layer", GST_PROPS_INT (layer)); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_BE) || + !strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_LE)) { + gint endianness = -1; + GstPropsEntry *depth, *width, *sign; + + if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_BE)) + endianness = G_BIG_ENDIAN; + else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_LE)) + endianness = G_LITTLE_ENDIAN; + else + g_assert (0); + + if (context != NULL) { + width = gst_props_entry_new ("width", + GST_PROPS_INT (audiocontext->bitdepth)); + depth = gst_props_entry_new ("depth", + GST_PROPS_INT (audiocontext->bitdepth)); + sign = gst_props_entry_new ("signed", + GST_PROPS_BOOLEAN (audiocontext->bitdepth == 8)); + } else { + width = gst_props_entry_new ("width", GST_PROPS_LIST ( + GST_PROPS_INT (8), + GST_PROPS_INT (16))); + depth = gst_props_entry_new ("depth", GST_PROPS_LIST ( + GST_PROPS_INT (8), + GST_PROPS_INT (16))); + sign = gst_props_entry_new ("signed", GST_PROPS_LIST ( + GST_PROPS_BOOLEAN (TRUE), + GST_PROPS_BOOLEAN (FALSE))); + } + + caps = GST_CAPS_NEW ("matroskademux_audio_raw", + "audio/x-raw-int", + "endianness", GST_PROPS_INT (endianness)); + gst_props_add_entry (caps->properties, width); + gst_props_add_entry (caps->properties, depth); + gst_props_add_entry (caps->properties, sign); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_PCM_FLOAT)) { + GstPropsEntry *width; + + if (audiocontext != NULL) { + width = gst_props_entry_new ("width", + GST_PROPS_INT (audiocontext->bitdepth)); + } else { + width = gst_props_entry_new ("width", GST_PROPS_LIST ( + GST_PROPS_INT (32), + GST_PROPS_INT (64))); + } + + caps = GST_CAPS_NEW ("matroskademux_audio_float", + "audio/x-raw-float", + "endianness", GST_PROPS_INT (G_BYTE_ORDER), + "buffer-frames", GST_PROPS_INT_RANGE (1, G_MAXINT)); + + gst_props_add_entry (caps->properties, width); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_AC3) || + !strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_DTS)) { + caps = GST_CAPS_NEW ("matroskademux_audio_ac3/dts", + "audio/x-ac3", + NULL); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_VORBIS)) { + caps = GST_CAPS_NEW ("matroskademux_audio_vorbis", + "audio/x-vorbis", + NULL); + } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_ACM)) { + gst_riff_strf_auds *auds = NULL; + + if (data) { + auds = (gst_riff_strf_auds *) data; + + /* little-endian -> byte-order */ + auds->format = GUINT16_FROM_LE (auds->format); + auds->channels = GUINT16_FROM_LE (auds->channels); + auds->rate = GUINT32_FROM_LE (auds->rate); + auds->av_bps = GUINT32_FROM_LE (auds->av_bps); + auds->blockalign = GUINT16_FROM_LE (auds->blockalign); + auds->size = GUINT16_FROM_LE (auds->size); + } + + caps = gst_matroskademux_acm_caps (auds->format, auds); + } else if (!strncmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_MPEG2, + strlen (GST_MATROSKA_CODEC_ID_AUDIO_MPEG2)) || + !strncmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_MPEG4, + strlen (GST_MATROSKA_CODEC_ID_AUDIO_MPEG4))) { + gint mpegversion = -1; + + if (!strncmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_MPEG2, + strlen (GST_MATROSKA_CODEC_ID_AUDIO_MPEG2))) + mpegversion = 2; + else if (!strncmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_MPEG4, + strlen (GST_MATROSKA_CODEC_ID_AUDIO_MPEG4))) + mpegversion = 4; + else + g_assert (0); + + caps = GST_CAPS_NEW ("matroska_demux_aac_mpeg2", + "audio/mpeg", + "mpegversion", GST_PROPS_INT (mpegversion), + "systemstream", GST_PROPS_BOOLEAN (FALSE)); + } else { + GST_WARNING ("Unknown codec '%s', cannot build Caps", + codec_id); + } + + if (caps != NULL) { + GstCaps *one; + GstPropsEntry *chans = NULL, *rate = NULL; + + for (one = caps; one != NULL; one = one->next) { + if (audiocontext != NULL) { + if (audiocontext->samplerate > 0 && + audiocontext->channels > 0) { + chans = gst_props_entry_new ("channels", + GST_PROPS_INT (audiocontext->channels)); + rate = gst_props_entry_new ("rate", + GST_PROPS_INT (audiocontext->samplerate)); + } + } else { + chans = gst_props_entry_new ("channels", + GST_PROPS_INT_RANGE (1, 6)); + rate = gst_props_entry_new ("rate", + GST_PROPS_INT_RANGE (4000, 96000)); + } + + if (caps->properties == NULL) { + caps->properties = gst_props_empty_new (); + } + + if (chans != NULL && rate != NULL) { + gst_props_add_entry (caps->properties, chans); + gst_props_add_entry (caps->properties, rate); + } + } + } + + return caps; +} + +static GstCaps * +gst_matroska_demux_complex_caps (GstMatroskaTrackComplexContext *complexcontext, + const gchar *codec_id, + gpointer data, + guint size) +{ + GstCaps *caps = NULL; + + //.. + + return caps; +} + +static GstCaps * +gst_matroska_demux_subtitle_caps (GstMatroskaTrackSubtitleContext *subtitlecontext, + const gchar *codec_id, + gpointer data, + guint size) +{ + GstCaps *caps = NULL; + + //.. + + return caps; +} + +static GstElementStateReturn +gst_matroska_demux_change_state (GstElement *element) +{ + GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (element); + + switch (GST_STATE_TRANSITION (element)) { + case GST_STATE_PAUSED_TO_READY: + gst_matroska_demux_reset (GST_ELEMENT (demux)); + break; + default: + break; + } + + if (((GstElementClass *) parent_class)->change_state) + return ((GstElementClass *) parent_class)->change_state (element); + + return GST_STATE_SUCCESS; +} + +static void +gst_matroska_demux_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GstMatroskaDemux *demux; + + g_return_if_fail (GST_IS_MATROSKA_DEMUX (object)); + demux = GST_MATROSKA_DEMUX (object); + + switch (prop_id) { + case ARG_STREAMINFO: + g_value_set_boxed (value, demux->streaminfo); + break; + case ARG_METADATA: + g_value_set_boxed (value, demux->metadata); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +gboolean +gst_matroska_demux_plugin_init (GstPlugin *plugin) +{ + gint i; + GstCaps *videosrccaps = NULL, *audiosrccaps = NULL, + *subtitlesrccaps = NULL, *temp; + const gchar *video_id[] = { + GST_MATROSKA_CODEC_ID_VIDEO_UNCOMPRESSED, + GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_SP, + GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP, + GST_MATROSKA_CODEC_ID_VIDEO_MSMPEG4V3, + GST_MATROSKA_CODEC_ID_VIDEO_MPEG1, + GST_MATROSKA_CODEC_ID_VIDEO_MPEG2, + GST_MATROSKA_CODEC_ID_VIDEO_MJPEG, + /* TODO: Real/Quicktime */ + /* FILLME */ + NULL, + }, *audio_id[] = { + GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L1, + GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L2, + GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L3, + GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_BE, + GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_LE, + GST_MATROSKA_CODEC_ID_AUDIO_PCM_FLOAT, + GST_MATROSKA_CODEC_ID_AUDIO_AC3, + GST_MATROSKA_CODEC_ID_AUDIO_VORBIS, + GST_MATROSKA_CODEC_ID_AUDIO_MPEG2, + GST_MATROSKA_CODEC_ID_AUDIO_MPEG4, + /* TODO: AC3-9/10, Real, Musepack, Quicktime */ + /* FILLME */ + NULL, + }, *complex_id[] = { + /* FILLME */ + NULL, + }, *subtitle_id[] = { + /* FILLME */ + NULL, + }; + guint32 video_fourcc[] = { + GST_MAKE_FOURCC ('I','4','2','0'), + GST_MAKE_FOURCC ('Y','U','Y','2'), + GST_MAKE_FOURCC ('M','J','P','G'), + GST_MAKE_FOURCC ('H','F','Y','U'), + GST_MAKE_FOURCC ('M','P','E','G'), + GST_MAKE_FOURCC ('H','2','6','3'), + GST_MAKE_FOURCC ('D','I','V','3'), + GST_MAKE_FOURCC ('D','X','5','0'), + GST_MAKE_FOURCC ('X','V','I','D'), + GST_MAKE_FOURCC ('M','P','G','4'), + GST_MAKE_FOURCC ('M','P','4','2'), + GST_MAKE_FOURCC ('M','P','4','3'), + GST_MAKE_FOURCC ('3','I','V','1'), + GST_MAKE_FOURCC ('D','V','S','D'), + GST_MAKE_FOURCC ('W','M','V','1'), + GST_MAKE_FOURCC ('W','M','V','2'), + /* FILLME */ + 0, + }; + guint16 audio_tag[] = { + GST_RIFF_WAVE_FORMAT_MPEGL3, + GST_RIFF_WAVE_FORMAT_MPEGL12, + GST_RIFF_WAVE_FORMAT_PCM, + GST_RIFF_WAVE_FORMAT_MULAW, + GST_RIFF_WAVE_FORMAT_ALAW, + GST_RIFF_WAVE_FORMAT_VORBIS1, + GST_RIFF_WAVE_FORMAT_A52, + /* FILLME */ + 0, + }; + + /* this filter needs the riff parser */ + if (!gst_library_load ("gstbytestream")) + return FALSE; + + /* video src template */ + for (i = 0; video_id[i] != NULL; i++) { + temp = gst_matroska_demux_video_caps (NULL, video_id[i], NULL, 0); + videosrccaps = gst_caps_append (videosrccaps, temp); + } + for (i = 0; video_fourcc[i] != 0; i++) { + temp = gst_matroska_demux_vfw_caps (video_fourcc[i], NULL); + videosrccaps = gst_caps_append (videosrccaps, temp); + } + for (i = 0; complex_id[i] != NULL; i++) { + temp = gst_matroska_demux_complex_caps (NULL, video_id[i], NULL, 0); + videosrccaps = gst_caps_append (videosrccaps, temp); + } + videosrctempl = gst_pad_template_new ("video_%02d", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + videosrccaps, NULL); + + /* audio src template */ + for (i = 0; audio_id[i] != NULL; i++) { + temp = gst_matroska_demux_audio_caps (NULL, audio_id[i], NULL, 0); + audiosrccaps = gst_caps_append (audiosrccaps, temp); + } + for (i = 0; audio_tag[i] != 0; i++) { + temp = gst_matroskademux_acm_caps (audio_tag[i], NULL); + audiosrccaps = gst_caps_append (audiosrccaps, temp); + } + audiosrctempl = gst_pad_template_new ("audio_%02d", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + audiosrccaps, NULL); + + /* subtitle src template */ + for (i = 0; subtitle_id[i] != NULL; i++) { + temp = gst_matroska_demux_subtitle_caps (NULL, subtitle_id[i], NULL, 0); + subtitlesrccaps = gst_caps_append (subtitlesrccaps, temp); + } + subtitlesrctempl = gst_pad_template_new ("subtitle_%02d", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + subtitlesrccaps, NULL); + + /* create an elementfactory for the matroska_demux element */ + if (!gst_element_register (plugin, "matroskademux", + GST_RANK_PRIMARY, GST_TYPE_MATROSKA_DEMUX)) + return FALSE; + + return TRUE; +} diff --git a/gst/matroska/matroska-demux.h b/gst/matroska/matroska-demux.h new file mode 100644 index 0000000..4e2062a --- /dev/null +++ b/gst/matroska/matroska-demux.h @@ -0,0 +1,102 @@ +/* GStreamer Matroska muxer/demuxer + * (c) 2003 Ronald Bultje + * + * matroska-demux.h: matroska file/stream demuxer definition + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_MATROSKA_DEMUX_H__ +#define __GST_MATROSKA_DEMUX_H__ + +#include +#include + +#include "ebml-read.h" +#include "matroska-ids.h" + +G_BEGIN_DECLS + +#define GST_TYPE_MATROSKA_DEMUX \ + (gst_matroska_demux_get_type ()) +#define GST_MATROSKA_DEMUX(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_MATROSKA_DEMUX, GstMatroskaDemux)) +#define GST_MATROSKA_DEMUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_MATROSKA_DEMUX, GstMatroskaDemux)) +#define GST_IS_MATROSKA_DEMUX(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_MATROSKA_DEMUX)) +#define GST_IS_MATROSKA_DEMUX_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_MATROSKA_DEMUX)) + +#define GST_MATROSKA_DEMUX_MAX_STREAMS 64 + +typedef enum { + GST_MATROSKA_DEMUX_STATE_START, + GST_MATROSKA_DEMUX_STATE_HEADER, + GST_MATROSKA_DEMUX_STATE_DATA +} GstMatroskaDemuxState; + +typedef struct _GstMatroskaDemuxIndex { + guint64 pos; /* of the corresponding *cluster*! */ + guint16 track; /* reference to 'num' */ + guint64 time; /* in nanoseconds */ +} GstMatroskaDemuxIndex; + +typedef struct _GstMatroskaDemux { + GstEbmlRead parent; + + /* pads */ + GstPad *sinkpad; + GstMatroskaTrackContext *src[GST_MATROSKA_DEMUX_MAX_STREAMS]; + guint num_streams, + num_v_streams, num_a_streams, num_t_streams; + + /* metadata */ + GstCaps *metadata, + *streaminfo; + gchar *muxing_app, *writing_app; + gint64 created; + + /* state */ + GstMatroskaDemuxState state; + guint level_up; + + /* a cue (index) table */ + GstMatroskaIndex *index; + guint num_indexes; + + /* timescale in the file */ + guint64 time_scale; + + /* length, position (time, ns) */ + guint64 duration, + pos; + + /* a possible pending seek */ + guint64 seek_pending; +} GstMatroskaDemux; + +typedef struct _GstMatroskaDemuxClass { + GstEbmlReadClass parent; +} GstMatroskaDemuxClass; + +GType gst_matroska_demux_get_type (void); + +gboolean gst_matroska_demux_plugin_init (GstPlugin *plugin); + +G_END_DECLS + +#endif /* __GST_MATROSKA_DEMUX_H__ */ diff --git a/gst/matroska/matroska-ids.h b/gst/matroska/matroska-ids.h new file mode 100644 index 0000000..455925e --- /dev/null +++ b/gst/matroska/matroska-ids.h @@ -0,0 +1,237 @@ +/* GStreamer Matroska muxer/demuxer + * (c) 2003 Ronald Bultje + * + * matroska-ids.h: matroska file/stream data IDs + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_MATROSKA_IDS_H__ +#define __GST_MATROSKA_IDS_H__ + +#include "ebml-ids.h" + +/* + * EBML element IDs. max. 32-bit. + */ + +/* toplevel segment */ +#define GST_MATROSKA_ID_SEGMENT 0x18538067 + +/* matroska top-level master IDs */ +#define GST_MATROSKA_ID_INFO 0x1549A966 +#define GST_MATROSKA_ID_TRACKS 0x1654AE6B +#define GST_MATROSKA_ID_CUES 0x1C53BB6B +#define GST_MATROSKA_ID_TAGS 0x1254C367 +#define GST_MATROSKA_ID_SEEKHEAD 0x114D9B74 +#define GST_MATROSKA_ID_CLUSTER 0x1F43B675 + +/* IDs in the info master */ +#define GST_MATROSKA_ID_TIMECODESCALE 0x2AD7B1 +#define GST_MATROSKA_ID_DURATION 0x4489 +#define GST_MATROSKA_ID_WRITINGAPP 0x5741 +#define GST_MATROSKA_ID_MUXINGAPP 0x4D80 +#define GST_MATROSKA_ID_DATEUTC 0x4461 + +/* ID in the tracks master */ +#define GST_MATROSKA_ID_TRACKENTRY 0xAE + +/* IDs in the trackentry master */ +#define GST_MATROSKA_ID_TRACKNUMBER 0xD7 +#define GST_MATROSKA_ID_TRACKUID 0x73C5 +#define GST_MATROSKA_ID_TRACKTYPE 0x83 +#define GST_MATROSKA_ID_TRACKAUDIO 0xE1 +#define GST_MATROSKA_ID_TRACKVIDEO 0xE0 +#define GST_MATROSKA_ID_CODECID 0x86 +#define GST_MATROSKA_ID_CODECPRIVATE 0x63A2 +#define GST_MATROSKA_ID_CODECNAME 0x258688 +#define GST_MATROSKA_ID_CODECINFOURL 0x3B4040 +#define GST_MATROSKA_ID_CODECDOWNLOADURL 0x26B240 +#define GST_MATROSKA_ID_TRACKNAME 0x536E +#define GST_MATROSKA_ID_TRACKLANGUAGE 0x22B59C +#define GST_MATROSKA_ID_TRACKFLAGENABLED 0xB9 +#define GST_MATROSKA_ID_TRACKFLAGDEFAULT 0x88 +#define GST_MATROSKA_ID_TRACKFLAGLACING 0x9C +#define GST_MATROSKA_ID_TRACKMINCACHE 0x6DE7 +#define GST_MATROSKA_ID_TRACKMAXCACHE 0x6DF8 +#define GST_MATROSKA_ID_TRACKDEFAULTDURATION 0x23E383 + +/* IDs in the trackvideo master */ +#define GST_MATROSKA_ID_VIDEOFRAMERATE 0x2383E3 +#define GST_MATROSKA_ID_VIDEODISPLAYWIDTH 0x54B0 +#define GST_MATROSKA_ID_VIDEODISPLAYHEIGHT 0x54BA +#define GST_MATROSKA_ID_VIDEOPIXELWIDTH 0xB0 +#define GST_MATROSKA_ID_VIDEOPIXELHEIGHT 0xBA +#define GST_MATROSKA_ID_VIDEOFLAGINTERLACED 0x9A +#define GST_MATROSKA_ID_VIDEOSTEREOMODE 0x53B9 +#define GST_MATROSKA_ID_VIDEOASPECTRATIO 0x54B3 +#define GST_MATROSKA_ID_VIDEOCOLOURSPACE 0x2EB524 + +/* IDs in the trackaudio master */ +#define GST_MATROSKA_ID_AUDIOSAMPLINGFREQ 0xB5 +#define GST_MATROSKA_ID_AUDIOBITDEPTH 0x6264 +#define GST_MATROSKA_ID_AUDIOCHANNELS 0x9F + +/* ID in the cues master */ +#define GST_MATROSKA_ID_POINTENTRY 0xBB + +/* IDs in the pointentry master */ +#define GST_MATROSKA_ID_CUETIME 0xB3 +#define GST_MATROSKA_ID_CUETRACKPOSITION 0xB7 + +/* IDs in the cuetrackposition master */ +#define GST_MATROSKA_ID_CUETRACK 0xF7 +#define GST_MATROSKA_ID_CUECLUSTERPOSITION 0xF1 + +/* IDs in the tags master */ +/* TODO */ + +/* IDs in the seekhead master */ +#define GST_MATROSKA_ID_SEEKENTRY 0x4DBB + +/* IDs in the seekpoint master */ +#define GST_MATROSKA_ID_SEEKID 0x53AB +#define GST_MATROSKA_ID_SEEKPOSITION 0x53AC + +/* IDs in the cluster master */ +#define GST_MATROSKA_ID_CLUSTERTIMECODE 0xE7 +#define GST_MATROSKA_ID_BLOCKGROUP 0xA0 + +/* IDs in the blockgroup master */ +#define GST_MATROSKA_ID_BLOCK 0xA1 +#define GST_MATROSKA_ID_BLOCKDURATION 0x9B + +/* + * Matroska Codec IDs. Strings. + */ + +#define GST_MATROSKA_CODEC_ID_VIDEO_VFW_FOURCC "V_MS/VFW/FOURCC" +#define GST_MATROSKA_CODEC_ID_VIDEO_UNCOMPRESSED "V_UNCOMPRESSED" +#define GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_SP "V_MPEG4/ISO/SP" +#define GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP "V_MPEG4/ISO/ASP" +#define GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AP "V_MPEG4/ISO/AP" +#define GST_MATROSKA_CODEC_ID_VIDEO_MSMPEG4V3 "V_MPEG4/MS/V3" +#define GST_MATROSKA_CODEC_ID_VIDEO_MPEG1 "V_MPEG1" +#define GST_MATROSKA_CODEC_ID_VIDEO_MPEG2 "V_MPEG2" +#define GST_MATROSKA_CODEC_ID_VIDEO_MJPEG "V_MJPEG" +/* TODO: Real/Quicktime */ + +#define GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L1 "A_MPEG/L1" +#define GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L2 "A_MPEG/L2" +#define GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L3 "A_MPEG/L3" +#define GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_BE "A_PCM/INT/BIG" +#define GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_LE "A_PCM/INT/LIT" +#define GST_MATROSKA_CODEC_ID_AUDIO_PCM_FLOAT "A_PCM/FLOAT/IEEE" +#define GST_MATROSKA_CODEC_ID_AUDIO_AC3 "A_AC3" +#define GST_MATROSKA_CODEC_ID_AUDIO_DTS "A_DTS" +#define GST_MATROSKA_CODEC_ID_AUDIO_VORBIS "A_VORBIS" +#define GST_MATROSKA_CODEC_ID_AUDIO_ACM "A_MS/ACM" +#define GST_MATROSKA_CODEC_ID_AUDIO_MPEG2 "A_AAC/MPEG2/" +#define GST_MATROSKA_CODEC_ID_AUDIO_MPEG4 "A_AAC/MPEG4/" +/* TODO: AC3-9/10, Real, Musepack, Quicktime */ + +/* + * Enumerations for various types (mapping from binary + * value to what it actually means). + */ + +typedef enum { + GST_MATROSKA_TRACK_TYPE_VIDEO = 0x1, + GST_MATROSKA_TRACK_TYPE_AUDIO = 0x2, + GST_MATROSKA_TRACK_TYPE_COMPLEX = 0x3, + GST_MATROSKA_TRACK_TYPE_LOGO = 0x10, + GST_MATROSKA_TRACK_TYPE_SUBTITLE = 0x11, + GST_MATROSKA_TRACK_TYPE_CONTROL = 0x20, +} GstMatroskaTrackType; + +typedef enum { + GST_MATROSKA_EYE_MODE_MONO = 0x0, + GST_MATROSKA_EYE_MODE_RIGHT = 0x1, + GST_MATROSKA_EYE_MODE_LEFT = 0x2, + GST_MATROSKA_EYE_MODE_BOTH = 0x3, +} GstMatroskaEyeMode; + +typedef enum { + GST_MATROSKA_ASPECT_RATIO_MODE_FREE = 0x0, + GST_MATROSKA_ASPECT_RATIO_MODE_KEEP = 0x1, + GST_MATROSKA_ASPECT_RATIO_MODE_FIXED = 0x2, +} GstMatroskaAspectRatioMode; + +/* + * These aren't in any way "matroska-form" things, + * it's just something I use in the muxer/demuxer. + */ + +typedef enum { + GST_MATROSKA_TRACK_ENABLED = (1<<0), + GST_MATROSKA_TRACK_DEFAULT = (1<<1), + GST_MATROSKA_TRACK_LACING = (1<<2), + GST_MATROSKA_TRACK_SHIFT = (1<<16) +} GstMatroskaTrackFlags; + +typedef enum { + GST_MATROSKA_VIDEOTRACK_INTERLACED = (GST_MATROSKA_TRACK_SHIFT<<0) +} GstMatroskaVideoTrackFlags; + +typedef struct _GstMatroskaTrackContext { + GstPad *pad; + guint index; + + /* some often-used info */ + gchar *codec_id, *codec_name, *name, *language; + gpointer codec_priv; + guint codec_priv_size; + GstMatroskaTrackType type; + guint uid, num; + GstMatroskaTrackFlags flags; + guint64 default_duration; +} GstMatroskaTrackContext; + +typedef struct _GstMatroskaTrackVideoContext { + GstMatroskaTrackContext parent; + + guint pixel_width, pixel_height, + display_width, display_height; + GstMatroskaEyeMode eye_mode; + GstMatroskaAspectRatioMode asr_mode; + guint32 fourcc; +} GstMatroskaTrackVideoContext; + +typedef struct _GstMatroskaTrackAudioContext { + GstMatroskaTrackContext parent; + + guint samplerate, channels, bitdepth; +} GstMatroskaTrackAudioContext; + +typedef struct _GstMatroskaTrackComplexContext { + GstMatroskaTrackContext parent; + + /* nothing special goes here, apparently */ +} GstMatroskaTrackComplexContext; + +typedef struct _GstMatroskaTrackSubtitleContext { + GstMatroskaTrackContext parent; + + /* or here... */ +} GstMatroskaTrackSubtitleContext; + +typedef struct _GstMatroskaIndex { + guint64 pos; /* of the corresponding *cluster*! */ + guint16 track; /* reference to 'num' */ + guint64 time; /* in nanoseconds */ +} GstMatroskaIndex; + +#endif /* __GST_MATROSKA_IDS_H__ */ diff --git a/gst/matroska/matroska.c b/gst/matroska/matroska.c new file mode 100644 index 0000000..dc1768a --- /dev/null +++ b/gst/matroska/matroska.c @@ -0,0 +1,47 @@ +/* GStreamer Matroska muxer/demuxer + * (c) 2003 Ronald Bultje + * + * matroska.c: plugin loader + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "matroska-demux.h" +#include "matroska-mux.h" + +static gboolean +plugin_init (GstPlugin *plugin) +{ + return (gst_matroska_demux_plugin_init (plugin) && + gst_matroska_mux_plugin_init (plugin)); +} + +GST_PLUGIN_DEFINE ( + GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "matroska", + "Matroska stream handling", + plugin_init, + VERSION, + "LGPL", + GST_COPYRIGHT, + GST_PACKAGE, + GST_ORIGIN +)