From f7ea1932ed239117284675968adbbb1f38187e11 Mon Sep 17 00:00:00 2001 From: "Ronald S. Bultje" Date: Tue, 12 Oct 2004 12:29:45 +0000 Subject: [PATCH] ext/gnomevfs/gstgnomevfssrc.c: Some debug. Original commit message from CVS: * ext/gnomevfs/gstgnomevfssrc.c: (gst_gnomevfssrc_get), (gst_gnomevfssrc_srcpad_query), (gst_gnomevfssrc_srcpad_event): Some debug. * gst/avi/gstavidemux.c: (gst_avi_demux_reset), (gst_avi_demux_handle_src_event), (gst_avi_demux_read_superindex), (gst_avi_demux_read_subindexes), (gst_avi_demux_add_stream), (gst_avi_demux_stream_index), (gst_avi_demux_skip), (gst_avi_demux_sync), (gst_avi_demux_stream_scan), (gst_avi_demux_massage_index), (gst_avi_demux_stream_header): * gst/avi/gstavidemux.h: Support for openDML-2.0 indx/ix## chunks. Support for broken index recovery (where, if part of the index is broken, we will still read the rest of the index and recover the broken part by stream scanning). More broken media support. EOS workarounds. General AVI braindamage headache recovery. Aspirin included. --- ChangeLog | 18 +++ gst/avi/gstavidemux.c | 358 +++++++++++++++++++++++++++++++++++++----- gst/avi/gstavidemux.h | 2 + 3 files changed, 339 insertions(+), 39 deletions(-) diff --git a/ChangeLog b/ChangeLog index d970ef518f..93056936c8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,21 @@ +2004-10-12 Ronald S. Bultje + + * ext/gnomevfs/gstgnomevfssrc.c: (gst_gnomevfssrc_get), + (gst_gnomevfssrc_srcpad_query), (gst_gnomevfssrc_srcpad_event): + Some debug. + * gst/avi/gstavidemux.c: (gst_avi_demux_reset), + (gst_avi_demux_handle_src_event), (gst_avi_demux_read_superindex), + (gst_avi_demux_read_subindexes), (gst_avi_demux_add_stream), + (gst_avi_demux_stream_index), (gst_avi_demux_skip), + (gst_avi_demux_sync), (gst_avi_demux_stream_scan), + (gst_avi_demux_massage_index), (gst_avi_demux_stream_header): + * gst/avi/gstavidemux.h: + Support for openDML-2.0 indx/ix## chunks. Support for broken index + recovery (where, if part of the index is broken, we will still read + the rest of the index and recover the broken part by stream + scanning). More broken media support. EOS workarounds. General AVI + braindamage headache recovery. Aspirin included. + 2004-10-11 Ronald S. Bultje * ext/cdparanoia/gstcdparanoia.c: (cdparanoia_open), diff --git a/gst/avi/gstavidemux.c b/gst/avi/gstavidemux.c index 6367186b7b..2b5bc76326 100644 --- a/gst/avi/gstavidemux.c +++ b/gst/avi/gstavidemux.c @@ -177,6 +177,7 @@ gst_avi_demux_reset (GstAviDemux * avi) avi->index_entries = NULL; } avi->index_size = 0; + avi->index_offset = 0; avi->current_entry = 0; avi->num_frames = 0; @@ -558,6 +559,7 @@ gst_avi_demux_handle_src_event (GstPad * pad, GstEvent * event) avi->seek_flush = (GST_EVENT_SEEK_FLAGS (event) & GST_SEEK_FLAG_FLUSH); avi->seek_entry = entry->index_nr; + GST_DEBUG ("Will seek to entry %d", avi->seek_entry); } else { GST_DEBUG ("no index entry found for format=%d value=%" G_GINT64_FORMAT, GST_EVENT_SEEK_FORMAT (event), desired_offset); @@ -673,6 +675,197 @@ gst_avi_demux_stream_avih (GstAviDemux * avi, return TRUE; } +/* + * Read superindex/subindex (openDML-2). + */ + +static gboolean +gst_avi_demux_read_superindex (GstAviDemux * avi, + gint stream_nr, guint64 ** locations) +{ + GstRiffRead *riff = GST_RIFF_READ (avi); + guint32 tag; + GstBuffer *buf; + guint8 *data; + gint bpe = 16, num, i; + guint64 *indexes; + + if (!gst_riff_read_data (riff, &tag, &buf)) + return FALSE; + data = GST_BUFFER_DATA (buf); + if (tag != GST_MAKE_FOURCC ('i', 'n', 'd', 'x') && + tag != GST_MAKE_FOURCC ('i', 'x', '0' + stream_nr / 10, + '0' + stream_nr % 10)) { + g_warning ("Not an indx/ix## chunk"); + gst_buffer_unref (buf); + return FALSE; + } + + /* check type of index. The opendml2 specs state that + * there should be 4 dwords per array entry. Type can be + * either frame or field (and we don't care). */ + if (GST_READ_UINT16_LE (data) != 4 || + (data[2] & 0xfe) != 0x0 || data[3] != 0x0) { + GST_WARNING ("Superindex for stream %d has unexpected " + "size_entry %d (bytes) or flags 0x%02x/0x%02x", + GST_READ_UINT16_LE (data), data[2], data[3]); + bpe = GST_READ_UINT16_LE (data) * 4; + } + num = GST_READ_UINT32_LE (&data[4]); + + indexes = g_new (guint64, num + 1); + for (i = 0; i < num; i++) { + if (GST_BUFFER_SIZE (buf) < 24 + bpe * (i + 1)) + break; + indexes[i] = GST_READ_UINT64_LE (&data[24 + bpe * i]); + } + indexes[i] = 0; + *locations = indexes; + + gst_buffer_unref (buf); + + return TRUE; +} + +static gboolean +gst_avi_demux_read_subindexes (GstAviDemux * avi, + GList ** index, GList ** alloc_list) +{ + GstRiffRead *riff = GST_RIFF_READ (avi); + guint64 pos = gst_bytestream_tell (riff->bs), + length = gst_bytestream_length (riff->bs), baseoff; + GstEvent *event; + GList *list = NULL; + gst_avi_index_entry *entries, *entry; + guint32 tag; + GstBuffer *buf; + guint8 *data; + GstFormat format = GST_FORMAT_TIME; + gint bpe, num, x, i, n; + + for (n = 0; n < avi->num_streams; n++) { + avi_stream_context *stream = &avi->stream[n]; + + for (i = 0; stream->indexes[i] != 0; i++) { + /* eos check again */ + if (stream->indexes[i] + 8 >= length) { + GST_WARNING ("Subindex %d for stream %d doesn't exist", i, n); + continue; + } + + /* seek to that point */ + if (!(event = gst_riff_read_seek (riff, stream->indexes[i]))) { + g_list_free (list); + return FALSE; + } + gst_event_unref (event); + if (gst_bytestream_peek_bytes (riff->bs, &data, 8) != 8) { + g_list_free (list); + return FALSE; + } + + /* eos check again */ + if (GST_READ_UINT32_LE (&data[4]) + gst_bytestream_tell (riff->bs) > + length) { + GST_WARNING ("Subindex %d for stream %d lies outside file", i, n); + continue; + } + + /* now read */ + if (!gst_riff_read_data (riff, &tag, &buf)) + return FALSE; + data = GST_BUFFER_DATA (buf); + if (tag != GST_MAKE_FOURCC ('i', 'x', '0' + stream->num / 10, + '0' + stream->num % 10)) { + GST_WARNING ("Not an ix## chunk (" GST_FOURCC_FORMAT ")", + GST_FOURCC_ARGS (tag)); + gst_buffer_unref (buf); + continue; + } + + /* We don't support index-data yet */ + if (data[3] & 0x80) { + GST_ELEMENT_ERROR (avi, STREAM, NOT_IMPLEMENTED, (NULL), + ("Subindex-is-data is not implemented")); + return FALSE; + } + + /* check type of index. The opendml2 specs state that + * there should be 4 dwords per array entry. Type can be + * either frame or field (and we don't care). */ + bpe = (data[2] & 0x01) ? 12 : 8; + if (GST_READ_UINT16_LE (data) != bpe / 4 || + (data[2] & 0xfe) != 0x0 || data[3] != 0x1) { + GST_WARNING ("Superindex for stream %d has unexpected " + "size_entry %d (bytes) or flags 0x%02x/0x%02x", + GST_READ_UINT16_LE (data), data[2], data[3]); + bpe = GST_READ_UINT16_LE (data) * 4; + } + num = GST_READ_UINT32_LE (&data[4]); + baseoff = GST_READ_UINT64_LE (&data[12]); + + entries = g_new (gst_avi_index_entry, num); + *alloc_list = g_list_append (*alloc_list, entries); + for (x = 0; x < num; x++) { + entry = &entries[x]; + + if (GST_BUFFER_SIZE (buf) < 24 + bpe * (x + 1)) + break; + + /* fill in */ + entry->offset = baseoff + GST_READ_UINT32_LE (&data[24 + bpe * x]); + entry->size = GST_READ_UINT32_LE (&data[24 + bpe * x + 4]); + entry->flags = (entry->size & 0x80000000) ? 0 : GST_RIFF_IF_KEYFRAME; + entry->size &= ~0x80000000; + entry->index_nr = x; + entry->stream_nr = stream->num; + + /* timestamps */ + if (stream->strh->samplesize && stream->strh->type == GST_RIFF_FCC_auds) { + /* constant rate stream */ + gst_pad_convert (stream->pad, GST_FORMAT_BYTES, + stream->total_bytes, &format, &entry->ts); + gst_pad_convert (stream->pad, GST_FORMAT_BYTES, + stream->total_bytes + entry->size, &format, &entry->dur); + } else { + /* VBR stream */ + gst_pad_convert (stream->pad, GST_FORMAT_DEFAULT, + stream->total_frames, &format, &entry->ts); + gst_pad_convert (stream->pad, GST_FORMAT_DEFAULT, + stream->total_frames + 1, &format, &entry->dur); + } + entry->dur -= entry->ts; + + /* stream position */ + entry->bytes_before = stream->total_bytes; + stream->total_bytes += entry->size; + entry->frames_before = stream->total_frames; + stream->total_frames++; + + list = g_list_prepend (list, entry); + } + + GST_LOG ("Read %d index entries in subindex %d for stream %d " + "at location %" G_GUINT64_FORMAT, num, i, n, stream->indexes[i]); + + gst_buffer_unref (buf); + } + + g_free (stream->indexes); + stream->indexes = NULL; + } + + /* seek back */ + if (!(event = gst_riff_read_seek (riff, pos))) { + g_list_free (list); + return FALSE; + } + gst_event_unref (event); + *index = g_list_reverse (list); + + return TRUE; +} + /* * Add a stream. */ @@ -691,6 +884,7 @@ gst_avi_demux_add_stream (GstAviDemux * avi) GstPad *pad; avi_stream_context *stream; gint blockalign = 0, bitrate = 0; + guint64 *locations = NULL; union { gst_riff_strf_vids *vids; @@ -754,13 +948,21 @@ gst_avi_demux_add_stream (GstAviDemux * avi) break; case GST_RIFF_TAG_strn: - if (name) - g_free (name); + g_free (name); if (!gst_riff_read_ascii (riff, &tag, &name)) return FALSE; break; default: + if (tag == GST_MAKE_FOURCC ('i', 'n', 'd', 'x') || + tag == GST_MAKE_FOURCC ('i', 'x', avi->num_streams / 10, + avi->num_streams % 10)) { + g_free (locations); + if (!gst_avi_demux_read_superindex (avi, + avi->num_streams, &locations)) + return FALSE; + break; + } GST_WARNING ("Unknown tag " GST_FOURCC_FORMAT " in AVI header", GST_FOURCC_ARGS (tag)); /* fall-through */ @@ -874,6 +1076,7 @@ gst_avi_demux_add_stream (GstAviDemux * avi) stream->skip = 0; stream->blockalign = blockalign; stream->bitrate = bitrate; + stream->indexes = locations; gst_pad_set_element_private (pad, stream); avi->num_streams++; @@ -1041,7 +1244,7 @@ gst_avi_demux_stream_index (GstAviDemux * avi, entry.size = GUINT32_FROM_LE (_entry->size); target = &index_entries[i]; - if (entry.id == GST_RIFF_rec) + if (entry.id == GST_RIFF_rec || entry.id == 0 || entry.size == 0) continue; stream_nr = CHUNKID_TO_STREAMNR (entry.id); @@ -1093,6 +1296,8 @@ gst_avi_demux_stream_index (GstAviDemux * avi, stream->total_bytes += target->size; stream->total_frames++; + GST_DEBUG ("Adding index entry of size %u at offset %" + G_GUINT64_FORMAT, target->size, target->offset); list = g_list_prepend (list, target); } @@ -1129,6 +1334,38 @@ end: * Sync to next data chunk. */ +static gboolean +gst_avi_demux_skip (GstAviDemux * avi, gboolean prevent_eos) +{ + GstRiffRead *riff = GST_RIFF_READ (avi); + + if (prevent_eos) { + guint64 pos, length; + guint size; + guint8 *data; + + pos = gst_bytestream_tell (riff->bs); + length = gst_bytestream_length (riff->bs); + + if (pos + 8 > length) + return FALSE; + + if (gst_bytestream_peek_bytes (riff->bs, &data, 8) != 8) + return FALSE; + + size = GST_READ_UINT32_LE (&data[4]); + if (size & 1) + size++; + + /* Note, we're going to skip which might involve seeks. Therefore, + * we need 1 byte more! */ + if (pos + 8 + size >= length) + return FALSE; + } + + return gst_riff_read_skip (riff); +} + static gboolean gst_avi_demux_sync (GstAviDemux * avi, guint32 * ret_tag, gboolean prevent_eos) { @@ -1136,7 +1373,7 @@ gst_avi_demux_sync (GstAviDemux * avi, guint32 * ret_tag, gboolean prevent_eos) guint32 tag; guint64 length = gst_bytestream_length (riff->bs); - if (gst_bytestream_tell (riff->bs) + 12 >= length) + if (prevent_eos && gst_bytestream_tell (riff->bs) + 12 >= length) return FALSE; /* peek first (for the end of this 'list/movi' section) */ @@ -1147,8 +1384,8 @@ gst_avi_demux_sync (GstAviDemux * avi, guint32 * ret_tag, gboolean prevent_eos) * list tag yet. This can also be 'AVIX' in case of * openDML-2.0 AVI files. Lastly, it might be idx1, * in which case we skip it so we come at EOS. */ - while (g_list_length (riff->level) < 2) { - if (gst_bytestream_tell (riff->bs) + 12 >= length) + while (1) { + if (prevent_eos && gst_bytestream_tell (riff->bs) + 12 >= length) return FALSE; if (!(tag = gst_riff_peek_tag (riff, NULL))) @@ -1161,11 +1398,17 @@ gst_avi_demux_sync (GstAviDemux * avi, guint32 * ret_tag, gboolean prevent_eos) switch (tag) { case GST_RIFF_LIST_AVIX: + if (!gst_riff_read_list (riff, &tag)) + return FALSE; + break; + case GST_RIFF_LIST_movi: if (!gst_riff_read_list (riff, &tag)) return FALSE; - /* we're now going to read buffers! */ - break; + /* fall-through */ + + case GST_RIFF_rec: + goto done; default: GST_WARNING ("Unknown list " GST_FOURCC_FORMAT " before AVI data", @@ -1173,28 +1416,31 @@ gst_avi_demux_sync (GstAviDemux * avi, guint32 * ret_tag, gboolean prevent_eos) /* fall-through */ case GST_RIFF_TAG_JUNK: - if (!gst_riff_read_skip (riff)) + if (!gst_avi_demux_skip (avi, prevent_eos)) return FALSE; break; } - break; default: - GST_WARNING ("Unknown tag " GST_FOURCC_FORMAT " before AVI data", - GST_FOURCC_ARGS (tag)); - /* fall-through */ + if ((tag & 0xff) >= '0' && + (tag & 0xff) <= '9' && + ((tag >> 8) & 0xff) >= '0' && ((tag >> 8) & 0xff) <= '9') { + goto done; + } + /* pass-through */ case GST_RIFF_TAG_idx1: case GST_RIFF_TAG_JUNK: - if (!gst_riff_read_skip (riff)) + if (!gst_avi_demux_skip (avi, prevent_eos)) { return FALSE; + } break; } } - +done: /* And then, we get the data */ - if (gst_bytestream_tell (riff->bs) + 12 >= length) + if (prevent_eos && gst_bytestream_tell (riff->bs) + 12 >= length) return FALSE; if (!(tag = gst_riff_peek_tag (riff, NULL))) @@ -1215,7 +1461,7 @@ gst_avi_demux_sync (GstAviDemux * avi, guint32 * ret_tag, gboolean prevent_eos) break; case GST_RIFF_TAG_JUNK: - gst_riff_read_skip (riff); + gst_avi_demux_skip (avi, prevent_eos); return FALSE; } @@ -1238,7 +1484,8 @@ gst_avi_demux_stream_scan (GstAviDemux * avi, GstRiffRead *riff = GST_RIFF_READ (avi); gst_avi_index_entry *entry, *entries = NULL; avi_stream_context *stream; - guint64 pos = gst_bytestream_tell (riff->bs); + guint64 pos = gst_bytestream_tell (riff->bs), + length = gst_bytestream_length (riff->bs); guint32 tag; GstEvent *event; GList *list = NULL; @@ -1248,6 +1495,28 @@ gst_avi_demux_stream_scan (GstAviDemux * avi, * - implement non-seekable source support. */ + if (*index) { + GstEvent *event; + guint64 off; + + entry = g_list_last (*index)->data; + off = entry->offset + avi->index_offset + entry->size; + if (entry->size & 1) + off++; + + if (off < length) { + GST_LOG ("Incomplete index, seeking to last valid entry @ %" + G_GUINT64_FORMAT " of %" G_GUINT64_FORMAT " (%" + G_GUINT64_FORMAT "+%u)", off, length, entry->offset, entry->size); + + if (!(event = gst_riff_read_seek (riff, off))) + return FALSE; + gst_event_unref (event); + } else { + return TRUE; + } + } + GST_LOG_OBJECT (avi, "Creating index"); while (gst_avi_demux_sync (avi, &tag, TRUE)) { @@ -1273,8 +1542,8 @@ gst_avi_demux_stream_scan (GstAviDemux * avi, /* fill in */ entry->index_nr = index_size++; entry->stream_nr = stream_nr; - entry->flags = 0; - entry->offset = gst_bytestream_tell (riff->bs) + 8; + entry->flags = GST_RIFF_IF_KEYFRAME; + entry->offset = gst_bytestream_tell (riff->bs) + 8 - avi->index_offset; entry->size = GST_READ_UINT32_LE (&data[4]); /* timestamps */ @@ -1302,12 +1571,10 @@ gst_avi_demux_stream_scan (GstAviDemux * avi, list = g_list_prepend (list, entry); next: - if (!gst_riff_read_skip (riff)) + if (!gst_avi_demux_skip (avi, TRUE)) break; } - avi->index_offset = 0; - /* seek back */ if (!(event = gst_riff_read_seek (riff, pos))) { g_list_free (list); @@ -1317,7 +1584,7 @@ gst_avi_demux_stream_scan (GstAviDemux * avi, GST_LOG_OBJECT (avi, "index created, %d items", index_size); - *index = g_list_reverse (list); + *index = g_list_concat (*index, g_list_reverse (list)); return TRUE; } @@ -1470,6 +1737,11 @@ gst_avi_demux_massage_index (GstAviDemux * avi, g_list_free (alloc_list); g_list_free (list); + for (i = 0; i < avi->num_streams; i++) { + GST_LOG ("Stream %d, %d frames, %" G_GUINT64_FORMAT " bytes", i, + avi->stream[i].total_frames, avi->stream[i].total_bytes); + } + GST_LOG ("Index massaging done"); } @@ -1575,10 +1847,6 @@ gst_avi_demux_stream_header (GstAviDemux * avi) g_warning ("Stream header mentioned %d streams, but %d available", streams, avi->num_streams); } - /* at this point we know all the streams and we can signal the no more - * pads signal */ - GST_DEBUG ("signaling no more pads"); - gst_element_no_more_pads (GST_ELEMENT (avi)); /* Now, find the data (i.e. skip all junk between header and data) */ while (1) { @@ -1604,28 +1872,40 @@ gst_avi_demux_stream_header (GstAviDemux * avi) } /* create or read stream index (for seeking) */ - if (flags & GST_RIFF_AVIH_HASINDEX) { - if (!gst_avi_demux_stream_index (avi, &index, &alloc)) { - g_list_foreach (alloc, (GFunc) g_free, NULL); - g_list_free (alloc); + if (avi->stream[0].indexes != NULL) { + if (!gst_avi_demux_read_subindexes (avi, &index, &alloc)) return FALSE; - } } if (!index) { - if (!gst_avi_demux_stream_scan (avi, &index, &alloc)) { - g_list_foreach (alloc, (GFunc) g_free, NULL); - g_list_free (alloc); - return FALSE; + if (flags & GST_RIFF_AVIH_HASINDEX) { + if (!gst_avi_demux_stream_index (avi, &index, &alloc)) { + g_list_foreach (alloc, (GFunc) g_free, NULL); + g_list_free (alloc); + return FALSE; + } + } + /* some indexes are incomplete, continue streaming from there */ + if (!index || avi->stream[0].total_frames < avi->num_frames) { + if (!gst_avi_demux_stream_scan (avi, &index, &alloc)) { + g_list_foreach (alloc, (GFunc) g_free, NULL); + g_list_free (alloc); + return FALSE; + } } } - if (index) + if (index) { gst_avi_demux_massage_index (avi, index, alloc); - else { + } else { g_list_free (index); g_list_foreach (alloc, (GFunc) g_free, NULL); g_list_free (alloc); } + /* at this point we know all the streams and we can signal the no more + * pads signal */ + GST_DEBUG ("signaling no more pads"); + gst_element_no_more_pads (GST_ELEMENT (avi)); + return TRUE; } diff --git a/gst/avi/gstavidemux.h b/gst/avi/gstavidemux.h index 47dd7015f7..b506802edc 100644 --- a/gst/avi/gstavidemux.h +++ b/gst/avi/gstavidemux.h @@ -78,6 +78,8 @@ typedef struct { guint32 total_frames; guint32 skip; + + guint64 *indexes; } avi_stream_context; typedef enum {