X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gst%2Favi%2Fgstavidemux.c;h=6bcdffd73c5159058bfab0d2f2f1899f2001ad42;hb=86933b40e928d1697a1eb72ded294e8714f00b9b;hp=6d8fff794b7ab4fa4be8bb38da9c98c92e1ad08b;hpb=28ccc40babf1b87333d2194ab31dae9d9b85733a;p=platform%2Fupstream%2Fgst-plugins-good.git diff --git a/gst/avi/gstavidemux.c b/gst/avi/gstavidemux.c index 6d8fff7..6bcdffd 100644 --- a/gst/avi/gstavidemux.c +++ b/gst/avi/gstavidemux.c @@ -1,6 +1,7 @@ /* GStreamer * Copyright (C) <1999> Erik Walthinsen * Copyright (C) <2006> Nokia Corporation (contact ) + * Copyright (C) <2009-2010> STEricsson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -45,6 +46,7 @@ #endif #include +#include #include "gst/riff/riff-media.h" #include "gstavidemux.h" @@ -55,6 +57,12 @@ #define DIV_ROUND_UP(s,v) (((s) + ((v)-1)) / (v)) +#define GST_AVI_KEYFRAME 1 +#define ENTRY_IS_KEYFRAME(e) ((e)->flags == GST_AVI_KEYFRAME) +#define ENTRY_SET_KEYFRAME(e) ((e)->flags = GST_AVI_KEYFRAME) +#define ENTRY_UNSET_KEYFRAME(e) ((e)->flags = 0) + + GST_DEBUG_CATEGORY_STATIC (avidemux_debug); #define GST_CAT_DEFAULT avidemux_debug @@ -90,6 +98,8 @@ static gboolean gst_avi_demux_src_convert (GstPad * pad, GstFormat src_format, static gboolean gst_avi_demux_do_seek (GstAviDemux * avi, GstSegment * segment); static gboolean gst_avi_demux_handle_seek (GstAviDemux * avi, GstPad * pad, GstEvent * event); +static gboolean gst_avi_demux_handle_seek_push (GstAviDemux * avi, GstPad * pad, + GstEvent * event); static void gst_avi_demux_loop (GstPad * pad); static gboolean gst_avi_demux_sink_activate (GstPad * sinkpad); static gboolean gst_avi_demux_sink_activate_pull (GstPad * sinkpad, @@ -97,8 +107,16 @@ static gboolean gst_avi_demux_sink_activate_pull (GstPad * sinkpad, static gboolean gst_avi_demux_activate_push (GstPad * pad, gboolean active); static GstFlowReturn gst_avi_demux_chain (GstPad * pad, GstBuffer * buf); +static void gst_avi_demux_set_index (GstElement * element, GstIndex * index); +static GstIndex *gst_avi_demux_get_index (GstElement * element); static GstStateChangeReturn gst_avi_demux_change_state (GstElement * element, GstStateChange transition); +static void gst_avi_demux_calculate_durations_from_index (GstAviDemux * avi); +static void gst_avi_demux_get_buffer_info (GstAviDemux * avi, + GstAviStream * stream, guint entry_n, GstClockTime * timestamp, + GstClockTime * ts_end, guint64 * offset, guint64 * offset_end); + +static void gst_avi_demux_parse_idit (GstAviDemux * avi, GstBuffer * buf); static GstElementClass *parent_class = NULL; @@ -133,13 +151,6 @@ gst_avi_demux_get_type (void) static void gst_avi_demux_base_init (GstAviDemuxClass * klass) { - static const GstElementDetails gst_avi_demux_details = - GST_ELEMENT_DETAILS ("Avi demuxer", - "Codec/Demuxer", - "Demultiplex an avi file into audio and video", - "Erik Walthinsen \n" - "Wim Taymans \n" - "Thijs Vermeir "); GstElementClass *element_class = GST_ELEMENT_CLASS (klass); GstPadTemplate *videosrctempl, *audiosrctempl, *subsrctempl; GstCaps *audcaps, *vidcaps, *subcaps; @@ -163,7 +174,12 @@ gst_avi_demux_base_init (GstAviDemuxClass * klass) gst_element_class_add_pad_template (element_class, subsrctempl); gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&sink_templ)); - gst_element_class_set_details (element_class, &gst_avi_demux_details); + gst_element_class_set_details_simple (element_class, "Avi demuxer", + "Codec/Demuxer", + "Demultiplex an avi file into audio and video", + "Erik Walthinsen , " + "Wim Taymans , " + "Thijs Vermeir "); } static void @@ -180,6 +196,9 @@ gst_avi_demux_class_init (GstAviDemuxClass * klass) gobject_class->finalize = gst_avi_demux_finalize; gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_avi_demux_change_state); + + gstelement_class->set_index = GST_DEBUG_FUNCPTR (gst_avi_demux_set_index); + gstelement_class->get_index = GST_DEBUG_FUNCPTR (gst_avi_demux_get_index); } static void @@ -196,7 +215,7 @@ gst_avi_demux_init (GstAviDemux * avi) GST_DEBUG_FUNCPTR (gst_avi_demux_chain)); gst_pad_set_event_function (avi->sinkpad, GST_DEBUG_FUNCPTR (gst_avi_demux_handle_sink_event)); - gst_element_add_pad (GST_ELEMENT (avi), avi->sinkpad); + gst_element_add_pad (GST_ELEMENT_CAST (avi), avi->sinkpad); avi->adapter = gst_adapter_new (); @@ -228,8 +247,11 @@ gst_avi_demux_reset_stream (GstAviDemux * avi, GstAviStream * stream) if (stream->extradata) gst_buffer_unref (stream->extradata); if (stream->pad) { - gst_pad_set_active (stream->pad, FALSE); - gst_element_remove_pad (GST_ELEMENT (avi), stream->pad); + if (stream->exposed) { + gst_pad_set_active (stream->pad, FALSE); + gst_element_remove_pad (GST_ELEMENT_CAST (avi), stream->pad); + } else + gst_object_unref (stream->pad); } if (stream->taglist) { gst_tag_list_free (stream->taglist); @@ -253,14 +275,28 @@ gst_avi_demux_reset (GstAviDemux * avi) avi->num_v_streams = 0; avi->num_a_streams = 0; avi->num_t_streams = 0; + avi->main_stream = -1; avi->state = GST_AVI_DEMUX_START; avi->offset = 0; + avi->building_index = FALSE; avi->index_offset = 0; g_free (avi->avih); avi->avih = NULL; + if (avi->element_index) + gst_object_unref (avi->element_index); + avi->element_index = NULL; + + if (avi->close_seg_event) { + gst_event_unref (avi->close_seg_event); + avi->close_seg_event = NULL; + } + if (avi->seg_event) { + gst_event_unref (avi->seg_event); + avi->seg_event = NULL; + } if (avi->seek_event) { gst_event_unref (avi->seek_event); avi->seek_event = NULL; @@ -272,6 +308,7 @@ gst_avi_demux_reset (GstAviDemux * avi) avi->got_tags = TRUE; /* we always want to push global tags */ avi->have_eos = FALSE; + avi->seekable = TRUE; gst_adapter_clear (avi->adapter); @@ -505,18 +542,24 @@ gst_avi_demux_handle_src_query (GstPad * pad, GstQuery * query) case GST_QUERY_DURATION: { GstFormat fmt; + GstClockTime duration; + /* only act on audio or video streams */ if (stream->strh->type != GST_RIFF_FCC_auds && stream->strh->type != GST_RIFF_FCC_vids) { res = FALSE; break; } + /* take stream duration, fall back to avih duration */ + if ((duration = stream->duration) == -1) + duration = avi->duration; + gst_query_parse_duration (query, &fmt, NULL); switch (fmt) { case GST_FORMAT_TIME: - gst_query_set_duration (query, fmt, stream->duration); + gst_query_set_duration (query, fmt, duration); break; case GST_FORMAT_DEFAULT: { @@ -527,7 +570,7 @@ gst_avi_demux_handle_src_query (GstPad * pad, GstQuery * query) if (stream->idx_n >= 0) gst_query_set_duration (query, fmt, stream->idx_n); else if (gst_pad_query_convert (pad, GST_FORMAT_TIME, - stream->duration, &fmt, &dur)) + duration, &fmt, &dur)) gst_query_set_duration (query, fmt, dur); break; } @@ -545,7 +588,7 @@ gst_avi_demux_handle_src_query (GstPad * pad, GstQuery * query) gboolean seekable = TRUE; if (avi->streaming) { - seekable = FALSE; + seekable = avi->seekable; } gst_query_set_seeking (query, GST_FORMAT_TIME, seekable, @@ -588,6 +631,119 @@ gst_avi_demux_get_event_mask (GstPad * pad) } #endif +static guint64 +gst_avi_demux_seek_streams (GstAviDemux * avi, guint64 offset, gboolean before) +{ + GstAviStream *stream; + GstIndexEntry *entry; + gint i; + gint64 val, min = offset; + + for (i = 0; i < avi->num_streams; i++) { + stream = &avi->stream[i]; + + entry = gst_index_get_assoc_entry (avi->element_index, stream->index_id, + before ? GST_INDEX_LOOKUP_BEFORE : GST_INDEX_LOOKUP_AFTER, + GST_ASSOCIATION_FLAG_NONE, GST_FORMAT_BYTES, offset); + + if (before) { + if (entry) { + gst_index_entry_assoc_map (entry, GST_FORMAT_BYTES, &val); + GST_DEBUG_OBJECT (avi, "stream %d, previous entry at %" + G_GUINT64_FORMAT, i, val); + if (val < min) + min = val; + } + continue; + } + + if (!entry) { + GST_DEBUG_OBJECT (avi, "no position for stream %d, assuming at start", i); + stream->current_entry = 0; + stream->current_total = 0; + continue; + } + + gst_index_entry_assoc_map (entry, GST_FORMAT_BYTES, &val); + GST_DEBUG_OBJECT (avi, "stream %d, next entry at %" G_GUINT64_FORMAT, + i, val); + + gst_index_entry_assoc_map (entry, GST_FORMAT_TIME, &val); + stream->current_total = val; + gst_index_entry_assoc_map (entry, GST_FORMAT_DEFAULT, &val); + stream->current_entry = val; + } + + return min; +} + +static guint +gst_avi_demux_index_entry_offset_search (GstAviIndexEntry * entry, + guint64 * offset) +{ + if (entry->offset < *offset) + return -1; + else if (entry->offset > *offset) + return 1; + return 0; +} + +static guint64 +gst_avi_demux_seek_streams_index (GstAviDemux * avi, guint64 offset, + gboolean before) +{ + GstAviStream *stream; + GstAviIndexEntry *entry; + gint i; + gint64 val, min = offset; + guint index; + + for (i = 0; i < avi->num_streams; i++) { + stream = &avi->stream[i]; + + /* compensate for chunk header */ + offset += 8; + entry = + gst_util_array_binary_search (stream->index, stream->idx_n, + sizeof (GstAviIndexEntry), + (GCompareDataFunc) gst_avi_demux_index_entry_offset_search, + before ? GST_SEARCH_MODE_BEFORE : GST_SEARCH_MODE_AFTER, &offset, NULL); + offset -= 8; + + if (entry) + index = entry - stream->index; + + if (before) { + if (entry) { + val = stream->index[index].offset; + GST_DEBUG_OBJECT (avi, + "stream %d, previous entry at %" G_GUINT64_FORMAT, i, val); + if (val < min) + min = val; + } + continue; + } + + if (!entry) { + GST_DEBUG_OBJECT (avi, "no position for stream %d, assuming at start", i); + stream->current_entry = 0; + stream->current_total = 0; + continue; + } + + val = stream->index[index].offset - 8; + GST_DEBUG_OBJECT (avi, "stream %d, next entry at %" G_GUINT64_FORMAT, i, + val); + + stream->current_total = stream->index[index].total; + stream->current_entry = index; + } + + return min; +} + +#define GST_AVI_SEEK_PUSH_DISPLACE (4 * GST_SECOND) + static gboolean gst_avi_demux_handle_sink_event (GstPad * pad, GstEvent * event) { @@ -599,9 +755,134 @@ gst_avi_demux_handle_sink_event (GstPad * pad, GstEvent * event) switch (GST_EVENT_TYPE (event)) { case GST_EVENT_NEWSEGMENT: - /* Drop NEWSEGMENT events, new ones are generated later */ + { + GstFormat format; + gdouble rate, arate; + gint64 start, stop, time, offset = 0; + gboolean update; + GstSegment segment; + + /* some debug output */ + gst_segment_init (&segment, GST_FORMAT_UNDEFINED); + gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format, + &start, &stop, &time); + gst_segment_set_newsegment_full (&segment, update, rate, arate, format, + start, stop, time); + GST_DEBUG_OBJECT (avi, + "received format %d newsegment %" GST_SEGMENT_FORMAT, format, + &segment); + + /* chain will send initial newsegment after pads have been added */ + if (avi->state != GST_AVI_DEMUX_MOVI) { + GST_DEBUG_OBJECT (avi, "still starting, eating event"); + goto exit; + } + + /* we only expect a BYTE segment, e.g. following a seek */ + if (format != GST_FORMAT_BYTES) { + GST_DEBUG_OBJECT (avi, "unsupported segment format, ignoring"); + goto exit; + } + + if (avi->have_index) { + GstAviIndexEntry *entry; + guint i = 0, index = 0, k = 0; + GstAviStream *stream; + + /* compensate chunk header, stored index offset points after header */ + start += 8; + /* find which stream we're on */ + do { + stream = &avi->stream[i]; + + /* find the index for start bytes offset */ + entry = gst_util_array_binary_search (stream->index, + stream->idx_n, sizeof (GstAviIndexEntry), + (GCompareDataFunc) gst_avi_demux_index_entry_offset_search, + GST_SEARCH_MODE_AFTER, &start, NULL); + + if (entry == NULL) + continue; + index = entry - stream->index; + + /* we are on the stream with a chunk start offset closest to start */ + if (!offset || stream->index[index].offset < offset) { + offset = stream->index[index].offset; + k = i; + } + /* exact match needs no further searching */ + if (stream->index[index].offset == start) + break; + } while (++i < avi->num_streams); + start -= 8; + offset -= 8; + stream = &avi->stream[k]; + + /* so we have no idea what is to come, or where we are */ + if (!offset) { + GST_WARNING_OBJECT (avi, "insufficient index data, forcing EOS"); + goto eos; + } + + /* get the ts corresponding to start offset bytes for the stream */ + gst_avi_demux_get_buffer_info (avi, stream, index, + (GstClockTime *) & time, NULL, NULL, NULL); + } else if (avi->element_index) { + GstIndexEntry *entry; + + /* Let's check if we have an index entry for this position */ + entry = gst_index_get_assoc_entry (avi->element_index, avi->index_id, + GST_INDEX_LOOKUP_AFTER, GST_ASSOCIATION_FLAG_NONE, + GST_FORMAT_BYTES, start); + + /* we can not go where we have not yet been before ... */ + if (!entry) { + GST_WARNING_OBJECT (avi, "insufficient index data, forcing EOS"); + goto eos; + } + + gst_index_entry_assoc_map (entry, GST_FORMAT_TIME, &time); + gst_index_entry_assoc_map (entry, GST_FORMAT_BYTES, &offset); + } else { + GST_WARNING_OBJECT (avi, "no index data, forcing EOS"); + goto eos; + } + + stop = GST_CLOCK_TIME_NONE; + + /* set up segment and send downstream */ + gst_segment_set_newsegment_full (&avi->segment, update, rate, arate, + GST_FORMAT_TIME, time, stop, time); + GST_DEBUG_OBJECT (avi, "Pushing newseg update %d, rate %g, " + "applied rate %g, format %d, start %" G_GINT64_FORMAT ", " + "stop %" G_GINT64_FORMAT, update, rate, arate, GST_FORMAT_TIME, + time, stop); + gst_avi_demux_push_event (avi, + gst_event_new_new_segment_full (update, rate, arate, GST_FORMAT_TIME, + time, stop, time)); + + GST_DEBUG_OBJECT (avi, "next chunk expected at %" G_GINT64_FORMAT, start); + + /* adjust state for streaming thread accordingly */ + if (avi->have_index) + gst_avi_demux_seek_streams_index (avi, offset, FALSE); + else + gst_avi_demux_seek_streams (avi, offset, FALSE); + + /* set up streaming thread */ + g_assert (offset >= start); + avi->offset = start; + avi->todrop = offset - start; + + exit: gst_event_unref (event); + res = TRUE; break; + eos: + /* set up for EOS */ + avi->have_eos = TRUE; + goto exit; + } case GST_EVENT_EOS: { if (avi->state != GST_AVI_DEMUX_MOVI) { @@ -614,6 +895,18 @@ gst_avi_demux_handle_sink_event (GstPad * pad, GstEvent * event) } break; } + case GST_EVENT_FLUSH_STOP: + { + gint i; + + gst_adapter_clear (avi->adapter); + avi->have_eos = FALSE; + for (i = 0; i < avi->num_streams; i++) { + avi->stream[i].last_flow = GST_FLOW_OK; + avi->stream[i].discont = TRUE; + } + /* fall through to default case so that the event gets passed downstream */ + } default: res = gst_pad_event_default (pad, event); break; @@ -635,13 +928,12 @@ gst_avi_demux_handle_src_event (GstPad * pad, GstEvent * event) switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEEK: - /* handle seeking only in pull mode */ if (!avi->streaming) { res = gst_avi_demux_handle_seek (avi, pad, event); - gst_event_unref (event); } else { - res = gst_pad_event_default (pad, event); + res = gst_avi_demux_handle_seek_push (avi, pad, event); } + gst_event_unref (event); break; case GST_EVENT_QOS: case GST_EVENT_NAVIGATION: @@ -775,7 +1067,7 @@ gst_avi_demux_parse_file_header (GstElement * element, GstBuffer * buf) goto not_avi; stamp = gst_util_get_timestamp () - stamp; - GST_DEBUG_OBJECT (element, "parsing header %" GST_TIME_FORMAT, + GST_DEBUG_OBJECT (element, "header parsing took %" GST_TIME_FORMAT, GST_TIME_ARGS (stamp)); return TRUE; @@ -802,7 +1094,7 @@ gst_avi_demux_stream_init_push (GstAviDemux * avi) tmp = gst_adapter_take_buffer (avi->adapter, 12); GST_DEBUG ("Parsing avi header"); - if (!gst_avi_demux_parse_file_header (GST_ELEMENT (avi), tmp)) { + if (!gst_avi_demux_parse_file_header (GST_ELEMENT_CAST (avi), tmp)) { return GST_FLOW_ERROR; } GST_DEBUG ("header ok"); @@ -841,10 +1133,9 @@ wrong_header: } /* AVI header handling */ - /* * gst_avi_demux_parse_avih: - * @element: caller element (used for errors/debug). + * @avi: caller element (used for errors/debug). * @buf: input data to be used for parsing. * @avih: pointer to structure (filled in by function) containing * stream information (such as flags, number of streams, etc.). @@ -856,7 +1147,7 @@ wrong_header: * (fatal). */ static gboolean -gst_avi_demux_parse_avih (GstElement * element, +gst_avi_demux_parse_avih (GstAviDemux * avi, GstBuffer * buf, gst_riff_avih ** _avih) { gst_riff_avih *avih; @@ -887,36 +1178,45 @@ gst_avi_demux_parse_avih (GstElement * element, #endif /* debug stuff */ - GST_INFO_OBJECT (element, "avih tag found:"); - GST_INFO_OBJECT (element, " us_frame %u", avih->us_frame); - GST_INFO_OBJECT (element, " max_bps %u", avih->max_bps); - GST_INFO_OBJECT (element, " pad_gran %u", avih->pad_gran); - GST_INFO_OBJECT (element, " flags 0x%08x", avih->flags); - GST_INFO_OBJECT (element, " tot_frames %u", avih->tot_frames); - GST_INFO_OBJECT (element, " init_frames %u", avih->init_frames); - GST_INFO_OBJECT (element, " streams %u", avih->streams); - GST_INFO_OBJECT (element, " bufsize %u", avih->bufsize); - GST_INFO_OBJECT (element, " width %u", avih->width); - GST_INFO_OBJECT (element, " height %u", avih->height); - GST_INFO_OBJECT (element, " scale %u", avih->scale); - GST_INFO_OBJECT (element, " rate %u", avih->rate); - GST_INFO_OBJECT (element, " start %u", avih->start); - GST_INFO_OBJECT (element, " length %u", avih->length); + GST_INFO_OBJECT (avi, "avih tag found:"); + GST_INFO_OBJECT (avi, " us_frame %u", avih->us_frame); + GST_INFO_OBJECT (avi, " max_bps %u", avih->max_bps); + GST_INFO_OBJECT (avi, " pad_gran %u", avih->pad_gran); + GST_INFO_OBJECT (avi, " flags 0x%08x", avih->flags); + GST_INFO_OBJECT (avi, " tot_frames %u", avih->tot_frames); + GST_INFO_OBJECT (avi, " init_frames %u", avih->init_frames); + GST_INFO_OBJECT (avi, " streams %u", avih->streams); + GST_INFO_OBJECT (avi, " bufsize %u", avih->bufsize); + GST_INFO_OBJECT (avi, " width %u", avih->width); + GST_INFO_OBJECT (avi, " height %u", avih->height); + GST_INFO_OBJECT (avi, " scale %u", avih->scale); + GST_INFO_OBJECT (avi, " rate %u", avih->rate); + GST_INFO_OBJECT (avi, " start %u", avih->start); + GST_INFO_OBJECT (avi, " length %u", avih->length); *_avih = avih; gst_buffer_unref (buf); + if (avih->us_frame != 0 && avih->tot_frames != 0) + avi->duration = + (guint64) avih->us_frame * (guint64) avih->tot_frames * 1000; + else + avi->duration = GST_CLOCK_TIME_NONE; + + GST_INFO_OBJECT (avi, " header duration %" GST_TIME_FORMAT, + GST_TIME_ARGS (avi->duration)); + return TRUE; /* ERRORS */ no_buffer: { - GST_ELEMENT_ERROR (element, STREAM, DEMUX, (NULL), ("No buffer")); + GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("No buffer")); return FALSE; } avih_too_small: { - GST_ELEMENT_ERROR (element, STREAM, DEMUX, (NULL), + GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("Too small avih (%d available, %d needed)", GST_BUFFER_SIZE (buf), (int) sizeof (gst_riff_avih))); gst_buffer_unref (buf); @@ -968,11 +1268,14 @@ gst_avi_demux_parse_superindex (GstAviDemux * avi, } num = GST_READ_UINT32_LE (&data[4]); + GST_DEBUG_OBJECT (avi, "got %d indexes", num); + indexes = g_new (guint64, num + 1); for (i = 0; i < num; i++) { if (size < 24 + bpe * (i + 1)) break; indexes[i] = GST_READ_UINT64_LE (&data[24 + bpe * i]); + GST_DEBUG_OBJECT (avi, "index %d at %" G_GUINT64_FORMAT, i, indexes[i]); } indexes[i] = GST_BUFFER_OFFSET_NONE; *_indexes = indexes; @@ -1088,7 +1391,7 @@ gst_avi_demux_get_buffer_info (GstAviDemux * avi, GstAviStream * stream, *ts_end = avi_stream_convert_frames_to_time_unchecked (stream, entry_n + 1); } - } else { + } else if (stream->strh->type == GST_RIFF_FCC_auds) { /* constant rate stream */ if (timestamp) *timestamp = @@ -1114,13 +1417,16 @@ gst_avi_demux_get_buffer_info (GstAviDemux * avi, GstAviStream * stream, /* collect and debug stats about the indexes for all streams. * This method is also responsible for filling in the stream duration - * as measured by the amount of index entries. */ -static void + * as measured by the amount of index entries. + * + * Returns TRUE if the index is not empty, else FALSE */ +static gboolean gst_avi_demux_do_index_stats (GstAviDemux * avi) { + guint total_idx = 0; guint i; #ifndef GST_DISABLE_GST_DEBUG - guint total_idx = 0, total_max = 0; + guint total_max = 0; #endif /* get stream stats now */ @@ -1139,8 +1445,8 @@ gst_avi_demux_do_index_stats (GstAviDemux * avi) gst_avi_demux_get_buffer_info (avi, stream, stream->idx_n - 1, NULL, &stream->idx_duration, NULL, NULL); -#ifndef GST_DISABLE_GST_DEBUG total_idx += stream->idx_n; +#ifndef GST_DISABLE_GST_DEBUG total_max += stream->idx_max; #endif GST_INFO_OBJECT (avi, "Stream %d, dur %" GST_TIME_FORMAT ", %6u entries, " @@ -1150,12 +1456,18 @@ gst_avi_demux_do_index_stats (GstAviDemux * avi) (guint) (stream->idx_n * sizeof (GstAviIndexEntry)), (guint) (stream->idx_max * sizeof (GstAviIndexEntry))); } -#ifndef GST_DISABLE_GST_DEBUG total_idx *= sizeof (GstAviIndexEntry); +#ifndef GST_DISABLE_GST_DEBUG total_max *= sizeof (GstAviIndexEntry); #endif GST_INFO_OBJECT (avi, "%u bytes for index vs %u ideally, %u wasted", total_max, total_idx, total_max - total_idx); + + if (total_idx == 0) { + GST_WARNING_OBJECT (avi, "Index is empty !"); + return FALSE; + } + return TRUE; } /* @@ -1281,53 +1593,90 @@ out_of_mem: } } -#if 0 +/* + * Create and push a flushing seek event upstream + */ +static gboolean +perform_seek_to_offset (GstAviDemux * demux, guint64 offset) +{ + GstEvent *event; + gboolean res = 0; + + GST_DEBUG_OBJECT (demux, "Seeking to %" G_GUINT64_FORMAT, offset); + + event = + gst_event_new_seek (1.0, GST_FORMAT_BYTES, + GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET, offset, + GST_SEEK_TYPE_NONE, -1); + + res = gst_pad_push_event (demux->sinkpad, event); + + if (res) + demux->offset = offset; + return res; +} + /* * Read AVI index when streaming */ -static void +static gboolean gst_avi_demux_read_subindexes_push (GstAviDemux * avi) { guint32 tag = 0, size; GstBuffer *buf = NULL; - gint i, n; + guint odml_stream; GST_DEBUG_OBJECT (avi, "read subindexes for %d streams", avi->num_streams); - for (n = 0; n < avi->num_streams; n++) { - GstAviStream *stream = &avi->stream[n]; + if (avi->odml_subidxs[avi->odml_subidx] != avi->offset) + return FALSE; - for (i = 0; stream->indexes[i] != GST_BUFFER_OFFSET_NONE; i++) { - if (!gst_avi_demux_peek_chunk (avi, &tag, &size)) - continue; - else if ((tag != GST_MAKE_FOURCC ('i', 'x', '0' + stream->num / 10, - '0' + stream->num % 10)) && - (tag != GST_MAKE_FOURCC ('0' + stream->num / 10, - '0' + stream->num % 10, 'i', 'x'))) { - GST_WARNING_OBJECT (avi, "Not an ix## chunk (%" GST_FOURCC_FORMAT ")", - GST_FOURCC_ARGS (tag)); - continue; - } + if (!gst_avi_demux_peek_chunk (avi, &tag, &size)) + return TRUE; - avi->offset += 8 + GST_ROUND_UP_2 (size); + /* this is the ODML chunk we expect */ + odml_stream = avi->odml_stream; - buf = gst_buffer_new (); - GST_BUFFER_DATA (buf) = gst_adapter_take (avi->adapter, size); - GST_BUFFER_SIZE (buf) = size; + if ((tag != GST_MAKE_FOURCC ('i', 'x', '0' + odml_stream / 10, + '0' + odml_stream % 10)) && + (tag != GST_MAKE_FOURCC ('0' + odml_stream / 10, + '0' + odml_stream % 10, 'i', 'x'))) { + GST_WARNING_OBJECT (avi, "Not an ix## chunk (%" GST_FOURCC_FORMAT ")", + GST_FOURCC_ARGS (tag)); + return FALSE; + } - if (!gst_avi_demux_parse_subindex (avi, stream, buf)) - continue; - } + avi->offset += 8 + GST_ROUND_UP_2 (size); + /* flush chunk header so we get just the 'size' payload data */ + gst_adapter_flush (avi->adapter, 8); + buf = gst_adapter_take_buffer (avi->adapter, size); - g_free (stream->indexes); - stream->indexes = NULL; + if (!gst_avi_demux_parse_subindex (avi, &avi->stream[odml_stream], buf)) + return FALSE; + + /* we parsed the index, go to next subindex */ + avi->odml_subidx++; + + if (avi->odml_subidxs[avi->odml_subidx] == GST_BUFFER_OFFSET_NONE) { + /* we reached the end of the indexes for this stream, move to the next + * stream to handle the first index */ + avi->odml_stream++; + avi->odml_subidx = 0; + + if (avi->odml_stream < avi->num_streams) { + /* there are more indexes */ + avi->odml_subidxs = avi->stream[avi->odml_stream].indexes; + } else { + /* we're done, get stream stats now */ + avi->have_index = gst_avi_demux_do_index_stats (avi); + + return TRUE; + } } - /* get stream stats now */ - gst_avi_demux_do_index_stats (avi); - avi->have_index = TRUE; + /* seek to next index */ + return perform_seek_to_offset (avi, avi->odml_subidxs[avi->odml_subidx]); } -#endif /* * Read AVI index @@ -1344,6 +1693,9 @@ gst_avi_demux_read_subindexes_pull (GstAviDemux * avi) for (n = 0; n < avi->num_streams; n++) { GstAviStream *stream = &avi->stream[n]; + if (stream->indexes == NULL) + continue; + for (i = 0; stream->indexes[i] != GST_BUFFER_OFFSET_NONE; i++) { if (gst_riff_read_chunk (GST_ELEMENT_CAST (avi), avi->sinkpad, &stream->indexes[i], &tag, &buf) != GST_FLOW_OK) @@ -1368,9 +1720,7 @@ gst_avi_demux_read_subindexes_pull (GstAviDemux * avi) stream->indexes = NULL; } /* get stream stats now */ - gst_avi_demux_do_index_stats (avi); - - avi->have_index = TRUE; + avi->have_index = gst_avi_demux_do_index_stats (avi); } /* @@ -1494,6 +1844,31 @@ too_small: } } +static void +gst_avi_demux_expose_streams (GstAviDemux * avi, gboolean force) +{ + guint i; + + GST_DEBUG_OBJECT (avi, "force : %d", force); + + for (i = 0; i < avi->num_streams; i++) { + GstAviStream *stream = &avi->stream[i]; + + if (force || stream->idx_n != 0) { + GST_LOG_OBJECT (avi, "Added pad %s with caps %" GST_PTR_FORMAT, + GST_PAD_NAME (stream->pad), GST_PAD_CAPS (stream->pad)); + gst_element_add_pad ((GstElement *) avi, stream->pad); + stream->exposed = TRUE; + if (avi->main_stream == -1) + avi->main_stream = i; + } else { + GST_WARNING_OBJECT (avi, "Stream #%d doesn't have any entry, removing it", + i); + gst_avi_demux_reset_stream (avi, stream); + } + } +} + /* * gst_avi_demux_parse_stream: * @avi: calling element (used for debugging/errors). @@ -1617,14 +1992,36 @@ gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf) GST_DEBUG_OBJECT (element, "marking video as VBR, res %d", res); break; case GST_RIFF_FCC_auds: - stream->is_vbr = (stream->strh->samplesize == 0) - && stream->strh->scale > 1; res = gst_riff_parse_strf_auds (element, sub, &stream->strf.auds, &stream->extradata); + stream->is_vbr = (stream->strh->samplesize == 0) + && stream->strh->scale > 1 + && stream->strf.auds->blockalign != 1; sub = NULL; GST_DEBUG_OBJECT (element, "marking audio as VBR:%d, res %d", stream->is_vbr, res); + /* we need these or we have no way to come up with timestamps */ + if ((!stream->is_vbr && !stream->strf.auds->av_bps) || + (stream->is_vbr && (!stream->strh->scale || + !stream->strh->rate))) { + GST_WARNING_OBJECT (element, + "invalid audio header, ignoring stream"); + goto fail; + } + /* some more sanity checks */ + if (stream->is_vbr) { + if (stream->strf.auds->blockalign <= 4) { + /* that would mean (too) many frames per chunk, + * so not likely set as expected */ + GST_DEBUG_OBJECT (element, + "suspicious blockalign %d for VBR audio; " + "overriding to 1 frame per chunk", + stream->strf.auds->blockalign); + /* this should top any likely value */ + stream->strf.auds->blockalign = (1 << 12); + } + } break; case GST_RIFF_FCC_iavs: stream->is_vbr = TRUE; @@ -1698,6 +2095,9 @@ gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf) } GST_DEBUG_OBJECT (avi, "stream name: %s", stream->name); break; + case GST_RIFF_IDIT: + gst_avi_demux_parse_idit (avi, sub); + break; default: if (tag == GST_MAKE_FOURCC ('i', 'n', 'd', 'x') || tag == GST_MAKE_FOURCC ('i', 'x', '0' + avi->num_streams / 10, @@ -1712,6 +2112,7 @@ gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf) "Unknown stream header tag %" GST_FOURCC_FORMAT ", ignoring", GST_FOURCC_ARGS (tag)); /* fall-through */ + case GST_RIFF_TAG_JUNQ: case GST_RIFF_TAG_JUNK: break; } @@ -1844,6 +2245,10 @@ gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf) GST_DEBUG_FUNCPTR (gst_avi_demux_src_convert)); #endif + if (avi->element_index) + gst_index_get_writer_id (avi->element_index, GST_OBJECT_CAST (stream->pad), + &stream->index_id); + stream->num = avi->num_streams; stream->start_entry = 0; @@ -1868,10 +2273,6 @@ gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf) gst_pad_set_caps (pad, caps); gst_pad_set_active (pad, TRUE); - gst_element_add_pad (GST_ELEMENT (avi), pad); - - GST_LOG_OBJECT (element, "Added pad %s with caps %" GST_PTR_FORMAT, - GST_PAD_NAME (pad), caps); gst_caps_unref (caps); /* make tags */ @@ -1953,6 +2354,7 @@ gst_avi_demux_parse_odml (GstAviDemux * avi, GstBuffer * buf) "Unknown tag %" GST_FOURCC_FORMAT " in ODML header", GST_FOURCC_ARGS (tag)); /* fall-through */ + case GST_RIFF_TAG_JUNQ: case GST_RIFF_TAG_JUNK: next: /* skip and move to next chunk */ @@ -1971,7 +2373,7 @@ gst_avi_demux_parse_odml (GstAviDemux * avi, GstBuffer * buf) static guint gst_avi_demux_index_last (GstAviDemux * avi, GstAviStream * stream) { - return stream->idx_n - 1; + return stream->idx_n; } /* find a previous entry in the index with the given flags */ @@ -2195,15 +2597,12 @@ gst_avi_demux_parse_index (GstAviDemux * avi, GstBuffer * buf) gst_buffer_unref (buf); /* get stream stats now */ - gst_avi_demux_do_index_stats (avi); + avi->have_index = gst_avi_demux_do_index_stats (avi); stamp = gst_util_get_timestamp () - stamp; - GST_DEBUG_OBJECT (avi, "parsing index %" GST_TIME_FORMAT, + GST_DEBUG_OBJECT (avi, "index parsing took %" GST_TIME_FORMAT, GST_TIME_ARGS (stamp)); - /* we have an index now */ - avi->have_index = TRUE; - return TRUE; /* ERRORS */ @@ -2330,46 +2729,129 @@ zero_index: } /* - * gst_avi_demux_peek_tag: + * gst_avi_demux_stream_index_push: + * @avi: avi demuxer object. * - * Returns the tag and size of the next chunk + * Read index. */ -static GstFlowReturn -gst_avi_demux_peek_tag (GstAviDemux * avi, guint64 offset, guint32 * tag, - guint * size) +static void +gst_avi_demux_stream_index_push (GstAviDemux * avi) { - GstFlowReturn res = GST_FLOW_OK; - GstBuffer *buf = NULL; - guint bufsize; - guint8 *bufdata; + guint64 offset = avi->idx1_offset; + GstBuffer *buf; + guint32 tag; + guint32 size; - res = gst_pad_pull_range (avi->sinkpad, offset, 8, &buf); - if (res != GST_FLOW_OK) - goto pull_failed; + GST_DEBUG ("demux stream index at offset %" G_GUINT64_FORMAT, offset); - bufsize = GST_BUFFER_SIZE (buf); - if (bufsize != 8) - goto wrong_size; + /* get chunk information */ + if (!gst_avi_demux_peek_chunk (avi, &tag, &size)) + return; - bufdata = GST_BUFFER_DATA (buf); + /* check tag first before blindly trying to read 'size' bytes */ + if (tag == GST_RIFF_TAG_LIST) { + /* this is the movi tag */ + GST_DEBUG_OBJECT (avi, "skip LIST chunk, size %" G_GUINT32_FORMAT, + (8 + GST_ROUND_UP_2 (size))); + avi->idx1_offset = offset + 8 + GST_ROUND_UP_2 (size); + /* issue seek to allow chain function to handle it and return! */ + perform_seek_to_offset (avi, avi->idx1_offset); + return; + } - *tag = GST_READ_UINT32_LE (bufdata); - *size = GST_READ_UINT32_LE (bufdata + 4); + if (tag != GST_RIFF_TAG_idx1) + goto no_index; - GST_LOG_OBJECT (avi, "Tag[%" GST_FOURCC_FORMAT "] (size:%d) %" - G_GINT64_FORMAT " -- %" G_GINT64_FORMAT, GST_FOURCC_ARGS (*tag), - *size, offset + 8, offset + 8 + (gint64) * size); + GST_DEBUG ("index found at offset %" G_GUINT64_FORMAT, offset); -done: - gst_buffer_unref (buf); + /* flush chunk header */ + gst_adapter_flush (avi->adapter, 8); + /* read chunk payload */ + buf = gst_adapter_take_buffer (avi->adapter, size); + if (!buf) + goto pull_failed; + /* advance offset */ + offset += 8 + GST_ROUND_UP_2 (size); - return res; + GST_DEBUG ("will parse index chunk size %u for tag %" + GST_FOURCC_FORMAT, GST_BUFFER_SIZE (buf), GST_FOURCC_ARGS (tag)); - /* ERRORS */ -pull_failed: + avi->offset = avi->first_movi_offset; + gst_avi_demux_parse_index (avi, buf); + +#ifndef GST_DISABLE_GST_DEBUG + /* debug our indexes */ { - GST_DEBUG_OBJECT (avi, "pull_ranged returned %s", gst_flow_get_name (res)); - return res; + gint i; + GstAviStream *stream; + + for (i = 0; i < avi->num_streams; i++) { + stream = &avi->stream[i]; + GST_DEBUG_OBJECT (avi, "stream %u: %u frames, %" G_GINT64_FORMAT " bytes", + i, stream->idx_n, stream->total_bytes); + } + } +#endif + return; + + /* ERRORS */ +pull_failed: + { + GST_DEBUG_OBJECT (avi, + "taking data from adapter failed: pos=%" G_GUINT64_FORMAT " size=%u", + offset, size); + return; + } +no_index: + { + GST_WARNING_OBJECT (avi, + "No index data (idx1) after movi chunk, but %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (tag)); + return; + } +} + +/* + * gst_avi_demux_peek_tag: + * + * Returns the tag and size of the next chunk + */ +static GstFlowReturn +gst_avi_demux_peek_tag (GstAviDemux * avi, guint64 offset, guint32 * tag, + guint * size) +{ + GstFlowReturn res = GST_FLOW_OK; + GstBuffer *buf = NULL; + guint bufsize; + guint8 *bufdata; + + res = gst_pad_pull_range (avi->sinkpad, offset, 8, &buf); + if (res != GST_FLOW_OK) + goto pull_failed; + + bufsize = GST_BUFFER_SIZE (buf); + if (bufsize != 8) + goto wrong_size; + + bufdata = GST_BUFFER_DATA (buf); + + *tag = GST_READ_UINT32_LE (bufdata); + *size = GST_READ_UINT32_LE (bufdata + 4); + + GST_LOG_OBJECT (avi, "Tag[%" GST_FOURCC_FORMAT "] (size:%d) %" + G_GINT64_FORMAT " -- %" G_GINT64_FORMAT, GST_FOURCC_ARGS (*tag), + *size, offset + 8, offset + 8 + (gint64) * size); + +done: + gst_buffer_unref (buf); + + return res; + + /* ERRORS */ +pull_failed: + { + GST_DEBUG_OBJECT (avi, "pull_ranged returned %s", gst_flow_get_name (res)); + return res; } wrong_size: { @@ -2474,11 +2956,9 @@ gst_avi_demux_stream_scan (GstAviDemux * avi) break; } } - /* collect stats */ - gst_avi_demux_do_index_stats (avi); - /* we have an index now */ - avi->have_index = TRUE; + /* collect stats */ + avi->have_index = gst_avi_demux_do_index_stats (avi); return TRUE; @@ -2508,7 +2988,7 @@ gst_avi_demux_calculate_durations_from_index (GstAviDemux * avi) gst_riff_strh *strh; stream = &avi->stream[i]; - if (G_UNLIKELY (!stream || !(strh = stream->strh))) + if (G_UNLIKELY (!stream || !stream->idx_n || !(strh = stream->strh))) continue; /* get header duration for the stream */ @@ -2525,11 +3005,14 @@ gst_avi_demux_calculate_durations_from_index (GstAviDemux * avi) /* fall back to header info to calculate a duration */ duration = hduration; } + GST_INFO ("Setting duration of stream #%d to %" GST_TIME_FORMAT, + i, GST_TIME_ARGS (duration)); /* set duration for the stream */ stream->duration = duration; /* find total duration */ - if (total == GST_CLOCK_TIME_NONE || duration > total) + if (total == GST_CLOCK_TIME_NONE || + (GST_CLOCK_TIME_IS_VALID (duration) && duration > total)) total = duration; } @@ -2579,6 +3062,44 @@ gst_avi_demux_push_event (GstAviDemux * avi, GstEvent * event) return result; } +static void +gst_avi_demux_check_seekability (GstAviDemux * avi) +{ + GstQuery *query; + gboolean seekable = FALSE; + gint64 start = -1, stop = -1; + + query = gst_query_new_seeking (GST_FORMAT_BYTES); + if (!gst_pad_peer_query (avi->sinkpad, query)) { + GST_DEBUG_OBJECT (avi, "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 (avi, "doing duration query to fix up unset stop"); + gst_pad_query_peer_duration (avi->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 (avi, "seekable but unknown start/stop -> disable"); + seekable = FALSE; + } + +done: + GST_INFO_OBJECT (avi, "seekable: %d (%" G_GUINT64_FORMAT " - %" + G_GUINT64_FORMAT ")", seekable, start, stop); + avi->seekable = seekable; + + gst_query_unref (query); +} + /* * Read AVI headers when streaming */ @@ -2593,6 +3114,8 @@ gst_avi_demux_stream_header_push (GstAviDemux * avi) GstBuffer *buf = NULL, *sub = NULL; guint offset = 4; gint64 stop; + gint i; + GstTagList *tags = NULL; GST_DEBUG ("Reading and parsing avi headers: %d", avi->header_state); @@ -2618,19 +3141,20 @@ gst_avi_demux_stream_header_push (GstAviDemux * avi) GST_DEBUG ("'hdrl' LIST tag found. Parsing next chunk"); /* the hdrl starts with a 'avih' header */ - if (!gst_riff_parse_chunk (GST_ELEMENT (avi), buf, &offset, &tag, &sub)) + if (!gst_riff_parse_chunk (GST_ELEMENT_CAST (avi), buf, &offset, &tag, + &sub)) goto header_no_avih; if (tag != GST_RIFF_TAG_avih) goto header_no_avih; - if (!gst_avi_demux_parse_avih (GST_ELEMENT (avi), sub, &avi->avih)) + if (!gst_avi_demux_parse_avih (avi, sub, &avi->avih)) goto header_wrong_avih; GST_DEBUG_OBJECT (avi, "AVI header ok, reading elemnts from header"); /* now, read the elements from the header until the end */ - while (gst_riff_parse_chunk (GST_ELEMENT (avi), buf, &offset, &tag, + while (gst_riff_parse_chunk (GST_ELEMENT_CAST (avi), buf, &offset, &tag, &sub)) { /* sub can be NULL on empty tags */ if (!sub) @@ -2661,15 +3185,20 @@ gst_avi_demux_stream_header_push (GstAviDemux * avi) GST_FOURCC_ARGS (GST_READ_UINT32_LE (GST_BUFFER_DATA (sub)))); /* fall-through */ + case GST_RIFF_TAG_JUNQ: case GST_RIFF_TAG_JUNK: goto next; } break; + case GST_RIFF_IDIT: + gst_avi_demux_parse_idit (avi, sub); + goto next; default: GST_WARNING_OBJECT (avi, "Unknown off %d tag %" GST_FOURCC_FORMAT " in AVI header", offset, GST_FOURCC_ARGS (tag)); /* fall-through */ + case GST_RIFF_TAG_JUNQ: case GST_RIFF_TAG_JUNK: next: /* move to next chunk */ @@ -2712,7 +3241,10 @@ gst_avi_demux_stream_header_push (GstAviDemux * avi) switch (ltag) { case GST_RIFF_LIST_movi: gst_adapter_flush (avi->adapter, 12); + if (!avi->first_movi_offset) + avi->first_movi_offset = avi->offset; avi->offset += 12; + avi->idx1_offset = avi->offset + size - 4; goto skipping_done; case GST_RIFF_LIST_INFO: GST_DEBUG ("Found INFO chunk"); @@ -2725,8 +3257,16 @@ gst_avi_demux_stream_header_push (GstAviDemux * avi) /* mind padding */ if (size & 1) gst_adapter_flush (avi->adapter, 1); - gst_riff_parse_info (GST_ELEMENT (avi), buf, - &avi->globaltags); + gst_riff_parse_info (GST_ELEMENT_CAST (avi), buf, &tags); + if (tags) { + if (avi->globaltags) { + gst_tag_list_insert (avi->globaltags, tags, + GST_TAG_MERGE_REPLACE); + } else { + avi->globaltags = tags; + } + } + tags = NULL; gst_buffer_unref (buf); avi->offset += GST_ROUND_UP_2 (size) - 4; @@ -2773,22 +3313,33 @@ skipping_done: GST_DEBUG ("Found movi chunk. Starting to stream data"); avi->state = GST_AVI_DEMUX_MOVI; + /* no indexes in push mode, but it still sets some variables */ + gst_avi_demux_calculate_durations_from_index (avi); + + gst_avi_demux_expose_streams (avi, TRUE); + + /* prepare all streams for index 0 */ + for (i = 0; i < avi->num_streams; i++) + avi->stream[i].current_entry = 0; + /* create initial NEWSEGMENT event */ if ((stop = avi->segment.stop) == GST_CLOCK_TIME_NONE) stop = avi->segment.duration; GST_DEBUG_OBJECT (avi, "segment stop %" G_GINT64_FORMAT, stop); - if (avi->seek_event) - gst_event_unref (avi->seek_event); - avi->seek_event = gst_event_new_new_segment - (FALSE, avi->segment.rate, GST_FORMAT_TIME, + if (avi->seg_event) + gst_event_unref (avi->seg_event); + avi->seg_event = gst_event_new_new_segment_full + (FALSE, avi->segment.rate, avi->segment.applied_rate, GST_FORMAT_TIME, avi->segment.start, stop, avi->segment.time); + gst_avi_demux_check_seekability (avi); + /* at this point we know all the streams and we can signal the no more * pads signal */ GST_DEBUG_OBJECT (avi, "signaling no more pads"); - gst_element_no_more_pads (GST_ELEMENT (avi)); + gst_element_no_more_pads (GST_ELEMENT_CAST (avi)); return GST_FLOW_OK; @@ -2831,6 +3382,150 @@ header_wrong_avih: } } +static void +gst_avi_demux_add_date_tag (GstAviDemux * avi, gint y, gint m, gint d, + gint h, gint min, gint s) +{ + GDate *date; + GstDateTime *dt; + + date = g_date_new_dmy (d, m, y); + if (!g_date_valid (date)) { + /* bogus date */ + GST_WARNING_OBJECT (avi, "Refusing to add invalid date %d-%d-%d", y, m, d); + g_date_free (date); + return; + } + + dt = gst_date_time_new_local_time (y, m, d, h, min, s); + + if (avi->globaltags == NULL) + avi->globaltags = gst_tag_list_new (); + + gst_tag_list_add (avi->globaltags, GST_TAG_MERGE_REPLACE, GST_TAG_DATE, date, + NULL); + g_date_free (date); + if (dt) { + gst_tag_list_add (avi->globaltags, GST_TAG_MERGE_REPLACE, GST_TAG_DATE_TIME, + dt, NULL); + gst_date_time_unref (dt); + } +} + +static void +gst_avi_demux_parse_idit_nums_only (GstAviDemux * avi, gchar * data) +{ + gint y, m, d; + gint ret; + + ret = sscanf (data, "%d:%d:%d", &y, &m, &d); + if (ret != 3) { + GST_WARNING_OBJECT (avi, "Failed to parse IDIT tag"); + return; + } + gst_avi_demux_add_date_tag (avi, y, m, d, 0, 0, 0); +} + +static gint +get_month_num (gchar * data, guint size) +{ + if (g_ascii_strncasecmp (data, "jan", 3) == 0) { + return 1; + } else if (g_ascii_strncasecmp (data, "feb", 3) == 0) { + return 2; + } else if (g_ascii_strncasecmp (data, "mar", 3) == 0) { + return 3; + } else if (g_ascii_strncasecmp (data, "apr", 3) == 0) { + return 4; + } else if (g_ascii_strncasecmp (data, "may", 3) == 0) { + return 5; + } else if (g_ascii_strncasecmp (data, "jun", 3) == 0) { + return 6; + } else if (g_ascii_strncasecmp (data, "jul", 3) == 0) { + return 7; + } else if (g_ascii_strncasecmp (data, "aug", 3) == 0) { + return 8; + } else if (g_ascii_strncasecmp (data, "sep", 3) == 0) { + return 9; + } else if (g_ascii_strncasecmp (data, "oct", 3) == 0) { + return 10; + } else if (g_ascii_strncasecmp (data, "nov", 3) == 0) { + return 11; + } else if (g_ascii_strncasecmp (data, "dec", 3) == 0) { + return 12; + } + + return 0; +} + +static void +gst_avi_demux_parse_idit_text (GstAviDemux * avi, gchar * data) +{ + gint year, month, day; + gint hour, min, sec; + gint ret; + gchar weekday[4]; + gchar monthstr[4]; + + ret = sscanf (data, "%3s %3s %d %d:%d:%d %d", weekday, monthstr, &day, &hour, + &min, &sec, &year); + if (ret != 7) { + GST_WARNING_OBJECT (avi, "Failed to parse IDIT tag"); + return; + } + month = get_month_num (monthstr, strlen (monthstr)); + gst_avi_demux_add_date_tag (avi, year, month, day, hour, min, sec); +} + +static void +gst_avi_demux_parse_idit (GstAviDemux * avi, GstBuffer * buf) +{ + gchar *data = (gchar *) GST_BUFFER_DATA (buf); + guint size = GST_BUFFER_SIZE (buf); + gchar *safedata = NULL; + + /* + * According to: + * http://www.eden-foundation.org/products/code/film_date_stamp/index.html + * + * This tag could be in one of the below formats + * 2005:08:17 11:42:43 + * THU OCT 26 16:46:04 2006 + * Mon Mar 3 09:44:56 2008 + * + * FIXME: Our date tag doesn't include hours + */ + + /* skip eventual initial whitespace */ + while (size > 0 && g_ascii_isspace (data[0])) { + data++; + size--; + } + + if (size == 0) { + goto non_parsable; + } + + /* make a safe copy to add a \0 to the end of the string */ + safedata = g_strndup (data, size); + + /* test if the first char is a alpha or a number */ + if (g_ascii_isdigit (data[0])) { + gst_avi_demux_parse_idit_nums_only (avi, safedata); + g_free (safedata); + return; + } else if (g_ascii_isalpha (data[0])) { + gst_avi_demux_parse_idit_text (avi, safedata); + g_free (safedata); + return; + } + + g_free (safedata); + +non_parsable: + GST_WARNING_OBJECT (avi, "IDIT tag has no parsable info"); +} + /* * Read full AVI headers. */ @@ -2844,6 +3539,7 @@ gst_avi_demux_stream_header_pull (GstAviDemux * avi) gint64 stop; GstElement *element = GST_ELEMENT_CAST (avi); GstClockTime stamp; + GstTagList *tags = NULL; stamp = gst_util_get_timestamp (); @@ -2883,7 +3579,7 @@ gst_avi_demux_stream_header_pull (GstAviDemux * avi) goto no_avih; else if (tag != GST_RIFF_TAG_avih) goto no_avih; - else if (!gst_avi_demux_parse_avih (element, sub, &avi->avih)) + else if (!gst_avi_demux_parse_avih (avi, sub, &avi->avih)) goto invalid_avih; GST_DEBUG_OBJECT (avi, "AVI header ok, reading elements from header"); @@ -2919,6 +3615,20 @@ gst_avi_demux_stream_header_pull (GstAviDemux * avi) gst_avi_demux_parse_odml (avi, sub); sub = NULL; break; + case GST_RIFF_LIST_INFO: + GST_BUFFER_DATA (sub) = data + 4; + GST_BUFFER_SIZE (sub) -= 4; + gst_riff_parse_info (element, sub, &tags); + if (tags) { + if (avi->globaltags) { + gst_tag_list_insert (avi->globaltags, tags, + GST_TAG_MERGE_REPLACE); + } else { + avi->globaltags = tags; + } + } + tags = NULL; + break; default: GST_WARNING_OBJECT (avi, "Unknown list %" GST_FOURCC_FORMAT " in AVI header", @@ -2926,11 +3636,15 @@ gst_avi_demux_stream_header_pull (GstAviDemux * avi) GST_MEMDUMP_OBJECT (avi, "Unknown list", GST_BUFFER_DATA (sub), GST_BUFFER_SIZE (sub)); /* fall-through */ + case GST_RIFF_TAG_JUNQ: case GST_RIFF_TAG_JUNK: goto next; } break; } + case GST_RIFF_IDIT: + gst_avi_demux_parse_idit (avi, sub); + goto next; default: GST_WARNING_OBJECT (avi, "Unknown tag %" GST_FOURCC_FORMAT " in AVI header at off %d", @@ -2938,6 +3652,7 @@ gst_avi_demux_stream_header_pull (GstAviDemux * avi) GST_MEMDUMP_OBJECT (avi, "Unknown tag", GST_BUFFER_DATA (sub), GST_BUFFER_SIZE (sub)); /* fall-through */ + case GST_RIFF_TAG_JUNQ: case GST_RIFF_TAG_JUNK: next: if (sub) @@ -3013,7 +3728,16 @@ gst_avi_demux_stream_header_pull (GstAviDemux * avi) } sub = gst_buffer_create_sub (buf, 4, GST_BUFFER_SIZE (buf) - 4); - gst_riff_parse_info (element, sub, &avi->globaltags); + gst_riff_parse_info (element, sub, &tags); + if (tags) { + if (avi->globaltags) { + gst_tag_list_insert (avi->globaltags, tags, + GST_TAG_MERGE_REPLACE); + } else { + avi->globaltags = tags; + } + } + tags = NULL; if (sub) { gst_buffer_unref (sub); sub = NULL; @@ -3036,6 +3760,16 @@ gst_avi_demux_stream_header_pull (GstAviDemux * avi) /* Fall-through */ case GST_MAKE_FOURCC ('J', 'U', 'N', 'Q'): case GST_MAKE_FOURCC ('J', 'U', 'N', 'K'): + /* Only get buffer for debugging if the memdump is needed */ + if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= 9) { + res = gst_pad_pull_range (avi->sinkpad, avi->offset, size, &buf); + if (res != GST_FLOW_OK) { + GST_DEBUG_OBJECT (avi, "couldn't read INFO chunk"); + goto pull_range_failed; + } + GST_MEMDUMP ("Junk", GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf)); + gst_buffer_unref (buf); + } avi->offset += 8 + GST_ROUND_UP_2 (size); break; } @@ -3068,6 +3802,8 @@ skipping_done: /* use the indexes now to construct nice durations */ gst_avi_demux_calculate_durations_from_index (avi); + gst_avi_demux_expose_streams (avi, FALSE); + /* create initial NEWSEGMENT event */ if ((stop = avi->segment.stop) == GST_CLOCK_TIME_NONE) stop = avi->segment.duration; @@ -3076,15 +3812,16 @@ skipping_done: /* do initial seek to the default segment values */ gst_avi_demux_do_seek (avi, &avi->segment); + /* prepare initial segment */ - if (avi->seek_event) - gst_event_unref (avi->seek_event); - avi->seek_event = gst_event_new_new_segment - (FALSE, avi->segment.rate, GST_FORMAT_TIME, + if (avi->seg_event) + gst_event_unref (avi->seg_event); + avi->seg_event = gst_event_new_new_segment_full + (FALSE, avi->segment.rate, avi->segment.applied_rate, GST_FORMAT_TIME, avi->segment.start, stop, avi->segment.time); stamp = gst_util_get_timestamp () - stamp; - GST_DEBUG_OBJECT (avi, "pulling header %" GST_TIME_FORMAT, + GST_DEBUG_OBJECT (avi, "pulling header took %" GST_TIME_FORMAT, GST_TIME_ARGS (stamp)); /* at this point we know all the streams and we can signal the no more @@ -3195,6 +3932,9 @@ gst_avi_demux_move_stream (GstAviDemux * avi, GstAviStream * stream, GST_TIME_ARGS (stream->current_timestamp), GST_TIME_ARGS (stream->current_ts_end), stream->current_offset, stream->current_offset_end); + + GST_DEBUG_OBJECT (avi, "Seeking to offset %" G_GUINT64_FORMAT, + stream->index[index].offset); } /* @@ -3209,14 +3949,14 @@ gst_avi_demux_do_seek (GstAviDemux * avi, GstSegment * segment) GstAviStream *stream; seek_time = segment->last_stop; - keyframe = !!(segment->flags & GST_SEEK_FLAG_KEY_UNIT); + keyframe = ! !(segment->flags & GST_SEEK_FLAG_KEY_UNIT); GST_DEBUG_OBJECT (avi, "seek to: %" GST_TIME_FORMAT " keyframe seeking:%d", GST_TIME_ARGS (seek_time), keyframe); /* FIXME, this code assumes the main stream with keyframes is stream 0, * which is mostly correct... */ - stream = &avi->stream[0]; + stream = &avi->stream[avi->main_stream]; /* get the entry index for the requested position */ index = gst_avi_demux_index_for_time (avi, stream, seek_time); @@ -3253,7 +3993,7 @@ gst_avi_demux_do_seek (GstAviDemux * avi, GstSegment * segment) GstAviStream *ostream; ostream = &avi->stream[i]; - if (ostream == stream) + if ((ostream == stream) || (ostream->index == NULL)) continue; /* get the entry index for the requested position */ @@ -3272,7 +4012,7 @@ gst_avi_demux_do_seek (GstAviDemux * avi, GstSegment * segment) } /* - * Handle seek event. + * Handle seek event in pull mode. */ static gboolean gst_avi_demux_handle_seek (GstAviDemux * avi, GstPad * pad, GstEvent * event) @@ -3354,29 +4094,21 @@ gst_avi_demux_handle_seek (GstAviDemux * avi, GstPad * pad, GstEvent * event) * actually never fails. */ gst_avi_demux_do_seek (avi, &seeksegment); + gst_event_replace (&avi->close_seg_event, NULL); if (flush) { GstEvent *fevent = gst_event_new_flush_stop (); GST_DEBUG_OBJECT (avi, "sending flush stop"); gst_avi_demux_push_event (avi, gst_event_ref (fevent)); gst_pad_push_event (avi->sinkpad, fevent); - - /* reset the last flow and mark discont, FLUSH is always DISCONT */ - for (i = 0; i < avi->num_streams; i++) { - avi->stream[i].last_flow = GST_FLOW_OK; - avi->stream[i].discont = TRUE; - } } else if (avi->segment_running) { - GstEvent *seg; - /* we are running the current segment and doing a non-flushing seek, * close the segment first based on the last_stop. */ GST_DEBUG_OBJECT (avi, "closing running segment %" G_GINT64_FORMAT " to %" G_GINT64_FORMAT, avi->segment.start, avi->segment.last_stop); - seg = gst_event_new_new_segment (TRUE, - avi->segment.rate, avi->segment.format, + avi->close_seg_event = gst_event_new_new_segment_full (TRUE, + avi->segment.rate, avi->segment.applied_rate, avi->segment.format, avi->segment.start, avi->segment.last_stop, avi->segment.time); - gst_avi_demux_push_event (avi, seg); } /* now update the real segment info */ @@ -3384,8 +4116,8 @@ gst_avi_demux_handle_seek (GstAviDemux * avi, GstPad * pad, GstEvent * event) /* post the SEGMENT_START message when we do segmented playback */ if (avi->segment.flags & GST_SEEK_FLAG_SEGMENT) { - gst_element_post_message (GST_ELEMENT (avi), - gst_message_new_segment_start (GST_OBJECT (avi), + gst_element_post_message (GST_ELEMENT_CAST (avi), + gst_message_new_segment_start (GST_OBJECT_CAST (avi), avi->segment.format, avi->segment.last_stop)); } @@ -3394,17 +4126,17 @@ gst_avi_demux_handle_seek (GstAviDemux * avi, GstPad * pad, GstEvent * event) stop = avi->segment.duration; /* queue the segment event for the streaming thread. */ - if (avi->seek_event) - gst_event_unref (avi->seek_event); + if (avi->seg_event) + gst_event_unref (avi->seg_event); if (avi->segment.rate > 0.0) { /* forwards goes from last_stop to stop */ - avi->seek_event = gst_event_new_new_segment (FALSE, - avi->segment.rate, avi->segment.format, + avi->seg_event = gst_event_new_new_segment_full (FALSE, + avi->segment.rate, avi->segment.applied_rate, avi->segment.format, avi->segment.last_stop, stop, avi->segment.time); } else { /* reverse goes from start to last_stop */ - avi->seek_event = gst_event_new_new_segment (FALSE, - avi->segment.rate, avi->segment.format, + avi->seg_event = gst_event_new_new_segment_full (FALSE, + avi->segment.rate, avi->segment.applied_rate, avi->segment.format, avi->segment.start, avi->segment.last_stop, avi->segment.time); } @@ -3413,6 +4145,12 @@ gst_avi_demux_handle_seek (GstAviDemux * avi, GstPad * pad, GstEvent * event) gst_pad_start_task (avi->sinkpad, (GstTaskFunction) gst_avi_demux_loop, avi->sinkpad); } + /* reset the last flow and mark discont, seek is always DISCONT */ + for (i = 0; i < avi->num_streams; i++) { + GST_DEBUG_OBJECT (avi, "marking DISCONT"); + avi->stream[i].last_flow = GST_FLOW_OK; + avi->stream[i].discont = TRUE; + } GST_PAD_STREAM_UNLOCK (avi->sinkpad); return TRUE; @@ -3426,6 +4164,216 @@ no_format: } /* + * Handle seek event in push mode. + */ +static gboolean +avi_demux_handle_seek_push (GstAviDemux * avi, GstPad * pad, GstEvent * event) +{ + gdouble rate; + GstFormat format; + GstSeekFlags flags; + GstSeekType cur_type = GST_SEEK_TYPE_NONE, stop_type; + gint64 cur, stop; + gboolean keyframe; + GstAviStream *stream; + guint index; + guint n, str_num; + guint64 min_offset; + GstSegment seeksegment; + gboolean update; + + /* check we have the index */ + if (!avi->have_index) { + GST_DEBUG_OBJECT (avi, "no seek index built, seek aborted."); + return FALSE; + } else { + GST_DEBUG_OBJECT (avi, "doing push-based seek with event"); + } + + gst_event_parse_seek (event, &rate, &format, &flags, + &cur_type, &cur, &stop_type, &stop); + + if (format != GST_FORMAT_TIME) { + GstFormat fmt = GST_FORMAT_TIME; + gboolean res = TRUE; + + if (cur_type != GST_SEEK_TYPE_NONE) + res = gst_pad_query_convert (pad, format, cur, &fmt, &cur); + if (res && stop_type != GST_SEEK_TYPE_NONE) + res = gst_pad_query_convert (pad, format, stop, &fmt, &stop); + if (!res) { + GST_DEBUG_OBJECT (avi, "unsupported format given, seek aborted."); + return FALSE; + } + + format = fmt; + } + + /* let gst_segment handle any tricky stuff */ + GST_DEBUG_OBJECT (avi, "configuring seek"); + memcpy (&seeksegment, &avi->segment, sizeof (GstSegment)); + gst_segment_set_seek (&seeksegment, rate, format, flags, + cur_type, cur, stop_type, stop, &update); + + keyframe = ! !(flags & GST_SEEK_FLAG_KEY_UNIT); + cur = seeksegment.last_stop; + + GST_DEBUG_OBJECT (avi, + "Seek requested: ts %" GST_TIME_FORMAT " stop %" GST_TIME_FORMAT + ", kf %u, rate %lf", GST_TIME_ARGS (cur), GST_TIME_ARGS (stop), keyframe, + rate); + + if (rate < 0) { + GST_DEBUG_OBJECT (avi, "negative rate seek not supported in push mode"); + return FALSE; + } + + /* FIXME, this code assumes the main stream with keyframes is stream 0, + * which is mostly correct... */ + str_num = avi->main_stream; + stream = &avi->stream[str_num]; + + /* get the entry index for the requested position */ + index = gst_avi_demux_index_for_time (avi, stream, cur); + GST_DEBUG_OBJECT (avi, "str %u: Found entry %u for %" GST_TIME_FORMAT, + str_num, index, GST_TIME_ARGS (cur)); + + /* check if we are already on a keyframe */ + if (!ENTRY_IS_KEYFRAME (&stream->index[index])) { + GST_DEBUG_OBJECT (avi, "Entry is not a keyframe - searching back"); + /* now go to the previous keyframe, this is where we should start + * decoding from. */ + index = gst_avi_demux_index_prev (avi, stream, index, TRUE); + GST_DEBUG_OBJECT (avi, "Found previous keyframe at %u", index); + } + + gst_avi_demux_get_buffer_info (avi, stream, index, + &stream->current_timestamp, &stream->current_ts_end, + &stream->current_offset, &stream->current_offset_end); + + /* re-use cur to be the timestamp of the seek as it _will_ be */ + cur = stream->current_timestamp; + + min_offset = stream->index[index].offset; + avi->seek_kf_offset = min_offset - 8; + + GST_DEBUG_OBJECT (avi, + "Seek to: ts %" GST_TIME_FORMAT " (on str %u, idx %u, offset %" + G_GUINT64_FORMAT ")", GST_TIME_ARGS (stream->current_timestamp), str_num, + index, min_offset); + + for (n = 0; n < avi->num_streams; n++) { + GstAviStream *str = &avi->stream[n]; + guint idx; + + if (n == avi->main_stream) + continue; + + /* get the entry index for the requested position */ + idx = gst_avi_demux_index_for_time (avi, str, cur); + GST_DEBUG_OBJECT (avi, "str %u: Found entry %u for %" GST_TIME_FORMAT, n, + idx, GST_TIME_ARGS (cur)); + + /* check if we are already on a keyframe */ + if (!ENTRY_IS_KEYFRAME (&str->index[idx])) { + GST_DEBUG_OBJECT (avi, "Entry is not a keyframe - searching back"); + /* now go to the previous keyframe, this is where we should start + * decoding from. */ + idx = gst_avi_demux_index_prev (avi, str, idx, TRUE); + GST_DEBUG_OBJECT (avi, "Found previous keyframe at %u", idx); + } + + gst_avi_demux_get_buffer_info (avi, str, idx, + &str->current_timestamp, &str->current_ts_end, + &str->current_offset, &str->current_offset_end); + + if (str->index[idx].offset < min_offset) { + min_offset = str->index[idx].offset; + GST_DEBUG_OBJECT (avi, + "Found an earlier offset at %" G_GUINT64_FORMAT ", str %u", + min_offset, n); + str_num = n; + stream = str; + index = idx; + } + } + + GST_DEBUG_OBJECT (avi, + "Seek performed: str %u, offset %" G_GUINT64_FORMAT ", idx %u, ts %" + GST_TIME_FORMAT ", ts_end %" GST_TIME_FORMAT ", off %" G_GUINT64_FORMAT + ", off_end %" G_GUINT64_FORMAT, str_num, min_offset, index, + GST_TIME_ARGS (stream->current_timestamp), + GST_TIME_ARGS (stream->current_ts_end), stream->current_offset, + stream->current_offset_end); + + /* index data refers to data, not chunk header (for pull mode convenience) */ + min_offset -= 8; + GST_DEBUG_OBJECT (avi, "seeking to chunk at offset %" G_GUINT64_FORMAT, + min_offset); + + if (!perform_seek_to_offset (avi, min_offset)) { + GST_DEBUG_OBJECT (avi, "seek event failed!"); + return FALSE; + } + + return TRUE; +} + +/* + * Handle whether we can perform the seek event or if we have to let the chain + * function handle seeks to build the seek indexes first. + */ +static gboolean +gst_avi_demux_handle_seek_push (GstAviDemux * avi, GstPad * pad, + GstEvent * event) +{ + /* check for having parsed index already */ + if (!avi->have_index) { + guint64 offset = 0; + gboolean building_index; + + GST_OBJECT_LOCK (avi); + /* handle the seek event in the chain function */ + avi->state = GST_AVI_DEMUX_SEEK; + + /* copy the event */ + if (avi->seek_event) + gst_event_unref (avi->seek_event); + avi->seek_event = gst_event_ref (event); + + /* set the building_index flag so that only one thread can setup the + * structures for index seeking. */ + building_index = avi->building_index; + if (!building_index) { + avi->building_index = TRUE; + if (avi->stream[0].indexes) { + avi->odml_stream = 0; + avi->odml_subidxs = avi->stream[avi->odml_stream].indexes; + offset = avi->odml_subidxs[0]; + } else { + offset = avi->idx1_offset; + } + } + GST_OBJECT_UNLOCK (avi); + + if (!building_index) { + /* seek to the first subindex or legacy index */ + GST_INFO_OBJECT (avi, + "Seeking to legacy index/first subindex at %" G_GUINT64_FORMAT, + offset); + return perform_seek_to_offset (avi, offset); + } + + /* FIXME: we have to always return true so that we don't block the seek + * thread. + * Note: maybe it is OK to return true if we're still building the index */ + return TRUE; + } + + return avi_demux_handle_seek_push (avi, pad, event); +} + +/* * Helper for gst_avi_demux_invert() */ static inline void @@ -3496,6 +4444,25 @@ gst_avi_demux_invert (GstAviStream * stream, GstBuffer * buf) return buf; } +static void +gst_avi_demux_add_assoc (GstAviDemux * avi, GstAviStream * stream, + GstClockTime timestamp, guint64 offset, gboolean keyframe) +{ + /* do not add indefinitely for open-ended streaming */ + if (G_UNLIKELY (avi->element_index && avi->seekable)) { + GST_LOG_OBJECT (avi, "adding association %" GST_TIME_FORMAT "-> %" + G_GUINT64_FORMAT, GST_TIME_ARGS (timestamp), offset); + gst_index_add_association (avi->element_index, avi->index_id, + keyframe ? GST_ASSOCIATION_FLAG_KEY_UNIT : GST_ASSOCIATION_FLAG_NONE, + GST_FORMAT_TIME, timestamp, GST_FORMAT_BYTES, offset, NULL); + /* well, current_total determines TIME and entry DEFAULT (frame #) ... */ + gst_index_add_association (avi->element_index, stream->index_id, + GST_ASSOCIATION_FLAG_NONE, + GST_FORMAT_TIME, stream->current_total, GST_FORMAT_BYTES, offset, + GST_FORMAT_DEFAULT, stream->current_entry, NULL); + } +} + /* * Returns the aggregated GstFlowReturn. */ @@ -3504,12 +4471,13 @@ gst_avi_demux_combine_flows (GstAviDemux * avi, GstAviStream * stream, GstFlowReturn ret) { guint i; + gboolean unexpected = FALSE, not_linked = TRUE; /* store the value */ stream->last_flow = ret; - /* any other error that is not-linked can be returned right away */ - if (G_UNLIKELY (ret != GST_FLOW_NOT_LINKED)) + /* any other error that is not-linked or eos can be returned right away */ + if (G_LIKELY (ret != GST_FLOW_UNEXPECTED && ret != GST_FLOW_NOT_LINKED)) goto done; /* only return NOT_LINKED if all other pads returned NOT_LINKED */ @@ -3517,13 +4485,19 @@ gst_avi_demux_combine_flows (GstAviDemux * avi, GstAviStream * stream, GstAviStream *ostream = &avi->stream[i]; ret = ostream->last_flow; - /* some other return value (must be SUCCESS but we can return - * other values as well) */ - if (G_UNLIKELY (ret != GST_FLOW_NOT_LINKED)) + /* no unexpected or unlinked, return */ + if (G_LIKELY (ret != GST_FLOW_UNEXPECTED && ret != GST_FLOW_NOT_LINKED)) goto done; + + /* we check to see if we have at least 1 unexpected or all unlinked */ + unexpected |= (ret == GST_FLOW_UNEXPECTED); + not_linked &= (ret == GST_FLOW_NOT_LINKED); } - /* if we get here, all other pads were unlinked and we return - * NOT_LINKED then */ + /* when we get here, we all have unlinked or unexpected */ + if (not_linked) + ret = GST_FLOW_NOT_LINKED; + else if (unexpected) + ret = GST_FLOW_UNEXPECTED; done: GST_LOG_OBJECT (avi, "combined %s to return %s", gst_flow_get_name (stream->last_flow), gst_flow_get_name (ret)); @@ -3625,6 +4599,11 @@ gst_avi_demux_find_next (GstAviDemux * avi, gfloat rate) GstAviStream *stream; stream = &avi->stream[i]; + + /* ignore streams that finished */ + if (stream->last_flow == GST_FLOW_UNEXPECTED) + continue; + position = stream->current_timestamp; /* position of -1 is EOS */ @@ -3732,10 +4711,13 @@ gst_avi_demux_loop_data (GstAviDemux * avi) /* mark discont when pending */ if (stream->discont) { + GST_DEBUG_OBJECT (avi, "setting DISCONT flag"); GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); stream->discont = FALSE; } + gst_avi_demux_add_assoc (avi, stream, timestamp, offset, keyframe); + gst_buffer_set_caps (buf, GST_PAD_CAPS (stream->pad)); /* update current position in the segment */ @@ -3786,7 +4768,8 @@ eos_stop: " setting EOS (%" GST_TIME_FORMAT " > %" GST_TIME_FORMAT ")", GST_TIME_ARGS (timestamp), GST_TIME_ARGS (avi->segment.stop)); ret = GST_FLOW_UNEXPECTED; - goto beach; + /* move to next stream */ + goto next; } pull_failed: { @@ -3821,7 +4804,20 @@ gst_avi_demux_stream_data (GstAviDemux * avi) if (G_UNLIKELY (avi->have_eos)) { /* Clean adapter, we're done */ gst_adapter_clear (avi->adapter); - return res; + return GST_FLOW_UNEXPECTED; + } + + if (G_UNLIKELY (avi->todrop)) { + guint drop; + + if ((drop = gst_adapter_available (avi->adapter))) { + if (drop > avi->todrop) + drop = avi->todrop; + GST_DEBUG_OBJECT (avi, "Dropping %d bytes", drop); + gst_adapter_flush (avi->adapter, drop); + avi->todrop -= drop; + avi->offset += drop; + } } /* Iterate until need more data, so adapter won't grow too much */ @@ -3845,10 +4841,23 @@ gst_avi_demux_stream_data (GstAviDemux * avi) gst_adapter_flush (avi->adapter, 8 + GST_ROUND_UP_2 (size)); } return GST_FLOW_OK; + } else if (tag == GST_RIFF_TAG_RIFF) { + /* RIFF tags can appear in ODML files, just jump over them */ + if (gst_adapter_available (avi->adapter) >= 12) { + GST_DEBUG ("Found RIFF tag, skipping RIFF header"); + gst_adapter_flush (avi->adapter, 12); + continue; + } + return GST_FLOW_OK; } else if (tag == GST_RIFF_TAG_idx1) { - GST_DEBUG ("Found index tag, stream done"); - avi->have_eos = TRUE; - return GST_FLOW_UNEXPECTED; + GST_DEBUG ("Found index tag"); + if (gst_avi_demux_peek_chunk (avi, &tag, &size) || size == 0) { + /* accept 0 size buffer here */ + avi->abort_buffering = FALSE; + GST_DEBUG (" skipping %d bytes for now", size); + gst_adapter_flush (avi->adapter, 8 + GST_ROUND_UP_2 (size)); + } + return GST_FLOW_OK; } else if (tag == GST_RIFF_TAG_LIST) { /* movi chunks might be grouped in rec list */ if (gst_adapter_available (avi->adapter) >= 12) { @@ -3857,7 +4866,7 @@ gst_avi_demux_stream_data (GstAviDemux * avi) continue; } return GST_FLOW_OK; - } else if (tag == GST_RIFF_TAG_JUNK) { + } else if (tag == GST_RIFF_TAG_JUNK || tag == GST_RIFF_TAG_JUNQ) { /* rec list might contain JUNK chunks */ GST_DEBUG ("Found JUNK tag"); if (gst_avi_demux_peek_chunk (avi, &tag, &size) || size == 0) { @@ -3879,9 +4888,13 @@ gst_avi_demux_stream_data (GstAviDemux * avi) * through the whole file */ if (avi->abort_buffering) { avi->abort_buffering = FALSE; - gst_adapter_flush (avi->adapter, 8); + if (size) { + gst_adapter_flush (avi->adapter, 8); + return GST_FLOW_OK; + } + } else { + return GST_FLOW_OK; } - return GST_FLOW_OK; } GST_DEBUG ("chunk ID %" GST_FOURCC_FORMAT ", size %u", GST_FOURCC_ARGS (tag), size); @@ -3897,14 +4910,33 @@ gst_avi_demux_stream_data (GstAviDemux * avi) } else { GstAviStream *stream; GstClockTime next_ts = 0; - GstBuffer *buf; + GstBuffer *buf = NULL; + guint64 offset; + gboolean saw_desired_kf = stream_nr != avi->main_stream + || avi->offset >= avi->seek_kf_offset; + + if (stream_nr == avi->main_stream && avi->offset == avi->seek_kf_offset) { + GST_DEBUG_OBJECT (avi, "Desired keyframe reached"); + avi->seek_kf_offset = 0; + } - gst_adapter_flush (avi->adapter, 8); + if (saw_desired_kf) { + gst_adapter_flush (avi->adapter, 8); + /* get buffer */ + if (size) { + buf = gst_adapter_take_buffer (avi->adapter, GST_ROUND_UP_2 (size)); + /* patch the size */ + GST_BUFFER_SIZE (buf) = size; + } else { + buf = NULL; + } + } else { + GST_DEBUG_OBJECT (avi, + "Desired keyframe not yet reached, flushing chunk"); + gst_adapter_flush (avi->adapter, 8 + GST_ROUND_UP_2 (size)); + } - /* get buffer */ - buf = gst_adapter_take_buffer (avi->adapter, GST_ROUND_UP_2 (size)); - /* patch the size */ - GST_BUFFER_SIZE (buf) = size; + offset = avi->offset; avi->offset += 8 + GST_ROUND_UP_2 (size); stream = &avi->stream[stream_nr]; @@ -3919,59 +4951,66 @@ gst_avi_demux_stream_data (GstAviDemux * avi) if (G_UNLIKELY (!stream->pad)) { GST_WARNING_OBJECT (avi, "no pad for stream ID %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (tag)); - gst_buffer_unref (buf); + if (buf) + gst_buffer_unref (buf); } else { - GstClockTime dur_ts = 0; - /* get time of this buffer */ gst_pad_query_position (stream->pad, &format, (gint64 *) & next_ts); if (G_UNLIKELY (format != GST_FORMAT_TIME)) goto wrong_format; + gst_avi_demux_add_assoc (avi, stream, next_ts, offset, FALSE); + /* increment our positions */ stream->current_entry++; stream->current_total += size; - /* invert the picture if needed */ - buf = gst_avi_demux_invert (stream, buf); + /* update current position in the segment */ + gst_segment_set_last_stop (&avi->segment, GST_FORMAT_TIME, next_ts); - gst_pad_query_position (stream->pad, &format, (gint64 *) & dur_ts); - if (G_UNLIKELY (format != GST_FORMAT_TIME)) - goto wrong_format; + if (saw_desired_kf && buf) { + GstClockTime dur_ts = 0; - GST_BUFFER_TIMESTAMP (buf) = next_ts; - GST_BUFFER_DURATION (buf) = dur_ts - next_ts; - if (stream->strh->type == GST_RIFF_FCC_vids) { - GST_BUFFER_OFFSET (buf) = stream->current_entry - 1; - GST_BUFFER_OFFSET_END (buf) = stream->current_entry; - } else { - GST_BUFFER_OFFSET (buf) = GST_BUFFER_OFFSET_NONE; - GST_BUFFER_OFFSET_END (buf) = GST_BUFFER_OFFSET_NONE; - } + /* invert the picture if needed */ + buf = gst_avi_demux_invert (stream, buf); - gst_buffer_set_caps (buf, GST_PAD_CAPS (stream->pad)); - GST_DEBUG_OBJECT (avi, - "Pushing buffer with time=%" GST_TIME_FORMAT ", duration %" - GST_TIME_FORMAT ", offset %" G_GUINT64_FORMAT - " and size %d over pad %s", GST_TIME_ARGS (next_ts), - GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)), GST_BUFFER_OFFSET (buf), - size, GST_PAD_NAME (stream->pad)); + gst_pad_query_position (stream->pad, &format, (gint64 *) & dur_ts); + if (G_UNLIKELY (format != GST_FORMAT_TIME)) + goto wrong_format; - /* update current position in the segment */ - gst_segment_set_last_stop (&avi->segment, GST_FORMAT_TIME, next_ts); - - /* mark discont when pending */ - if (G_UNLIKELY (stream->discont)) { - GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); - stream->discont = FALSE; - } - res = gst_pad_push (stream->pad, buf); + GST_BUFFER_TIMESTAMP (buf) = next_ts; + GST_BUFFER_DURATION (buf) = dur_ts - next_ts; + if (stream->strh->type == GST_RIFF_FCC_vids) { + GST_BUFFER_OFFSET (buf) = stream->current_entry - 1; + GST_BUFFER_OFFSET_END (buf) = stream->current_entry; + } else { + GST_BUFFER_OFFSET (buf) = GST_BUFFER_OFFSET_NONE; + GST_BUFFER_OFFSET_END (buf) = GST_BUFFER_OFFSET_NONE; + } - /* combine flows */ - res = gst_avi_demux_combine_flows (avi, stream, res); - if (G_UNLIKELY (res != GST_FLOW_OK)) { - GST_DEBUG ("Push failed; %s", gst_flow_get_name (res)); - return res; + gst_buffer_set_caps (buf, GST_PAD_CAPS (stream->pad)); + GST_DEBUG_OBJECT (avi, + "Pushing buffer with time=%" GST_TIME_FORMAT ", duration %" + GST_TIME_FORMAT ", offset %" G_GUINT64_FORMAT + " and size %d over pad %s", GST_TIME_ARGS (next_ts), + GST_TIME_ARGS (GST_BUFFER_DURATION (buf)), + GST_BUFFER_OFFSET (buf), size, GST_PAD_NAME (stream->pad)); + + /* mark discont when pending */ + if (G_UNLIKELY (stream->discont)) { + GST_DEBUG_OBJECT (avi, "Setting DISCONT"); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); + stream->discont = FALSE; + } + res = gst_pad_push (stream->pad, buf); + buf = NULL; + + /* combine flows */ + res = gst_avi_demux_combine_flows (avi, stream, res); + if (G_UNLIKELY (res != GST_FLOW_OK)) { + GST_DEBUG ("Push failed; %s", gst_flow_get_name (res)); + return res; + } } } } @@ -4054,9 +5093,13 @@ gst_avi_demux_loop (GstPad * pad) avi->state = GST_AVI_DEMUX_MOVI; break; case GST_AVI_DEMUX_MOVI: - if (G_UNLIKELY (avi->seek_event)) { - gst_avi_demux_push_event (avi, avi->seek_event); - avi->seek_event = NULL; + if (G_UNLIKELY (avi->close_seg_event)) { + gst_avi_demux_push_event (avi, avi->close_seg_event); + avi->close_seg_event = NULL; + } + if (G_UNLIKELY (avi->seg_event)) { + gst_avi_demux_push_event (avi, avi->seg_event); + avi->seg_event = NULL; } if (G_UNLIKELY (avi->got_tags)) { push_tag_lists (avi); @@ -4079,13 +5122,13 @@ gst_avi_demux_loop (GstPad * pad) return; /* ERRORS */ -pause: - GST_LOG_OBJECT (avi, "pausing task, reason %s", gst_flow_get_name (res)); - avi->segment_running = FALSE; - gst_pad_pause_task (avi->sinkpad); +pause:{ + + gboolean push_eos = FALSE; + GST_LOG_OBJECT (avi, "pausing task, reason %s", gst_flow_get_name (res)); + avi->segment_running = FALSE; + gst_pad_pause_task (avi->sinkpad); - if (GST_FLOW_IS_FATAL (res) || (res == GST_FLOW_NOT_LINKED)) { - gboolean push_eos = TRUE; if (res == GST_FLOW_UNEXPECTED) { /* handle end-of-stream/segment */ @@ -4098,16 +5141,20 @@ pause: GST_INFO_OBJECT (avi, "sending segment_done"); gst_element_post_message - (GST_ELEMENT (avi), - gst_message_new_segment_done (GST_OBJECT (avi), GST_FORMAT_TIME, - stop)); - push_eos = FALSE; + (GST_ELEMENT_CAST (avi), + gst_message_new_segment_done (GST_OBJECT_CAST (avi), + GST_FORMAT_TIME, stop)); + } else { + push_eos = TRUE; } - } else { - /* for fatal errors we post an error message */ + } else if (res == GST_FLOW_NOT_LINKED || res < GST_FLOW_UNEXPECTED) { + /* for fatal errors we post an error message, wrong-state is + * not fatal because it happens due to flushes and only means + * that we should stop now. */ GST_ELEMENT_ERROR (avi, STREAM, FAILED, (_("Internal data stream error.")), ("streaming stopped, reason %s", gst_flow_get_name (res))); + push_eos = TRUE; } if (push_eos) { GST_INFO_OBJECT (avi, "sending eos"); @@ -4126,6 +5173,15 @@ gst_avi_demux_chain (GstPad * pad, GstBuffer * buf) { GstFlowReturn res; GstAviDemux *avi = GST_AVI_DEMUX (GST_PAD_PARENT (pad)); + gint i; + + if (GST_BUFFER_IS_DISCONT (buf)) { + GST_DEBUG_OBJECT (avi, "got DISCONT"); + gst_adapter_clear (avi->adapter); + /* mark all streams DISCONT */ + for (i = 0; i < avi->num_streams; i++) + avi->stream[i].discont = TRUE; + } GST_DEBUG ("Store %d bytes in adapter", GST_BUFFER_SIZE (buf)); gst_adapter_push (avi->adapter, buf); @@ -4144,15 +5200,55 @@ gst_avi_demux_chain (GstPad * pad, GstBuffer * buf) } break; case GST_AVI_DEMUX_MOVI: - if (G_UNLIKELY (avi->seek_event)) { - gst_avi_demux_push_event (avi, avi->seek_event); - avi->seek_event = NULL; + if (G_UNLIKELY (avi->close_seg_event)) { + gst_avi_demux_push_event (avi, avi->close_seg_event); + avi->close_seg_event = NULL; + } + if (G_UNLIKELY (avi->seg_event)) { + gst_avi_demux_push_event (avi, avi->seg_event); + avi->seg_event = NULL; } if (G_UNLIKELY (avi->got_tags)) { push_tag_lists (avi); } res = gst_avi_demux_stream_data (avi); break; + case GST_AVI_DEMUX_SEEK: + { + GstEvent *event; + + res = GST_FLOW_OK; + + /* obtain and parse indexes */ + if (avi->stream[0].indexes && !gst_avi_demux_read_subindexes_push (avi)) + /* seek in subindex read function failed */ + goto index_failed; + + if (!avi->stream[0].indexes && !avi->have_index + && avi->avih->flags & GST_RIFF_AVIH_HASINDEX) + gst_avi_demux_stream_index_push (avi); + + if (avi->have_index) { + /* use the indexes now to construct nice durations */ + gst_avi_demux_calculate_durations_from_index (avi); + } else { + /* still parsing indexes */ + break; + } + + GST_OBJECT_LOCK (avi); + event = avi->seek_event; + avi->seek_event = NULL; + GST_OBJECT_UNLOCK (avi); + + /* calculate and perform seek */ + if (!avi_demux_handle_seek_push (avi, avi->sinkpad, event)) + goto seek_failed; + + gst_event_unref (event); + avi->state = GST_AVI_DEMUX_MOVI; + break; + } default: GST_ELEMENT_ERROR (avi, STREAM, FAILED, (NULL), ("Illegal internal state")); @@ -4163,13 +5259,28 @@ gst_avi_demux_chain (GstPad * pad, GstBuffer * buf) GST_DEBUG_OBJECT (avi, "state: %d res:%s", avi->state, gst_flow_get_name (res)); - if (G_UNLIKELY (avi->abort_buffering)) { + if (G_UNLIKELY (avi->abort_buffering)) + goto abort_buffering; + + return res; + + /* ERRORS */ +index_failed: + { + GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("failed to read indexes")); + return GST_FLOW_ERROR; + } +seek_failed: + { + GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("push mode seek failed")); + return GST_FLOW_ERROR; + } +abort_buffering: + { avi->abort_buffering = FALSE; - res = GST_FLOW_ERROR; GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("unhandled buffer size")); + return GST_FLOW_ERROR; } - - return res; } static gboolean @@ -4208,6 +5319,18 @@ gst_avi_demux_activate_push (GstPad * pad, gboolean active) if (active) { GST_DEBUG ("avi: activating push/chain function"); avi->streaming = TRUE; +#if 0 + /* create index for some push based seeking if not provided */ + GST_OBJECT_LOCK (avi); + if (!avi->element_index) { + GST_DEBUG_OBJECT (avi, "creating index"); + avi->element_index = gst_index_factory_make ("memindex"); + } + GST_OBJECT_UNLOCK (avi); + /* object lock might be taken again */ + gst_index_get_writer_id (avi->element_index, GST_OBJECT_CAST (avi), + &avi->index_id); +#endif } else { GST_DEBUG ("avi: deactivating push/chain function"); } @@ -4215,6 +5338,42 @@ gst_avi_demux_activate_push (GstPad * pad, gboolean active) return TRUE; } +static void +gst_avi_demux_set_index (GstElement * element, GstIndex * index) +{ + GstAviDemux *avi = GST_AVI_DEMUX (element); + + GST_OBJECT_LOCK (avi); + if (avi->element_index) + gst_object_unref (avi->element_index); + if (index) { + avi->element_index = gst_object_ref (index); + } else { + avi->element_index = NULL; + } + GST_OBJECT_UNLOCK (avi); + /* object lock might be taken again */ + if (index) + gst_index_get_writer_id (index, GST_OBJECT_CAST (element), &avi->index_id); + GST_DEBUG_OBJECT (avi, "Set index %" GST_PTR_FORMAT, avi->element_index); +} + +static GstIndex * +gst_avi_demux_get_index (GstElement * element) +{ + GstIndex *result = NULL; + GstAviDemux *avi = GST_AVI_DEMUX (element); + + GST_OBJECT_LOCK (avi); + if (avi->element_index) + result = gst_object_ref (avi->element_index); + GST_OBJECT_UNLOCK (avi); + + GST_DEBUG_OBJECT (avi, "Returning index %" GST_PTR_FORMAT, result); + + return result; +} + static GstStateChangeReturn gst_avi_demux_change_state (GstElement * element, GstStateChange transition) { @@ -4236,6 +5395,7 @@ gst_avi_demux_change_state (GstElement * element, GstStateChange transition) switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY: + avi->have_index = FALSE; gst_avi_demux_reset (avi); break; default: