From 085e333283086b3ed7d5836be85c60d5eee6db68 Mon Sep 17 00:00:00 2001 From: Mark Nauwelaerts Date: Tue, 18 May 2010 14:44:15 +0200 Subject: [PATCH] matroskademux: use bytereader based GstEbmlRead as a helper ... rather than basing on it by inheritance. Also use more common code for push and pull mode. Fixes #619198. Fixes #611117. --- gst/matroska/ebml-read.c | 858 +++++------------- gst/matroska/ebml-read.h | 95 +- gst/matroska/matroska-demux.c | 1984 +++++++++++++++++------------------------ gst/matroska/matroska-demux.h | 16 +- 4 files changed, 1128 insertions(+), 1825 deletions(-) diff --git a/gst/matroska/ebml-read.c b/gst/matroska/ebml-read.c index acf8d61..2a62f24 100644 --- a/gst/matroska/ebml-read.c +++ b/gst/matroska/ebml-read.c @@ -40,497 +40,253 @@ #define NAN (0.0 / 0.0) #endif -GST_DEBUG_CATEGORY_STATIC (ebmlread_debug); +GST_DEBUG_CATEGORY (ebmlread_debug); #define GST_CAT_DEFAULT ebmlread_debug -static void gst_ebml_read_class_init (GstEbmlReadClass * klass); - -static void gst_ebml_read_init (GstEbmlRead * ebml); - -static GstStateChangeReturn gst_ebml_read_change_state (GstElement * element, - GstStateChange transition); +/* Peeks following element id and element length in datastream provided + * by @peek with @ctx as user data. + * Returns GST_FLOW_UNEXPECTED if not enough data to read id and length. + * Otherwise, @needed provides the prefix length (id + length), and + * @length provides element length. + * + * @object and @offset are provided for informative messaging/debug purposes. + */ +GstFlowReturn +gst_ebml_peek_id_length (guint32 * _id, guint64 * _length, guint * _needed, + GstPeekData peek, gpointer * ctx, GstElement * el, guint64 offset) +{ + guint needed; + const guint8 *buf; + gint len_mask = 0x80, read = 1, n = 1, num_ffs = 0; + guint64 total; + guint8 b; -/* convenience functions */ -static GstFlowReturn gst_ebml_read_peek_bytes (GstEbmlRead * ebml, guint size, - GstBuffer ** p_buf, guint8 ** bytes); -static GstFlowReturn gst_ebml_read_pull_bytes (GstEbmlRead * ebml, guint size, - GstBuffer ** p_buf, guint8 ** bytes); + g_return_val_if_fail (_id != NULL, GST_FLOW_ERROR); + g_return_val_if_fail (_length != NULL, GST_FLOW_ERROR); + g_return_val_if_fail (_needed != NULL, GST_FLOW_ERROR); + /* well ... */ + *_id = (guint32) GST_EBML_SIZE_UNKNOWN; + *_length = GST_EBML_SIZE_UNKNOWN; -static GstElementClass *parent_class; /* NULL */ + /* read element id */ + needed = 2; + buf = peek (ctx, needed); + if (!buf) + goto not_enough_data; -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); + b = GST_READ_UINT8 (buf); + total = (guint64) b; + while (read <= 4 && !(total & len_mask)) { + read++; + len_mask >>= 1; } + if (G_UNLIKELY (read > 4)) + goto invalid_id; - return gst_ebml_read_type; -} + /* need id and at least something for subsequent length */ + needed = read + 1; + buf = peek (ctx, needed); + if (!buf) + goto not_enough_data; -void -gst_ebml_level_free (GstEbmlLevel * level) -{ - g_slice_free (GstEbmlLevel, level); -} + while (n < read) { + b = GST_READ_UINT8 (buf + n); + total = (total << 8) | b; + ++n; + } + *_id = (guint32) total; -static void -gst_ebml_finalize (GObject * obj) -{ - GstEbmlRead *ebml = GST_EBML_READ (obj); - - g_list_foreach (ebml->level, (GFunc) gst_ebml_level_free, NULL); - g_list_free (ebml->level); - ebml->level = NULL; - if (ebml->cached_buffer) { - gst_buffer_unref (ebml->cached_buffer); - ebml->cached_buffer = NULL; + /* read element length */ + b = GST_READ_UINT8 (buf + n); + total = (guint64) b; + len_mask = 0x80; + read = 1; + while (read <= 8 && !(total & len_mask)) { + read++; + len_mask >>= 1; } + if (G_UNLIKELY (read > 8)) + goto invalid_length; + if ((total &= (len_mask - 1)) == len_mask - 1) + num_ffs++; - G_OBJECT_CLASS (parent_class)->finalize (obj); -} + needed += read - 1; + buf = peek (ctx, needed); + if (!buf) + goto not_enough_data; -static void -gst_ebml_read_class_init (GstEbmlReadClass * klass) -{ - GstElementClass *gstelement_class = (GstElementClass *) klass; - GObjectClass *gobject_class = (GObjectClass *) klass; - - parent_class = g_type_class_peek_parent (klass); + buf += (needed - read); + n = 1; + while (n < read) { + guint8 b = GST_READ_UINT8 (buf + n); - GST_DEBUG_CATEGORY_INIT (ebmlread_debug, "ebmlread", - 0, "EBML stream helper class"); + if (G_UNLIKELY (b == 0xff)) + num_ffs++; + total = (total << 8) | b; + ++n; + } - gobject_class->finalize = gst_ebml_finalize; + if (G_UNLIKELY (read == num_ffs)) + *_length = G_MAXUINT64; + else + *_length = total; + *_length = total; - gstelement_class->change_state = - GST_DEBUG_FUNCPTR (gst_ebml_read_change_state); -} + *_needed = needed; -static void -gst_ebml_read_init (GstEbmlRead * ebml) -{ - ebml->sinkpad = NULL; - ebml->level = NULL; -} + return GST_FLOW_OK; -static GstStateChangeReturn -gst_ebml_read_change_state (GstElement * element, GstStateChange transition) -{ - GstStateChangeReturn ret; - GstEbmlRead *ebml = GST_EBML_READ (element); - - switch (transition) { - case GST_STATE_CHANGE_READY_TO_PAUSED: - if (!ebml->sinkpad) { - g_return_val_if_reached (GST_STATE_CHANGE_FAILURE); - } - break; - default: - break; + /* ERRORS */ +not_enough_data: + { + *_needed = needed; + return GST_FLOW_UNEXPECTED; } - - ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); - - switch (transition) { - case GST_STATE_CHANGE_PAUSED_TO_READY: - { - g_list_foreach (ebml->level, (GFunc) gst_ebml_level_free, NULL); - g_list_free (ebml->level); - ebml->level = NULL; - if (ebml->cached_buffer) { - gst_buffer_unref (ebml->cached_buffer); - ebml->cached_buffer = NULL; - } - ebml->offset = 0; - break; - } - default: - break; +invalid_id: + { + GST_ERROR_OBJECT (el, + "Invalid EBML ID size tag (0x%x) at position %" G_GUINT64_FORMAT " (0x%" + G_GINT64_MODIFIER "x)", (guint) b, offset, offset); + return GST_FLOW_ERROR; + } +invalid_length: + { + GST_ERROR_OBJECT (el, + "Invalid EBML length size tag (0x%x) at position %" G_GUINT64_FORMAT + " (0x%" G_GINT64_MODIFIER "x)", (guint) b, offset, offset); + return GST_FLOW_ERROR; } - - return ret; } -/* - * Used in push mode. - * Provided buffer is used as cache, based on offset 0, and no further reads - * will be issued. - */ - +/* setup for parsing @buf at position @offset on behalf of @el. + * Takes ownership of @buf. */ void -gst_ebml_read_reset_cache (GstEbmlRead * ebml, GstBuffer * buffer, +gst_ebml_read_init (GstEbmlRead * ebml, GstElement * el, GstBuffer * buf, guint64 offset) { - if (ebml->cached_buffer) - gst_buffer_unref (ebml->cached_buffer); - - ebml->cached_buffer = buffer; - ebml->push_cache = TRUE; - buffer = gst_buffer_make_metadata_writable (buffer); - GST_BUFFER_OFFSET (buffer) = offset; - ebml->offset = offset; - g_list_foreach (ebml->level, (GFunc) gst_ebml_level_free, NULL); - g_list_free (ebml->level); - ebml->level = NULL; -} + GstEbmlMaster m; -/* - * 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. - */ + g_return_if_fail (el); + g_return_if_fail (buf); -static guint -gst_ebml_read_element_level_up (GstEbmlRead * ebml) -{ - guint num = 0; - guint64 pos = ebml->offset; - - while (ebml->level != NULL) { - GstEbmlLevel *level = ebml->level->data; - - if (pos >= level->start + level->length) { - ebml->level = g_list_delete_link (ebml->level, ebml->level); - gst_ebml_level_free (level); - num++; - } else { - break; - } - } - - return num; + ebml->el = el; + ebml->offset = offset; + ebml->buf = buf; + ebml->readers = g_array_sized_new (FALSE, FALSE, sizeof (GstEbmlMaster), 10); + m.offset = ebml->offset; + gst_byte_reader_init (&m.br, GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf)); + g_array_append_val (ebml->readers, m); } -/* - * Calls pull_range for (offset,size) without advancing our offset - */ -static GstFlowReturn -gst_ebml_read_peek_bytes (GstEbmlRead * ebml, guint size, GstBuffer ** p_buf, - guint8 ** bytes) +void +gst_ebml_read_clear (GstEbmlRead * ebml) { - GstFlowReturn ret; - - /* Caching here actually makes much less difference than one would expect. - * We do it mainly to avoid pulling buffers of 1 byte all the time */ - if (ebml->cached_buffer) { - guint64 cache_offset = GST_BUFFER_OFFSET (ebml->cached_buffer); - guint cache_size = GST_BUFFER_SIZE (ebml->cached_buffer); - - if (cache_offset <= ebml->offset && - (ebml->offset + size) <= (cache_offset + cache_size)) { - if (p_buf) - *p_buf = gst_buffer_create_sub (ebml->cached_buffer, - ebml->offset - cache_offset, size); - if (bytes) - *bytes = - GST_BUFFER_DATA (ebml->cached_buffer) + ebml->offset - cache_offset; - return GST_FLOW_OK; - } - /* not enough data in the cache, free cache and get a new one */ - /* never drop pushed cache */ - if (ebml->push_cache) { - if (ebml->offset == cache_offset + cache_size) - return GST_FLOW_END; - else - return GST_FLOW_UNEXPECTED; - } - gst_buffer_unref (ebml->cached_buffer); - ebml->cached_buffer = NULL; - } - - /* refill the cache */ - ret = gst_pad_pull_range (ebml->sinkpad, ebml->offset, MAX (size, 64 * 1024), - &ebml->cached_buffer); - if (ret != GST_FLOW_OK) { - ebml->cached_buffer = NULL; - return ret; - } - - if (GST_BUFFER_SIZE (ebml->cached_buffer) >= size) { - if (p_buf) - *p_buf = gst_buffer_create_sub (ebml->cached_buffer, 0, size); - if (bytes) - *bytes = GST_BUFFER_DATA (ebml->cached_buffer); - return GST_FLOW_OK; - } - - /* Not possible to get enough data, try a last time with - * requesting exactly the size we need */ - gst_buffer_unref (ebml->cached_buffer); - ebml->cached_buffer = NULL; - - ret = - gst_pad_pull_range (ebml->sinkpad, ebml->offset, size, - &ebml->cached_buffer); - if (ret != GST_FLOW_OK) { - GST_DEBUG_OBJECT (ebml, "pull_range returned %d", ret); - if (p_buf) - *p_buf = NULL; - if (bytes) - *bytes = NULL; - return ret; - } - - if (GST_BUFFER_SIZE (ebml->cached_buffer) < size) { - GST_WARNING_OBJECT (ebml, "Dropping short buffer at offset %" - G_GUINT64_FORMAT ": wanted %u bytes, got %u bytes", ebml->offset, - size, GST_BUFFER_SIZE (ebml->cached_buffer)); - - gst_buffer_unref (ebml->cached_buffer); - ebml->cached_buffer = NULL; - if (p_buf) - *p_buf = NULL; - if (bytes) - *bytes = NULL; - return GST_FLOW_UNEXPECTED; - } - - if (p_buf) - *p_buf = gst_buffer_create_sub (ebml->cached_buffer, 0, size); - if (bytes) - *bytes = GST_BUFFER_DATA (*p_buf); - - return GST_FLOW_OK; + if (ebml->readers) + g_array_free (ebml->readers, TRUE); + ebml->readers = NULL; + if (ebml->buf) + gst_buffer_unref (ebml->buf); + ebml->buf = NULL; + ebml->el = NULL; } -/* - * Calls pull_range for (offset,size) and advances our offset by size - */ -static GstFlowReturn -gst_ebml_read_pull_bytes (GstEbmlRead * ebml, guint size, GstBuffer ** p_buf, - guint8 ** bytes) +static const guint8 * +gst_ebml_read_peek (GstByteReader * br, guint peek) { - GstFlowReturn ret; - - ret = gst_ebml_read_peek_bytes (ebml, size, p_buf, bytes); - if (ret != GST_FLOW_OK) - return ret; + const guint8 *data; - ebml->offset += size; - return GST_FLOW_OK; + if (G_LIKELY (gst_byte_reader_peek_data (br, peek, &data))) + return data; + else + return NULL; } -/* - * Read: the element content data ID. - * Return: FALSE on error. - */ - static GstFlowReturn -gst_ebml_read_element_id (GstEbmlRead * ebml, guint32 * id, guint * level_up) +gst_ebml_peek_id_full (GstEbmlRead * ebml, guint32 * id, guint64 * length, + guint * prefix) { - guint8 *buf; - gint len_mask = 0x80, read = 1, n = 1; - guint32 total; - guint8 b; GstFlowReturn ret; + const guint8 *data = NULL; - ret = gst_ebml_read_peek_bytes (ebml, 1, NULL, &buf); + ret = gst_ebml_peek_id_length (id, length, prefix, + (GstPeekData) gst_ebml_read_peek, (gpointer) gst_ebml_read_br (ebml), + ebml->el, gst_ebml_read_get_pos (ebml)); if (ret != GST_FLOW_OK) return ret; - b = GST_READ_UINT8 (buf); - - total = (guint32) b; - - while (read <= 4 && !(total & len_mask)) { - read++; - len_mask >>= 1; - } - if (read > 4) { - GST_ERROR_OBJECT (ebml, - "Invalid EBML ID size tag (0x%x) at position %" G_GUINT64_FORMAT " (0x%" - G_GINT64_MODIFIER "x)", (guint) b, ebml->offset, ebml->offset); - return GST_FLOW_ERROR; - } + GST_LOG_OBJECT (ebml->el, "id 0x%x at offset 0x%" G_GINT64_MODIFIER "x" + " of length %" G_GUINT64_FORMAT ", prefix %d", *id, + gst_ebml_read_get_pos (ebml), *length, *prefix); - ret = gst_ebml_read_peek_bytes (ebml, read, NULL, &buf); - if (ret != GST_FLOW_OK) - return ret; +#ifndef GST_DISABLE_GST_DEBUG + { + GstByteReader *br = gst_ebml_read_br (ebml); + guint size = gst_byte_reader_get_remaining (br); + gst_byte_reader_peek_data (br, size, &data); - while (n < read) { - b = GST_READ_UINT8 (buf + n); - total = (total << 8) | b; - ++n; + GST_LOG_OBJECT (ebml->el, "current br %p; remaining %d", br, size); + if (data) + GST_MEMDUMP_OBJECT (ebml->el, "element", data, MIN (size, *length)); } +#endif - *id = total; + return ret; +} - /* level */ - if (level_up) - *level_up = gst_ebml_read_element_level_up (ebml); +GstFlowReturn +gst_ebml_peek_id (GstEbmlRead * ebml, guint32 * id) +{ + guint64 length; + guint needed; - ebml->offset += read; - return GST_FLOW_OK; + return gst_ebml_peek_id_full (ebml, id, &length, &needed); } /* - * Read: element content length. - * Return: the number of bytes read or -1 on error. + * Read the next element, the contents are supposed to be sub-elements which + * can be read separately. A new bytereader is setup for doing so. */ - -static GstFlowReturn -gst_ebml_read_element_length (GstEbmlRead * ebml, guint64 * length, - gint * rread) +GstFlowReturn +gst_ebml_read_master (GstEbmlRead * ebml, guint32 * id) { + guint64 length; + guint prefix; + const guint8 *data; GstFlowReturn ret; - guint8 *buf; - gint len_mask = 0x80, read = 1, n = 1, num_ffs = 0; - guint64 total; - guint8 b; + GstEbmlMaster m; - ret = gst_ebml_read_peek_bytes (ebml, 1, NULL, &buf); + ret = gst_ebml_peek_id_full (ebml, id, &length, &prefix); if (ret != GST_FLOW_OK) return ret; - b = GST_READ_UINT8 (buf); + /* we just at least peeked the id */ + g_assert (gst_byte_reader_skip (gst_ebml_read_br (ebml), prefix)); - total = (guint64) b; + m.offset = gst_ebml_read_get_pos (ebml); + if (!gst_byte_reader_get_data (gst_ebml_read_br (ebml), length, &data)) + return GST_FLOW_PARSE; - while (read <= 8 && !(total & len_mask)) { - read++; - len_mask >>= 1; - } - if (read > 8) { - GST_ERROR_OBJECT (ebml, - "Invalid EBML length size tag (0x%x) at position %" G_GUINT64_FORMAT - " (0x%" G_GINT64_MODIFIER "x)", (guint) b, ebml->offset, ebml->offset); - return GST_FLOW_ERROR; - } - - if ((total &= (len_mask - 1)) == len_mask - 1) - num_ffs++; - - ret = gst_ebml_read_peek_bytes (ebml, read, NULL, &buf); - if (ret != GST_FLOW_OK) - return ret; - - while (n < read) { - guint8 b = GST_READ_UINT8 (buf + n); - - if (b == 0xff) - num_ffs++; - total = (total << 8) | b; - ++n; - } - - if (read == num_ffs) - *length = G_MAXUINT64; - else - *length = total; - - if (rread) - *rread = read; - - ebml->offset += read; + GST_LOG_OBJECT (ebml->el, "pushing level %d at offset %" G_GUINT64_FORMAT, + ebml->readers->len, m.offset); + gst_byte_reader_init (&m.br, data, length); + g_array_append_val (ebml->readers, m); return GST_FLOW_OK; } -/* - * Return: the ID of the next element. - * Level_up contains the amount of levels that this - * next element lies higher than the previous one. - */ - +/* explicitly pop a bytereader from stack. Usually invoked automagically. */ GstFlowReturn -gst_ebml_peek_id (GstEbmlRead * ebml, guint * level_up, guint32 * id) +gst_ebml_read_pop_master (GstEbmlRead * ebml) { - guint64 off; - guint level_up_tmp = 0; - GstFlowReturn ret; - - g_assert (level_up); - g_assert (id); - - *level_up = 0; - -next: - off = ebml->offset; /* save offset */ + g_return_val_if_fail (ebml->readers, GST_FLOW_ERROR); - if ((ret = gst_ebml_read_element_id (ebml, id, &level_up_tmp)) != GST_FLOW_OK) { - if (ret != GST_FLOW_END) - return ret; - else { - /* simulate dummy VOID element, - * and have the call stack bail out all the way */ - *id = GST_EBML_ID_VOID; - *level_up = G_MAXUINT32 >> 2; - return GST_FLOW_OK; - } - } - - ebml->offset = off; /* restore offset */ - - *level_up += level_up_tmp; - level_up_tmp = 0; - - switch (*id) { - case GST_EBML_ID_VOID: - GST_DEBUG_OBJECT (ebml, "Skipping EBML Void element"); - if ((ret = gst_ebml_read_skip (ebml)) != GST_FLOW_OK) - return ret; - goto next; - break; - case GST_EBML_ID_CRC32: - GST_DEBUG_OBJECT (ebml, "Skipping EBML CRC32 element"); - if ((ret = gst_ebml_read_skip (ebml)) != GST_FLOW_OK) - return ret; - goto next; - break; + /* never remove initial bytereader */ + if (ebml->readers->len > 1) { + GST_LOG_OBJECT (ebml->el, "popping level %d", ebml->readers->len - 1); + g_array_remove_index (ebml->readers, ebml->readers->len - 1); } - return ret; -} - -/* - * Return the length of the stream in bytes - */ - -gint64 -gst_ebml_read_get_length (GstEbmlRead * ebml) -{ - GstFormat fmt = GST_FORMAT_BYTES; - gint64 end; - - /* FIXME: what to do if we don't get the upstream length */ - if (!gst_pad_query_peer_duration (ebml->sinkpad, &fmt, &end) || - fmt != GST_FORMAT_BYTES || end < 0) - g_return_val_if_reached (0); - - return end; -} - -/* - * Seek to a given offset. - */ - -GstFlowReturn -gst_ebml_read_seek (GstEbmlRead * ebml, guint64 offset) -{ - if (offset >= gst_ebml_read_get_length (ebml)) - return GST_FLOW_UNEXPECTED; - - ebml->offset = offset; - return GST_FLOW_OK; } @@ -543,17 +299,16 @@ gst_ebml_read_skip (GstEbmlRead * ebml) { guint64 length; guint32 id; + guint prefix; GstFlowReturn ret; - ret = gst_ebml_read_element_id (ebml, &id, NULL); + ret = gst_ebml_peek_id_full (ebml, &id, &length, &prefix); if (ret != GST_FLOW_OK) return ret; - ret = gst_ebml_read_element_length (ebml, &length, NULL); - if (ret != GST_FLOW_OK) - return ret; + if (!gst_byte_reader_skip (gst_ebml_read_br (ebml), length + prefix)) + return GST_FLOW_PARSE; - ebml->offset += length; return ret; } @@ -565,24 +320,30 @@ GstFlowReturn gst_ebml_read_buffer (GstEbmlRead * ebml, guint32 * id, GstBuffer ** buf) { guint64 length; + guint prefix; GstFlowReturn ret; - ret = gst_ebml_read_element_id (ebml, id, NULL); + ret = gst_ebml_peek_id_full (ebml, id, &length, &prefix); if (ret != GST_FLOW_OK) return ret; - ret = gst_ebml_read_element_length (ebml, &length, NULL); - if (ret != GST_FLOW_OK) - return ret; + /* we just at least peeked the id */ + g_assert (gst_byte_reader_skip (gst_ebml_read_br (ebml), prefix)); + + if (G_LIKELY (length > 0)) { + guint offset; - if (length == 0) { + offset = gst_ebml_read_get_pos (ebml) - ebml->offset; + if (G_LIKELY (gst_byte_reader_skip (gst_ebml_read_br (ebml), length))) { + *buf = gst_buffer_create_sub (ebml->buf, offset, length); + } else { + *buf = NULL; + return GST_FLOW_PARSE; + } + } else { *buf = gst_buffer_new (); - return GST_FLOW_OK; } - *buf = NULL; - ret = gst_ebml_read_pull_bytes (ebml, (guint) length, buf, NULL); - return ret; } @@ -591,33 +352,29 @@ gst_ebml_read_buffer (GstEbmlRead * ebml, guint32 * id, GstBuffer ** buf) */ static GstFlowReturn -gst_ebml_read_bytes (GstEbmlRead * ebml, guint32 * id, guint8 ** data, +gst_ebml_read_bytes (GstEbmlRead * ebml, guint32 * id, const guint8 ** data, guint * size) { guint64 length; + guint prefix; GstFlowReturn ret; *size = 0; - ret = gst_ebml_read_element_id (ebml, id, NULL); + ret = gst_ebml_peek_id_full (ebml, id, &length, &prefix); if (ret != GST_FLOW_OK) return ret; - ret = gst_ebml_read_element_length (ebml, &length, NULL); - if (ret != GST_FLOW_OK) - return ret; - - if (length == 0) { - *data = NULL; - return ret; - } + /* we just at least peeked the id */ + g_assert (gst_byte_reader_skip (gst_ebml_read_br (ebml), prefix)); *data = NULL; - ret = gst_ebml_read_pull_bytes (ebml, (guint) length, NULL, data); - if (ret != GST_FLOW_OK) - return ret; + if (G_LIKELY (length >= 0)) { + if (!gst_byte_reader_get_data (gst_ebml_read_br (ebml), length, data)) + return GST_FLOW_PARSE; + } - *size = (guint) length; + *size = length; return ret; } @@ -629,7 +386,7 @@ gst_ebml_read_bytes (GstEbmlRead * ebml, guint32 * id, guint8 ** data, GstFlowReturn gst_ebml_read_uint (GstEbmlRead * ebml, guint32 * id, guint64 * num) { - guint8 *data; + const guint8 *data; guint size; GstFlowReturn ret; @@ -638,9 +395,10 @@ gst_ebml_read_uint (GstEbmlRead * ebml, guint32 * id, guint64 * num) return ret; if (size < 1 || size > 8) { - GST_ERROR_OBJECT (ebml, + GST_ERROR_OBJECT (ebml->el, "Invalid integer element size %d at position %" G_GUINT64_FORMAT " (0x%" - G_GINT64_MODIFIER "x)", size, ebml->offset - size, ebml->offset - size); + G_GINT64_MODIFIER "x)", size, gst_ebml_read_get_pos (ebml) - size, + gst_ebml_read_get_pos (ebml) - size); return GST_FLOW_ERROR; } *num = 0; @@ -660,7 +418,7 @@ gst_ebml_read_uint (GstEbmlRead * ebml, guint32 * id, guint64 * num) GstFlowReturn gst_ebml_read_sint (GstEbmlRead * ebml, guint32 * id, gint64 * num) { - guint8 *data; + const guint8 *data; guint size; gboolean negative = 0; GstFlowReturn ret; @@ -670,9 +428,10 @@ gst_ebml_read_sint (GstEbmlRead * ebml, guint32 * id, gint64 * num) return ret; if (size < 1 || size > 8) { - GST_ERROR_OBJECT (ebml, + GST_ERROR_OBJECT (ebml->el, "Invalid integer element size %d at position %" G_GUINT64_FORMAT " (0x%" - G_GINT64_MODIFIER "x)", size, ebml->offset - size, ebml->offset - size); + G_GINT64_MODIFIER "x)", size, gst_ebml_read_get_pos (ebml) - size, + gst_ebml_read_get_pos (ebml) - size); return GST_FLOW_ERROR; } @@ -709,7 +468,7 @@ struct _ext_float }; static gdouble -_ext2dbl (guint8 * data) +_ext2dbl (const guint8 * data) { struct _ext_float ext; guint64 m = 0; @@ -738,7 +497,7 @@ _ext2dbl (guint8 * data) GstFlowReturn gst_ebml_read_float (GstEbmlRead * ebml, guint32 * id, gdouble * num) { - guint8 *data; + const guint8 *data; guint size; GstFlowReturn ret; @@ -747,9 +506,10 @@ gst_ebml_read_float (GstEbmlRead * ebml, guint32 * id, gdouble * num) return ret; if (size != 4 && size != 8 && size != 10) { - GST_ERROR_OBJECT (ebml, + GST_ERROR_OBJECT (ebml->el, "Invalid float element size %d at position %" G_GUINT64_FORMAT " (0x%" - G_GINT64_MODIFIER "x)", size, ebml->offset - size, ebml->offset - size); + G_GINT64_MODIFIER "x)", size, gst_ebml_read_get_pos (ebml) - size, + gst_ebml_read_get_pos (ebml) - size); return GST_FLOW_ERROR; } @@ -781,7 +541,7 @@ gst_ebml_read_float (GstEbmlRead * ebml, guint32 * id, gdouble * num) static GstFlowReturn gst_ebml_read_string (GstEbmlRead * ebml, guint32 * id, gchar ** str) { - guint8 *data; + const guint8 *data; guint size; GstFlowReturn ret; @@ -838,7 +598,7 @@ gst_ebml_read_utf8 (GstEbmlRead * ebml, guint32 * id, gchar ** str) GstFlowReturn ret; #ifndef GST_DISABLE_GST_DEBUG - guint64 oldoff = ebml->offset; + guint64 oldoff = gst_ebml_read_get_pos (ebml); #endif ret = gst_ebml_read_string (ebml, id, str); @@ -847,7 +607,7 @@ gst_ebml_read_utf8 (GstEbmlRead * ebml, guint32 * id, gchar ** str) if (str != NULL && *str != NULL && **str != '\0' && !g_utf8_validate (*str, -1, NULL)) { - GST_WARNING_OBJECT (ebml, + GST_WARNING_OBJECT (ebml->el, "Invalid UTF-8 string at offset %" G_GUINT64_FORMAT, oldoff); } @@ -875,35 +635,6 @@ gst_ebml_read_date (GstEbmlRead * ebml, guint32 * id, gint64 * date) } /* - * Read the next element, but only the header. The contents - * are supposed to be sub-elements which can be read separately. - */ - -GstFlowReturn -gst_ebml_read_master (GstEbmlRead * ebml, guint32 * id) -{ - GstEbmlLevel *level; - guint64 length; - GstFlowReturn ret; - - ret = gst_ebml_read_element_id (ebml, id, NULL); - if (ret != GST_FLOW_OK) - return ret; - - ret = gst_ebml_read_element_length (ebml, &length, NULL); - if (ret != GST_FLOW_OK) - return ret; - - /* remember level */ - level = g_slice_new (GstEbmlLevel); - level->start = ebml->offset; - level->length = length; - ebml->level = g_list_prepend (ebml->level, level); - - return GST_FLOW_OK; -} - -/* * Read the next element as binary data. */ @@ -911,7 +642,7 @@ GstFlowReturn gst_ebml_read_binary (GstEbmlRead * ebml, guint32 * id, guint8 ** binary, guint64 * length) { - guint8 *data; + const guint8 *data; guint size; GstFlowReturn ret; @@ -924,146 +655,3 @@ gst_ebml_read_binary (GstEbmlRead * ebml, return GST_FLOW_OK; } - -/* - * Read an EBML header. - */ - -GstFlowReturn -gst_ebml_read_header (GstEbmlRead * ebml, gchar ** doctype, guint * version) -{ - /* this function is the first to be called */ - guint32 id; - guint level_up; - GstFlowReturn ret; - - /* default init */ - if (doctype) - *doctype = NULL; - if (version) - *version = 1; - - ret = gst_ebml_peek_id (ebml, &level_up, &id); - if (ret != GST_FLOW_OK) - return ret; - - GST_DEBUG_OBJECT (ebml, "id: %08x", GST_READ_UINT32_BE (&id)); - - if (level_up != 0 || id != GST_EBML_ID_HEADER) { - GST_ERROR_OBJECT (ebml, "Failed to read header"); - return GST_FLOW_ERROR; - } - ret = gst_ebml_read_master (ebml, &id); - if (ret != GST_FLOW_OK) - return ret; - - while (TRUE) { - ret = gst_ebml_peek_id (ebml, &level_up, &id); - if (ret != GST_FLOW_OK) - return ret; - - /* end-of-header */ - if (level_up) - break; - - switch (id) { - /* is our read version uptodate? */ - case GST_EBML_ID_EBMLREADVERSION:{ - guint64 num; - - ret = gst_ebml_read_uint (ebml, &id, &num); - if (ret != GST_FLOW_OK) - return ret; - g_assert (id == GST_EBML_ID_EBMLREADVERSION); - if (num != GST_EBML_VERSION) { - GST_ERROR_OBJECT (ebml, "Unsupported EBML version %" G_GUINT64_FORMAT, - num); - return GST_FLOW_ERROR; - } - - GST_DEBUG_OBJECT (ebml, "EbmlReadVersion: %" G_GUINT64_FORMAT, num); - break; - } - - /* we only handle 8 byte lengths at max */ - case GST_EBML_ID_EBMLMAXSIZELENGTH:{ - guint64 num; - - ret = gst_ebml_read_uint (ebml, &id, &num); - if (ret != GST_FLOW_OK) - return ret; - g_assert (id == GST_EBML_ID_EBMLMAXSIZELENGTH); - if (num > sizeof (guint64)) { - GST_ERROR_OBJECT (ebml, - "Unsupported EBML maximum size %" G_GUINT64_FORMAT, num); - return GST_FLOW_ERROR; - } - GST_DEBUG_OBJECT (ebml, "EbmlMaxSizeLength: %" G_GUINT64_FORMAT, num); - break; - } - - /* we handle 4 byte IDs at max */ - case GST_EBML_ID_EBMLMAXIDLENGTH:{ - guint64 num; - - ret = gst_ebml_read_uint (ebml, &id, &num); - if (ret != GST_FLOW_OK) - return ret; - g_assert (id == GST_EBML_ID_EBMLMAXIDLENGTH); - if (num > sizeof (guint32)) { - GST_ERROR_OBJECT (ebml, - "Unsupported EBML maximum ID %" G_GUINT64_FORMAT, num); - return GST_FLOW_ERROR; - } - GST_DEBUG_OBJECT (ebml, "EbmlMaxIdLength: %" G_GUINT64_FORMAT, num); - break; - } - - case GST_EBML_ID_DOCTYPE:{ - gchar *text; - - ret = gst_ebml_read_ascii (ebml, &id, &text); - if (ret != GST_FLOW_OK) - return ret; - g_assert (id == GST_EBML_ID_DOCTYPE); - - GST_DEBUG_OBJECT (ebml, "EbmlDocType: %s", GST_STR_NULL (text)); - - if (doctype) { - g_free (*doctype); - *doctype = text; - } else - g_free (text); - break; - } - - case GST_EBML_ID_DOCTYPEREADVERSION:{ - guint64 num; - - ret = gst_ebml_read_uint (ebml, &id, &num); - if (ret != GST_FLOW_OK) - return ret; - g_assert (id == GST_EBML_ID_DOCTYPEREADVERSION); - if (version) - *version = num; - GST_DEBUG_OBJECT (ebml, "EbmlReadVersion: %" G_GUINT64_FORMAT, num); - break; - } - - default: - GST_WARNING_OBJECT (ebml, - "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_EBMLVERSION: - case GST_EBML_ID_DOCTYPEVERSION: - ret = gst_ebml_read_skip (ebml); - if (ret != GST_FLOW_OK) - return ret; - break; - } - } - - return GST_FLOW_OK; -} diff --git a/gst/matroska/ebml-read.h b/gst/matroska/ebml-read.h index d23d129..b40c31d 100644 --- a/gst/matroska/ebml-read.h +++ b/gst/matroska/ebml-read.h @@ -23,6 +23,7 @@ #define __GST_EBML_READ_H__ #include +#include G_BEGIN_DECLS @@ -39,47 +40,42 @@ G_BEGIN_DECLS #define GST_EBML_READ_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_EBML_READ, GstEbmlReadClass)) +GST_DEBUG_CATEGORY_EXTERN (ebmlread_debug); + /* custom flow return code */ -#define GST_FLOW_END GST_FLOW_CUSTOM_SUCCESS +#define GST_FLOW_PARSE GST_FLOW_CUSTOM_ERROR -typedef struct _GstEbmlLevel { - guint64 start; - guint64 length; -} GstEbmlLevel; +typedef struct _GstEbmlMaster { + guint64 offset; + GstByteReader br; +} GstEbmlMaster; typedef struct _GstEbmlRead { - GstElement parent; - - GstBuffer *cached_buffer; - gboolean push_cache; + GstElement *el; - GstPad *sinkpad; + GstBuffer *buf; guint64 offset; - GList *level; + GArray *readers; } GstEbmlRead; -typedef struct _GstEbmlReadClass { - GstElementClass parent; -} GstEbmlReadClass; +typedef const guint8 * (*GstPeekData) (gpointer * context, guint peek); -GType gst_ebml_read_get_type (void); +/* returns UNEXPECTED if not enough data */ +GstFlowReturn gst_ebml_peek_id_length (guint32 * _id, guint64 * _length, + guint * _needed, + GstPeekData peek, gpointer * ctx, + GstElement * el, guint64 offset); -void gst_ebml_level_free (GstEbmlLevel *level); - -void gst_ebml_read_reset_cache (GstEbmlRead * ebml, - GstBuffer * buffer, +void gst_ebml_read_init (GstEbmlRead * ebml, + GstElement * el, GstBuffer * buf, guint64 offset); -GstFlowReturn gst_ebml_peek_id (GstEbmlRead *ebml, - guint *level_up, - guint32 *id); +void gst_ebml_read_clear (GstEbmlRead * ebml); -GstFlowReturn gst_ebml_read_seek (GstEbmlRead *ebml, - guint64 offset); - -gint64 gst_ebml_read_get_length (GstEbmlRead *ebml); +GstFlowReturn gst_ebml_peek_id (GstEbmlRead * ebml, guint32 * id); +/* return _PARSE if not enough data to read what is needed, _ERROR or _OK */ GstFlowReturn gst_ebml_read_skip (GstEbmlRead *ebml); GstFlowReturn gst_ebml_read_buffer (GstEbmlRead *ebml, @@ -113,6 +109,8 @@ GstFlowReturn gst_ebml_read_date (GstEbmlRead *ebml, GstFlowReturn gst_ebml_read_master (GstEbmlRead *ebml, guint32 *id); +GstFlowReturn gst_ebml_read_pop_master (GstEbmlRead *ebml); + GstFlowReturn gst_ebml_read_binary (GstEbmlRead *ebml, guint32 *id, guint8 **binary, @@ -122,6 +120,51 @@ GstFlowReturn gst_ebml_read_header (GstEbmlRead *read, gchar **doctype, guint *version); +/* Returns current (absolute) position of Ebml parser, + * i.e. taking into account offset provided at init */ +static inline guint64 +gst_ebml_read_get_pos (GstEbmlRead * ebml) +{ + GstEbmlMaster *m; + + g_return_val_if_fail (ebml->readers, 0); + g_return_val_if_fail (ebml->readers->len, 0); + + m = &(g_array_index (ebml->readers, GstEbmlMaster, ebml->readers->len - 1)); + return m->offset + gst_byte_reader_get_pos (&m->br); +} + +/* Returns starting offset of Ebml parser */ +static inline guint64 +gst_ebml_read_get_offset (GstEbmlRead * ebml) +{ + return ebml->offset; +} + +static inline GstByteReader * +gst_ebml_read_br (GstEbmlRead * ebml) +{ + g_return_val_if_fail (ebml->readers, NULL); + g_return_val_if_fail (ebml->readers->len, NULL); + + return &(g_array_index (ebml->readers, + GstEbmlMaster, ebml->readers->len - 1).br); +} + +static inline gboolean +gst_ebml_read_has_remaining (GstEbmlRead * ebml, guint64 bytes_needed, + gboolean auto_pop) +{ + gboolean res; + + res = (gst_byte_reader_get_remaining (gst_ebml_read_br (ebml)) >= bytes_needed); + if (G_LIKELY (!res && auto_pop)) { + gst_ebml_read_pop_master (ebml); + } + + return G_LIKELY (res); +} + G_END_DECLS #endif /* __GST_EBML_READ_H__ */ diff --git a/gst/matroska/matroska-demux.c b/gst/matroska/matroska-demux.c index a104d6e..3c47d7e 100644 --- a/gst/matroska/matroska-demux.c +++ b/gst/matroska/matroska-demux.c @@ -80,12 +80,11 @@ GST_DEBUG_CATEGORY_STATIC (matroskademux_debug); #define DEBUG_ELEMENT_START(demux, ebml, element) \ GST_DEBUG_OBJECT (demux, "Parsing " element " element at offset %" \ - G_GUINT64_FORMAT, ebml->offset) + G_GUINT64_FORMAT, gst_ebml_read_get_pos (ebml)) #define DEBUG_ELEMENT_STOP(demux, ebml, element, ret) \ - GST_DEBUG_OBJECT (demux, "Parsing " element " element at offset %" \ - G_GUINT64_FORMAT " finished with '%s'", ebml->offset, \ - gst_flow_get_name (ret)) + GST_DEBUG_OBJECT (demux, "Parsing " element " element " \ + " finished with '%s'", gst_flow_get_name (ret)) enum { @@ -125,8 +124,8 @@ static GstStaticPadTemplate subtitle_src_templ = "subpicture/x-pgs; subtitle/x-kate; " "application/x-subtitle-unknown") ); -static GstFlowReturn gst_matroska_demux_parse_contents (GstMatroskaDemux * - demux); +static GstFlowReturn gst_matroska_demux_parse_id (GstMatroskaDemux * demux, + guint32 id, guint64 length, guint needed); /* element functions */ static void gst_matroska_demux_loop (GstPad * pad); @@ -179,8 +178,8 @@ static gboolean perform_seek_to_offset (GstMatroskaDemux * demux, guint64 offset); GType gst_matroska_demux_get_type (void); -GST_BOILERPLATE (GstMatroskaDemux, gst_matroska_demux, GstEbmlRead, - GST_TYPE_EBML_READ); +GST_BOILERPLATE (GstMatroskaDemux, gst_matroska_demux, GstElement, + GST_TYPE_ELEMENT); static void gst_matroska_demux_base_init (gpointer klass) @@ -228,6 +227,10 @@ gst_matroska_demux_class_init (GstMatroskaDemuxClass * klass) GObjectClass *gobject_class = (GObjectClass *) klass; GstElementClass *gstelement_class = (GstElementClass *) klass; + /* parser helper separate debug */ + GST_DEBUG_CATEGORY_INIT (ebmlread_debug, "ebmlread", + 0, "EBML stream helper class"); + GST_DEBUG_CATEGORY_INIT (matroskademux_debug, "matroskademux", 0, "Matroska demuxer"); @@ -260,7 +263,6 @@ gst_matroska_demux_init (GstMatroskaDemux * demux, gst_pad_set_event_function (demux->sinkpad, GST_DEBUG_FUNCPTR (gst_matroska_demux_handle_sink_event)); gst_element_add_pad (GST_ELEMENT (demux), demux->sinkpad); - GST_EBML_READ (demux)->sinkpad = demux->sinkpad; /* initial stream no. */ demux->src = NULL; @@ -346,6 +348,12 @@ done: } static void +gst_matroska_demux_free_parsed_el (gpointer mem, gpointer user_data) +{ + g_slice_free (guint64, mem); +} + +static void gst_matroska_demux_reset (GstElement * element) { GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (element); @@ -399,10 +407,16 @@ gst_matroska_demux_reset (GstElement * element) demux->segmentinfo_parsed = FALSE; demux->attachments_parsed = FALSE; - g_list_foreach (demux->tags_parsed, (GFunc) gst_ebml_level_free, NULL); + g_list_foreach (demux->tags_parsed, + (GFunc) gst_matroska_demux_free_parsed_el, NULL); g_list_free (demux->tags_parsed); demux->tags_parsed = NULL; + g_list_foreach (demux->seek_parsed, + (GFunc) gst_matroska_demux_free_parsed_el, NULL); + g_list_free (demux->seek_parsed); + demux->seek_parsed = NULL; + gst_segment_init (&demux->segment, GST_FORMAT_TIME); demux->last_stop_end = GST_CLOCK_TIME_NONE; demux->seek_block = 0; @@ -444,6 +458,128 @@ gst_matroska_demux_reset (GstElement * element) gst_tag_list_free (demux->global_tags); } demux->global_tags = gst_tag_list_new (); + + if (demux->cached_buffer) { + gst_buffer_unref (demux->cached_buffer); + demux->cached_buffer = NULL; + } +} + +/* + * Calls pull_range for (offset,size) without advancing our offset + */ +static GstFlowReturn +gst_matroska_demux_peek_bytes (GstMatroskaDemux * demux, guint64 offset, + guint size, GstBuffer ** p_buf, guint8 ** bytes) +{ + GstFlowReturn ret; + + /* Caching here actually makes much less difference than one would expect. + * We do it mainly to avoid pulling buffers of 1 byte all the time */ + if (demux->cached_buffer) { + guint64 cache_offset = GST_BUFFER_OFFSET (demux->cached_buffer); + guint cache_size = GST_BUFFER_SIZE (demux->cached_buffer); + + if (cache_offset <= demux->offset && + (demux->offset + size) <= (cache_offset + cache_size)) { + if (p_buf) + *p_buf = gst_buffer_create_sub (demux->cached_buffer, + demux->offset - cache_offset, size); + if (bytes) + *bytes = GST_BUFFER_DATA (demux->cached_buffer) + demux->offset - + cache_offset; + return GST_FLOW_OK; + } + /* not enough data in the cache, free cache and get a new one */ + gst_buffer_unref (demux->cached_buffer); + demux->cached_buffer = NULL; + } + + /* refill the cache */ + ret = gst_pad_pull_range (demux->sinkpad, demux->offset, + MAX (size, 64 * 1024), &demux->cached_buffer); + if (ret != GST_FLOW_OK) { + demux->cached_buffer = NULL; + return ret; + } + + if (GST_BUFFER_SIZE (demux->cached_buffer) >= size) { + if (p_buf) + *p_buf = gst_buffer_create_sub (demux->cached_buffer, 0, size); + if (bytes) + *bytes = GST_BUFFER_DATA (demux->cached_buffer); + return GST_FLOW_OK; + } + + /* Not possible to get enough data, try a last time with + * requesting exactly the size we need */ + gst_buffer_unref (demux->cached_buffer); + demux->cached_buffer = NULL; + + ret = + gst_pad_pull_range (demux->sinkpad, demux->offset, size, + &demux->cached_buffer); + if (ret != GST_FLOW_OK) { + GST_DEBUG_OBJECT (demux, "pull_range returned %d", ret); + if (p_buf) + *p_buf = NULL; + if (bytes) + *bytes = NULL; + return ret; + } + + if (GST_BUFFER_SIZE (demux->cached_buffer) < size) { + GST_WARNING_OBJECT (demux, "Dropping short buffer at offset %" + G_GUINT64_FORMAT ": wanted %u bytes, got %u bytes", demux->offset, + size, GST_BUFFER_SIZE (demux->cached_buffer)); + + gst_buffer_unref (demux->cached_buffer); + demux->cached_buffer = NULL; + if (p_buf) + *p_buf = NULL; + if (bytes) + *bytes = NULL; + return GST_FLOW_UNEXPECTED; + } + + if (p_buf) + *p_buf = gst_buffer_create_sub (demux->cached_buffer, 0, size); + if (bytes) + *bytes = GST_BUFFER_DATA (demux->cached_buffer); + + return GST_FLOW_OK; +} + +static const guint8 * +gst_matroska_demux_peek_pull (GstMatroskaDemux * demux, guint peek) +{ + guint8 *data = NULL; + + gst_matroska_demux_peek_bytes (demux, demux->offset, peek, NULL, &data); + return data; +} + +static GstFlowReturn +gst_matroska_demux_peek_id_length_pull (GstMatroskaDemux * demux, guint32 * _id, + guint64 * _length, guint * _needed) +{ + return gst_ebml_peek_id_length (_id, _length, _needed, + (GstPeekData) gst_matroska_demux_peek_pull, (gpointer) demux, + GST_ELEMENT_CAST (demux), demux->offset); +} + +static gint64 +gst_matroska_demux_get_length (GstMatroskaDemux * demux) +{ + GstFormat fmt = GST_FORMAT_BYTES; + gint64 end; + + /* FIXME: what to do if we don't get the upstream length */ + if (!gst_pad_query_peer_duration (demux->sinkpad, &fmt, &end) || + fmt != GST_FORMAT_BYTES || end < 0) + g_return_val_if_reached (0); + + return end; } static gint @@ -496,10 +632,9 @@ gst_matroska_demux_encoding_order_unique (GArray * encodings, guint64 order) static GstFlowReturn gst_matroska_demux_read_track_encoding (GstMatroskaDemux * demux, - GstMatroskaTrackContext * context) + GstEbmlRead * ebml, GstMatroskaTrackContext * context) { GstMatroskaTrackEncoding enc = { 0, }; - GstEbmlRead *ebml = GST_EBML_READ (demux); GstFlowReturn ret; guint32 id; @@ -513,14 +648,9 @@ gst_matroska_demux_read_track_encoding (GstMatroskaDemux * demux, return ret; } - while (ret == GST_FLOW_OK) { - if ((ret = gst_ebml_peek_id (ebml, &demux->level_up, &id)) != GST_FLOW_OK) - break; - - if (demux->level_up) { - demux->level_up--; + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) break; - } switch (id) { case GST_MATROSKA_ID_CONTENTENCODINGORDER:{ @@ -588,15 +718,10 @@ gst_matroska_demux_read_track_encoding (GstMatroskaDemux * demux, if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) break; - while (ret == GST_FLOW_OK) { - if ((ret = gst_ebml_peek_id (ebml, &demux->level_up, - &id)) != GST_FLOW_OK) - break; - - if (demux->level_up) { - demux->level_up--; + while (ret == GST_FLOW_OK && + gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) break; - } switch (id) { case GST_MATROSKA_ID_CONTENTCOMPALGO:{ @@ -638,11 +763,6 @@ gst_matroska_demux_read_track_encoding (GstMatroskaDemux * demux, ret = gst_ebml_read_skip (ebml); break; } - - if (demux->level_up) { - demux->level_up--; - break; - } } DEBUG_ELEMENT_STOP (demux, ebml, "ContentCompression", ret); break; @@ -659,11 +779,6 @@ gst_matroska_demux_read_track_encoding (GstMatroskaDemux * demux, ret = gst_ebml_read_skip (ebml); break; } - - if (demux->level_up) { - demux->level_up--; - break; - } } DEBUG_ELEMENT_STOP (demux, ebml, "ContentEncoding", ret); @@ -992,10 +1107,9 @@ gst_matroska_decode_content_encodings (GArray * encodings) static GstFlowReturn gst_matroska_demux_read_track_encodings (GstMatroskaDemux * demux, - GstMatroskaTrackContext * context) + GstEbmlRead * ebml, GstMatroskaTrackContext * context) { GstFlowReturn ret; - GstEbmlRead *ebml = GST_EBML_READ (demux); guint32 id; DEBUG_ELEMENT_START (demux, ebml, "ContentEncodings"); @@ -1008,18 +1122,13 @@ gst_matroska_demux_read_track_encodings (GstMatroskaDemux * demux, context->encodings = g_array_sized_new (FALSE, FALSE, sizeof (GstMatroskaTrackEncoding), 1); - while (ret == GST_FLOW_OK) { - if ((ret = gst_ebml_peek_id (ebml, &demux->level_up, &id)) != GST_FLOW_OK) - break; - - if (demux->level_up) { - demux->level_up--; + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) break; - } switch (id) { case GST_MATROSKA_ID_CONTENTENCODING: - ret = gst_matroska_demux_read_track_encoding (demux, context); + ret = gst_matroska_demux_read_track_encoding (demux, ebml, context); break; default: GST_WARNING_OBJECT (demux, @@ -1027,11 +1136,6 @@ gst_matroska_demux_read_track_encodings (GstMatroskaDemux * demux, ret = gst_ebml_read_skip (ebml); break; } - - if (demux->level_up) { - demux->level_up--; - break; - } } DEBUG_ELEMENT_STOP (demux, ebml, "ContentEncodings", ret); @@ -1062,10 +1166,9 @@ gst_matroska_demux_tracknumber_unique (GstMatroskaDemux * demux, guint64 num) } static GstFlowReturn -gst_matroska_demux_add_stream (GstMatroskaDemux * demux) +gst_matroska_demux_add_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml) { GstElementClass *klass = GST_ELEMENT_GET_CLASS (demux); - GstEbmlRead *ebml = GST_EBML_READ (demux); GstMatroskaTrackContext *context; GstPadTemplate *templ = NULL; GstCaps *caps = NULL; @@ -1104,15 +1207,10 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux) GST_DEBUG_OBJECT (demux, "Stream number %d", context->index); /* try reading the trackentry headers */ - while (ret == GST_FLOW_OK) { - if ((ret = gst_ebml_peek_id (ebml, &demux->level_up, &id)) != GST_FLOW_OK) + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) break; - if (demux->level_up) { - demux->level_up--; - break; - } - switch (id) { /* track number (unique stream ID) */ case GST_MATROSKA_ID_TRACKNUMBER:{ @@ -1217,16 +1315,10 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux) videocontext = (GstMatroskaTrackVideoContext *) context; g_ptr_array_index (demux->src, demux->num_streams - 1) = context; - while (ret == GST_FLOW_OK) { - if ((ret = - gst_ebml_peek_id (ebml, &demux->level_up, - &id)) != GST_FLOW_OK) - break; - - if (demux->level_up) { - demux->level_up--; + while (ret == GST_FLOW_OK && + gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) break; - } switch (id) { /* Should be one level up but some broken muxers write it here. */ @@ -1418,11 +1510,6 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux) ret = gst_ebml_read_skip (ebml); break; } - - if (demux->level_up) { - demux->level_up--; - break; - } } DEBUG_ELEMENT_STOP (demux, ebml, "TrackVideo", ret); @@ -1448,16 +1535,10 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux) audiocontext = (GstMatroskaTrackAudioContext *) context; g_ptr_array_index (demux->src, demux->num_streams - 1) = context; - while (ret == GST_FLOW_OK) { - if ((ret = - gst_ebml_peek_id (ebml, &demux->level_up, - &id)) != GST_FLOW_OK) - break; - - if (demux->level_up) { - demux->level_up--; + while (ret == GST_FLOW_OK && + gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) break; - } switch (id) { /* samplerate */ @@ -1524,11 +1605,6 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux) ret = gst_ebml_read_skip (ebml); break; } - - if (demux->level_up) { - demux->level_up--; - break; - } } DEBUG_ELEMENT_STOP (demux, ebml, "TrackAudio", ret); @@ -1697,7 +1773,7 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux) } case GST_MATROSKA_ID_CONTENTENCODINGS:{ - ret = gst_matroska_demux_read_track_encodings (demux, context); + ret = gst_matroska_demux_read_track_encodings (demux, ebml, context); break; } @@ -1737,11 +1813,6 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux) ret = gst_ebml_read_skip (ebml); break; } - - if (demux->level_up) { - demux->level_up--; - break; - } } DEBUG_ELEMENT_STOP (demux, ebml, "TrackEntry", ret); @@ -2213,12 +2284,8 @@ gst_matroska_demux_move_to_entry (GstMatroskaDemux * demux, GST_OBJECT_LOCK (demux); /* seek (relative to matroska segment) */ - if (gst_ebml_read_seek (GST_EBML_READ (demux), - entry->pos + demux->ebml_segment_start) != GST_FLOW_OK) { - GST_DEBUG_OBJECT (demux, "Failed to seek to offset %" G_GUINT64_FORMAT, - entry->pos + demux->ebml_segment_start); - goto seek_error; - } + /* position might be invalid; will error when streaming resumes ... */ + demux->offset = entry->pos + demux->ebml_segment_start; GST_DEBUG_OBJECT (demux, "Seeked to offset %" G_GUINT64_FORMAT ", block %d, " "time %" GST_TIME_FORMAT, entry->pos + demux->ebml_segment_start, @@ -2228,6 +2295,7 @@ gst_matroska_demux_move_to_entry (GstMatroskaDemux * demux, gst_matroska_demux_reset_streams (demux, entry->time, TRUE); demux->segment.last_stop = entry->time; demux->seek_block = entry->block; + demux->seek_first = TRUE; demux->last_stop_end = GST_CLOCK_TIME_NONE; if (reset) { @@ -2238,13 +2306,6 @@ gst_matroska_demux_move_to_entry (GstMatroskaDemux * demux, GST_OBJECT_UNLOCK (demux); return TRUE; - -seek_error: - { - GST_OBJECT_UNLOCK (demux); - GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), ("Got a seek error")); - return FALSE; - } } static gboolean @@ -2589,17 +2650,155 @@ exit: return ret; } +/* skip unknown or alike element */ static GstFlowReturn -gst_matroska_demux_parse_header (GstMatroskaDemux * demux) +gst_matroska_demux_parse_skip (GstMatroskaDemux * demux, GstEbmlRead * ebml, + const gchar * parent_name, guint id) +{ + if (id == GST_EBML_ID_VOID) { + GST_DEBUG_OBJECT (demux, "Skipping EBML Void element"); + } else if (id == GST_EBML_ID_CRC32) { + GST_DEBUG_OBJECT (demux, "Skipping EBML CRC32 element"); + } else { + GST_WARNING_OBJECT (demux, + "Unknown %s subelement 0x%x - ignoring", parent_name, id); + } + + return gst_ebml_read_skip (ebml); +} + +static GstFlowReturn +gst_matroska_demux_parse_header (GstMatroskaDemux * demux, GstEbmlRead * ebml) { - GstEbmlRead *ebml = GST_EBML_READ (demux); GstFlowReturn ret; gchar *doctype; guint version; + guint32 id; + + /* this function is the first to be called */ + + /* default init */ + doctype = NULL; + version = 1; + + ret = gst_ebml_peek_id (ebml, &id); + if (ret != GST_FLOW_OK) + return ret; + + GST_DEBUG_OBJECT (demux, "id: %08x", id); + + if (id != GST_EBML_ID_HEADER) { + GST_ERROR_OBJECT (demux, "Failed to read header"); + goto exit; + } - if ((ret = gst_ebml_read_header (ebml, &doctype, &version)) != GST_FLOW_OK) + ret = gst_ebml_read_master (ebml, &id); + if (ret != GST_FLOW_OK) return ret; + while (gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + ret = gst_ebml_peek_id (ebml, &id); + if (ret != GST_FLOW_OK) + return ret; + + switch (id) { + /* is our read version uptodate? */ + case GST_EBML_ID_EBMLREADVERSION:{ + guint64 num; + + ret = gst_ebml_read_uint (ebml, &id, &num); + if (ret != GST_FLOW_OK) + return ret; + g_assert (id == GST_EBML_ID_EBMLREADVERSION); + if (num != GST_EBML_VERSION) { + GST_ERROR_OBJECT (ebml, "Unsupported EBML version %" G_GUINT64_FORMAT, + num); + return GST_FLOW_ERROR; + } + + GST_DEBUG_OBJECT (ebml, "EbmlReadVersion: %" G_GUINT64_FORMAT, num); + break; + } + + /* we only handle 8 byte lengths at max */ + case GST_EBML_ID_EBMLMAXSIZELENGTH:{ + guint64 num; + + ret = gst_ebml_read_uint (ebml, &id, &num); + if (ret != GST_FLOW_OK) + return ret; + g_assert (id == GST_EBML_ID_EBMLMAXSIZELENGTH); + if (num > sizeof (guint64)) { + GST_ERROR_OBJECT (ebml, + "Unsupported EBML maximum size %" G_GUINT64_FORMAT, num); + return GST_FLOW_ERROR; + } + GST_DEBUG_OBJECT (ebml, "EbmlMaxSizeLength: %" G_GUINT64_FORMAT, num); + break; + } + + /* we handle 4 byte IDs at max */ + case GST_EBML_ID_EBMLMAXIDLENGTH:{ + guint64 num; + + ret = gst_ebml_read_uint (ebml, &id, &num); + if (ret != GST_FLOW_OK) + return ret; + g_assert (id == GST_EBML_ID_EBMLMAXIDLENGTH); + if (num > sizeof (guint32)) { + GST_ERROR_OBJECT (ebml, + "Unsupported EBML maximum ID %" G_GUINT64_FORMAT, num); + return GST_FLOW_ERROR; + } + GST_DEBUG_OBJECT (ebml, "EbmlMaxIdLength: %" G_GUINT64_FORMAT, num); + break; + } + + case GST_EBML_ID_DOCTYPE:{ + gchar *text; + + ret = gst_ebml_read_ascii (ebml, &id, &text); + if (ret != GST_FLOW_OK) + return ret; + g_assert (id == GST_EBML_ID_DOCTYPE); + + GST_DEBUG_OBJECT (ebml, "EbmlDocType: %s", GST_STR_NULL (text)); + + if (doctype) + g_free (doctype); + doctype = text; + break; + } + + case GST_EBML_ID_DOCTYPEREADVERSION:{ + guint64 num; + + ret = gst_ebml_read_uint (ebml, &id, &num); + if (ret != GST_FLOW_OK) + return ret; + g_assert (id == GST_EBML_ID_DOCTYPEREADVERSION); + version = num; + GST_DEBUG_OBJECT (ebml, "EbmlReadVersion: %" G_GUINT64_FORMAT, num); + break; + } + + default: + ret = gst_matroska_demux_parse_skip (demux, ebml, "EBML header", id); + if (ret != GST_FLOW_OK) + return ret; + break; + + /* we ignore these two, as they don't tell us anything we care about */ + case GST_EBML_ID_EBMLVERSION: + case GST_EBML_ID_DOCTYPEVERSION: + ret = gst_ebml_read_skip (ebml); + if (ret != GST_FLOW_OK) + return ret; + break; + } + } + +exit: ret = GST_FLOW_ERROR; if (doctype) { if (g_str_equal (doctype, GST_MATROSKA_DOCTYPE_MATROSKA) || @@ -2626,58 +2825,8 @@ gst_matroska_demux_parse_header (GstMatroskaDemux * demux) } static GstFlowReturn -gst_matroska_demux_init_stream (GstMatroskaDemux * demux) -{ - GstEbmlRead *ebml = GST_EBML_READ (demux); - guint32 id; - GstFlowReturn ret; - - GST_DEBUG_OBJECT (demux, "Init stream"); - - if ((ret = gst_matroska_demux_parse_header (demux)) != GST_FLOW_OK) - return ret; - - /* find segment, must be the next element but search as long as - * we find it anyway */ - while (TRUE) { - guint last_level; - - if ((ret = gst_ebml_peek_id (ebml, &last_level, &id)) != GST_FLOW_OK) { - GST_DEBUG_OBJECT (demux, "gst_ebml_peek_id() failed!"); - return ret; - } - - if (id == GST_MATROSKA_ID_SEGMENT) - break; - - /* oi! */ - GST_WARNING_OBJECT (demux, - "Expected a Segment ID (0x%x), but received 0x%x!", - GST_MATROSKA_ID_SEGMENT, id); - - if ((ret = gst_ebml_read_skip (ebml)) != GST_FLOW_OK) - return ret; - } - - /* we now have a EBML segment */ - if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { - GST_DEBUG_OBJECT (demux, "gst_ebml_read_master() failed!"); - return ret; - } - - GST_DEBUG_OBJECT (demux, "Found Segment start at offset %" G_GUINT64_FORMAT, - ebml->offset); - /* seeks are from the beginning of the segment, - * after the segment ID/length */ - demux->ebml_segment_start = ebml->offset; - - return ret; -} - -static GstFlowReturn -gst_matroska_demux_parse_tracks (GstMatroskaDemux * demux) +gst_matroska_demux_parse_tracks (GstMatroskaDemux * demux, GstEbmlRead * ebml) { - GstEbmlRead *ebml = GST_EBML_READ (demux); GstFlowReturn ret = GST_FLOW_OK; guint32 id; @@ -2688,31 +2837,20 @@ gst_matroska_demux_parse_tracks (GstMatroskaDemux * demux) return ret; } - while (ret == GST_FLOW_OK) { - if ((ret = gst_ebml_peek_id (ebml, &demux->level_up, &id)) != GST_FLOW_OK) - break; - - if (demux->level_up) { - demux->level_up--; + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) break; - } switch (id) { /* one track within the "all-tracks" header */ case GST_MATROSKA_ID_TRACKENTRY: - ret = gst_matroska_demux_add_stream (demux); + ret = gst_matroska_demux_add_stream (demux, ebml); break; default: - GST_WARNING ("Unknown Track subelement 0x%x - ignoring", id); - ret = gst_ebml_read_skip (ebml); + ret = gst_matroska_demux_parse_skip (demux, ebml, "Track", id); break; } - - if (demux->level_up) { - demux->level_up--; - break; - } } DEBUG_ELEMENT_STOP (demux, ebml, "Tracks", ret); @@ -2723,9 +2861,8 @@ gst_matroska_demux_parse_tracks (GstMatroskaDemux * demux) static GstFlowReturn gst_matroska_demux_parse_index_cuetrack (GstMatroskaDemux * demux, - guint * nentries) + GstEbmlRead * ebml, guint * nentries) { - GstEbmlRead *ebml = GST_EBML_READ (demux); guint32 id; GstFlowReturn ret; GstMatroskaIndex idx; @@ -2742,15 +2879,10 @@ gst_matroska_demux_parse_index_cuetrack (GstMatroskaDemux * demux, return ret; } - while (ret == GST_FLOW_OK) { - if ((ret = gst_ebml_peek_id (ebml, &demux->level_up, &id)) != GST_FLOW_OK) + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) break; - if (demux->level_up) { - demux->level_up--; - break; - } - switch (id) { /* track number */ case GST_MATROSKA_ID_CUETRACK: @@ -2814,21 +2946,15 @@ gst_matroska_demux_parse_index_cuetrack (GstMatroskaDemux * demux, } default: - GST_WARNING ("Unknown CueTrackPositions subelement 0x%x - ignoring", + ret = gst_matroska_demux_parse_skip (demux, ebml, "CueTrackPositions", id); - /* fall-through */ + break; case GST_MATROSKA_ID_CUECODECSTATE: case GST_MATROSKA_ID_CUEREFERENCE: - if ((ret = gst_ebml_read_skip (ebml)) != GST_FLOW_OK) - break; + ret = gst_ebml_read_skip (ebml); break; } - - if (demux->level_up) { - demux->level_up--; - break; - } } DEBUG_ELEMENT_STOP (demux, ebml, "CueTrackPositions", ret); @@ -2845,9 +2971,9 @@ gst_matroska_demux_parse_index_cuetrack (GstMatroskaDemux * demux, } static GstFlowReturn -gst_matroska_demux_parse_index_pointentry (GstMatroskaDemux * demux) +gst_matroska_demux_parse_index_pointentry (GstMatroskaDemux * demux, + GstEbmlRead * ebml) { - GstEbmlRead *ebml = GST_EBML_READ (demux); guint32 id; GstFlowReturn ret; GstClockTime time = GST_CLOCK_TIME_NONE; @@ -2860,14 +2986,9 @@ gst_matroska_demux_parse_index_pointentry (GstMatroskaDemux * demux) return ret; } - while (ret == GST_FLOW_OK) { - if ((ret = gst_ebml_peek_id (ebml, &demux->level_up, &id)) != GST_FLOW_OK) - break; - - if (demux->level_up) { - demux->level_up--; + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) break; - } switch (id) { /* one single index entry ('point') */ @@ -2885,24 +3006,16 @@ gst_matroska_demux_parse_index_pointentry (GstMatroskaDemux * demux) case GST_MATROSKA_ID_CUETRACKPOSITIONS: { if ((ret = - gst_matroska_demux_parse_index_cuetrack (demux, + gst_matroska_demux_parse_index_cuetrack (demux, ebml, &nentries)) != GST_FLOW_OK) break; break; } default: - GST_WARNING_OBJECT (demux, - "Unknown CuePoint subelement 0x%x - ignoring", id); - if ((ret = gst_ebml_read_skip (ebml)) != GST_FLOW_OK) - break; + ret = gst_matroska_demux_parse_skip (demux, ebml, "CuePoint", id); break; } - - if (demux->level_up) { - demux->level_up--; - break; - } } DEBUG_ELEMENT_STOP (demux, ebml, "CuePoint", ret); @@ -2948,9 +3061,8 @@ gst_matroska_index_compare (GstMatroskaIndex * i1, GstMatroskaIndex * i2) } static GstFlowReturn -gst_matroska_demux_parse_index (GstMatroskaDemux * demux) +gst_matroska_demux_parse_index (GstMatroskaDemux * demux, GstEbmlRead * ebml) { - GstEbmlRead *ebml = GST_EBML_READ (demux); guint32 id; GstFlowReturn ret = GST_FLOW_OK; guint i; @@ -2967,31 +3079,20 @@ gst_matroska_demux_parse_index (GstMatroskaDemux * demux) return ret; } - while (ret == GST_FLOW_OK) { - if ((ret = gst_ebml_peek_id (ebml, &demux->level_up, &id)) != GST_FLOW_OK) - break; - - if (demux->level_up) { - demux->level_up--; + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) break; - } switch (id) { /* one single index entry ('point') */ case GST_MATROSKA_ID_POINTENTRY: - ret = gst_matroska_demux_parse_index_pointentry (demux); + ret = gst_matroska_demux_parse_index_pointentry (demux, ebml); break; default: - GST_WARNING ("Unknown Cues subelement 0x%x - ignoring", id); - ret = gst_ebml_read_skip (ebml); + ret = gst_matroska_demux_parse_skip (demux, ebml, "Cues", id); break; } - - if (demux->level_up) { - demux->level_up--; - break; - } } DEBUG_ELEMENT_STOP (demux, ebml, "Cues", ret); @@ -3053,9 +3154,8 @@ gst_matroska_demux_parse_index (GstMatroskaDemux * demux) } static GstFlowReturn -gst_matroska_demux_parse_info (GstMatroskaDemux * demux) +gst_matroska_demux_parse_info (GstMatroskaDemux * demux, GstEbmlRead * ebml) { - GstEbmlRead *ebml = GST_EBML_READ (demux); GstFlowReturn ret = GST_FLOW_OK; guint32 id; @@ -3066,14 +3166,9 @@ gst_matroska_demux_parse_info (GstMatroskaDemux * demux) return ret; } - while (ret == GST_FLOW_OK) { - if ((ret = gst_ebml_peek_id (ebml, &demux->level_up, &id)) != GST_FLOW_OK) - break; - - if (demux->level_up) { - demux->level_up--; + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) break; - } switch (id) { /* cluster timecode */ @@ -3160,8 +3255,8 @@ gst_matroska_demux_parse_info (GstMatroskaDemux * demux) } default: - GST_WARNING_OBJECT (demux, - "Unknown SegmentInfo subelement 0x%x - ignoring", id); + ret = gst_matroska_demux_parse_skip (demux, ebml, "SegmentInfo", id); + break; /* fall through */ case GST_MATROSKA_ID_SEGMENTUID: @@ -3175,11 +3270,6 @@ gst_matroska_demux_parse_info (GstMatroskaDemux * demux) ret = gst_ebml_read_skip (ebml); break; } - - if (demux->level_up) { - demux->level_up--; - break; - } } DEBUG_ELEMENT_STOP (demux, ebml, "SegmentInfo", ret); @@ -3191,7 +3281,7 @@ gst_matroska_demux_parse_info (GstMatroskaDemux * demux) static GstFlowReturn gst_matroska_demux_parse_metadata_id_simple_tag (GstMatroskaDemux * demux, - GstTagList ** p_taglist) + GstEbmlRead * ebml, GstTagList ** p_taglist) { /* FIXME: check if there are more useful mappings */ struct @@ -3217,7 +3307,6 @@ gst_matroska_demux_parse_metadata_id_simple_tag (GstMatroskaDemux * demux, GST_MATROSKA_TAG_ID_LEAD_PERFORMER, GST_TAG_PERFORMER}, { GST_MATROSKA_TAG_ID_GENRE, GST_TAG_GENRE} }; - GstEbmlRead *ebml = GST_EBML_READ (demux); GstFlowReturn ret; guint32 id; gchar *value = NULL; @@ -3230,16 +3319,11 @@ gst_matroska_demux_parse_metadata_id_simple_tag (GstMatroskaDemux * demux, return ret; } - while (ret == GST_FLOW_OK) { + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { /* read all sub-entries */ - if ((ret = gst_ebml_peek_id (ebml, &demux->level_up, &id)) != GST_FLOW_OK) - break; - - if (demux->level_up) { - demux->level_up--; + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) break; - } switch (id) { case GST_MATROSKA_ID_TAGNAME: @@ -3257,8 +3341,8 @@ gst_matroska_demux_parse_metadata_id_simple_tag (GstMatroskaDemux * demux, break; default: - GST_WARNING_OBJECT (demux, - "Unknown SimpleTag subelement 0x%x - ignoring", id); + ret = gst_matroska_demux_parse_skip (demux, ebml, "SimpleTag", id); + break; /* fall-through */ case GST_MATROSKA_ID_TAGLANGUAGE: @@ -3267,11 +3351,6 @@ gst_matroska_demux_parse_metadata_id_simple_tag (GstMatroskaDemux * demux, ret = gst_ebml_read_skip (ebml); break; } - - if (demux->level_up) { - demux->level_up--; - break; - } } DEBUG_ELEMENT_STOP (demux, ebml, "SimpleTag", ret); @@ -3311,9 +3390,8 @@ gst_matroska_demux_parse_metadata_id_simple_tag (GstMatroskaDemux * demux, static GstFlowReturn gst_matroska_demux_parse_metadata_id_tag (GstMatroskaDemux * demux, - GstTagList ** p_taglist) + GstEbmlRead * ebml, GstTagList ** p_taglist) { - GstEbmlRead *ebml = GST_EBML_READ (demux); guint32 id; GstFlowReturn ret; @@ -3324,35 +3402,23 @@ gst_matroska_demux_parse_metadata_id_tag (GstMatroskaDemux * demux, return ret; } - while (ret == GST_FLOW_OK) { + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { /* read all sub-entries */ - if ((ret = gst_ebml_peek_id (ebml, &demux->level_up, &id)) != GST_FLOW_OK) - break; - - if (demux->level_up) { - demux->level_up--; + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) break; - } switch (id) { case GST_MATROSKA_ID_SIMPLETAG: - ret = - gst_matroska_demux_parse_metadata_id_simple_tag (demux, p_taglist); + ret = gst_matroska_demux_parse_metadata_id_simple_tag (demux, ebml, + p_taglist); break; default: - GST_WARNING_OBJECT (demux, "Unknown Tag subelement 0x%x - ignoring", - id); - ret = gst_ebml_read_skip (ebml); + ret = gst_matroska_demux_parse_skip (demux, ebml, "Tag", id); break; } - - if (demux->level_up) { - demux->level_up--; - break; - } - } + } DEBUG_ELEMENT_STOP (demux, ebml, "Tag", ret); @@ -3360,40 +3426,33 @@ gst_matroska_demux_parse_metadata_id_tag (GstMatroskaDemux * demux, } static GstFlowReturn -gst_matroska_demux_parse_metadata (GstMatroskaDemux * demux) +gst_matroska_demux_parse_metadata (GstMatroskaDemux * demux, GstEbmlRead * ebml) { - GstEbmlRead *ebml = GST_EBML_READ (demux); GstTagList *taglist; GstFlowReturn ret = GST_FLOW_OK; guint32 id; GList *l; - GstEbmlLevel *curlevel; + guint64 curpos; - if (ebml->level == NULL) { - GST_ERROR_OBJECT (demux, "Unexpected metadata, bailing"); - return GST_FLOW_ERROR; - } - curlevel = ebml->level->data; + curpos = gst_ebml_read_get_pos (ebml); /* Make sure we don't parse a tags element twice and * post it's tags twice */ + curpos = gst_ebml_read_get_pos (ebml); for (l = demux->tags_parsed; l; l = l->next) { - GstEbmlLevel *level = l->data; - - if (ebml->level) - curlevel = ebml->level->data; - else - break; + guint64 *pos = l->data; - if (level->start == curlevel->start && level->length == curlevel->length) { + if (*pos == curpos) { GST_DEBUG_OBJECT (demux, "Skipping already parsed Tags at offset %" - G_GUINT64_FORMAT, ebml->offset); - ret = gst_ebml_read_skip (ebml); - return ret; + G_GUINT64_FORMAT, curpos); + return GST_FLOW_OK; } } - DEBUG_ELEMENT_START (demux, ebml, "Tags"); + demux->tags_parsed = + g_list_prepend (demux->tags_parsed, g_slice_new (guint64)); + *((guint64 *) demux->tags_parsed->data) = curpos; + /* fall-through */ if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { DEBUG_ELEMENT_STOP (demux, ebml, "Tags", ret); @@ -3402,38 +3461,23 @@ gst_matroska_demux_parse_metadata (GstMatroskaDemux * demux) taglist = gst_tag_list_new (); - /* TODO: g_slice_dup() if we depend on GLib 2.14 */ - curlevel = g_slice_new (GstEbmlLevel); - memcpy (curlevel, ebml->level->data, sizeof (GstEbmlLevel)); - demux->tags_parsed = g_list_prepend (demux->tags_parsed, curlevel); - - while (ret == GST_FLOW_OK) { - if ((ret = gst_ebml_peek_id (ebml, &demux->level_up, &id)) != GST_FLOW_OK) - break; - - if (demux->level_up) { - demux->level_up--; + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) break; - } switch (id) { case GST_MATROSKA_ID_TAG: - ret = gst_matroska_demux_parse_metadata_id_tag (demux, &taglist); + ret = gst_matroska_demux_parse_metadata_id_tag (demux, ebml, &taglist); break; default: - GST_WARNING_OBJECT (demux, "Unknown Tags subelement 0x%x - ignoring", - id); + ret = gst_matroska_demux_parse_skip (demux, ebml, "Tags", id); + break; /* FIXME: Use to limit the tags to specific pads */ case GST_MATROSKA_ID_TARGETS: ret = gst_ebml_read_skip (ebml); break; } - - if (demux->level_up) { - demux->level_up--; - break; - } } DEBUG_ELEMENT_STOP (demux, ebml, "Tags", ret); @@ -3445,9 +3489,8 @@ gst_matroska_demux_parse_metadata (GstMatroskaDemux * demux) static GstFlowReturn gst_matroska_demux_parse_attached_file (GstMatroskaDemux * demux, - GstTagList * taglist) + GstEbmlRead * ebml, GstTagList * taglist) { - GstEbmlRead *ebml = GST_EBML_READ (demux); guint32 id; GstFlowReturn ret; gchar *description = NULL; @@ -3463,17 +3506,12 @@ gst_matroska_demux_parse_attached_file (GstMatroskaDemux * demux, return ret; } - while (ret == GST_FLOW_OK) { + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { /* read all sub-entries */ - if ((ret = gst_ebml_peek_id (ebml, &demux->level_up, &id)) != GST_FLOW_OK) + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) break; - if (demux->level_up) { - demux->level_up--; - break; - } - switch (id) { case GST_MATROSKA_ID_FILEDESCRIPTION: if (description) { @@ -3516,18 +3554,12 @@ gst_matroska_demux_parse_attached_file (GstMatroskaDemux * demux, break; default: - GST_WARNING_OBJECT (demux, - "Unknown AttachedFile subelement 0x%x - ignoring", id); - /* fall through */ + ret = gst_matroska_demux_parse_skip (demux, ebml, "AttachedFile", id); + break; case GST_MATROSKA_ID_FILEUID: ret = gst_ebml_read_skip (ebml); break; } - - if (demux->level_up) { - demux->level_up--; - break; - } } DEBUG_ELEMENT_STOP (demux, ebml, "AttachedFile", ret); @@ -3609,9 +3641,9 @@ gst_matroska_demux_parse_attached_file (GstMatroskaDemux * demux, } static GstFlowReturn -gst_matroska_demux_parse_attachments (GstMatroskaDemux * demux) +gst_matroska_demux_parse_attachments (GstMatroskaDemux * demux, + GstEbmlRead * ebml) { - GstEbmlRead *ebml = GST_EBML_READ (demux); guint32 id; GstFlowReturn ret = GST_FLOW_OK; GstTagList *taglist; @@ -3625,30 +3657,19 @@ gst_matroska_demux_parse_attachments (GstMatroskaDemux * demux) taglist = gst_tag_list_new (); - while (ret == GST_FLOW_OK) { - if ((ret = gst_ebml_peek_id (ebml, &demux->level_up, &id)) != GST_FLOW_OK) - break; - - if (demux->level_up) { - demux->level_up--; + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) break; - } switch (id) { case GST_MATROSKA_ID_ATTACHEDFILE: - ret = gst_matroska_demux_parse_attached_file (demux, taglist); + ret = gst_matroska_demux_parse_attached_file (demux, ebml, taglist); break; default: - GST_WARNING ("Unknown Attachments subelement 0x%x - ignoring", id); - ret = gst_ebml_read_skip (ebml); + ret = gst_matroska_demux_parse_skip (demux, ebml, "Attachments", id); break; } - - if (demux->level_up) { - demux->level_up--; - break; - } } DEBUG_ELEMENT_STOP (demux, ebml, "Attachments", ret); @@ -3666,9 +3687,8 @@ gst_matroska_demux_parse_attachments (GstMatroskaDemux * demux) } static GstFlowReturn -gst_matroska_demux_parse_chapters (GstMatroskaDemux * demux) +gst_matroska_demux_parse_chapters (GstMatroskaDemux * demux, GstEbmlRead * ebml) { - GstEbmlRead *ebml = GST_EBML_READ (demux); guint32 id; GstFlowReturn ret = GST_FLOW_OK; @@ -3683,25 +3703,15 @@ gst_matroska_demux_parse_chapters (GstMatroskaDemux * demux) return ret; } - while (ret == GST_FLOW_OK) { - if ((ret = gst_ebml_peek_id (ebml, &demux->level_up, &id)) != GST_FLOW_OK) + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) break; - if (demux->level_up) { - demux->level_up--; - break; - } - switch (id) { default: ret = gst_ebml_read_skip (ebml); break; } - - if (demux->level_up) { - demux->level_up--; - break; - } } DEBUG_ELEMENT_STOP (demux, ebml, "Chapters", ret); @@ -4329,10 +4339,10 @@ gst_matroska_demux_check_subtitle_buffer (GstElement * element, static GstFlowReturn gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux, - guint64 cluster_time, guint64 cluster_offset, gboolean is_simpleblock) + GstEbmlRead * ebml, guint64 cluster_time, guint64 cluster_offset, + gboolean is_simpleblock) { GstMatroskaTrackContext *stream = NULL; - GstEbmlRead *ebml = GST_EBML_READ (demux); GstFlowReturn ret = GST_FLOW_OK; gboolean readblock = FALSE; guint32 id; @@ -4346,17 +4356,12 @@ gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux, gint64 referenceblock = 0; gint64 offset; - offset = demux->parent.offset; + offset = gst_ebml_read_get_offset (ebml); - while (ret == GST_FLOW_OK) { + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { if (!is_simpleblock) { - if ((ret = gst_ebml_peek_id (ebml, &demux->level_up, &id)) != GST_FLOW_OK) - break; - - if (demux->level_up) { - demux->level_up--; + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) break; - } } else { id = GST_MATROSKA_ID_SIMPLEBLOCK; } @@ -4579,9 +4584,8 @@ gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux, } default: - GST_WARNING_OBJECT (demux, - "Unknown BlockGroup subelement 0x%x - ignoring", id); - /* fall-through */ + ret = gst_matroska_demux_parse_skip (demux, ebml, "BlockGroup", id); + break; case GST_MATROSKA_ID_BLOCKVIRTUAL: case GST_MATROSKA_ID_BLOCKADDITIONS: @@ -4596,11 +4600,6 @@ gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux, if (is_simpleblock) break; - - if (demux->level_up) { - demux->level_up--; - break; - } } if (ret == GST_FLOW_OK && readblock) { @@ -4914,116 +4913,9 @@ gst_matroska_demux_seek_block (GstMatroskaDemux * demux) } static GstFlowReturn -gst_matroska_demux_parse_cluster (GstMatroskaDemux * demux) -{ - GstEbmlRead *ebml = GST_EBML_READ (demux); - GstFlowReturn ret = GST_FLOW_OK; - guint64 cluster_time = GST_CLOCK_TIME_NONE; - guint32 id; - guint64 cluster_offset = demux->parent.offset; - - DEBUG_ELEMENT_START (demux, ebml, "Cluster"); - - if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { - DEBUG_ELEMENT_STOP (demux, ebml, "Cluster", ret); - return ret; - } - - while (ret == GST_FLOW_OK) { - if ((ret = gst_ebml_peek_id (ebml, &demux->level_up, &id)) != GST_FLOW_OK) - break; - - if (demux->level_up) { - demux->level_up--; - break; - } - - switch (id) { - /* cluster timecode */ - case GST_MATROSKA_ID_CLUSTERTIMECODE: - { - guint64 num; - - if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) - break; - - GST_DEBUG_OBJECT (demux, "ClusterTimeCode: %" G_GUINT64_FORMAT, num); - cluster_time = num; - break; - } - - /* a group of blocks inside a cluster */ - case GST_MATROSKA_ID_BLOCKGROUP: - if (!gst_matroska_demux_seek_block (demux)) - goto skip; - DEBUG_ELEMENT_START (demux, ebml, "BlockGroup"); - if ((ret = gst_ebml_read_master (ebml, &id)) == GST_FLOW_OK) { - ret = gst_matroska_demux_parse_blockgroup_or_simpleblock (demux, - cluster_time, cluster_offset, FALSE); - } - DEBUG_ELEMENT_STOP (demux, ebml, "BlockGroup", ret); - break; - - case GST_MATROSKA_ID_SIMPLEBLOCK: - { - if (!gst_matroska_demux_seek_block (demux)) - goto skip; - DEBUG_ELEMENT_START (demux, ebml, "SimpleBlock"); - ret = gst_matroska_demux_parse_blockgroup_or_simpleblock (demux, - cluster_time, cluster_offset, TRUE); - DEBUG_ELEMENT_STOP (demux, ebml, "SimpleBlock", ret); - break; - } - - default: - GST_WARNING ("Unknown Cluster subelement 0x%x - ignoring", id); - /* fall-through */ - case GST_MATROSKA_ID_POSITION: - case GST_MATROSKA_ID_PREVSIZE: - case GST_MATROSKA_ID_ENCRYPTEDBLOCK: - case GST_MATROSKA_ID_SILENTTRACKS: - skip: - GST_DEBUG ("Skipping Cluster subelement 0x%x - ignoring", id); - ret = gst_ebml_read_skip (ebml); - break; - } - - if (demux->level_up) { - demux->level_up--; - break; - } - } - - if (demux->element_index) { - if (demux->element_index_writer_id == -1) - gst_index_get_writer_id (demux->element_index, - GST_OBJECT (demux), &demux->element_index_writer_id); - - GST_LOG_OBJECT (demux, "adding association %" GST_TIME_FORMAT "-> %" - G_GUINT64_FORMAT " for writer id %d", - GST_TIME_ARGS (cluster_time), cluster_offset, - demux->element_index_writer_id); - gst_index_add_association (demux->element_index, - demux->element_index_writer_id, GST_ASSOCIATION_FLAG_KEY_UNIT, - GST_FORMAT_TIME, cluster_time, GST_FORMAT_BYTES, cluster_offset, NULL); - } - - DEBUG_ELEMENT_STOP (demux, ebml, "Cluster", ret); - - if (G_UNLIKELY (demux->seek_block)) { - GST_DEBUG_OBJECT (demux, "seek target block %" G_GUINT64_FORMAT - " not found in Cluster, trying next Cluster's first block instead", - demux->seek_block); - demux->seek_block = 0; - } - - return ret; -} - -static GstFlowReturn -gst_matroska_demux_parse_contents_seekentry (GstMatroskaDemux * demux) +gst_matroska_demux_parse_contents_seekentry (GstMatroskaDemux * demux, + GstEbmlRead * ebml) { - GstEbmlRead *ebml = GST_EBML_READ (demux); GstFlowReturn ret; guint64 seek_pos = (guint64) - 1; guint32 seek_id = 0; @@ -5036,14 +4928,9 @@ gst_matroska_demux_parse_contents_seekentry (GstMatroskaDemux * demux) return ret; } - while (ret == GST_FLOW_OK) { - if ((ret = gst_ebml_peek_id (ebml, &demux->level_up, &id)) != GST_FLOW_OK) - break; - - if (demux->level_up) { - demux->level_up--; + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) break; - } switch (id) { case GST_MATROSKA_ID_SEEKID: @@ -5077,16 +4964,9 @@ gst_matroska_demux_parse_contents_seekentry (GstMatroskaDemux * demux) } default: - GST_WARNING_OBJECT (demux, - "Unknown SeekHead subelement 0x%x - ignoring", id); - ret = gst_ebml_read_skip (ebml); + ret = gst_matroska_demux_parse_skip (demux, ebml, "SeekHead", id); break; } - - if (demux->level_up) { - demux->level_up--; - break; - } } if (ret != GST_FLOW_OK && ret != GST_FLOW_UNEXPECTED) @@ -5099,21 +4979,22 @@ gst_matroska_demux_parse_contents_seekentry (GstMatroskaDemux * demux) } switch (seek_id) { + case GST_MATROSKA_ID_SEEKHEAD: + { + } case GST_MATROSKA_ID_CUES: case GST_MATROSKA_ID_TAGS: case GST_MATROSKA_ID_TRACKS: - case GST_MATROSKA_ID_SEEKHEAD: case GST_MATROSKA_ID_SEGMENTINFO: case GST_MATROSKA_ID_ATTACHMENTS: case GST_MATROSKA_ID_CHAPTERS: { - guint level_up = demux->level_up; guint64 before_pos, length; - GstEbmlLevel *level; + guint needed; /* remember */ - length = gst_ebml_read_get_length (ebml); - before_pos = ebml->offset; + length = gst_matroska_demux_get_length (demux); + before_pos = demux->offset; /* check for validity */ if (seek_pos + demux->ebml_segment_start + 12 >= length) { @@ -5135,97 +5016,25 @@ gst_matroska_demux_parse_contents_seekentry (GstMatroskaDemux * demux) } /* seek */ - if (gst_ebml_read_seek (ebml, seek_pos + demux->ebml_segment_start) != - GST_FLOW_OK) - break; - - /* we don't want to lose our seekhead level, so we add - * a dummy. This is a crude hack. */ - level = g_slice_new (GstEbmlLevel); - level->start = 0; - level->length = G_MAXUINT64; - ebml->level = g_list_prepend (ebml->level, level); + demux->offset = seek_pos + demux->ebml_segment_start; /* check ID */ - if ((ret = gst_ebml_peek_id (ebml, &demux->level_up, &id)) != GST_FLOW_OK) + if ((ret = gst_matroska_demux_peek_id_length_pull (demux, &id, &length, + &needed)) != GST_FLOW_OK) goto finish; if (id != seek_id) { GST_WARNING_OBJECT (demux, "We looked for ID=0x%x but got ID=0x%x (pos=%" G_GUINT64_FORMAT ")", seek_id, id, seek_pos + demux->ebml_segment_start); - goto finish; - } - - /* read master + parse */ - switch (id) { - case GST_MATROSKA_ID_CUES: - if (!demux->index_parsed) { - ret = gst_matroska_demux_parse_index (demux); - } - break; - case GST_MATROSKA_ID_TAGS: - ret = gst_matroska_demux_parse_metadata (demux); - break; - case GST_MATROSKA_ID_TRACKS: - if (!demux->tracks_parsed) { - ret = gst_matroska_demux_parse_tracks (demux); - } - break; - - case GST_MATROSKA_ID_SEGMENTINFO: - if (!demux->segmentinfo_parsed) { - ret = gst_matroska_demux_parse_info (demux); - } - break; - case GST_MATROSKA_ID_SEEKHEAD: - { - GList *l; - - DEBUG_ELEMENT_START (demux, ebml, "SeekHead"); - if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) - goto finish; - - /* Prevent infinite recursion if there's a cycle from - * one seekhead to the same again. Simply break if - * we already had this seekhead, finish will clean up - * everything. */ - for (l = ebml->level; l; l = l->next) { - GstEbmlLevel *level = (GstEbmlLevel *) l->data; - - if (level->start == ebml->offset && l->prev) - goto finish; - } - - ret = gst_matroska_demux_parse_contents (demux); - break; - } - case GST_MATROSKA_ID_ATTACHMENTS: - if (!demux->attachments_parsed) { - ret = gst_matroska_demux_parse_attachments (demux); - } - break; - case GST_MATROSKA_ID_CHAPTERS: - ret = gst_matroska_demux_parse_chapters (demux); - break; + } else { + /* now parse */ + ret = gst_matroska_demux_parse_id (demux, id, length, needed); } finish: - /* remove dummy level */ - while (ebml->level) { - guint64 length; - - level = ebml->level->data; - ebml->level = g_list_delete_link (ebml->level, ebml->level); - length = level->length; - g_slice_free (GstEbmlLevel, level); - if (length == G_MAXUINT64) - break; - } - /* seek back */ - (void) gst_ebml_read_seek (ebml, before_pos); - demux->level_up = level_up; + demux->offset = before_pos; break; } @@ -5239,25 +5048,26 @@ gst_matroska_demux_parse_contents_seekentry (GstMatroskaDemux * demux) } static GstFlowReturn -gst_matroska_demux_parse_contents (GstMatroskaDemux * demux) +gst_matroska_demux_parse_contents (GstMatroskaDemux * demux, GstEbmlRead * ebml) { - GstEbmlRead *ebml = GST_EBML_READ (demux); GstFlowReturn ret = GST_FLOW_OK; guint32 id; - while (ret == GST_FLOW_OK) { - if ((ret = gst_ebml_peek_id (ebml, &demux->level_up, &id)) != GST_FLOW_OK) - break; + DEBUG_ELEMENT_START (demux, ebml, "SeekHead"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (demux, ebml, "SeekHead", ret); + return ret; + } - if (demux->level_up) { - demux->level_up--; + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) break; - } switch (id) { case GST_MATROSKA_ID_SEEKENTRY: { - ret = gst_matroska_demux_parse_contents_seekentry (demux); + ret = gst_matroska_demux_parse_contents_seekentry (demux, ebml); /* Ignore EOS and errors here */ if (ret != GST_FLOW_OK) { GST_DEBUG_OBJECT (demux, "Ignoring %s", gst_flow_get_name (ret)); @@ -5267,15 +5077,9 @@ gst_matroska_demux_parse_contents (GstMatroskaDemux * demux) } default: - GST_WARNING ("Unknown SeekHead subelement 0x%x - ignoring", id); - ret = gst_ebml_read_skip (ebml); + ret = gst_matroska_demux_parse_skip (demux, ebml, "SeekHead", id); break; } - - if (demux->level_up) { - demux->level_up--; - break; - } } DEBUG_ELEMENT_STOP (demux, ebml, "SeekHead", ret); @@ -5283,245 +5087,432 @@ gst_matroska_demux_parse_contents (GstMatroskaDemux * demux) return ret; } -/* returns FALSE on error, otherwise TRUE */ -static GstFlowReturn -gst_matroska_demux_loop_stream_parse_id (GstMatroskaDemux * demux, - guint32 id, gboolean * p_run_loop) +static inline GstFlowReturn +gst_matroska_demux_check_read_size (GstMatroskaDemux * demux, guint64 bytes) { - GstEbmlRead *ebml = GST_EBML_READ (demux); - GstFlowReturn ret = GST_FLOW_OK; + if (G_UNLIKELY (bytes > 10 * 1024 * 1024)) { + /* only a few blocks are expected/allowed to be large, + * and will be recursed into, whereas others will be read and must fit */ + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), + ("reading large block of size %" G_GUINT64_FORMAT " not supported; " + "file might be corrupt.", bytes)); + return GST_FLOW_ERROR; + } else { + return GST_FLOW_OK; + } +} - switch (id) { - /* stream info - * Can exist more than once but following occurences - * must have the same content so ignore them */ - case GST_MATROSKA_ID_SEGMENTINFO: - if (!demux->segmentinfo_parsed) { - if ((ret = gst_matroska_demux_parse_info (demux)) != GST_FLOW_OK) - return ret; - } else { - if ((ret = gst_ebml_read_skip (ebml)) != GST_FLOW_OK) - return ret; - } - break; +/* initializes @ebml with @bytes from input stream at current offset. + * Returns UNEXPECTED if insufficient available, + * ERROR if too much was attempted to read. */ +static inline GstFlowReturn +gst_matroska_demux_take (GstMatroskaDemux * demux, guint64 bytes, + GstEbmlRead * ebml) +{ + GstBuffer *buffer = NULL; + GstFlowReturn ret = GST_FLOW_OK; - /* track info headers - * Can exist more than once but following occurences - * must have the same content so ignore them */ - case GST_MATROSKA_ID_TRACKS: - { - if (!demux->tracks_parsed) { - if ((ret = gst_matroska_demux_parse_tracks (demux)) != GST_FLOW_OK) - return ret; - } else { - if ((ret = gst_ebml_read_skip (ebml)) != GST_FLOW_OK) - return ret; - } - break; - } + GST_LOG_OBJECT (demux, "taking %" G_GUINT64_FORMAT " bytes for parsing", + bytes); + ret = gst_matroska_demux_check_read_size (demux, bytes); + if (ret != GST_FLOW_OK) + goto exit; + if (demux->streaming) { + if (gst_adapter_available (demux->adapter) >= bytes) + buffer = gst_adapter_take_buffer (demux->adapter, bytes); + else + ret = GST_FLOW_UNEXPECTED; + } else + ret = gst_matroska_demux_peek_bytes (demux, demux->offset, bytes, &buffer, + NULL); + if (G_LIKELY (buffer)) { + gst_ebml_read_init (ebml, GST_ELEMENT_CAST (demux), buffer, demux->offset); + demux->offset += bytes; + } +exit: + return ret; +} - /* cues - seek table - * Either exists exactly one time or never but ignore - * following occurences for the sake of sanity */ - case GST_MATROSKA_ID_CUES: - { - if (!demux->index_parsed) { - if ((ret = gst_matroska_demux_parse_index (demux)) != GST_FLOW_OK) - return ret; - } else { - if ((ret = gst_ebml_read_skip (ebml)) != GST_FLOW_OK) - return ret; - } - break; - } +static inline GstFlowReturn +gst_matroska_demux_flush (GstMatroskaDemux * demux, guint flush) +{ + GST_LOG_OBJECT (demux, "skipping %d bytes", flush); + demux->offset += flush; + if (demux->streaming) { + GstFlowReturn ret; - /* metadata - * can exist more than one time with different content */ - case GST_MATROSKA_ID_TAGS: - { - if ((ret = gst_matroska_demux_parse_metadata (demux)) != GST_FLOW_OK) - return ret; - break; - } + /* hard to skip large blocks when streaming */ + ret = gst_matroska_demux_check_read_size (demux, flush); + if (ret != GST_FLOW_OK) + return ret; + if (flush <= gst_adapter_available (demux->adapter)) + gst_adapter_flush (demux->adapter, flush); + else + return GST_FLOW_UNEXPECTED; + } + return GST_FLOW_OK; +} - /* file index (if seekable, seek to Cues/Tags/etc to parse it) */ - case GST_MATROSKA_ID_SEEKHEAD: - { - DEBUG_ELEMENT_START (demux, ebml, "SeekHead"); - if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) - return ret; - if ((ret = gst_matroska_demux_parse_contents (demux)) != GST_FLOW_OK) - return ret; - break; - } +static void +gst_matroska_demux_check_seekability (GstMatroskaDemux * demux) +{ + GstQuery *query; + gboolean seekable = FALSE; + gint64 start = -1, stop = -1; - /* cluster - contains the payload */ - case GST_MATROSKA_ID_CLUSTER: - { - if (demux->state != GST_MATROSKA_DEMUX_STATE_DATA) { - /* We need a Tracks element first before we can output anything. - * Search it! - */ - if (!demux->tracks_parsed) { - GstEbmlLevel *level; - guint32 iid; - guint level_up; - guint64 before_pos; + query = gst_query_new_seeking (GST_FORMAT_BYTES); + if (!gst_pad_peer_query (demux->sinkpad, query)) { + GST_DEBUG_OBJECT (demux, "seeking query failed"); + goto done; + } - GST_WARNING_OBJECT (demux, - "Found Cluster element before Tracks, searching Tracks"); + gst_query_parse_seeking (query, NULL, &seekable, &start, &stop); - /* remember */ - level_up = demux->level_up; - before_pos = ebml->offset; + /* try harder to query upstream size if we didn't get it the first time */ + if (seekable && stop == -1) { + GstFormat fmt = GST_FORMAT_BYTES; - /* we don't want to lose our seekhead level, so we add - * a dummy. This is a crude hack. */ + GST_DEBUG_OBJECT (demux, "doing duration query to fix up unset stop"); + gst_pad_query_peer_duration (demux->sinkpad, &fmt, &stop); + } - level = g_slice_new (GstEbmlLevel); - level->start = 0; - level->length = G_MAXUINT64; - ebml->level = g_list_prepend (ebml->level, level); + /* if upstream doesn't know the size, it's likely that it's not seekable in + * practice even if it technically may be seekable */ + if (seekable && (start != 0 || stop <= start)) { + GST_DEBUG_OBJECT (demux, "seekable but unknown start/stop -> disable"); + seekable = FALSE; + } - /* Search Tracks element */ - while (TRUE) { - if (gst_ebml_read_skip (ebml) != GST_FLOW_OK) - break; - - if (gst_ebml_peek_id (ebml, &demux->level_up, &iid) != GST_FLOW_OK) - break; - - if (iid != GST_MATROSKA_ID_TRACKS) - continue; - - gst_matroska_demux_parse_tracks (demux); - break; - } - - if (!demux->tracks_parsed) { - GST_ERROR_OBJECT (demux, "No Tracks element found"); - ret = GST_FLOW_ERROR; - } +done: + GST_INFO_OBJECT (demux, "seekable: %d (%" G_GUINT64_FORMAT " - %" + G_GUINT64_FORMAT ")", seekable, start, stop); + demux->seekable = seekable; - /* remove dummy level */ - while (ebml->level) { - guint64 length; + gst_query_unref (query); +} - level = ebml->level->data; - ebml->level = g_list_delete_link (ebml->level, ebml->level); - length = level->length; - g_slice_free (GstEbmlLevel, level); - if (length == G_MAXUINT64) - break; - } +static GstFlowReturn +gst_matroska_demux_find_tracks (GstMatroskaDemux * demux) +{ + guint32 id; + guint64 before_pos; + guint64 length; + guint needed; + GstFlowReturn ret = GST_FLOW_OK; - /* seek back */ - gst_ebml_read_seek (ebml, before_pos); - demux->level_up = level_up; - } + GST_WARNING_OBJECT (demux, + "Found Cluster element before Tracks, searching Tracks"); - if (ret != GST_FLOW_OK) - return ret; + /* remember */ + before_pos = demux->offset; - demux->state = GST_MATROSKA_DEMUX_STATE_DATA; - GST_DEBUG_OBJECT (demux, "signaling no more pads"); - gst_element_no_more_pads (GST_ELEMENT (demux)); - /* send initial discont */ - gst_matroska_demux_send_event (demux, - gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME, 0, -1, 0)); - } else { - /* 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. */ - if ((ret = gst_matroska_demux_parse_cluster (demux)) != GST_FLOW_OK) - return ret; - *p_run_loop = FALSE; - } + /* Search Tracks element */ + while (TRUE) { + ret = gst_matroska_demux_peek_id_length_pull (demux, &id, &length, &needed); + if (ret != GST_FLOW_OK) break; - } - /* attachments - contains files attached to the mkv container - * like album art, etc */ - case GST_MATROSKA_ID_ATTACHMENTS:{ - if (!demux->attachments_parsed) { - if ((ret = gst_matroska_demux_parse_attachments (demux)) != GST_FLOW_OK) - return ret; + if (id != GST_MATROSKA_ID_TRACKS) { + /* we may be skipping large cluster here, so forego size check etc */ + /* ... but we can't skip undefined size; force error */ + if (length == G_MAXUINT64) { + ret = gst_matroska_demux_check_read_size (demux, length); + break; } else { - if ((ret = gst_ebml_read_skip (ebml)) != GST_FLOW_OK) - return ret; + demux->offset += needed; + demux->offset += length; } - break; - } - - /* chapters - contains meta information about how to group - * the file into chapters, similar to DVD */ - case GST_MATROSKA_ID_CHAPTERS:{ - if ((ret = gst_matroska_demux_parse_chapters (demux)) != GST_FLOW_OK) - return ret; - break; + continue; } - default: - GST_WARNING_OBJECT (demux, "Unknown Segment subelement 0x%x at %" - G_GUINT64_FORMAT " - ignoring", id, GST_EBML_READ (demux)->offset); - if ((ret = gst_ebml_read_skip (ebml)) != GST_FLOW_OK) - return ret; - break; + /* will lead to track parsing ... */ + ret = gst_matroska_demux_parse_id (demux, id, length, needed); + break; } + + /* seek back */ + demux->offset = before_pos; + return ret; } +#define GST_READ_CHECK(stmt) \ +G_STMT_START { \ + if (G_UNLIKELY ((ret = (stmt)) != GST_FLOW_OK)) \ + goto read_error; \ +} G_STMT_END + static GstFlowReturn -gst_matroska_demux_loop_stream (GstMatroskaDemux * demux) +gst_matroska_demux_parse_id (GstMatroskaDemux * demux, guint32 id, + guint64 length, guint needed) { - GstEbmlRead *ebml = GST_EBML_READ (demux); + GstEbmlRead ebml = { 0, }; GstFlowReturn ret = GST_FLOW_OK; - gboolean run_loop = TRUE; - guint32 id; + guint64 read; - /* we've found our segment, start reading the different contents in here */ - while (run_loop && ret == GST_FLOW_OK) { - if ((ret = gst_ebml_peek_id (ebml, &demux->level_up, &id)) != GST_FLOW_OK) - return ret; + /* if we plan to read and parse this element, we need prefix (id + length) + * and the contents */ + /* mind about overflow wrap-around when dealing with undefined size */ + read = length; + if (G_LIKELY (length != G_MAXUINT64)) + read += needed; - if (demux->level_up) { - demux->level_up--; + switch (demux->state) { + case GST_MATROSKA_DEMUX_STATE_START: + switch (id) { + case GST_EBML_ID_HEADER: + GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); + ret = gst_matroska_demux_parse_header (demux, &ebml); + if (ret != GST_FLOW_OK) + goto parse_failed; + demux->state = GST_MATROSKA_DEMUX_STATE_SEGMENT; + gst_matroska_demux_check_seekability (demux); + break; + default: + goto invalid_header; + break; + } break; - } + case GST_MATROSKA_DEMUX_STATE_SEGMENT: + switch (id) { + case GST_MATROSKA_ID_SEGMENT: + /* eat segment prefix */ + GST_READ_CHECK (gst_matroska_demux_flush (demux, needed)); + GST_DEBUG_OBJECT (demux, + "Found Segment start at offset %" G_GUINT64_FORMAT, + demux->offset); + /* seeks are from the beginning of the segment, + * after the segment ID/length */ + demux->ebml_segment_start = demux->offset; + demux->state = GST_MATROSKA_DEMUX_STATE_HEADER; + break; + default: + GST_WARNING_OBJECT (demux, + "Expected a Segment ID (0x%x), but received 0x%x!", + GST_MATROSKA_ID_SEGMENT, id); + GST_READ_CHECK (gst_matroska_demux_flush (demux, read)); + break; + } + break; + case GST_MATROSKA_DEMUX_STATE_HEADER: + case GST_MATROSKA_DEMUX_STATE_DATA: + case GST_MATROSKA_DEMUX_STATE_SEEK: + switch (id) { + case GST_MATROSKA_ID_SEGMENTINFO: + if (!demux->segmentinfo_parsed) { + GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); + ret = gst_matroska_demux_parse_info (demux, &ebml); + } else { + GST_READ_CHECK (gst_matroska_demux_flush (demux, read)); + } + break; + case GST_MATROSKA_ID_TRACKS: + if (!demux->tracks_parsed) { + GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); + ret = gst_matroska_demux_parse_tracks (demux, &ebml); + } else { + GST_READ_CHECK (gst_matroska_demux_flush (demux, read)); + } + break; + case GST_MATROSKA_ID_CLUSTER: + if (G_UNLIKELY (!demux->tracks_parsed)) { + if (demux->streaming) { + GST_DEBUG_OBJECT (demux, "Cluster before Track"); + goto not_streamable; + } else { + ret = gst_matroska_demux_find_tracks (demux); + if (!demux->tracks_parsed) + goto no_tracks; + } + } + if (G_UNLIKELY (demux->state == GST_MATROSKA_DEMUX_STATE_HEADER)) { + demux->state = GST_MATROSKA_DEMUX_STATE_DATA; + GST_DEBUG_OBJECT (demux, "signaling no more pads"); + gst_element_no_more_pads (GST_ELEMENT (demux)); + /* send initial newsegment */ + gst_matroska_demux_send_event (demux, + gst_event_new_new_segment (FALSE, 1.0, + GST_FORMAT_TIME, 0, + (demux->segment.duration > + 0) ? demux->segment.duration : -1, 0)); + } + demux->cluster_time = GST_CLOCK_TIME_NONE; + demux->cluster_offset = demux->offset; + if (G_UNLIKELY (!demux->seek_first && demux->seek_block)) { + GST_DEBUG_OBJECT (demux, "seek target block %" G_GUINT64_FORMAT + " not found in Cluster, trying next Cluster's first block instead", + demux->seek_block); + demux->seek_block = 0; + } + demux->seek_first = FALSE; + /* eat cluster prefix */ + gst_matroska_demux_flush (demux, needed); + break; + case GST_MATROSKA_ID_CLUSTERTIMECODE: + { + guint64 num; - ret = gst_matroska_demux_loop_stream_parse_id (demux, id, &run_loop); + GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); + if ((ret = gst_ebml_read_uint (&ebml, &id, &num)) != GST_FLOW_OK) + goto parse_failed; + GST_DEBUG_OBJECT (demux, "ClusterTimeCode: %" G_GUINT64_FORMAT, num); + demux->cluster_time = num; + if (demux->element_index) { + if (demux->element_index_writer_id == -1) + gst_index_get_writer_id (demux->element_index, + GST_OBJECT (demux), &demux->element_index_writer_id); + GST_LOG_OBJECT (demux, "adding association %" GST_TIME_FORMAT "-> %" + G_GUINT64_FORMAT " for writer id %d", + GST_TIME_ARGS (demux->cluster_time), demux->cluster_offset, + demux->element_index_writer_id); + gst_index_add_association (demux->element_index, + demux->element_index_writer_id, GST_ASSOCIATION_FLAG_KEY_UNIT, + GST_FORMAT_TIME, demux->cluster_time, + GST_FORMAT_BYTES, demux->cluster_offset, NULL); + } + break; + } + case GST_MATROSKA_ID_BLOCKGROUP: + if (!gst_matroska_demux_seek_block (demux)) + goto skip; + GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); + DEBUG_ELEMENT_START (demux, &ebml, "BlockGroup"); + if ((ret = gst_ebml_read_master (&ebml, &id)) == GST_FLOW_OK) { + ret = gst_matroska_demux_parse_blockgroup_or_simpleblock (demux, + &ebml, demux->cluster_time, demux->cluster_offset, FALSE); + } + DEBUG_ELEMENT_STOP (demux, &ebml, "BlockGroup", ret); + break; + case GST_MATROSKA_ID_SIMPLEBLOCK: + if (!gst_matroska_demux_seek_block (demux)) + goto skip; + GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); + DEBUG_ELEMENT_START (demux, &ebml, "SimpleBlock"); + ret = gst_matroska_demux_parse_blockgroup_or_simpleblock (demux, + &ebml, demux->cluster_time, demux->cluster_offset, TRUE); + DEBUG_ELEMENT_STOP (demux, &ebml, "SimpleBlock", ret); + break; + case GST_MATROSKA_ID_ATTACHMENTS: + if (!demux->attachments_parsed) { + GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); + ret = gst_matroska_demux_parse_attachments (demux, &ebml); + } else { + GST_READ_CHECK (gst_matroska_demux_flush (demux, read)); + } + break; + case GST_MATROSKA_ID_TAGS: + GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); + ret = gst_matroska_demux_parse_metadata (demux, &ebml); + break; + case GST_MATROSKA_ID_CHAPTERS: + GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); + ret = gst_matroska_demux_parse_chapters (demux, &ebml); + break; + case GST_MATROSKA_ID_SEEKHEAD: + GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); + ret = gst_matroska_demux_parse_contents (demux, &ebml); + break; + case GST_MATROSKA_ID_CUES: + if (demux->index_parsed) { + GST_READ_CHECK (gst_matroska_demux_flush (demux, read)); + break; + } + GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); + ret = gst_matroska_demux_parse_index (demux, &ebml); + /* only push based; delayed index building */ + if (demux->state == GST_MATROSKA_DEMUX_STATE_SEEK) { + GstEvent *event; - if (demux->level_up) { - demux->level_up--; + GST_OBJECT_LOCK (demux); + event = demux->seek_event; + demux->seek_event = NULL; + GST_OBJECT_UNLOCK (demux); + + g_assert (event); + /* unlikely to fail, since we managed to seek to this point */ + if (!gst_matroska_demux_handle_seek_event (demux, NULL, event)) + goto seek_failed; + /* resume data handling, main thread clear to seek again */ + GST_OBJECT_LOCK (demux); + demux->state = GST_MATROSKA_DEMUX_STATE_DATA; + GST_OBJECT_UNLOCK (demux); + } + break; + case GST_MATROSKA_ID_POSITION: + case GST_MATROSKA_ID_PREVSIZE: + case GST_MATROSKA_ID_ENCRYPTEDBLOCK: + case GST_MATROSKA_ID_SILENTTRACKS: + GST_DEBUG_OBJECT (demux, + "Skipping Cluster subelement 0x%x - ignoring", id); + /* fall-through */ + default: + skip: + GST_DEBUG_OBJECT (demux, "skipping Element 0x%x", id); + GST_READ_CHECK (gst_matroska_demux_flush (demux, read)); + break; + } break; - } } + + if (ret == GST_FLOW_PARSE) + goto parse_failed; + +exit: + gst_ebml_read_clear (&ebml); return ret; + + /* ERRORS */ +read_error: + { + /* simply exit, maybe not enough data yet */ + /* no ebml to clear if read error */ + return ret; + } +parse_failed: + { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), + ("Failed to parse Element 0x%x", id)); + ret = GST_FLOW_ERROR; + goto exit; + } +not_streamable: + { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), + ("File layout does not permit streaming")); + ret = GST_FLOW_ERROR; + goto exit; + } +no_tracks: + { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), + ("No Tracks element found")); + ret = GST_FLOW_ERROR; + goto exit; + } +invalid_header: + { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), ("Invalid header")); + ret = GST_FLOW_ERROR; + goto exit; + } +seek_failed: + { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), ("Failed to seek")); + ret = GST_FLOW_ERROR; + goto exit; + } } static void gst_matroska_demux_loop (GstPad * pad) { GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (GST_PAD_PARENT (pad)); - GstEbmlRead *ebml = GST_EBML_READ (demux); GstFlowReturn ret; - - /* first, if we're to start, let's actually get starting */ - if (G_UNLIKELY (demux->state == GST_MATROSKA_DEMUX_STATE_START)) { - ret = gst_matroska_demux_init_stream (demux); - if (ret != GST_FLOW_OK) { - GST_WARNING_OBJECT (demux, "init stream failed!"); - goto pause; - } - demux->state = GST_MATROSKA_DEMUX_STATE_HEADER; - } + guint32 id; + guint64 length; + guint needed; /* If we have to close a segment, send a new segment to do this now */ - if (G_LIKELY (demux->state == GST_MATROSKA_DEMUX_STATE_DATA)) { if (G_UNLIKELY (demux->close_segment)) { gst_matroska_demux_send_event (demux, demux->close_segment); @@ -5533,7 +5524,17 @@ gst_matroska_demux_loop (GstPad * pad) } } - ret = gst_matroska_demux_loop_stream (demux); + ret = gst_matroska_demux_peek_id_length_pull (demux, &id, &length, &needed); + if (ret == GST_FLOW_UNEXPECTED) + goto eos; + if (ret != GST_FLOW_OK) + goto pause; + + GST_LOG_OBJECT (demux, "Offset %" G_GUINT64_FORMAT ", Element id 0x%x, " + "size %" G_GUINT64_FORMAT ", needed %d", demux->offset, id, + length, needed); + + ret = gst_matroska_demux_parse_id (demux, id, length, needed); if (ret == GST_FLOW_UNEXPECTED) goto eos; if (ret != GST_FLOW_OK) @@ -5558,7 +5559,7 @@ gst_matroska_demux_loop (GstPad * pad) } next: - if (G_UNLIKELY (ebml->offset == gst_ebml_read_get_length (ebml))) { + if (G_UNLIKELY (demux->offset == gst_matroska_demux_get_length (demux))) { GST_LOG_OBJECT (demux, "Reached end of stream, sending EOS"); ret = GST_FLOW_UNEXPECTED; goto pause; @@ -5658,177 +5659,30 @@ perform_seek_to_offset (GstMatroskaDemux * demux, guint64 offset) return res; } -static void -gst_matroska_demux_check_seekability (GstMatroskaDemux * demux) -{ - GstQuery *query; - gboolean seekable = FALSE; - gint64 start = -1, stop = -1; - - query = gst_query_new_seeking (GST_FORMAT_BYTES); - if (!gst_pad_peer_query (demux->sinkpad, query)) { - GST_DEBUG_OBJECT (demux, "seeking query failed"); - goto done; - } - - gst_query_parse_seeking (query, NULL, &seekable, &start, &stop); - - /* try harder to query upstream size if we didn't get it the first time */ - if (seekable && stop == -1) { - GstFormat fmt = GST_FORMAT_BYTES; - - GST_DEBUG_OBJECT (demux, "doing duration query to fix up unset stop"); - gst_pad_query_peer_duration (demux->sinkpad, &fmt, &stop); - } - - /* if upstream doesn't know the size, it's likely that it's not seekable in - * practice even if it technically may be seekable */ - if (seekable && (start != 0 || stop <= start)) { - GST_DEBUG_OBJECT (demux, "seekable but unknown start/stop -> disable"); - seekable = FALSE; - } - -done: - GST_INFO_OBJECT (demux, "seekable: %d (%" G_GUINT64_FORMAT " - %" - G_GUINT64_FORMAT ")", seekable, start, stop); - demux->seekable = seekable; - - gst_query_unref (query); -} - -static inline void -gst_matroska_demux_take (GstMatroskaDemux * demux, guint bytes) -{ - GstBuffer *buffer; - - GST_LOG_OBJECT (demux, "caching %d bytes for parsing", bytes); - buffer = gst_adapter_take_buffer (demux->adapter, bytes); - gst_ebml_read_reset_cache (GST_EBML_READ (demux), buffer, demux->offset); - demux->offset += bytes; -} - -static inline void -gst_matroska_demux_flush (GstMatroskaDemux * demux, guint flush) +static const guint8 * +gst_matroska_demux_peek_adapter (GstMatroskaDemux * demux, guint peek) { - GST_LOG_OBJECT (demux, "skipping %d bytes", flush); - gst_adapter_flush (demux->adapter, flush); - demux->offset += flush; + return gst_adapter_peek (demux->adapter, peek); } static GstFlowReturn -gst_matroska_demux_peek_id_length (GstMatroskaDemux * demux, guint32 * _id, +gst_matroska_demux_peek_id_length_push (GstMatroskaDemux * demux, guint32 * _id, guint64 * _length, guint * _needed) { - guint avail, needed; - const guint8 *buf; - gint len_mask = 0x80, read = 1, n = 1, num_ffs = 0; - guint64 total; - guint8 b; - - g_return_val_if_fail (_id != NULL, GST_FLOW_ERROR); - g_return_val_if_fail (_length != NULL, GST_FLOW_ERROR); - g_return_val_if_fail (_needed != NULL, GST_FLOW_ERROR); - - /* well ... */ - *_id = (guint32) GST_EBML_SIZE_UNKNOWN; - *_length = GST_EBML_SIZE_UNKNOWN; - - /* read element id */ - needed = 2; - avail = gst_adapter_available (demux->adapter); - if (avail < needed) - goto exit; - - buf = gst_adapter_peek (demux->adapter, 1); - b = GST_READ_UINT8 (buf); - - total = (guint64) b; - while (read <= 4 && !(total & len_mask)) { - read++; - len_mask >>= 1; - } - if (G_UNLIKELY (read > 4)) - goto invalid_id; - /* need id and at least something for subsequent length */ - if ((needed = read + 1) > avail) - goto exit; - - buf = gst_adapter_peek (demux->adapter, needed); - while (n < read) { - b = GST_READ_UINT8 (buf + n); - total = (total << 8) | b; - ++n; - } - *_id = (guint32) total; - - /* read element length */ - b = GST_READ_UINT8 (buf + n); - total = (guint64) b; - len_mask = 0x80; - read = 1; - while (read <= 8 && !(total & len_mask)) { - read++; - len_mask >>= 1; - } - if (G_UNLIKELY (read > 8)) - goto invalid_length; - if ((needed += read - 1) > avail) - goto exit; - if ((total &= (len_mask - 1)) == len_mask - 1) - num_ffs++; - - buf = gst_adapter_peek (demux->adapter, needed); - buf += (needed - read); - n = 1; - while (n < read) { - guint8 b = GST_READ_UINT8 (buf + n); - - if (G_UNLIKELY (b == 0xff)) - num_ffs++; - total = (total << 8) | b; - ++n; - } - - if (G_UNLIKELY (read == num_ffs)) - *_length = G_MAXUINT64; - else - *_length = total; - *_length = total; - -exit: - *_needed = needed; - - return GST_FLOW_OK; - - /* ERRORS */ -invalid_id: - { - GST_ERROR_OBJECT (demux, - "Invalid EBML ID size tag (0x%x) at position %" G_GUINT64_FORMAT " (0x%" - G_GINT64_MODIFIER "x)", (guint) b, demux->offset, demux->offset); - return GST_FLOW_ERROR; - } -invalid_length: - { - GST_ERROR_OBJECT (demux, - "Invalid EBML length size tag (0x%x) at position %" G_GUINT64_FORMAT - " (0x%" G_GINT64_MODIFIER "x)", (guint) b, demux->offset, - demux->offset); - return GST_FLOW_ERROR; - } + return gst_ebml_peek_id_length (_id, _length, _needed, + (GstPeekData) gst_matroska_demux_peek_adapter, (gpointer) demux, + GST_ELEMENT_CAST (demux), demux->offset); } static GstFlowReturn gst_matroska_demux_chain (GstPad * pad, GstBuffer * buffer) { GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (GST_PAD_PARENT (pad)); - GstEbmlRead *ebml = GST_EBML_READ (demux); guint available; GstFlowReturn ret = GST_FLOW_OK; guint needed = 0; guint32 id; guint64 length; - const gchar *name; if (G_UNLIKELY (GST_BUFFER_IS_DISCONT (buffer))) { GST_DEBUG_OBJECT (demux, "got DISCONT"); @@ -5844,9 +5698,9 @@ gst_matroska_demux_chain (GstPad * pad, GstBuffer * buffer) next: available = gst_adapter_available (demux->adapter); - ret = gst_matroska_demux_peek_id_length (demux, &id, &length, &needed); - if (G_UNLIKELY (ret != GST_FLOW_OK)) - goto parse_failed; + ret = gst_matroska_demux_peek_id_length_push (demux, &id, &length, &needed); + if (G_UNLIKELY (ret != GST_FLOW_OK && ret != GST_FLOW_UNEXPECTED)) + return ret; GST_LOG_OBJECT (demux, "Offset %" G_GUINT64_FORMAT ", Element id 0x%x, " "size %" G_GUINT64_FORMAT ", needed %d, available %d", demux->offset, id, @@ -5855,204 +5709,14 @@ next: if (needed > available) return GST_FLOW_OK; - /* only a few blocks are expected/allowed to be large, - * and will be recursed into, whereas others must fit */ - if (G_LIKELY (id != GST_MATROSKA_ID_SEGMENT && id != GST_MATROSKA_ID_CLUSTER)) { - if (needed + length > available) - return GST_FLOW_OK; - /* probably happens with 'large pieces' at the end, so consider it EOS */ - if (G_UNLIKELY (length > 10 * 1024 * 1024)) { - GST_WARNING_OBJECT (demux, "forcing EOS due to size %" G_GUINT64_FORMAT, - length); - return GST_FLOW_UNEXPECTED; - } - } - - switch (demux->state) { - case GST_MATROSKA_DEMUX_STATE_START: - switch (id) { - case GST_EBML_ID_HEADER: - gst_matroska_demux_take (demux, length + needed); - ret = gst_matroska_demux_parse_header (demux); - if (ret != GST_FLOW_OK) - goto parse_failed; - demux->state = GST_MATROSKA_DEMUX_STATE_HEADER; - gst_matroska_demux_check_seekability (demux); - break; - default: - goto invalid_header; - break; - } - break; - case GST_MATROSKA_DEMUX_STATE_HEADER: - case GST_MATROSKA_DEMUX_STATE_DATA: - case GST_MATROSKA_DEMUX_STATE_SEEK: - switch (id) { - case GST_MATROSKA_ID_SEGMENT: - /* eat segment prefix */ - gst_matroska_demux_flush (demux, needed); - GST_DEBUG_OBJECT (demux, - "Found Segment start at offset %" G_GUINT64_FORMAT, ebml->offset); - /* seeks are from the beginning of the segment, - * after the segment ID/length */ - demux->ebml_segment_start = demux->offset; - break; - case GST_MATROSKA_ID_SEGMENTINFO: - if (!demux->segmentinfo_parsed) { - gst_matroska_demux_take (demux, length + needed); - ret = gst_matroska_demux_parse_info (demux); - } else { - gst_matroska_demux_flush (demux, needed + length); - } - break; - case GST_MATROSKA_ID_TRACKS: - if (!demux->tracks_parsed) { - gst_matroska_demux_take (demux, length + needed); - ret = gst_matroska_demux_parse_tracks (demux); - } else { - gst_matroska_demux_flush (demux, needed + length); - } - break; - case GST_MATROSKA_ID_CLUSTER: - if (G_UNLIKELY (!demux->tracks_parsed)) { - GST_DEBUG_OBJECT (demux, "Cluster before Track"); - goto not_streamable; - } else if (demux->state == GST_MATROSKA_DEMUX_STATE_HEADER) { - demux->state = GST_MATROSKA_DEMUX_STATE_DATA; - GST_DEBUG_OBJECT (demux, "signaling no more pads"); - gst_element_no_more_pads (GST_ELEMENT (demux)); - /* send initial newsegment */ - gst_matroska_demux_send_event (demux, - gst_event_new_new_segment (FALSE, 1.0, - GST_FORMAT_TIME, 0, - (demux->segment.duration > - 0) ? demux->segment.duration : -1, 0)); - } - demux->cluster_time = GST_CLOCK_TIME_NONE; - demux->cluster_offset = demux->parent.offset; - /* eat cluster prefix */ - gst_matroska_demux_flush (demux, needed); - break; - case GST_MATROSKA_ID_CLUSTERTIMECODE: - { - guint64 num; - - gst_matroska_demux_take (demux, length + needed); - if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) - goto parse_failed; - GST_DEBUG_OBJECT (demux, "ClusterTimeCode: %" G_GUINT64_FORMAT, num); - demux->cluster_time = num; - break; - } - case GST_MATROSKA_ID_BLOCKGROUP: - gst_matroska_demux_take (demux, length + needed); - DEBUG_ELEMENT_START (demux, ebml, "BlockGroup"); - if ((ret = gst_ebml_read_master (ebml, &id)) == GST_FLOW_OK) { - ret = gst_matroska_demux_parse_blockgroup_or_simpleblock (demux, - demux->cluster_time, demux->cluster_offset, FALSE); - } - DEBUG_ELEMENT_STOP (demux, ebml, "BlockGroup", ret); - if (ret != GST_FLOW_OK) - goto exit; - break; - case GST_MATROSKA_ID_SIMPLEBLOCK: - gst_matroska_demux_take (demux, length + needed); - DEBUG_ELEMENT_START (demux, ebml, "SimpleBlock"); - ret = gst_matroska_demux_parse_blockgroup_or_simpleblock (demux, - demux->cluster_time, demux->cluster_offset, TRUE); - DEBUG_ELEMENT_STOP (demux, ebml, "SimpleBlock", ret); - if (ret != GST_FLOW_OK) - goto exit; - break; - case GST_MATROSKA_ID_ATTACHMENTS: - if (!demux->attachments_parsed) { - gst_matroska_demux_take (demux, length + needed); - ret = gst_matroska_demux_parse_attachments (demux); - } else { - gst_matroska_demux_flush (demux, needed + length); - } - break; - case GST_MATROSKA_ID_TAGS: - gst_matroska_demux_take (demux, length + needed); - ret = gst_matroska_demux_parse_metadata (demux); - break; - case GST_MATROSKA_ID_CHAPTERS: - name = "Cues"; - goto skip; - case GST_MATROSKA_ID_SEEKHEAD: - gst_matroska_demux_take (demux, length + needed); - DEBUG_ELEMENT_START (demux, ebml, "SeekHead"); - if ((ret = gst_ebml_read_master (ebml, &id)) == GST_FLOW_OK) - ret = gst_matroska_demux_parse_contents (demux); - if (ret != GST_FLOW_OK) - goto exit; - break; - case GST_MATROSKA_ID_CUES: - if (demux->index_parsed) { - gst_matroska_demux_flush (demux, needed + length); - break; - } - gst_matroska_demux_take (demux, length + needed); - ret = gst_matroska_demux_parse_index (demux); - if (demux->state == GST_MATROSKA_DEMUX_STATE_SEEK) { - GstEvent *event; - - GST_OBJECT_LOCK (demux); - event = demux->seek_event; - demux->seek_event = NULL; - GST_OBJECT_UNLOCK (demux); - - g_assert (event); - /* unlikely to fail, since we managed to seek to this point */ - if (!gst_matroska_demux_handle_seek_event (demux, NULL, event)) - goto seek_failed; - /* resume data handling, main thread clear to seek again */ - GST_OBJECT_LOCK (demux); - demux->state = GST_MATROSKA_DEMUX_STATE_DATA; - GST_OBJECT_UNLOCK (demux); - } - break; - default: - name = "Unknown"; - skip: - GST_DEBUG_OBJECT (demux, "skipping Element 0x%x (%s)", id, name); - gst_matroska_demux_flush (demux, needed + length); - break; - } - break; - } - - if (ret != GST_FLOW_OK) - goto parse_failed; - else + ret = gst_matroska_demux_parse_id (demux, id, length, needed); + if (ret == GST_FLOW_UNEXPECTED) { + /* need more data */ + return GST_FLOW_OK; + } else if (ret != GST_FLOW_OK) { + return ret; + } else goto next; - -exit: - return ret; - - /* ERRORS */ -parse_failed: - { - GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), - ("Failed to parse Element 0x%x", id)); - return GST_FLOW_ERROR; - } -not_streamable: - { - GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), - ("File layout does not permit streaming")); - return GST_FLOW_ERROR; - } -invalid_header: - { - GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), ("Invalid header")); - return GST_FLOW_ERROR; - } -seek_failed: - { - GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), ("Failed to seek")); - return GST_FLOW_ERROR; - } } static gboolean diff --git a/gst/matroska/matroska-demux.h b/gst/matroska/matroska-demux.h index 6c14d79..44376f4 100644 --- a/gst/matroska/matroska-demux.h +++ b/gst/matroska/matroska-demux.h @@ -43,13 +43,14 @@ G_BEGIN_DECLS typedef enum { GST_MATROSKA_DEMUX_STATE_START, + GST_MATROSKA_DEMUX_STATE_SEGMENT, GST_MATROSKA_DEMUX_STATE_HEADER, GST_MATROSKA_DEMUX_STATE_DATA, GST_MATROSKA_DEMUX_STATE_SEEK } GstMatroskaDemuxState; typedef struct _GstMatroskaDemux { - GstEbmlRead parent; + GstElement parent; /* < private > */ @@ -75,6 +76,7 @@ typedef struct _GstMatroskaDemux { GstMatroskaDemuxState state; guint level_up; guint64 seek_block; + gboolean seek_first; /* did we parse cues/tracks/segmentinfo already? */ gboolean index_parsed; @@ -82,6 +84,7 @@ typedef struct _GstMatroskaDemux { gboolean segmentinfo_parsed; gboolean attachments_parsed; GList *tags_parsed; + GList *seek_parsed; /* start-of-segment */ guint64 ebml_segment_start; @@ -101,12 +104,17 @@ typedef struct _GstMatroskaDemux { GstEvent *new_segment; GstTagList *global_tags; - /* push based mode usual suspects */ + /* pull mode caching */ + GstBuffer *cached_buffer; + + /* push and pull mode */ guint64 offset; - GstAdapter *adapter; /* some state saving */ GstClockTime cluster_time; guint64 cluster_offset; + + /* push based mode usual suspects */ + GstAdapter *adapter; /* index stuff */ gboolean seekable; gboolean building_index; @@ -122,7 +130,7 @@ typedef struct _GstMatroskaDemux { } GstMatroskaDemux; typedef struct _GstMatroskaDemuxClass { - GstEbmlReadClass parent; + GstElementClass parent; } GstMatroskaDemuxClass; gboolean gst_matroska_demux_plugin_init (GstPlugin *plugin); -- 2.7.4