/* 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 * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /* Element-Checklist-Version: 5 */ /** * SECTION:element-avidemux * * Demuxes an .avi file into raw or compressed audio and/or video streams. * * This element supports both push and pull-based scheduling, depending on the * capabilities of the upstream elements. * * * Example launch line * |[ * gst-launch filesrc location=test.avi ! avidemux name=demux demux.audio_00 ! decodebin ! audioconvert ! audioresample ! autoaudiosink demux.video_00 ! queue ! decodebin ! ffmpegcolorspace ! videoscale ! autovideosink * ]| Play (parse and decode) an .avi file and try to output it to * an automatically detected soundcard and videosink. If the AVI file contains * compressed audio or video data, this will only work if you have the * right decoder elements/plugins installed. * * * Last reviewed on 2006-12-29 (0.10.6) */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "gst/riff/riff-media.h" #include "gstavidemux.h" #include "avi-ids.h" #include #include #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 static GstStaticPadTemplate sink_templ = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("video/x-msvideo") ); static void gst_avi_demux_finalize (GObject * object); static void gst_avi_demux_reset (GstAviDemux * avi); #if 0 static const GstEventMask *gst_avi_demux_get_event_mask (GstPad * pad); #endif static gboolean gst_avi_demux_handle_src_event (GstPad * pad, GstObject * parent, GstEvent * event); static gboolean gst_avi_demux_handle_sink_event (GstPad * pad, GstObject * parent, GstEvent * event); static gboolean gst_avi_demux_push_event (GstAviDemux * avi, GstEvent * event); #if 0 static const GstFormat *gst_avi_demux_get_src_formats (GstPad * pad); #endif static gboolean gst_avi_demux_handle_src_query (GstPad * pad, GstObject * parent, GstQuery * query); static gboolean gst_avi_demux_src_convert (GstPad * pad, GstFormat src_format, gint64 src_value, GstFormat * dest_format, gint64 * dest_value); 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, GstObject * parent); static gboolean gst_avi_demux_sink_activate_mode (GstPad * sinkpad, GstObject * parent, GstPadMode mode, gboolean active); static GstFlowReturn gst_avi_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buf); #if 0 static void gst_avi_demux_set_index (GstElement * element, GstIndex * index); static GstIndex *gst_avi_demux_get_index (GstElement * element); #endif 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); /* GObject methods */ #define gst_avi_demux_parent_class parent_class G_DEFINE_TYPE (GstAviDemux, gst_avi_demux, GST_TYPE_ELEMENT); static void gst_avi_demux_class_init (GstAviDemuxClass * klass) { GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); GObjectClass *gobject_class = (GObjectClass *) klass; GstPadTemplate *videosrctempl, *audiosrctempl, *subsrctempl; GstCaps *audcaps, *vidcaps, *subcaps; GST_DEBUG_CATEGORY_INIT (avidemux_debug, "avidemux", 0, "Demuxer for AVI streams"); gobject_class->finalize = gst_avi_demux_finalize; gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_avi_demux_change_state); #if 0 gstelement_class->set_index = GST_DEBUG_FUNCPTR (gst_avi_demux_set_index); gstelement_class->get_index = GST_DEBUG_FUNCPTR (gst_avi_demux_get_index); #endif audcaps = gst_riff_create_audio_template_caps (); gst_caps_append (audcaps, gst_caps_new_empty_simple ("audio/x-avi-unknown")); audiosrctempl = gst_pad_template_new ("audio_%u", GST_PAD_SRC, GST_PAD_SOMETIMES, audcaps); vidcaps = gst_riff_create_video_template_caps (); gst_caps_append (vidcaps, gst_riff_create_iavs_template_caps ()); gst_caps_append (vidcaps, gst_caps_new_empty_simple ("video/x-avi-unknown")); videosrctempl = gst_pad_template_new ("video_%u", GST_PAD_SRC, GST_PAD_SOMETIMES, vidcaps); subcaps = gst_caps_new_empty_simple ("application/x-subtitle-avi"); subsrctempl = gst_pad_template_new ("subtitle_%u", GST_PAD_SRC, GST_PAD_SOMETIMES, subcaps); gst_element_class_add_pad_template (gstelement_class, audiosrctempl); gst_element_class_add_pad_template (gstelement_class, videosrctempl); gst_element_class_add_pad_template (gstelement_class, subsrctempl); gst_element_class_add_pad_template (gstelement_class, gst_static_pad_template_get (&sink_templ)); gst_element_class_set_details_simple (gstelement_class, "Avi demuxer", "Codec/Demuxer", "Demultiplex an avi file into audio and video", "Erik Walthinsen , " "Wim Taymans , " "Thijs Vermeir "); } static void gst_avi_demux_init (GstAviDemux * avi) { avi->sinkpad = gst_pad_new_from_static_template (&sink_templ, "sink"); gst_pad_set_activate_function (avi->sinkpad, GST_DEBUG_FUNCPTR (gst_avi_demux_sink_activate)); gst_pad_set_activatemode_function (avi->sinkpad, GST_DEBUG_FUNCPTR (gst_avi_demux_sink_activate_mode)); gst_pad_set_chain_function (avi->sinkpad, 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_CAST (avi), avi->sinkpad); avi->adapter = gst_adapter_new (); gst_avi_demux_reset (avi); GST_OBJECT_FLAG_SET (avi, GST_ELEMENT_FLAG_INDEXABLE); } static void gst_avi_demux_finalize (GObject * object) { GstAviDemux *avi = GST_AVI_DEMUX (object); GST_DEBUG ("AVI: finalize"); g_object_unref (avi->adapter); G_OBJECT_CLASS (parent_class)->finalize (object); } static void gst_avi_demux_reset_stream (GstAviDemux * avi, GstAviStream * stream) { g_free (stream->strh); g_free (stream->strf.data); g_free (stream->name); g_free (stream->index); g_free (stream->indexes); if (stream->initdata) gst_buffer_unref (stream->initdata); if (stream->extradata) gst_buffer_unref (stream->extradata); if (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); stream->taglist = NULL; } memset (stream, 0, sizeof (GstAviStream)); } static void gst_avi_demux_reset (GstAviDemux * avi) { gint i; GST_DEBUG ("AVI: reset"); for (i = 0; i < avi->num_streams; i++) gst_avi_demux_reset_stream (avi, &avi->stream[i]); avi->header_state = GST_AVI_DEMUX_HEADER_TAG_LIST; avi->num_streams = 0; 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 0 if (avi->element_index) gst_object_unref (avi->element_index); avi->element_index = NULL; #endif 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; } if (avi->globaltags) gst_tag_list_free (avi->globaltags); avi->globaltags = NULL; avi->got_tags = TRUE; /* we always want to push global tags */ avi->have_eos = FALSE; avi->seekable = TRUE; gst_adapter_clear (avi->adapter); gst_segment_init (&avi->segment, GST_FORMAT_TIME); } /* GstElement methods */ #if 0 static const GstFormat * gst_avi_demux_get_src_formats (GstPad * pad) { GstAviStream *stream = gst_pad_get_element_private (pad); static const GstFormat src_a_formats[] = { GST_FORMAT_TIME, GST_FORMAT_BYTES, GST_FORMAT_DEFAULT, 0 }; static const GstFormat src_v_formats[] = { GST_FORMAT_TIME, GST_FORMAT_DEFAULT, 0 }; return (stream->strh->type == GST_RIFF_FCC_auds ? src_a_formats : src_v_formats); } #endif /* assumes stream->strf.auds->av_bps != 0 */ static inline GstClockTime avi_stream_convert_bytes_to_time_unchecked (GstAviStream * stream, guint64 bytes) { return gst_util_uint64_scale_int (bytes, GST_SECOND, stream->strf.auds->av_bps); } static inline guint64 avi_stream_convert_time_to_bytes_unchecked (GstAviStream * stream, GstClockTime time) { return gst_util_uint64_scale_int (time, stream->strf.auds->av_bps, GST_SECOND); } /* assumes stream->strh->rate != 0 */ static inline GstClockTime avi_stream_convert_frames_to_time_unchecked (GstAviStream * stream, guint64 frames) { return gst_util_uint64_scale (frames, stream->strh->scale * GST_SECOND, stream->strh->rate); } static inline guint64 avi_stream_convert_time_to_frames_unchecked (GstAviStream * stream, GstClockTime time) { return gst_util_uint64_scale (time, stream->strh->rate, stream->strh->scale * GST_SECOND); } static gboolean gst_avi_demux_src_convert (GstPad * pad, GstFormat src_format, gint64 src_value, GstFormat * dest_format, gint64 * dest_value) { GstAviStream *stream = gst_pad_get_element_private (pad); gboolean res = TRUE; GST_LOG_OBJECT (pad, "Received src_format:%s, src_value:%" G_GUINT64_FORMAT ", dest_format:%s", gst_format_get_name (src_format), src_value, gst_format_get_name (*dest_format)); if (G_UNLIKELY (src_format == *dest_format)) { *dest_value = src_value; goto done; } if (G_UNLIKELY (!stream->strh || !stream->strf.data)) { res = FALSE; goto done; } if (G_UNLIKELY (stream->strh->type == GST_RIFF_FCC_vids && (src_format == GST_FORMAT_BYTES || *dest_format == GST_FORMAT_BYTES))) { res = FALSE; goto done; } switch (src_format) { case GST_FORMAT_TIME: switch (*dest_format) { case GST_FORMAT_BYTES: *dest_value = gst_util_uint64_scale_int (src_value, stream->strf.auds->av_bps, GST_SECOND); break; case GST_FORMAT_DEFAULT: *dest_value = gst_util_uint64_scale_round (src_value, stream->strh->rate, stream->strh->scale * GST_SECOND); break; default: res = FALSE; break; } break; case GST_FORMAT_BYTES: switch (*dest_format) { case GST_FORMAT_TIME: if (stream->strf.auds->av_bps != 0) { *dest_value = avi_stream_convert_bytes_to_time_unchecked (stream, src_value); } else res = FALSE; break; default: res = FALSE; break; } break; case GST_FORMAT_DEFAULT: switch (*dest_format) { case GST_FORMAT_TIME: *dest_value = avi_stream_convert_frames_to_time_unchecked (stream, src_value); break; default: res = FALSE; break; } break; default: res = FALSE; } done: GST_LOG_OBJECT (pad, "Returning res:%d dest_format:%s dest_value:%" G_GUINT64_FORMAT, res, gst_format_get_name (*dest_format), *dest_value); return res; } static gboolean gst_avi_demux_handle_src_query (GstPad * pad, GstObject * parent, GstQuery * query) { gboolean res = TRUE; GstAviDemux *avi = GST_AVI_DEMUX (parent); GstAviStream *stream = gst_pad_get_element_private (pad); if (!stream->strh || !stream->strf.data) return gst_pad_query_default (pad, parent, query); switch (GST_QUERY_TYPE (query)) { case GST_QUERY_POSITION:{ gint64 pos = 0; GST_DEBUG ("pos query for stream %u: frames %u, bytes %u", stream->num, stream->current_entry, stream->current_total); /* FIXME, this looks clumsy */ if (stream->strh->type == GST_RIFF_FCC_auds) { if (stream->is_vbr) { /* VBR */ pos = gst_util_uint64_scale ((gint64) stream->current_entry * stream->strh->scale, GST_SECOND, (guint64) stream->strh->rate); GST_DEBUG_OBJECT (avi, "VBR convert frame %u, time %" GST_TIME_FORMAT, stream->current_entry, GST_TIME_ARGS (pos)); } else if (stream->strf.auds->av_bps != 0) { /* CBR */ pos = gst_util_uint64_scale (stream->current_total, GST_SECOND, (guint64) stream->strf.auds->av_bps); GST_DEBUG_OBJECT (avi, "CBR convert bytes %u, time %" GST_TIME_FORMAT, stream->current_total, GST_TIME_ARGS (pos)); } else if (stream->idx_n != 0 && stream->total_bytes != 0) { /* calculate timestamps based on percentage of length */ guint64 xlen = avi->avih->us_frame * avi->avih->tot_frames * GST_USECOND; if (stream->is_vbr) { pos = gst_util_uint64_scale (xlen, stream->current_entry, stream->idx_n); GST_DEBUG_OBJECT (avi, "VBR perc convert frame %u, time %" GST_TIME_FORMAT, stream->current_entry, GST_TIME_ARGS (pos)); } else { pos = gst_util_uint64_scale (xlen, stream->current_total, stream->total_bytes); GST_DEBUG_OBJECT (avi, "CBR perc convert bytes %u, time %" GST_TIME_FORMAT, stream->current_total, GST_TIME_ARGS (pos)); } } else { /* we don't know */ res = FALSE; } } else { if (stream->strh->rate != 0) { pos = gst_util_uint64_scale ((guint64) stream->current_entry * stream->strh->scale, GST_SECOND, (guint64) stream->strh->rate); } else { pos = stream->current_entry * avi->avih->us_frame * GST_USECOND; } } if (res) { GST_DEBUG ("pos query : %" GST_TIME_FORMAT, GST_TIME_ARGS (pos)); gst_query_set_position (query, GST_FORMAT_TIME, pos); } else GST_WARNING ("pos query failed"); break; } 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, duration); break; case GST_FORMAT_DEFAULT: { gint64 dur; GST_DEBUG_OBJECT (query, "total frames is %" G_GUINT32_FORMAT, stream->idx_n); if (stream->idx_n > 0) gst_query_set_duration (query, fmt, stream->idx_n); else if (gst_pad_query_convert (pad, GST_FORMAT_TIME, duration, fmt, &dur)) gst_query_set_duration (query, fmt, dur); break; } default: res = FALSE; break; } break; } case GST_QUERY_SEEKING:{ GstFormat fmt; gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL); if (fmt == GST_FORMAT_TIME) { gboolean seekable = TRUE; if (avi->streaming) { seekable = avi->seekable; } gst_query_set_seeking (query, GST_FORMAT_TIME, seekable, 0, stream->duration); res = TRUE; } break; } case GST_QUERY_CONVERT:{ GstFormat src_fmt, dest_fmt; gint64 src_val, dest_val; gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val); if ((res = gst_avi_demux_src_convert (pad, src_fmt, src_val, &dest_fmt, &dest_val))) gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val); else res = gst_pad_query_default (pad, parent, query); break; } default: res = gst_pad_query_default (pad, parent, query); break; } return res; } #if 0 static const GstEventMask * gst_avi_demux_get_event_mask (GstPad * pad) { static const GstEventMask masks[] = { {GST_EVENT_SEEK, GST_SEEK_METHOD_SET | GST_SEEK_FLAG_KEY_UNIT}, {0,} }; return masks; } #endif #if 0 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; } #endif 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 = 0; 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, GstObject * parent, GstEvent * event) { gboolean res = TRUE; GstAviDemux *avi = GST_AVI_DEMUX (parent); GST_DEBUG_OBJECT (avi, "have event type %s: %p on sink pad", GST_EVENT_TYPE_NAME (event), event); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEGMENT: { gint64 boffset, offset = 0; GstSegment segment; /* some debug output */ gst_event_copy_segment (event, &segment); GST_DEBUG_OBJECT (avi, "received newsegment %" GST_SEGMENT_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 (segment.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 */ boffset = segment.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, &boffset, 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 == segment.start) break; } while (++i < avi->num_streams); boffset -= 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 *) & segment.time, NULL, NULL, NULL); #if 0 } 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, segment.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, (gint64 *) & segment.time); gst_index_entry_assoc_map (entry, GST_FORMAT_BYTES, &offset); #endif } else { GST_WARNING_OBJECT (avi, "no index data, forcing EOS"); goto eos; } segment.format = GST_FORMAT_TIME; segment.start = segment.time; segment.stop = GST_CLOCK_TIME_NONE; segment.position = segment.start; /* rescue duration */ segment.duration = avi->segment.duration; /* set up segment and send downstream */ gst_segment_copy_into (&segment, &avi->segment); GST_DEBUG_OBJECT (avi, "Pushing newseg %" GST_SEGMENT_FORMAT, &segment); gst_avi_demux_push_event (avi, gst_event_new_segment (&segment)); GST_DEBUG_OBJECT (avi, "next chunk expected at %" G_GINT64_FORMAT, boffset); /* adjust state for streaming thread accordingly */ if (avi->have_index) gst_avi_demux_seek_streams_index (avi, offset, FALSE); #if 0 else gst_avi_demux_seek_streams (avi, offset, FALSE); #endif /* set up streaming thread */ g_assert (offset >= boffset); avi->offset = boffset; avi->todrop = offset - boffset; 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) { gst_event_unref (event); GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("got eos and didn't receive a complete header object")); } else if (!gst_avi_demux_push_event (avi, event)) { GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("got eos but no streams (yet)")); } 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, parent, event); break; } return res; } static gboolean gst_avi_demux_handle_src_event (GstPad * pad, GstObject * parent, GstEvent * event) { gboolean res = TRUE; GstAviDemux *avi = GST_AVI_DEMUX (parent); GST_DEBUG_OBJECT (avi, "have event type %s: %p on src pad", GST_EVENT_TYPE_NAME (event), event); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEEK: if (!avi->streaming) { res = gst_avi_demux_handle_seek (avi, pad, event); } else { res = gst_avi_demux_handle_seek_push (avi, pad, event); } gst_event_unref (event); break; case GST_EVENT_QOS: case GST_EVENT_NAVIGATION: res = FALSE; gst_event_unref (event); break; default: res = gst_pad_event_default (pad, parent, event); break; } return res; } /* streaming helper (push) */ /* * gst_avi_demux_peek_chunk_info: * @avi: Avi object * @tag: holder for tag * @size: holder for tag size * * Peek next chunk info (tag and size) * * Returns: TRUE when one chunk info has been got */ static gboolean gst_avi_demux_peek_chunk_info (GstAviDemux * avi, guint32 * tag, guint32 * size) { const guint8 *data = NULL; if (gst_adapter_available (avi->adapter) < 8) return FALSE; data = gst_adapter_map (avi->adapter, 8); *tag = GST_READ_UINT32_LE (data); *size = GST_READ_UINT32_LE (data + 4); gst_adapter_unmap (avi->adapter); return TRUE; } /* * gst_avi_demux_peek_chunk: * @avi: Avi object * @tag: holder for tag * @size: holder for tag size * * Peek enough data for one full chunk * * Returns: %TRUE when one chunk has been got */ static gboolean gst_avi_demux_peek_chunk (GstAviDemux * avi, guint32 * tag, guint32 * size) { guint32 peek_size = 0; gint available; if (!gst_avi_demux_peek_chunk_info (avi, tag, size)) goto peek_failed; /* size 0 -> empty data buffer would surprise most callers, * large size -> do not bother trying to squeeze that into adapter, * so we throw poor man's exception, which can be caught if caller really * wants to handle 0 size chunk */ if (!(*size) || (*size) >= (1 << 30)) goto strange_size; peek_size = (*size + 1) & ~1; available = gst_adapter_available (avi->adapter); GST_DEBUG_OBJECT (avi, "Need to peek chunk of %d bytes to read chunk %" GST_FOURCC_FORMAT ", %d bytes available", *size, GST_FOURCC_ARGS (*tag), available); if (available < (8 + peek_size)) goto need_more; return TRUE; /* ERRORS */ peek_failed: { GST_INFO_OBJECT (avi, "Failed to peek"); return FALSE; } strange_size: { GST_INFO_OBJECT (avi, "Invalid/unexpected chunk size %d for tag %" GST_FOURCC_FORMAT, *size, GST_FOURCC_ARGS (*tag)); /* chain should give up */ avi->abort_buffering = TRUE; return FALSE; } need_more: { GST_INFO_OBJECT (avi, "need more %d < %" G_GUINT32_FORMAT, available, 8 + peek_size); return FALSE; } } /* AVI init */ /* * gst_avi_demux_parse_file_header: * @element: caller element (used for errors/debug). * @buf: input data to be used for parsing. * * "Open" a RIFF/AVI file. The buffer should be at least 12 * bytes long. Takes ownership of @buf. * * Returns: TRUE if the file is a RIFF/AVI file, FALSE otherwise. * Throws an error, caller should error out (fatal). */ static gboolean gst_avi_demux_parse_file_header (GstElement * element, GstBuffer * buf) { guint32 doctype; GstClockTime stamp; stamp = gst_util_get_timestamp (); /* riff_parse posts an error */ if (!gst_riff_parse_file_header (element, buf, &doctype)) return FALSE; if (doctype != GST_RIFF_RIFF_AVI) goto not_avi; stamp = gst_util_get_timestamp () - stamp; GST_DEBUG_OBJECT (element, "header parsing took %" GST_TIME_FORMAT, GST_TIME_ARGS (stamp)); return TRUE; /* ERRORS */ not_avi: { GST_ELEMENT_ERROR (element, STREAM, WRONG_TYPE, (NULL), ("File is not an AVI file: %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (doctype))); return FALSE; } } /* * Read AVI file tag when streaming */ static GstFlowReturn gst_avi_demux_stream_init_push (GstAviDemux * avi) { if (gst_adapter_available (avi->adapter) >= 12) { GstBuffer *tmp; tmp = gst_adapter_take_buffer (avi->adapter, 12); GST_DEBUG ("Parsing avi header"); if (!gst_avi_demux_parse_file_header (GST_ELEMENT_CAST (avi), tmp)) { return GST_FLOW_ERROR; } GST_DEBUG ("header ok"); avi->offset += 12; avi->state = GST_AVI_DEMUX_HEADER; } return GST_FLOW_OK; } /* * Read AVI file tag */ static GstFlowReturn gst_avi_demux_stream_init_pull (GstAviDemux * avi) { GstFlowReturn res; GstBuffer *buf = NULL; res = gst_pad_pull_range (avi->sinkpad, avi->offset, 12, &buf); if (res != GST_FLOW_OK) return res; else if (!gst_avi_demux_parse_file_header (GST_ELEMENT_CAST (avi), buf)) goto wrong_header; avi->offset += 12; return GST_FLOW_OK; /* ERRORS */ wrong_header: { GST_DEBUG_OBJECT (avi, "error parsing file header"); return GST_FLOW_ERROR; } } /* AVI header handling */ /* * gst_avi_demux_parse_avih: * @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.). * * Read 'avih' header. Discards buffer after use. * * Returns: TRUE on success, FALSE otherwise. Throws an error if * the header is invalid. The caller should error out * (fatal). */ static gboolean gst_avi_demux_parse_avih (GstAviDemux * avi, GstBuffer * buf, gst_riff_avih ** _avih) { gst_riff_avih *avih; gsize size; if (buf == NULL) goto no_buffer; size = gst_buffer_get_size (buf); if (size < sizeof (gst_riff_avih)) goto avih_too_small; avih = g_malloc (size); gst_buffer_extract (buf, 0, avih, size); #if (G_BYTE_ORDER == G_BIG_ENDIAN) avih->us_frame = GUINT32_FROM_LE (avih->us_frame); avih->max_bps = GUINT32_FROM_LE (avih->max_bps); avih->pad_gran = GUINT32_FROM_LE (avih->pad_gran); avih->flags = GUINT32_FROM_LE (avih->flags); avih->tot_frames = GUINT32_FROM_LE (avih->tot_frames); avih->init_frames = GUINT32_FROM_LE (avih->init_frames); avih->streams = GUINT32_FROM_LE (avih->streams); avih->bufsize = GUINT32_FROM_LE (avih->bufsize); avih->width = GUINT32_FROM_LE (avih->width); avih->height = GUINT32_FROM_LE (avih->height); avih->scale = GUINT32_FROM_LE (avih->scale); avih->rate = GUINT32_FROM_LE (avih->rate); avih->start = GUINT32_FROM_LE (avih->start); avih->length = GUINT32_FROM_LE (avih->length); #endif /* debug stuff */ 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 (avi, STREAM, DEMUX, (NULL), ("No buffer")); return FALSE; } avih_too_small: { GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("Too small avih (%" G_GSIZE_FORMAT " available, %d needed)", size, (int) sizeof (gst_riff_avih))); gst_buffer_unref (buf); return FALSE; } } /* * gst_avi_demux_parse_superindex: * @avi: caller element (used for debugging/errors). * @buf: input data to use for parsing. * @locations: locations in the file (byte-offsets) that contain * the actual indexes (see get_avi_demux_parse_subindex()). * The array ends with GST_BUFFER_OFFSET_NONE. * * Reads superindex (openDML-2 spec stuff) from the provided data. * * Returns: TRUE on success, FALSE otherwise. Indexes should be skipped * on error, but they are not fatal. */ static gboolean gst_avi_demux_parse_superindex (GstAviDemux * avi, GstBuffer * buf, guint64 ** _indexes) { GstMapInfo map; guint8 *data; guint16 bpe = 16; guint32 num, i; guint64 *indexes; gsize size; *_indexes = NULL; if (buf) { gst_buffer_map (buf, &map, GST_MAP_READ); data = map.data; size = map.size; } else { data = NULL; size = 0; } if (size < 24) goto too_small; /* 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_OBJECT (avi, "Superindex for stream 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]); GST_DEBUG_OBJECT (avi, "got %d indexes", num); /* this can't work out well ... */ if (num > G_MAXUINT32 >> 1 || bpe < 8) { goto invalid_params; } 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; gst_buffer_unmap (buf, &map); gst_buffer_unref (buf); return TRUE; /* ERRORS */ too_small: { GST_ERROR_OBJECT (avi, "Not enough data to parse superindex (%" G_GSIZE_FORMAT " available, 24 needed)", size); if (buf) { gst_buffer_unmap (buf, &map); gst_buffer_unref (buf); } return FALSE; } invalid_params: { GST_ERROR_OBJECT (avi, "invalid index parameters (num = %d, bpe = %d)", num, bpe); gst_buffer_unmap (buf, &map); gst_buffer_unref (buf); return FALSE; } } /* add an entry to the index of a stream. @num should be an estimate of the * total amount of index entries for all streams and is used to dynamically * allocate memory for the index entries. */ static inline gboolean gst_avi_demux_add_index (GstAviDemux * avi, GstAviStream * stream, guint num, GstAviIndexEntry * entry) { /* ensure index memory */ if (G_UNLIKELY (stream->idx_n >= stream->idx_max)) { guint idx_max = stream->idx_max; GstAviIndexEntry *new_idx; /* we need to make some more room */ if (idx_max == 0) { /* initial size guess, assume each stream has an equal amount of entries, * overshoot with at least 8K */ idx_max = (num / avi->num_streams) + (8192 / sizeof (GstAviIndexEntry)); } else { idx_max += 8192 / sizeof (GstAviIndexEntry); GST_DEBUG_OBJECT (avi, "expanded index from %u to %u", stream->idx_max, idx_max); } new_idx = g_try_renew (GstAviIndexEntry, stream->index, idx_max); /* out of memory, if this fails stream->index is untouched. */ if (G_UNLIKELY (!new_idx)) return FALSE; /* use new index */ stream->index = new_idx; stream->idx_max = idx_max; } /* update entry total and stream stats. The entry total can be converted to * the timestamp of the entry easily. */ if (stream->strh->type == GST_RIFF_FCC_auds) { gint blockalign; if (stream->is_vbr) { entry->total = stream->total_blocks; } else { entry->total = stream->total_bytes; } blockalign = stream->strf.auds->blockalign; if (blockalign > 0) stream->total_blocks += DIV_ROUND_UP (entry->size, blockalign); else stream->total_blocks++; } else { if (stream->is_vbr) { entry->total = stream->idx_n; } else { entry->total = stream->total_bytes; } } stream->total_bytes += entry->size; if (ENTRY_IS_KEYFRAME (entry)) stream->n_keyframes++; /* and add */ GST_LOG_OBJECT (avi, "Adding stream %u, index entry %d, kf %d, size %u " ", offset %" G_GUINT64_FORMAT ", total %" G_GUINT64_FORMAT, stream->num, stream->idx_n, ENTRY_IS_KEYFRAME (entry), entry->size, entry->offset, entry->total); stream->index[stream->idx_n++] = *entry; return TRUE; } /* given @entry_n in @stream, calculate info such as timestamps and * offsets for the entry. */ 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) { GstAviIndexEntry *entry; entry = &stream->index[entry_n]; if (stream->is_vbr) { /* VBR stream next timestamp */ if (stream->strh->type == GST_RIFF_FCC_auds) { if (timestamp) *timestamp = avi_stream_convert_frames_to_time_unchecked (stream, entry->total); if (ts_end) *ts_end = avi_stream_convert_frames_to_time_unchecked (stream, entry->total + 1); } else { if (timestamp) *timestamp = avi_stream_convert_frames_to_time_unchecked (stream, entry_n); if (ts_end) *ts_end = avi_stream_convert_frames_to_time_unchecked (stream, entry_n + 1); } } else if (stream->strh->type == GST_RIFF_FCC_auds) { /* constant rate stream */ if (timestamp) *timestamp = avi_stream_convert_bytes_to_time_unchecked (stream, entry->total); if (ts_end) *ts_end = avi_stream_convert_bytes_to_time_unchecked (stream, entry->total + entry->size); } if (stream->strh->type == GST_RIFF_FCC_vids) { /* video offsets are the frame number */ if (offset) *offset = entry_n; if (offset_end) *offset_end = entry_n + 1; } else { /* no offsets for audio */ if (offset) *offset = -1; if (offset_end) *offset_end = -1; } } /* 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. * * 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_max = 0; #endif /* get stream stats now */ for (i = 0; i < avi->num_streams; i++) { GstAviStream *stream; if (G_UNLIKELY (!(stream = &avi->stream[i]))) continue; if (G_UNLIKELY (!stream->strh)) continue; if (G_UNLIKELY (!stream->index || stream->idx_n == 0)) continue; /* we interested in the end_ts of the last entry, which is the total * duration of this stream */ gst_avi_demux_get_buffer_info (avi, stream, stream->idx_n - 1, NULL, &stream->idx_duration, NULL, NULL); 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, " "%5u keyframes, entry size = %2u, total size = %10u, allocated %10u", i, GST_TIME_ARGS (stream->idx_duration), stream->idx_n, stream->n_keyframes, (guint) sizeof (GstAviIndexEntry), (guint) (stream->idx_n * sizeof (GstAviIndexEntry)), (guint) (stream->idx_max * sizeof (GstAviIndexEntry))); } 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; } /* * gst_avi_demux_parse_subindex: * @avi: Avi object * @buf: input data to use for parsing. * @stream: stream context. * @entries_list: a list (returned by the function) containing all the * indexes parsed in this specific subindex. The first * entry is also a pointer to allocated memory that needs * to be free´ed. May be NULL if no supported indexes were * found. * * Reads superindex (openDML-2 spec stuff) from the provided data. * The buffer should contain a GST_RIFF_TAG_ix?? chunk. * * Returns: TRUE on success, FALSE otherwise. Errors are fatal, we * throw an error, caller should bail out asap. */ static gboolean gst_avi_demux_parse_subindex (GstAviDemux * avi, GstAviStream * stream, GstBuffer * buf) { GstMapInfo map; guint8 *data; guint16 bpe; guint32 num, i; guint64 baseoff; if (buf == NULL) return TRUE; gst_buffer_map (buf, &map, GST_MAP_READ); data = map.data; /* check size */ if (map.size < 24) goto too_small; /* We don't support index-data yet */ if (data[3] & 0x80) goto not_implemented; /* 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_OBJECT (avi, "Superindex for stream %d has unexpected " "size_entry %d (bytes) or flags 0x%02x/0x%02x", stream->num, 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]); /* If there's nothing, just return ! */ if (num == 0) goto empty_index; GST_INFO_OBJECT (avi, "Parsing subindex, nr_entries = %6d", num); for (i = 0; i < num; i++) { GstAviIndexEntry entry; if (map.size < 24 + bpe * (i + 1)) break; /* fill in offset and size. offset contains the keyframe flag in the * upper bit*/ entry.offset = baseoff + GST_READ_UINT32_LE (&data[24 + bpe * i]); entry.size = GST_READ_UINT32_LE (&data[24 + bpe * i + 4]); /* handle flags */ if (stream->strh->type == GST_RIFF_FCC_auds) { /* all audio frames are keyframes */ ENTRY_SET_KEYFRAME (&entry); } else { /* else read flags */ entry.flags = (entry.size & 0x80000000) ? 0 : GST_AVI_KEYFRAME; } entry.size &= ~0x80000000; /* and add */ if (G_UNLIKELY (!gst_avi_demux_add_index (avi, stream, num, &entry))) goto out_of_mem; } done: gst_buffer_unmap (buf, &map); gst_buffer_unref (buf); return TRUE; /* ERRORS */ too_small: { GST_ERROR_OBJECT (avi, "Not enough data to parse subindex (%" G_GSIZE_FORMAT " available, 24 needed)", map.size); goto done; /* continue */ } not_implemented: { GST_ELEMENT_ERROR (avi, STREAM, NOT_IMPLEMENTED, (NULL), ("Subindex-is-data is not implemented")); gst_buffer_unmap (buf, &map); gst_buffer_unref (buf); return FALSE; } empty_index: { GST_DEBUG_OBJECT (avi, "the index is empty"); goto done; /* continue */ } out_of_mem: { GST_ELEMENT_ERROR (avi, RESOURCE, NO_SPACE_LEFT, (NULL), ("Cannot allocate memory for %u*%u=%u bytes", (guint) sizeof (GstAviIndexEntry), num, (guint) sizeof (GstAviIndexEntry) * num)); gst_buffer_unmap (buf, &map); gst_buffer_unref (buf); return FALSE; } } /* * 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 gboolean gst_avi_demux_read_subindexes_push (GstAviDemux * avi) { guint32 tag = 0, size; GstBuffer *buf = NULL; guint odml_stream; GST_DEBUG_OBJECT (avi, "read subindexes for %d streams", avi->num_streams); if (avi->odml_subidxs[avi->odml_subidx] != avi->offset) return FALSE; if (!gst_avi_demux_peek_chunk (avi, &tag, &size)) return TRUE; /* this is the ODML chunk we expect */ odml_stream = avi->odml_stream; 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; } 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); 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; } } /* seek to next index */ return perform_seek_to_offset (avi, avi->odml_subidxs[avi->odml_subidx]); } /* * Read AVI index */ static void gst_avi_demux_read_subindexes_pull (GstAviDemux * avi) { guint32 tag; GstBuffer *buf; gint i, n; 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 (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) 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'))) { /* Some ODML files (created by god knows what muxer) have a ##ix format * instead of the 'official' ix##. They are still valid though. */ GST_WARNING_OBJECT (avi, "Not an ix## chunk (%" GST_FOURCC_FORMAT ")", GST_FOURCC_ARGS (tag)); gst_buffer_unref (buf); continue; } if (!gst_avi_demux_parse_subindex (avi, stream, buf)) continue; } g_free (stream->indexes); stream->indexes = NULL; } /* get stream stats now */ avi->have_index = gst_avi_demux_do_index_stats (avi); } /* * gst_avi_demux_riff_parse_vprp: * @element: caller element (used for debugging/error). * @buf: input data to be used for parsing, stripped from header. * @vprp: a pointer (returned by this function) to a filled-in vprp * structure. Caller should free it. * * Parses a video stream´s vprp. This function takes ownership of @buf. * * Returns: TRUE if parsing succeeded, otherwise FALSE. The stream * should be skipped on error, but it is not fatal. */ static gboolean gst_avi_demux_riff_parse_vprp (GstElement * element, GstBuffer * buf, gst_riff_vprp ** _vprp) { gst_riff_vprp *vprp; gint k; gsize size; g_return_val_if_fail (buf != NULL, FALSE); g_return_val_if_fail (_vprp != NULL, FALSE); size = gst_buffer_get_size (buf); if (size < G_STRUCT_OFFSET (gst_riff_vprp, field_info)) goto too_small; vprp = g_malloc (size); gst_buffer_extract (buf, 0, vprp, size); #if (G_BYTE_ORDER == G_BIG_ENDIAN) vprp->format_token = GUINT32_FROM_LE (vprp->format_token); vprp->standard = GUINT32_FROM_LE (vprp->standard); vprp->vert_rate = GUINT32_FROM_LE (vprp->vert_rate); vprp->hor_t_total = GUINT32_FROM_LE (vprp->hor_t_total); vprp->vert_lines = GUINT32_FROM_LE (vprp->vert_lines); vprp->aspect = GUINT32_FROM_LE (vprp->aspect); vprp->width = GUINT32_FROM_LE (vprp->width); vprp->height = GUINT32_FROM_LE (vprp->height); vprp->fields = GUINT32_FROM_LE (vprp->fields); #endif /* size checking */ /* calculate fields based on size */ k = (size - G_STRUCT_OFFSET (gst_riff_vprp, field_info)) / vprp->fields; if (vprp->fields > k) { GST_WARNING_OBJECT (element, "vprp header indicated %d fields, only %d available", vprp->fields, k); vprp->fields = k; } if (vprp->fields > GST_RIFF_VPRP_VIDEO_FIELDS) { GST_WARNING_OBJECT (element, "vprp header indicated %d fields, at most %d supported", vprp->fields, GST_RIFF_VPRP_VIDEO_FIELDS); vprp->fields = GST_RIFF_VPRP_VIDEO_FIELDS; } #if (G_BYTE_ORDER == G_BIG_ENDIAN) for (k = 0; k < vprp->fields; k++) { gst_riff_vprp_video_field_desc *fd; fd = &vprp->field_info[k]; fd->compressed_bm_height = GUINT32_FROM_LE (fd->compressed_bm_height); fd->compressed_bm_width = GUINT32_FROM_LE (fd->compressed_bm_width); fd->valid_bm_height = GUINT32_FROM_LE (fd->valid_bm_height); fd->valid_bm_width = GUINT16_FROM_LE (fd->valid_bm_width); fd->valid_bm_x_offset = GUINT16_FROM_LE (fd->valid_bm_x_offset); fd->valid_bm_y_offset = GUINT32_FROM_LE (fd->valid_bm_y_offset); fd->video_x_t_offset = GUINT32_FROM_LE (fd->video_x_t_offset); fd->video_y_start = GUINT32_FROM_LE (fd->video_y_start); } #endif /* debug */ GST_INFO_OBJECT (element, "vprp tag found in context vids:"); GST_INFO_OBJECT (element, " format_token %d", vprp->format_token); GST_INFO_OBJECT (element, " standard %d", vprp->standard); GST_INFO_OBJECT (element, " vert_rate %d", vprp->vert_rate); GST_INFO_OBJECT (element, " hor_t_total %d", vprp->hor_t_total); GST_INFO_OBJECT (element, " vert_lines %d", vprp->vert_lines); GST_INFO_OBJECT (element, " aspect %d:%d", vprp->aspect >> 16, vprp->aspect & 0xffff); GST_INFO_OBJECT (element, " width %d", vprp->width); GST_INFO_OBJECT (element, " height %d", vprp->height); GST_INFO_OBJECT (element, " fields %d", vprp->fields); for (k = 0; k < vprp->fields; k++) { gst_riff_vprp_video_field_desc *fd; fd = &(vprp->field_info[k]); GST_INFO_OBJECT (element, " field %u description:", k); GST_INFO_OBJECT (element, " compressed_bm_height %d", fd->compressed_bm_height); GST_INFO_OBJECT (element, " compressed_bm_width %d", fd->compressed_bm_width); GST_INFO_OBJECT (element, " valid_bm_height %d", fd->valid_bm_height); GST_INFO_OBJECT (element, " valid_bm_width %d", fd->valid_bm_width); GST_INFO_OBJECT (element, " valid_bm_x_offset %d", fd->valid_bm_x_offset); GST_INFO_OBJECT (element, " valid_bm_y_offset %d", fd->valid_bm_y_offset); GST_INFO_OBJECT (element, " video_x_t_offset %d", fd->video_x_t_offset); GST_INFO_OBJECT (element, " video_y_start %d", fd->video_y_start); } gst_buffer_unref (buf); *_vprp = vprp; return TRUE; /* ERRORS */ too_small: { GST_ERROR_OBJECT (element, "Too small vprp (%" G_GSIZE_FORMAT " available, at least %d needed)", size, (int) G_STRUCT_OFFSET (gst_riff_vprp, field_info)); gst_buffer_unref (buf); return FALSE; } } 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, "Adding pad %s" GST_PTR_FORMAT, GST_PAD_NAME (stream->pad)); gst_element_add_pad ((GstElement *) avi, stream->pad); #if 0 if (avi->element_index) gst_index_get_writer_id (avi->element_index, GST_OBJECT_CAST (stream->pad), &stream->index_id); #endif 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); } } } /* buf contains LIST chunk data, and will be padded to even size, * since some buggy files do not account for the padding of chunks * within a LIST in the size of the LIST */ static inline void gst_avi_demux_roundup_list (GstAviDemux * avi, GstBuffer ** buf) { gsize size; size = gst_buffer_get_size (*buf); if (G_UNLIKELY (size & 1)) { GstBuffer *obuf; GstMapInfo map; GST_DEBUG_OBJECT (avi, "rounding up dubious list size %" G_GSIZE_FORMAT, size); obuf = gst_buffer_new_and_alloc (size + 1); gst_buffer_map (obuf, &map, GST_MAP_WRITE); gst_buffer_extract (*buf, 0, map.data, size); /* assume 0 padding, at least makes outcome deterministic */ map.data[size] = 0; gst_buffer_unmap (obuf, &map); gst_buffer_replace (buf, obuf); } } /* * gst_avi_demux_parse_stream: * @avi: calling element (used for debugging/errors). * @buf: input buffer used to parse the stream. * * Parses all subchunks in a strl chunk (which defines a single * stream). Discards the buffer after use. This function will * increment the stream counter internally. * * Returns: whether the stream was identified successfully. * Errors are not fatal. It does indicate the stream * was skipped. */ static gboolean gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf) { GstAviStream *stream; GstElementClass *klass; GstPadTemplate *templ; GstBuffer *sub = NULL; guint offset = 4; guint32 tag = 0; gchar *codec_name = NULL, *padname = NULL; const gchar *tag_name; GstCaps *caps = NULL; GstPad *pad; GstElement *element; gboolean got_strh = FALSE, got_strf = FALSE, got_vprp = FALSE; gst_riff_vprp *vprp = NULL; element = GST_ELEMENT_CAST (avi); GST_DEBUG_OBJECT (avi, "Parsing stream"); gst_avi_demux_roundup_list (avi, &buf); if (avi->num_streams >= GST_AVI_DEMUX_MAX_STREAMS) { GST_WARNING_OBJECT (avi, "maximum no of streams (%d) exceeded, ignoring stream", GST_AVI_DEMUX_MAX_STREAMS); gst_buffer_unref (buf); /* not a fatal error, let's say */ return TRUE; } stream = &avi->stream[avi->num_streams]; /* initial settings */ stream->idx_duration = GST_CLOCK_TIME_NONE; stream->hdr_duration = GST_CLOCK_TIME_NONE; stream->duration = GST_CLOCK_TIME_NONE; while (gst_riff_parse_chunk (element, buf, &offset, &tag, &sub)) { /* sub can be NULL if the chunk is empty */ if (sub == NULL) { GST_DEBUG_OBJECT (avi, "ignoring empty chunk %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (tag)); continue; } switch (tag) { case GST_RIFF_TAG_strh: { gst_riff_strh *strh; if (got_strh) { GST_WARNING_OBJECT (avi, "Ignoring additional strh chunk"); break; } if (!gst_riff_parse_strh (element, sub, &stream->strh)) { /* ownership given away */ sub = NULL; GST_WARNING_OBJECT (avi, "Failed to parse strh chunk"); goto fail; } sub = NULL; strh = stream->strh; /* sanity check; stream header frame rate matches global header * frame duration */ if (stream->strh->type == GST_RIFF_FCC_vids) { GstClockTime s_dur; GstClockTime h_dur = avi->avih->us_frame * GST_USECOND; s_dur = gst_util_uint64_scale (GST_SECOND, strh->scale, strh->rate); GST_DEBUG_OBJECT (avi, "verifying stream framerate %d/%d, " "frame duration = %d ms", strh->rate, strh->scale, (gint) (s_dur / GST_MSECOND)); if (h_dur > (10 * GST_MSECOND) && (s_dur > 10 * h_dur)) { strh->rate = GST_SECOND / GST_USECOND; strh->scale = h_dur / GST_USECOND; GST_DEBUG_OBJECT (avi, "correcting stream framerate to %d/%d", strh->rate, strh->scale); } } /* determine duration as indicated by header */ stream->hdr_duration = gst_util_uint64_scale ((guint64) strh->length * strh->scale, GST_SECOND, (guint64) strh->rate); GST_INFO ("Stream duration according to header: %" GST_TIME_FORMAT, GST_TIME_ARGS (stream->hdr_duration)); if (stream->hdr_duration == 0) stream->hdr_duration = GST_CLOCK_TIME_NONE; got_strh = TRUE; break; } case GST_RIFF_TAG_strf: { gboolean res = FALSE; if (got_strf) { GST_WARNING_OBJECT (avi, "Ignoring additional strf chunk"); break; } if (!got_strh) { GST_ERROR_OBJECT (avi, "Found strf chunk before strh chunk"); goto fail; } switch (stream->strh->type) { case GST_RIFF_FCC_vids: stream->is_vbr = TRUE; res = gst_riff_parse_strf_vids (element, sub, &stream->strf.vids, &stream->extradata); sub = NULL; GST_DEBUG_OBJECT (element, "marking video as VBR, res %d", res); break; case GST_RIFF_FCC_auds: res = gst_riff_parse_strf_auds (element, sub, &stream->strf.auds, &stream->extradata); sub = NULL; if (!res) break; stream->is_vbr = (stream->strh->samplesize == 0) && stream->strh->scale > 1 && stream->strf.auds->blockalign != 1; 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; res = gst_riff_parse_strf_iavs (element, sub, &stream->strf.iavs, &stream->extradata); sub = NULL; GST_DEBUG_OBJECT (element, "marking iavs as VBR, res %d", res); break; case GST_RIFF_FCC_txts: /* nothing to parse here */ stream->is_vbr = (stream->strh->samplesize == 0) && (stream->strh->scale > 1); res = TRUE; break; default: GST_ERROR_OBJECT (avi, "Don´t know how to handle stream type %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (stream->strh->type)); break; } if (sub) { gst_buffer_unref (sub); sub = NULL; } if (!res) goto fail; got_strf = TRUE; break; } case GST_RIFF_TAG_vprp: { if (got_vprp) { GST_WARNING_OBJECT (avi, "Ignoring additional vprp chunk"); break; } if (!got_strh) { GST_ERROR_OBJECT (avi, "Found vprp chunk before strh chunk"); goto fail; } if (!got_strf) { GST_ERROR_OBJECT (avi, "Found vprp chunk before strf chunk"); goto fail; } if (!gst_avi_demux_riff_parse_vprp (element, sub, &vprp)) { GST_WARNING_OBJECT (avi, "Failed to parse vprp chunk"); /* not considered fatal */ g_free (vprp); vprp = NULL; } else got_vprp = TRUE; sub = NULL; break; } case GST_RIFF_TAG_strd: if (stream->initdata) gst_buffer_unref (stream->initdata); stream->initdata = sub; sub = NULL; break; case GST_RIFF_TAG_strn: g_free (stream->name); if (sub != NULL) { GstMapInfo map; gst_buffer_map (sub, &map, GST_MAP_READ); stream->name = g_strndup ((gchar *) map.data, map.size); gst_buffer_unmap (sub, &map); gst_buffer_unref (sub); sub = NULL; } else { stream->name = g_strdup (""); } 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, '0' + avi->num_streams % 10)) { g_free (stream->indexes); gst_avi_demux_parse_superindex (avi, sub, &stream->indexes); stream->superindex = TRUE; sub = NULL; break; } GST_WARNING_OBJECT (avi, "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; } if (sub != NULL) { gst_buffer_unref (sub); sub = NULL; } } if (!got_strh) { GST_WARNING_OBJECT (avi, "Failed to find strh chunk"); goto fail; } if (!got_strf) { GST_WARNING_OBJECT (avi, "Failed to find strf chunk"); goto fail; } /* get class to figure out the template */ klass = GST_ELEMENT_GET_CLASS (avi); /* we now have all info, let´s set up a pad and a caps and be done */ /* create stream name + pad */ switch (stream->strh->type) { case GST_RIFF_FCC_vids:{ guint32 fourcc; fourcc = (stream->strf.vids->compression) ? stream->strf.vids->compression : stream->strh->fcc_handler; padname = g_strdup_printf ("video_%u", avi->num_v_streams); templ = gst_element_class_get_pad_template (klass, "video_%u"); caps = gst_riff_create_video_caps (fourcc, stream->strh, stream->strf.vids, stream->extradata, stream->initdata, &codec_name); if (!caps) { caps = gst_caps_new_simple ("video/x-avi-unknown", "fourcc", G_TYPE_INT, fourcc, NULL); } else if (got_vprp && vprp) { guint32 aspect_n, aspect_d; gint n, d; aspect_n = vprp->aspect >> 16; aspect_d = vprp->aspect & 0xffff; /* calculate the pixel aspect ratio using w/h and aspect ratio */ n = aspect_n * stream->strf.vids->height; d = aspect_d * stream->strf.vids->width; if (n && d) gst_caps_set_simple (caps, "pixel-aspect-ratio", GST_TYPE_FRACTION, n, d, NULL); /* very local, not needed elsewhere */ g_free (vprp); vprp = NULL; } tag_name = GST_TAG_VIDEO_CODEC; avi->num_v_streams++; break; } case GST_RIFF_FCC_auds:{ /* FIXME: Do something with the channel reorder map */ padname = g_strdup_printf ("audio_%u", avi->num_a_streams); templ = gst_element_class_get_pad_template (klass, "audio_%u"); caps = gst_riff_create_audio_caps (stream->strf.auds->format, stream->strh, stream->strf.auds, stream->extradata, stream->initdata, &codec_name, NULL); if (!caps) { caps = gst_caps_new_simple ("audio/x-avi-unknown", "codec_id", G_TYPE_INT, stream->strf.auds->format, NULL); } tag_name = GST_TAG_AUDIO_CODEC; avi->num_a_streams++; break; } case GST_RIFF_FCC_iavs:{ guint32 fourcc = stream->strh->fcc_handler; padname = g_strdup_printf ("video_%u", avi->num_v_streams); templ = gst_element_class_get_pad_template (klass, "video_%u"); caps = gst_riff_create_iavs_caps (fourcc, stream->strh, stream->strf.iavs, stream->extradata, stream->initdata, &codec_name); if (!caps) { caps = gst_caps_new_simple ("video/x-avi-unknown", "fourcc", G_TYPE_INT, fourcc, NULL); } tag_name = GST_TAG_VIDEO_CODEC; avi->num_v_streams++; break; } case GST_RIFF_FCC_txts:{ padname = g_strdup_printf ("subtitle_%u", avi->num_t_streams); templ = gst_element_class_get_pad_template (klass, "subtitle_%u"); caps = gst_caps_new_empty_simple ("application/x-subtitle-avi"); tag_name = NULL; avi->num_t_streams++; break; } default: g_return_val_if_reached (FALSE); } /* no caps means no stream */ if (!caps) { GST_ERROR_OBJECT (element, "Did not find caps for stream %s", padname); goto fail; } GST_DEBUG_OBJECT (element, "codec-name=%s", (codec_name ? codec_name : "NULL")); GST_DEBUG_OBJECT (element, "caps=%" GST_PTR_FORMAT, caps); /* set proper settings and add it */ if (stream->pad) gst_object_unref (stream->pad); pad = stream->pad = gst_pad_new_from_template (templ, padname); g_free (padname); gst_pad_use_fixed_caps (pad); #if 0 gst_pad_set_formats_function (pad, GST_DEBUG_FUNCPTR (gst_avi_demux_get_src_formats)); gst_pad_set_event_mask_function (pad, GST_DEBUG_FUNCPTR (gst_avi_demux_get_event_mask)); #endif gst_pad_set_event_function (pad, GST_DEBUG_FUNCPTR (gst_avi_demux_handle_src_event)); gst_pad_set_query_function (pad, GST_DEBUG_FUNCPTR (gst_avi_demux_handle_src_query)); #if 0 gst_pad_set_convert_function (pad, GST_DEBUG_FUNCPTR (gst_avi_demux_src_convert)); #endif stream->num = avi->num_streams; stream->start_entry = 0; stream->step_entry = 0; stream->stop_entry = 0; stream->current_entry = -1; stream->current_total = 0; stream->last_flow = GST_FLOW_OK; stream->discont = TRUE; stream->total_bytes = 0; stream->total_blocks = 0; stream->n_keyframes = 0; stream->idx_n = 0; stream->idx_max = 0; gst_pad_set_element_private (pad, stream); avi->num_streams++; gst_pad_set_active (pad, TRUE); gst_pad_push_event (pad, gst_event_new_caps (caps)); gst_caps_unref (caps); /* make tags */ if (codec_name) { if (!stream->taglist) stream->taglist = gst_tag_list_new_empty (); avi->got_tags = TRUE; gst_tag_list_add (stream->taglist, GST_TAG_MERGE_APPEND, tag_name, codec_name, NULL); g_free (codec_name); } gst_buffer_unref (buf); return TRUE; /* ERRORS */ fail: { /* unref any mem that may be in use */ if (buf) gst_buffer_unref (buf); if (sub) gst_buffer_unref (sub); g_free (vprp); g_free (codec_name); gst_avi_demux_reset_stream (avi, stream); avi->num_streams++; return FALSE; } } /* * gst_avi_demux_parse_odml: * @avi: calling element (used for debug/error). * @buf: input buffer to be used for parsing. * * Read an openDML-2.0 extension header. Fills in the frame number * in the avi demuxer object when reading succeeds. */ static void gst_avi_demux_parse_odml (GstAviDemux * avi, GstBuffer * buf) { guint32 tag = 0; guint offset = 4; GstBuffer *sub = NULL; while (gst_riff_parse_chunk (GST_ELEMENT_CAST (avi), buf, &offset, &tag, &sub)) { switch (tag) { case GST_RIFF_TAG_dmlh:{ gst_riff_dmlh dmlh, *_dmlh; GstMapInfo map; /* sub == NULL is possible and means an empty buffer */ if (sub == NULL) goto next; gst_buffer_map (sub, &map, GST_MAP_READ); /* check size */ if (map.size < sizeof (gst_riff_dmlh)) { GST_ERROR_OBJECT (avi, "DMLH entry is too small (%" G_GSIZE_FORMAT " bytes, %d needed)", map.size, (int) sizeof (gst_riff_dmlh)); gst_buffer_unmap (sub, &map); goto next; } _dmlh = (gst_riff_dmlh *) map.data; dmlh.totalframes = GST_READ_UINT32_LE (&_dmlh->totalframes); gst_buffer_unmap (sub, &map); GST_INFO_OBJECT (avi, "dmlh tag found: totalframes: %u", dmlh.totalframes); avi->avih->tot_frames = dmlh.totalframes; goto next; } default: GST_WARNING_OBJECT (avi, "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 */ if (sub) { gst_buffer_unref (sub); sub = NULL; } break; } } if (buf) gst_buffer_unref (buf); } /* Index helper */ static guint gst_avi_demux_index_last (GstAviDemux * avi, GstAviStream * stream) { return stream->idx_n; } /* find a previous entry in the index with the given flags */ static guint gst_avi_demux_index_prev (GstAviDemux * avi, GstAviStream * stream, guint last, gboolean keyframe) { GstAviIndexEntry *entry; guint i; for (i = last; i > 0; i--) { entry = &stream->index[i - 1]; if (!keyframe || ENTRY_IS_KEYFRAME (entry)) { return i - 1; } } return 0; } static guint gst_avi_demux_index_next (GstAviDemux * avi, GstAviStream * stream, guint last, gboolean keyframe) { GstAviIndexEntry *entry; gint i; for (i = last + 1; i < stream->idx_n; i++) { entry = &stream->index[i]; if (!keyframe || ENTRY_IS_KEYFRAME (entry)) { return i; } } return stream->idx_n - 1; } static guint gst_avi_demux_index_entry_search (GstAviIndexEntry * entry, guint64 * total) { if (entry->total < *total) return -1; else if (entry->total > *total) return 1; return 0; } /* * gst_avi_demux_index_for_time: * @avi: Avi object * @stream: the stream * @time: a time position * * Finds the index entry which time is less or equal than the requested time. * Try to avoid binary search when we can convert the time to an index * position directly (for example for video frames with a fixed duration). * * Returns: the found position in the index. */ static guint gst_avi_demux_index_for_time (GstAviDemux * avi, GstAviStream * stream, guint64 time) { guint index = -1; guint64 total; GST_LOG_OBJECT (avi, "search time:%" GST_TIME_FORMAT, GST_TIME_ARGS (time)); /* easy (and common) cases */ if (time == 0 || stream->idx_n == 0) return 0; if (time >= stream->idx_duration) return stream->idx_n - 1; /* figure out where we need to go. For that we convert the time to an * index entry or we convert it to a total and then do a binary search. */ if (stream->is_vbr) { /* VBR stream next timestamp */ if (stream->strh->type == GST_RIFF_FCC_auds) { total = avi_stream_convert_time_to_frames_unchecked (stream, time); } else { index = avi_stream_convert_time_to_frames_unchecked (stream, time); } } else { /* constant rate stream */ total = avi_stream_convert_time_to_bytes_unchecked (stream, time); } if (index == -1) { GstAviIndexEntry *entry; /* no index, find index with binary search on total */ GST_LOG_OBJECT (avi, "binary search for entry with total %" G_GUINT64_FORMAT, total); entry = gst_util_array_binary_search (stream->index, stream->idx_n, sizeof (GstAviIndexEntry), (GCompareDataFunc) gst_avi_demux_index_entry_search, GST_SEARCH_MODE_BEFORE, &total, NULL); if (entry == NULL) { GST_LOG_OBJECT (avi, "not found, assume index 0"); index = 0; } else { index = entry - stream->index; GST_LOG_OBJECT (avi, "found at %u", index); } } else { GST_LOG_OBJECT (avi, "converted time to index %u", index); } return index; } static inline GstAviStream * gst_avi_demux_stream_for_id (GstAviDemux * avi, guint32 id) { guint stream_nr; GstAviStream *stream; /* get the stream for this entry */ stream_nr = CHUNKID_TO_STREAMNR (id); if (G_UNLIKELY (stream_nr >= avi->num_streams)) { GST_WARNING_OBJECT (avi, "invalid stream nr %d", stream_nr); return NULL; } stream = &avi->stream[stream_nr]; if (G_UNLIKELY (!stream->strh)) { GST_WARNING_OBJECT (avi, "Unhandled stream %d, skipping", stream_nr); return NULL; } return stream; } /* * gst_avi_demux_parse_index: * @avi: calling element (used for debugging/errors). * @buf: buffer containing the full index. * * Read index entries from the provided buffer. * The buffer should contain a GST_RIFF_TAG_idx1 chunk. */ static gboolean gst_avi_demux_parse_index (GstAviDemux * avi, GstBuffer * buf) { GstMapInfo map; guint i, num, n; gst_riff_index_entry *index; GstClockTime stamp; GstAviStream *stream; GstAviIndexEntry entry; guint32 id; if (!buf) return FALSE; gst_buffer_map (buf, &map, GST_MAP_READ); stamp = gst_util_get_timestamp (); /* see how many items in the index */ num = map.size / sizeof (gst_riff_index_entry); if (num == 0) goto empty_list; GST_INFO_OBJECT (avi, "Parsing index, nr_entries = %6d", num); index = (gst_riff_index_entry *) map.data; /* figure out if the index is 0 based or relative to the MOVI start */ entry.offset = GST_READ_UINT32_LE (&index[0].offset); if (entry.offset < avi->offset) { avi->index_offset = avi->offset + 8; GST_DEBUG ("index_offset = %" G_GUINT64_FORMAT, avi->index_offset); } else { avi->index_offset = 0; GST_DEBUG ("index is 0 based"); } for (i = 0, n = 0; i < num; i++) { id = GST_READ_UINT32_LE (&index[i].id); entry.offset = GST_READ_UINT32_LE (&index[i].offset); /* some sanity checks */ if (G_UNLIKELY (id == GST_RIFF_rec || id == 0 || (entry.offset == 0 && n > 0))) continue; /* get the stream for this entry */ stream = gst_avi_demux_stream_for_id (avi, id); if (G_UNLIKELY (!stream)) continue; /* handle offset and size */ entry.offset += avi->index_offset + 8; entry.size = GST_READ_UINT32_LE (&index[i].size); /* handle flags */ if (stream->strh->type == GST_RIFF_FCC_auds) { /* all audio frames are keyframes */ ENTRY_SET_KEYFRAME (&entry); } else { guint32 flags; /* else read flags */ flags = GST_READ_UINT32_LE (&index[i].flags); if (flags & GST_RIFF_IF_KEYFRAME) { ENTRY_SET_KEYFRAME (&entry); } else { ENTRY_UNSET_KEYFRAME (&entry); } } /* and add */ if (G_UNLIKELY (!gst_avi_demux_add_index (avi, stream, num, &entry))) goto out_of_mem; n++; } gst_buffer_unmap (buf, &map); gst_buffer_unref (buf); /* get stream stats now */ avi->have_index = gst_avi_demux_do_index_stats (avi); stamp = gst_util_get_timestamp () - stamp; GST_DEBUG_OBJECT (avi, "index parsing took %" GST_TIME_FORMAT, GST_TIME_ARGS (stamp)); return TRUE; /* ERRORS */ empty_list: { GST_DEBUG_OBJECT (avi, "empty index"); gst_buffer_unmap (buf, &map); gst_buffer_unref (buf); return FALSE; } out_of_mem: { GST_ELEMENT_ERROR (avi, RESOURCE, NO_SPACE_LEFT, (NULL), ("Cannot allocate memory for %u*%u=%u bytes", (guint) sizeof (GstAviIndexEntry), num, (guint) sizeof (GstAviIndexEntry) * num)); gst_buffer_unmap (buf, &map); gst_buffer_unref (buf); return FALSE; } } /* * gst_avi_demux_stream_index: * @avi: avi demuxer object. * * Seeks to index and reads it. */ static void gst_avi_demux_stream_index (GstAviDemux * avi) { GstFlowReturn res; guint64 offset = avi->offset; GstBuffer *buf = NULL; guint32 tag; guint32 size; GstMapInfo map; GST_DEBUG ("demux stream index at offset %" G_GUINT64_FORMAT, offset); /* get chunk information */ res = gst_pad_pull_range (avi->sinkpad, offset, 8, &buf); if (res != GST_FLOW_OK) goto pull_failed; gst_buffer_map (buf, &map, GST_MAP_READ); if (map.size < 8) goto too_small; /* check tag first before blindy trying to read 'size' bytes */ tag = GST_READ_UINT32_LE (map.data); size = GST_READ_UINT32_LE (map.data + 4); 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))); offset += 8 + GST_ROUND_UP_2 (size); gst_buffer_unmap (buf, &map); gst_buffer_unref (buf); buf = NULL; res = gst_pad_pull_range (avi->sinkpad, offset, 8, &buf); if (res != GST_FLOW_OK) goto pull_failed; gst_buffer_map (buf, &map, GST_MAP_READ); if (map.size < 8) goto too_small; tag = GST_READ_UINT32_LE (map.data); size = GST_READ_UINT32_LE (map.data + 4); } gst_buffer_unmap (buf, &map); gst_buffer_unref (buf); if (tag != GST_RIFF_TAG_idx1) goto no_index; if (!size) goto zero_index; GST_DEBUG ("index found at offset %" G_GUINT64_FORMAT, offset); /* read chunk, advance offset */ if (gst_riff_read_chunk (GST_ELEMENT_CAST (avi), avi->sinkpad, &offset, &tag, &buf) != GST_FLOW_OK) return; GST_DEBUG ("will parse index chunk size %" G_GSIZE_FORMAT " for tag %" GST_FOURCC_FORMAT, gst_buffer_get_size (buf), GST_FOURCC_ARGS (tag)); gst_avi_demux_parse_index (avi, buf); #ifndef GST_DISABLE_GST_DEBUG /* debug our indexes */ { 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, "pull range failed: pos=%" G_GUINT64_FORMAT " size=8", offset); return; } too_small: { GST_DEBUG_OBJECT (avi, "Buffer is too small"); gst_buffer_unmap (buf, &map); gst_buffer_unref (buf); return; } no_index: { GST_WARNING_OBJECT (avi, "No index data (idx1) after movi chunk, but %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (tag)); return; } zero_index: { GST_WARNING_OBJECT (avi, "Empty index data (idx1) after movi chunk"); return; } } /* * gst_avi_demux_stream_index_push: * @avi: avi demuxer object. * * Read index. */ static void gst_avi_demux_stream_index_push (GstAviDemux * avi) { guint64 offset = avi->idx1_offset; GstBuffer *buf; guint32 tag; guint32 size; GST_DEBUG ("demux stream index at offset %" G_GUINT64_FORMAT, offset); /* get chunk information */ if (!gst_avi_demux_peek_chunk (avi, &tag, &size)) return; /* 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; } if (tag != GST_RIFF_TAG_idx1) goto no_index; GST_DEBUG ("index found at offset %" G_GUINT64_FORMAT, offset); /* 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); GST_DEBUG ("will parse index chunk size %" G_GSIZE_FORMAT " for tag %" GST_FOURCC_FORMAT, gst_buffer_get_size (buf), GST_FOURCC_ARGS (tag)); avi->offset = avi->first_movi_offset; gst_avi_demux_parse_index (avi, buf); #ifndef GST_DISABLE_GST_DEBUG /* debug our indexes */ { 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; GstMapInfo map; res = gst_pad_pull_range (avi->sinkpad, offset, 8, &buf); if (res != GST_FLOW_OK) goto pull_failed; gst_buffer_map (buf, &map, GST_MAP_READ); if (map.size != 8) goto wrong_size; *tag = GST_READ_UINT32_LE (map.data); *size = GST_READ_UINT32_LE (map.data + 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_unmap (buf, &map); 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: { GST_DEBUG_OBJECT (avi, "got %" G_GSIZE_FORMAT " bytes which is <> 8 bytes", map.size); res = GST_FLOW_ERROR; goto done; } } /* * gst_avi_demux_next_data_buffer: * * Returns the offset and size of the next buffer * Position is the position of the buffer (after tag and size) */ static GstFlowReturn gst_avi_demux_next_data_buffer (GstAviDemux * avi, guint64 * offset, guint32 * tag, guint * size) { guint64 off = *offset; guint _size = 0; GstFlowReturn res; do { res = gst_avi_demux_peek_tag (avi, off, tag, &_size); if (res != GST_FLOW_OK) break; if (*tag == GST_RIFF_TAG_LIST || *tag == GST_RIFF_TAG_RIFF) off += 8 + 4; /* skip tag + size + subtag */ else { *offset = off + 8; *size = _size; break; } } while (TRUE); return res; } /* * gst_avi_demux_stream_scan: * @avi: calling element (used for debugging/errors). * * Scan the file for all chunks to "create" a new index. * pull-range based */ static gboolean gst_avi_demux_stream_scan (GstAviDemux * avi) { GstFlowReturn res; GstAviStream *stream; guint64 pos = 0; guint64 length; gint64 tmplength; guint32 tag = 0; guint num; /* FIXME: * - implement non-seekable source support. */ GST_DEBUG_OBJECT (avi, "Creating index"); /* get the size of the file */ if (!gst_pad_peer_query_duration (avi->sinkpad, GST_FORMAT_BYTES, &tmplength)) return FALSE; length = tmplength; /* guess the total amount of entries we expect */ num = 16000; while (TRUE) { GstAviIndexEntry entry; guint size = 0; /* start reading data buffers to find the id and offset */ res = gst_avi_demux_next_data_buffer (avi, &pos, &tag, &size); if (G_UNLIKELY (res != GST_FLOW_OK)) break; /* get stream */ stream = gst_avi_demux_stream_for_id (avi, tag); if (G_UNLIKELY (!stream)) goto next; /* we can't figure out the keyframes, assume they all are */ entry.flags = GST_AVI_KEYFRAME; entry.offset = pos; entry.size = size; /* and add to the index of this stream */ if (G_UNLIKELY (!gst_avi_demux_add_index (avi, stream, num, &entry))) goto out_of_mem; next: /* update position */ pos += GST_ROUND_UP_2 (size); if (G_UNLIKELY (pos > length)) { GST_WARNING_OBJECT (avi, "Stopping index lookup since we are further than EOF"); break; } } /* collect stats */ avi->have_index = gst_avi_demux_do_index_stats (avi); return TRUE; /* ERRORS */ out_of_mem: { GST_ELEMENT_ERROR (avi, RESOURCE, NO_SPACE_LEFT, (NULL), ("Cannot allocate memory for %u*%u=%u bytes", (guint) sizeof (GstAviIndexEntry), num, (guint) sizeof (GstAviIndexEntry) * num)); return FALSE; } } static void gst_avi_demux_calculate_durations_from_index (GstAviDemux * avi) { guint i; GstClockTime total; GstAviStream *stream; total = GST_CLOCK_TIME_NONE; /* all streams start at a timestamp 0 */ for (i = 0; i < avi->num_streams; i++) { GstClockTime duration, hduration; gst_riff_strh *strh; stream = &avi->stream[i]; if (G_UNLIKELY (!stream || !stream->idx_n || !(strh = stream->strh))) continue; /* get header duration for the stream */ hduration = stream->hdr_duration; /* index duration calculated during parsing */ duration = stream->idx_duration; /* now pick a good duration */ if (GST_CLOCK_TIME_IS_VALID (duration)) { /* index gave valid duration, use that */ GST_INFO ("Stream %p duration according to index: %" GST_TIME_FORMAT, stream, GST_TIME_ARGS (duration)); } else { /* 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 || (GST_CLOCK_TIME_IS_VALID (duration) && duration > total)) total = duration; } if (GST_CLOCK_TIME_IS_VALID (total) && (total > 0)) { /* now update the duration for those streams where we had none */ for (i = 0; i < avi->num_streams; i++) { stream = &avi->stream[i]; if (!GST_CLOCK_TIME_IS_VALID (stream->duration) || stream->duration == 0) { stream->duration = total; GST_INFO ("Stream %p duration according to total: %" GST_TIME_FORMAT, stream, GST_TIME_ARGS (total)); } } } /* and set the total duration in the segment. */ GST_INFO ("Setting total duration to: %" GST_TIME_FORMAT, GST_TIME_ARGS (total)); avi->segment.duration = total; } /* returns FALSE if there are no pads to deliver event to, * otherwise TRUE (whatever the outcome of event sending), * takes ownership of the event. */ static gboolean gst_avi_demux_push_event (GstAviDemux * avi, GstEvent * event) { gboolean result = FALSE; gint i; GST_DEBUG_OBJECT (avi, "sending %s event to %d streams", GST_EVENT_TYPE_NAME (event), avi->num_streams); for (i = 0; i < avi->num_streams; i++) { GstAviStream *stream = &avi->stream[i]; if (stream->pad) { result = TRUE; gst_pad_push_event (stream->pad, gst_event_ref (event)); } } gst_event_unref (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) { GST_DEBUG_OBJECT (avi, "doing duration query to fix up unset stop"); gst_pad_peer_query_duration (avi->sinkpad, GST_FORMAT_BYTES, &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 */ static GstFlowReturn gst_avi_demux_stream_header_push (GstAviDemux * avi) { GstFlowReturn ret = GST_FLOW_OK; guint32 tag = 0; guint32 ltag = 0; guint32 size = 0; const guint8 *data; GstBuffer *buf = NULL, *sub = NULL; guint offset = 4; gint i; GstTagList *tags = NULL; guint8 fourcc[4]; GST_DEBUG ("Reading and parsing avi headers: %d", avi->header_state); switch (avi->header_state) { case GST_AVI_DEMUX_HEADER_TAG_LIST: if (gst_avi_demux_peek_chunk (avi, &tag, &size)) { avi->offset += 8 + GST_ROUND_UP_2 (size); if (tag != GST_RIFF_TAG_LIST) goto header_no_list; gst_adapter_flush (avi->adapter, 8); /* Find the 'hdrl' LIST tag */ GST_DEBUG ("Reading %d bytes", size); buf = gst_adapter_take_buffer (avi->adapter, size); gst_buffer_extract (buf, 0, fourcc, 4); if (GST_READ_UINT32_LE (fourcc) != GST_RIFF_LIST_hdrl) goto header_no_hdrl; /* mind padding */ if (size & 1) gst_adapter_flush (avi->adapter, 1); GST_DEBUG ("'hdrl' LIST tag found. Parsing next chunk"); gst_avi_demux_roundup_list (avi, &buf); /* the hdrl starts with a 'avih' header */ 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 (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_CAST (avi), buf, &offset, &tag, &sub)) { /* sub can be NULL on empty tags */ if (!sub) continue; switch (tag) { case GST_RIFF_TAG_LIST: if (gst_buffer_get_size (sub) < 4) goto next; gst_buffer_extract (sub, 0, fourcc, 4); switch (GST_READ_UINT32_LE (fourcc)) { case GST_RIFF_LIST_strl: if (!(gst_avi_demux_parse_stream (avi, sub))) { sub = NULL; GST_ELEMENT_WARNING (avi, STREAM, DEMUX, (NULL), ("failed to parse stream, ignoring")); goto next; } sub = NULL; goto next; case GST_RIFF_LIST_odml: gst_avi_demux_parse_odml (avi, sub); sub = NULL; break; default: GST_WARNING_OBJECT (avi, "Unknown list %" GST_FOURCC_FORMAT " in AVI header", GST_FOURCC_ARGS (GST_READ_UINT32_LE (fourcc))); /* 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 */ if (sub) gst_buffer_unref (sub); sub = NULL; break; } } gst_buffer_unref (buf); GST_DEBUG ("elements parsed"); /* check parsed streams */ if (avi->num_streams == 0) { goto no_streams; } else if (avi->num_streams != avi->avih->streams) { GST_WARNING_OBJECT (avi, "Stream header mentioned %d streams, but %d available", avi->avih->streams, avi->num_streams); } GST_DEBUG ("Get junk and info next"); avi->header_state = GST_AVI_DEMUX_HEADER_INFO; } else { /* Need more data */ return ret; } /* fall-though */ case GST_AVI_DEMUX_HEADER_INFO: GST_DEBUG_OBJECT (avi, "skipping junk between header and data ..."); while (TRUE) { if (gst_adapter_available (avi->adapter) < 12) return GST_FLOW_OK; data = gst_adapter_map (avi->adapter, 12); tag = GST_READ_UINT32_LE (data); size = GST_READ_UINT32_LE (data + 4); ltag = GST_READ_UINT32_LE (data + 8); gst_adapter_unmap (avi->adapter); if (tag == GST_RIFF_TAG_LIST) { 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"); if (gst_avi_demux_peek_chunk (avi, &tag, &size)) { GST_DEBUG ("got size %d", size); avi->offset += 12; gst_adapter_flush (avi->adapter, 12); if (size > 4) { buf = gst_adapter_take_buffer (avi->adapter, size - 4); /* mind padding */ if (size & 1) gst_adapter_flush (avi->adapter, 1); 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; } else { GST_DEBUG ("skipping INFO LIST prefix"); } } else { /* Need more data */ return GST_FLOW_OK; } break; default: if (gst_avi_demux_peek_chunk (avi, &tag, &size)) { avi->offset += 8 + GST_ROUND_UP_2 (size); gst_adapter_flush (avi->adapter, 8 + GST_ROUND_UP_2 (size)); // ??? goto iterate; ??? } else { /* Need more data */ return GST_FLOW_OK; } break; } } else { if (gst_avi_demux_peek_chunk (avi, &tag, &size)) { avi->offset += 8 + GST_ROUND_UP_2 (size); gst_adapter_flush (avi->adapter, 8 + GST_ROUND_UP_2 (size)); //goto iterate; } else { /* Need more data */ return GST_FLOW_OK; } } } break; default: GST_WARNING ("unhandled header state: %d", avi->header_state); break; } skipping_done: GST_DEBUG_OBJECT (avi, "skipping done ... (streams=%u, stream[0].indexes=%p)", avi->num_streams, avi->stream[0].indexes); 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 (avi->seg_event) gst_event_unref (avi->seg_event); avi->seg_event = gst_event_new_segment (&avi->segment); 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_CAST (avi)); return GST_FLOW_OK; /* ERRORS */ no_streams: { GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("No streams found")); return GST_FLOW_ERROR; } header_no_list: { GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("Invalid AVI header (no LIST at start): %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (tag))); return GST_FLOW_ERROR; } header_no_hdrl: { GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("Invalid AVI header (no hdrl at start): %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (tag))); gst_buffer_unref (buf); return GST_FLOW_ERROR; } header_no_avih: { GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("Invalid AVI header (no avih at start): %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (tag))); if (sub) gst_buffer_unref (sub); gst_buffer_unref (buf); return GST_FLOW_ERROR; } header_wrong_avih: { gst_buffer_unref (buf); return GST_FLOW_ERROR; } } 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_empty (); 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 hr = 0, min = 0, sec = 0; gint ret; GST_DEBUG ("data : '%s'", data); ret = sscanf (data, "%d:%d:%d %d:%d:%d", &y, &m, &d, &hr, &min, &sec); if (ret < 3) { /* Attempt YYYY/MM/DD/ HH:MM variant (found in CASIO cameras) */ ret = sscanf (data, "%04d/%02d/%02d/ %d:%d", &y, &m, &d, &hr, &min); if (ret < 3) { GST_WARNING_OBJECT (avi, "Failed to parse IDIT tag"); return; } } gst_avi_demux_add_date_tag (avi, y, m, d, hr, min, sec); } 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) { GstMapInfo map; gchar *ptr; gsize left; gchar *safedata = NULL; gst_buffer_map (buf, &map, GST_MAP_READ); /* * 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 */ ptr = (gchar *) map.data; left = map.size; while (left > 0 && g_ascii_isspace (ptr[0])) { ptr++; left--; } if (left == 0) { goto non_parsable; } /* make a safe copy to add a \0 to the end of the string */ safedata = g_strndup (ptr, left); /* test if the first char is a alpha or a number */ if (g_ascii_isdigit (ptr[0])) { gst_avi_demux_parse_idit_nums_only (avi, safedata); g_free (safedata); return; } else if (g_ascii_isalpha (ptr[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"); gst_buffer_unmap (buf, &map); } /* * Read full AVI headers. */ static GstFlowReturn gst_avi_demux_stream_header_pull (GstAviDemux * avi) { GstFlowReturn res; GstBuffer *buf, *sub = NULL; guint32 tag; guint offset = 4; GstElement *element = GST_ELEMENT_CAST (avi); GstClockTime stamp; GstTagList *tags = NULL; guint8 fourcc[4]; stamp = gst_util_get_timestamp (); /* the header consists of a 'hdrl' LIST tag */ res = gst_riff_read_chunk (element, avi->sinkpad, &avi->offset, &tag, &buf); if (res != GST_FLOW_OK) goto pull_range_failed; else if (tag != GST_RIFF_TAG_LIST) goto no_list; else if (gst_buffer_get_size (buf) < 4) goto no_header; GST_DEBUG_OBJECT (avi, "parsing headers"); /* Find the 'hdrl' LIST tag */ gst_buffer_extract (buf, 0, fourcc, 4); while (GST_READ_UINT32_LE (fourcc) != GST_RIFF_LIST_hdrl) { GST_LOG_OBJECT (avi, "buffer contains %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (GST_READ_UINT32_LE (fourcc))); /* Eat up */ gst_buffer_unref (buf); /* read new chunk */ res = gst_riff_read_chunk (element, avi->sinkpad, &avi->offset, &tag, &buf); if (res != GST_FLOW_OK) goto pull_range_failed; else if (tag != GST_RIFF_TAG_LIST) goto no_list; else if (gst_buffer_get_size (buf) < 4) goto no_header; gst_buffer_extract (buf, 0, fourcc, 4); } GST_DEBUG_OBJECT (avi, "hdrl LIST tag found"); gst_avi_demux_roundup_list (avi, &buf); /* the hdrl starts with a 'avih' header */ if (!gst_riff_parse_chunk (element, buf, &offset, &tag, &sub)) goto no_avih; else if (tag != GST_RIFF_TAG_avih) goto no_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"); /* now, read the elements from the header until the end */ while (gst_riff_parse_chunk (element, buf, &offset, &tag, &sub)) { GstMapInfo map; /* sub can be NULL on empty tags */ if (!sub) continue; gst_buffer_map (sub, &map, GST_MAP_READ); switch (tag) { case GST_RIFF_TAG_LIST: if (map.size < 4) goto next; switch (GST_READ_UINT32_LE (map.data)) { case GST_RIFF_LIST_strl: if (!(gst_avi_demux_parse_stream (avi, sub))) { GST_ELEMENT_WARNING (avi, STREAM, DEMUX, (NULL), ("failed to parse stream, ignoring")); sub = NULL; } sub = NULL; goto next; case GST_RIFF_LIST_odml: gst_avi_demux_parse_odml (avi, sub); sub = NULL; break; case GST_RIFF_LIST_INFO: gst_buffer_resize (sub, 4, -1); 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", GST_FOURCC_ARGS (GST_READ_UINT32_LE (map.data))); GST_MEMDUMP_OBJECT (avi, "Unknown list", map.data, map.size); /* 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", GST_FOURCC_ARGS (tag), offset); GST_MEMDUMP_OBJECT (avi, "Unknown tag", map.data, map.size); /* fall-through */ case GST_RIFF_TAG_JUNQ: case GST_RIFF_TAG_JUNK: next: if (sub) { gst_buffer_unmap (sub, &map); gst_buffer_unref (sub); } sub = NULL; break; } } gst_buffer_unref (buf); GST_DEBUG ("elements parsed"); /* check parsed streams */ if (avi->num_streams == 0) goto no_streams; else if (avi->num_streams != avi->avih->streams) { GST_WARNING_OBJECT (avi, "Stream header mentioned %d streams, but %d available", avi->avih->streams, avi->num_streams); } GST_DEBUG_OBJECT (avi, "skipping junk between header and data, offset=%" G_GUINT64_FORMAT, avi->offset); /* Now, find the data (i.e. skip all junk between header and data) */ do { GstMapInfo map; guint size; guint32 tag, ltag; buf = NULL; res = gst_pad_pull_range (avi->sinkpad, avi->offset, 12, &buf); if (res != GST_FLOW_OK) { GST_DEBUG_OBJECT (avi, "pull_range failure while looking for tags"); goto pull_range_failed; } else if (gst_buffer_get_size (buf) < 12) { GST_DEBUG_OBJECT (avi, "got %" G_GSIZE_FORMAT " bytes which is less than 12 bytes", gst_buffer_get_size (buf)); gst_buffer_unref (buf); return GST_FLOW_ERROR; } gst_buffer_map (buf, &map, GST_MAP_READ); tag = GST_READ_UINT32_LE (map.data); size = GST_READ_UINT32_LE (map.data + 4); ltag = GST_READ_UINT32_LE (map.data + 8); GST_DEBUG ("tag %" GST_FOURCC_FORMAT ", size %u", GST_FOURCC_ARGS (tag), size); GST_MEMDUMP ("Tag content", map.data, map.size); gst_buffer_unmap (buf, &map); gst_buffer_unref (buf); switch (tag) { case GST_RIFF_TAG_LIST:{ switch (ltag) { case GST_RIFF_LIST_movi: GST_DEBUG_OBJECT (avi, "Reached the 'movi' tag, we're done with skipping"); goto skipping_done; case GST_RIFF_LIST_INFO: res = gst_riff_read_chunk (element, avi->sinkpad, &avi->offset, &tag, &buf); if (res != GST_FLOW_OK) { GST_DEBUG_OBJECT (avi, "couldn't read INFO chunk"); goto pull_range_failed; } GST_DEBUG ("got size %" G_GSIZE_FORMAT, gst_buffer_get_size (buf)); if (size < 4) { GST_DEBUG ("skipping INFO LIST prefix"); avi->offset += (4 - GST_ROUND_UP_2 (size)); gst_buffer_unref (buf); continue; } sub = gst_buffer_copy_region (buf, GST_BUFFER_COPY_ALL, 4, -1); 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; } gst_buffer_unref (buf); /* gst_riff_read_chunk() has already advanced avi->offset */ break; default: GST_WARNING_OBJECT (avi, "Skipping unknown list tag %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (ltag)); avi->offset += 8 + GST_ROUND_UP_2 (size); break; } } break; default: GST_WARNING_OBJECT (avi, "Skipping unknown tag %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (tag)); /* 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) { buf = NULL; 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_buffer_map (buf, &map, GST_MAP_READ); GST_MEMDUMP ("Junk", map.data, map.size); gst_buffer_unmap (buf, &map); gst_buffer_unref (buf); } avi->offset += 8 + GST_ROUND_UP_2 (size); break; } } while (1); skipping_done: GST_DEBUG_OBJECT (avi, "skipping done ... (streams=%u, stream[0].indexes=%p)", avi->num_streams, avi->stream[0].indexes); /* create or read stream index (for seeking) */ if (avi->stream[0].indexes != NULL) { /* we read a super index already (gst_avi_demux_parse_superindex() ) */ gst_avi_demux_read_subindexes_pull (avi); } if (!avi->have_index) { if (avi->avih->flags & GST_RIFF_AVIH_HASINDEX) gst_avi_demux_stream_index (avi); /* still no index, scan */ if (!avi->have_index) { gst_avi_demux_stream_scan (avi); /* still no index.. this is a fatal error for now. * FIXME, we should switch to plain push mode without seeking * instead of failing. */ if (!avi->have_index) goto no_index; } } /* use the indexes now to construct nice durations */ gst_avi_demux_calculate_durations_from_index (avi); gst_avi_demux_expose_streams (avi, FALSE); /* do initial seek to the default segment values */ gst_avi_demux_do_seek (avi, &avi->segment); /* create initial NEWSEGMENT event */ if (avi->seg_event) gst_event_unref (avi->seg_event); avi->seg_event = gst_event_new_segment (&avi->segment); stamp = gst_util_get_timestamp () - stamp; 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 * pads signal */ GST_DEBUG_OBJECT (avi, "signaling no more pads"); gst_element_no_more_pads (GST_ELEMENT_CAST (avi)); return GST_FLOW_OK; /* ERRORS */ no_list: { GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("Invalid AVI header (no LIST at start): %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (tag))); gst_buffer_unref (buf); return GST_FLOW_ERROR; } no_header: { GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("Invalid AVI header (no hdrl at start): %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (tag))); gst_buffer_unref (buf); return GST_FLOW_ERROR; } no_avih: { GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("Invalid AVI header (no avih at start): %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (tag))); if (sub) gst_buffer_unref (sub); gst_buffer_unref (buf); return GST_FLOW_ERROR; } invalid_avih: { GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("Invalid AVI header (cannot parse avih at start)")); gst_buffer_unref (buf); return GST_FLOW_ERROR; } no_streams: { GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("No streams found")); return GST_FLOW_ERROR; } no_index: { GST_WARNING ("file without or too big index"); GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("Could not get/create index")); return GST_FLOW_ERROR; } pull_range_failed: { GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("pull_range flow reading header: %s", gst_flow_get_name (res))); return GST_FLOW_ERROR; } } /* move a stream to @index */ static void gst_avi_demux_move_stream (GstAviDemux * avi, GstAviStream * stream, GstSegment * segment, guint index) { GST_DEBUG_OBJECT (avi, "Move stream %d to %u", stream->num, index); if (segment->rate < 0.0) { guint next_key; /* Because we don't know the frame order we need to push from the prev keyframe * to the next keyframe. If there is a smart decoder downstream he will notice * that there are too many encoded frames send and return EOS when there * are enough decoded frames to fill the segment. */ next_key = gst_avi_demux_index_next (avi, stream, index, TRUE); /* FIXME, we go back to 0, we should look at segment.start. We will however * stop earlier when the see the timestamp < segment.start */ stream->start_entry = 0; stream->step_entry = index; stream->current_entry = index; stream->stop_entry = next_key; GST_DEBUG_OBJECT (avi, "reverse seek: start %u, step %u, stop %u", stream->start_entry, stream->step_entry, stream->stop_entry); } else { stream->start_entry = index; stream->step_entry = index; stream->stop_entry = gst_avi_demux_index_last (avi, stream); } if (stream->current_entry != index) { GST_DEBUG_OBJECT (avi, "Move DISCONT from %u to %u", stream->current_entry, index); stream->current_entry = index; stream->discont = TRUE; } /* update the buffer info */ gst_avi_demux_get_buffer_info (avi, stream, index, &stream->current_timestamp, &stream->current_ts_end, &stream->current_offset, &stream->current_offset_end); GST_DEBUG_OBJECT (avi, "Moved to %u, ts %" GST_TIME_FORMAT ", ts_end %" GST_TIME_FORMAT ", off %" G_GUINT64_FORMAT ", off_end %" G_GUINT64_FORMAT, index, 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); } /* * Do the actual seeking. */ static gboolean gst_avi_demux_do_seek (GstAviDemux * avi, GstSegment * segment) { GstClockTime seek_time; gboolean keyframe; guint i, index; GstAviStream *stream; seek_time = segment->position; 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[avi->main_stream]; /* get the entry index for the requested position */ index = gst_avi_demux_index_for_time (avi, stream, seek_time); GST_DEBUG_OBJECT (avi, "Got entry %u", index); /* check if we are already on a keyframe */ if (!ENTRY_IS_KEYFRAME (&stream->index[index])) { GST_DEBUG_OBJECT (avi, "not 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, "previous keyframe at %u", index); } /* move the main stream to this position */ gst_avi_demux_move_stream (avi, stream, segment, index); if (keyframe) { /* when seeking to a keyframe, we update the result seek time * to the time of the keyframe. */ seek_time = stream->current_timestamp; GST_DEBUG_OBJECT (avi, "keyframe adjusted to %" GST_TIME_FORMAT, GST_TIME_ARGS (seek_time)); } /* the seek time is also the position and stream time when going * forwards */ segment->position = seek_time; if (segment->rate > 0.0) segment->time = seek_time; /* now set DISCONT and align the other streams */ for (i = 0; i < avi->num_streams; i++) { GstAviStream *ostream; ostream = &avi->stream[i]; if ((ostream == stream) || (ostream->index == NULL)) continue; /* get the entry index for the requested position */ index = gst_avi_demux_index_for_time (avi, ostream, seek_time); /* move to previous keyframe */ if (!ENTRY_IS_KEYFRAME (&ostream->index[index])) index = gst_avi_demux_index_prev (avi, ostream, index, TRUE); gst_avi_demux_move_stream (avi, ostream, segment, index); } GST_DEBUG_OBJECT (avi, "done seek to: %" GST_TIME_FORMAT, GST_TIME_ARGS (seek_time)); return TRUE; } /* * Handle seek event in pull mode. */ static gboolean gst_avi_demux_handle_seek (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 flush; gboolean update; GstSegment seeksegment = { 0, }; gint i; if (event) { GST_DEBUG_OBJECT (avi, "doing seek with event"); gst_event_parse_seek (event, &rate, &format, &flags, &cur_type, &cur, &stop_type, &stop); /* we have to have a format as the segment format. Try to convert * if not. */ if (format != GST_FORMAT_TIME) { gboolean res = TRUE; if (cur_type != GST_SEEK_TYPE_NONE) res = gst_pad_query_convert (pad, format, cur, GST_FORMAT_TIME, &cur); if (res && stop_type != GST_SEEK_TYPE_NONE) res = gst_pad_query_convert (pad, format, stop, GST_FORMAT_TIME, &stop); if (!res) goto no_format; format = GST_FORMAT_TIME; } GST_DEBUG_OBJECT (avi, "seek requested: rate %g cur %" GST_TIME_FORMAT " stop %" GST_TIME_FORMAT, rate, GST_TIME_ARGS (cur), GST_TIME_ARGS (stop)); /* FIXME: can we do anything with rate!=1.0 */ } else { GST_DEBUG_OBJECT (avi, "doing seek without event"); flags = 0; rate = 1.0; } /* save flush flag */ flush = flags & GST_SEEK_FLAG_FLUSH; if (flush) { GstEvent *fevent = gst_event_new_flush_start (); /* for a flushing seek, we send a flush_start on all pads. This will * eventually stop streaming with a WRONG_STATE. We can thus eventually * take the STREAM_LOCK. */ GST_DEBUG_OBJECT (avi, "sending flush start"); gst_avi_demux_push_event (avi, gst_event_ref (fevent)); gst_pad_push_event (avi->sinkpad, fevent); } else { /* a non-flushing seek, we PAUSE the task so that we can take the * STREAM_LOCK */ GST_DEBUG_OBJECT (avi, "non flushing seek, pausing task"); gst_pad_pause_task (avi->sinkpad); } /* wait for streaming to stop */ GST_DEBUG_OBJECT (avi, "wait for streaming to stop"); GST_PAD_STREAM_LOCK (avi->sinkpad); /* copy segment, we need this because we still need the old * segment when we close the current segment. */ memcpy (&seeksegment, &avi->segment, sizeof (GstSegment)); if (event) { GST_DEBUG_OBJECT (avi, "configuring seek"); gst_segment_do_seek (&seeksegment, rate, format, flags, cur_type, cur, stop_type, stop, &update); } /* do the seek, seeksegment.position contains the new position, this * actually never fails. */ gst_avi_demux_do_seek (avi, &seeksegment); if (flush) { GstEvent *fevent = gst_event_new_flush_stop (TRUE); GST_DEBUG_OBJECT (avi, "sending flush stop"); gst_avi_demux_push_event (avi, gst_event_ref (fevent)); gst_pad_push_event (avi->sinkpad, fevent); } /* now update the real segment info */ memcpy (&avi->segment, &seeksegment, sizeof (GstSegment)); /* post the SEGMENT_START message when we do segmented playback */ if (avi->segment.flags & GST_SEEK_FLAG_SEGMENT) { gst_element_post_message (GST_ELEMENT_CAST (avi), gst_message_new_segment_start (GST_OBJECT_CAST (avi), avi->segment.format, avi->segment.position)); } /* queue the segment event for the streaming thread. */ if (avi->seg_event) gst_event_unref (avi->seg_event); avi->seg_event = gst_event_new_segment (&avi->segment); if (!avi->streaming) { 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; /* ERRORS */ no_format: { GST_DEBUG_OBJECT (avi, "unsupported format given, seek aborted."); return FALSE; } } /* * 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) { gboolean res = TRUE; if (cur_type != GST_SEEK_TYPE_NONE) res = gst_pad_query_convert (pad, format, cur, GST_FORMAT_TIME, &cur); if (res && stop_type != GST_SEEK_TYPE_NONE) res = gst_pad_query_convert (pad, format, stop, GST_FORMAT_TIME, &stop); if (!res) { GST_DEBUG_OBJECT (avi, "unsupported format given, seek aborted."); return FALSE; } format = GST_FORMAT_TIME; } /* let gst_segment handle any tricky stuff */ GST_DEBUG_OBJECT (avi, "configuring seek"); memcpy (&seeksegment, &avi->segment, sizeof (GstSegment)); gst_segment_do_seek (&seeksegment, rate, format, flags, cur_type, cur, stop_type, stop, &update); keyframe = ! !(flags & GST_SEEK_FLAG_KEY_UNIT); cur = seeksegment.position; 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 swap_line (guint8 * d1, guint8 * d2, guint8 * tmp, gint bytes) { memcpy (tmp, d1, bytes); memcpy (d1, d2, bytes); memcpy (d2, tmp, bytes); } #define gst_avi_demux_is_uncompressed(fourcc) \ (fourcc && \ (fourcc == GST_RIFF_DIB || \ fourcc == GST_RIFF_rgb || \ fourcc == GST_RIFF_RGB || fourcc == GST_RIFF_RAW)) /* * Invert DIB buffers... Takes existing buffer and * returns either the buffer or a new one (with old * one dereferenced). * FIXME: can't we preallocate tmp? and remember stride, bpp? */ static GstBuffer * gst_avi_demux_invert (GstAviStream * stream, GstBuffer * buf) { GstStructure *s; gint y, w, h; gint bpp, stride; guint8 *tmp = NULL; GstMapInfo map; GstCaps *caps; if (stream->strh->type != GST_RIFF_FCC_vids) return buf; if (!gst_avi_demux_is_uncompressed (stream->strh->fcc_handler)) { return buf; /* Ignore non DIB buffers */ } caps = gst_pad_get_current_caps (stream->pad); s = gst_caps_get_structure (caps, 0); gst_caps_unref (caps); if (!gst_structure_get_int (s, "bpp", &bpp)) { GST_WARNING ("Failed to retrieve depth from caps"); return buf; } if (stream->strf.vids == NULL) { GST_WARNING ("Failed to retrieve vids for stream"); return buf; } h = stream->strf.vids->height; w = stream->strf.vids->width; stride = GST_ROUND_UP_4 (w * (bpp / 8)); buf = gst_buffer_make_writable (buf); gst_buffer_map (buf, &map, GST_MAP_READWRITE); if (map.size < (stride * h)) { GST_WARNING ("Buffer is smaller than reported Width x Height x Depth"); gst_buffer_unmap (buf, &map); return buf; } tmp = g_malloc (stride); for (y = 0; y < h / 2; y++) { swap_line (map.data + stride * y, map.data + stride * (h - 1 - y), tmp, stride); } g_free (tmp); gst_buffer_unmap (buf, &map); return buf; } #if 0 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_DELTA_UNIT, GST_FORMAT_TIME, timestamp, GST_FORMAT_BYTES, offset, NULL); /* current_entry is DEFAULT (frame #) */ gst_index_add_association (avi->element_index, stream->index_id, keyframe ? GST_ASSOCIATION_FLAG_KEY_UNIT : GST_ASSOCIATION_FLAG_DELTA_UNIT, GST_FORMAT_TIME, timestamp, GST_FORMAT_BYTES, offset, GST_FORMAT_DEFAULT, stream->current_entry, NULL); } } #endif /* * Returns the aggregated GstFlowReturn. */ static GstFlowReturn 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 or eos can be returned right away */ if (G_LIKELY (ret != GST_FLOW_EOS && ret != GST_FLOW_NOT_LINKED)) goto done; /* only return NOT_LINKED if all other pads returned NOT_LINKED */ for (i = 0; i < avi->num_streams; i++) { GstAviStream *ostream = &avi->stream[i]; ret = ostream->last_flow; /* no unexpected or unlinked, return */ if (G_LIKELY (ret != GST_FLOW_EOS && 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_EOS); not_linked &= (ret == GST_FLOW_NOT_LINKED); } /* when we get here, we all have unlinked or unexpected */ if (not_linked) ret = GST_FLOW_NOT_LINKED; else if (unexpected) ret = GST_FLOW_EOS; done: GST_LOG_OBJECT (avi, "combined %s to return %s", gst_flow_get_name (stream->last_flow), gst_flow_get_name (ret)); return ret; } /* move @stream to the next position in its index */ static GstFlowReturn gst_avi_demux_advance (GstAviDemux * avi, GstAviStream * stream, GstFlowReturn ret) { guint old_entry, new_entry; old_entry = stream->current_entry; /* move forwards */ new_entry = old_entry + 1; /* see if we reached the end */ if (new_entry >= stream->stop_entry) { if (avi->segment.rate < 0.0) { if (stream->step_entry == stream->start_entry) { /* we stepped all the way to the start, eos */ GST_DEBUG_OBJECT (avi, "reverse reached start %u", stream->start_entry); goto eos; } /* backwards, stop becomes step, find a new step */ stream->stop_entry = stream->step_entry; stream->step_entry = gst_avi_demux_index_prev (avi, stream, stream->stop_entry, TRUE); GST_DEBUG_OBJECT (avi, "reverse playback jump: start %u, step %u, stop %u", stream->start_entry, stream->step_entry, stream->stop_entry); /* and start from the previous keyframe now */ new_entry = stream->step_entry; } else { /* EOS */ GST_DEBUG_OBJECT (avi, "forward reached stop %u", stream->stop_entry); goto eos; } } if (new_entry != old_entry) { stream->current_entry = new_entry; stream->current_total = stream->index[new_entry].total; if (new_entry == old_entry + 1) { GST_DEBUG_OBJECT (avi, "moved forwards from %u to %u", old_entry, new_entry); /* we simply moved one step forwards, reuse current info */ stream->current_timestamp = stream->current_ts_end; stream->current_offset = stream->current_offset_end; gst_avi_demux_get_buffer_info (avi, stream, new_entry, NULL, &stream->current_ts_end, NULL, &stream->current_offset_end); } else { /* we moved DISCONT, full update */ gst_avi_demux_get_buffer_info (avi, stream, new_entry, &stream->current_timestamp, &stream->current_ts_end, &stream->current_offset, &stream->current_offset_end); /* and MARK discont for this stream */ stream->last_flow = GST_FLOW_OK; stream->discont = TRUE; GST_DEBUG_OBJECT (avi, "Moved from %u to %u, ts %" GST_TIME_FORMAT ", ts_end %" GST_TIME_FORMAT ", off %" G_GUINT64_FORMAT ", off_end %" G_GUINT64_FORMAT, old_entry, new_entry, GST_TIME_ARGS (stream->current_timestamp), GST_TIME_ARGS (stream->current_ts_end), stream->current_offset, stream->current_offset_end); } } return ret; /* ERROR */ eos: { GST_DEBUG_OBJECT (avi, "we are EOS"); /* setting current_timestamp to -1 marks EOS */ stream->current_timestamp = -1; return GST_FLOW_EOS; } } /* find the stream with the lowest current position when going forwards or with * the highest position when going backwards, this is the stream * we should push from next */ static gint gst_avi_demux_find_next (GstAviDemux * avi, gfloat rate) { guint64 min_time, max_time; guint stream_num, i; max_time = 0; min_time = G_MAXUINT64; stream_num = -1; for (i = 0; i < avi->num_streams; i++) { guint64 position; GstAviStream *stream; stream = &avi->stream[i]; /* ignore streams that finished */ if (stream->last_flow == GST_FLOW_EOS) continue; position = stream->current_timestamp; /* position of -1 is EOS */ if (position != -1) { if (rate > 0.0 && position < min_time) { min_time = position; stream_num = i; } else if (rate < 0.0 && position >= max_time) { max_time = position; stream_num = i; } } } return stream_num; } static GstFlowReturn gst_avi_demux_loop_data (GstAviDemux * avi) { GstFlowReturn ret = GST_FLOW_OK; guint stream_num; GstAviStream *stream; gboolean processed = FALSE; GstBuffer *buf; guint64 offset, size; GstClockTime timestamp, duration; guint64 out_offset, out_offset_end; gboolean keyframe; GstAviIndexEntry *entry; do { stream_num = gst_avi_demux_find_next (avi, avi->segment.rate); /* all are EOS */ if (G_UNLIKELY (stream_num == -1)) { GST_DEBUG_OBJECT (avi, "all streams are EOS"); goto eos; } /* we have the stream now */ stream = &avi->stream[stream_num]; /* skip streams without pads */ if (!stream->pad) { GST_DEBUG_OBJECT (avi, "skipping entry from stream %d without pad", stream_num); goto next; } /* get the timing info for the entry */ timestamp = stream->current_timestamp; duration = stream->current_ts_end - timestamp; out_offset = stream->current_offset; out_offset_end = stream->current_offset_end; /* get the entry data info */ entry = &stream->index[stream->current_entry]; offset = entry->offset; size = entry->size; keyframe = ENTRY_IS_KEYFRAME (entry); /* skip empty entries */ if (size == 0) { GST_DEBUG_OBJECT (avi, "Skipping entry %u (%" G_GUINT64_FORMAT ", %p)", stream->current_entry, size, stream->pad); goto next; } if (avi->segment.rate > 0.0) { /* only check this for fowards playback for now */ if (keyframe && GST_CLOCK_TIME_IS_VALID (avi->segment.stop) && (timestamp > avi->segment.stop)) { goto eos_stop; } } GST_LOG ("reading buffer (size=%" G_GUINT64_FORMAT "), stream %d, pos %" G_GUINT64_FORMAT " (0x%" G_GINT64_MODIFIER "x), kf %d", size, stream_num, offset, offset, keyframe); /* FIXME, check large chunks and cut them up */ /* pull in the data */ buf = NULL; ret = gst_pad_pull_range (avi->sinkpad, offset, size, &buf); if (ret != GST_FLOW_OK) goto pull_failed; /* check for short buffers, this is EOS as well */ if (gst_buffer_get_size (buf) < size) goto short_buffer; /* invert the picture if needed */ buf = gst_avi_demux_invert (stream, buf); /* mark non-keyframes */ if (keyframe) { GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DELTA_UNIT); GST_BUFFER_PTS (buf) = timestamp; } else { GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT); GST_BUFFER_PTS (buf) = GST_CLOCK_TIME_NONE; } GST_BUFFER_DTS (buf) = timestamp; GST_BUFFER_DURATION (buf) = duration; GST_BUFFER_OFFSET (buf) = out_offset; GST_BUFFER_OFFSET_END (buf) = out_offset_end; /* 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; } #if 0 gst_avi_demux_add_assoc (avi, stream, timestamp, offset, keyframe); #endif /* update current position in the segment */ avi->segment.position = timestamp; GST_DEBUG_OBJECT (avi, "Pushing buffer of size %" G_GSIZE_FORMAT ", ts %" GST_TIME_FORMAT ", dur %" GST_TIME_FORMAT ", off %" G_GUINT64_FORMAT ", off_end %" G_GUINT64_FORMAT, gst_buffer_get_size (buf), GST_TIME_ARGS (timestamp), GST_TIME_ARGS (duration), out_offset, out_offset_end); ret = gst_pad_push (stream->pad, buf); /* mark as processed, we increment the frame and byte counters then * leave the while loop and return the GstFlowReturn */ processed = TRUE; if (avi->segment.rate < 0) { if (timestamp > avi->segment.stop && ret == GST_FLOW_EOS) { /* In reverse playback we can get a GST_FLOW_EOS when * we are at the end of the segment, so we just need to jump * back to the previous section. */ GST_DEBUG_OBJECT (avi, "downstream has reached end of segment"); ret = GST_FLOW_OK; } } next: /* move to next item */ ret = gst_avi_demux_advance (avi, stream, ret); /* combine flows */ ret = gst_avi_demux_combine_flows (avi, stream, ret); } while (!processed); beach: return ret; /* special cases */ eos: { GST_DEBUG_OBJECT (avi, "No samples left for any streams - EOS"); ret = GST_FLOW_EOS; goto beach; } eos_stop: { GST_LOG_OBJECT (avi, "Found keyframe after segment," " setting EOS (%" GST_TIME_FORMAT " > %" GST_TIME_FORMAT ")", GST_TIME_ARGS (timestamp), GST_TIME_ARGS (avi->segment.stop)); ret = GST_FLOW_EOS; /* move to next stream */ goto next; } pull_failed: { GST_DEBUG_OBJECT (avi, "pull range failed: pos=%" G_GUINT64_FORMAT " size=%" G_GUINT64_FORMAT, offset, size); goto beach; } short_buffer: { GST_WARNING_OBJECT (avi, "Short read at offset %" G_GUINT64_FORMAT ", only got %" G_GSIZE_FORMAT "/%" G_GUINT64_FORMAT " bytes (truncated file?)", offset, gst_buffer_get_size (buf), size); gst_buffer_unref (buf); ret = GST_FLOW_EOS; goto beach; } } /* * Read data. If we have an index it delegates to * gst_avi_demux_process_next_entry(). */ static GstFlowReturn gst_avi_demux_stream_data (GstAviDemux * avi) { guint32 tag = 0; guint32 size = 0; gint stream_nr = 0; GstFlowReturn res = GST_FLOW_OK; if (G_UNLIKELY (avi->have_eos)) { /* Clean adapter, we're done */ gst_adapter_clear (avi->adapter); return GST_FLOW_EOS; } 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 */ while (1) { if (G_UNLIKELY (!gst_avi_demux_peek_chunk_info (avi, &tag, &size))) { return GST_FLOW_OK; } GST_DEBUG ("Trying chunk (%" GST_FOURCC_FORMAT "), size %d", GST_FOURCC_ARGS (tag), size); if (G_LIKELY ((tag & 0xff) >= '0' && (tag & 0xff) <= '9' && ((tag >> 8) & 0xff) >= '0' && ((tag >> 8) & 0xff) <= '9')) { GST_LOG ("Chunk ok"); } else if ((tag & 0xffff) == (('x' << 8) | 'i')) { GST_DEBUG ("Found sub-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_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"); 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) { GST_DEBUG ("Found LIST tag, skipping LIST header"); gst_adapter_flush (avi->adapter, 12); continue; } return GST_FLOW_OK; } 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) { /* 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 { GST_DEBUG ("No more stream chunks, send EOS"); avi->have_eos = TRUE; return GST_FLOW_EOS; } if (G_UNLIKELY (!gst_avi_demux_peek_chunk (avi, &tag, &size))) { /* supposedly one hopes to catch a nicer chunk later on ... */ /* FIXME ?? give up here rather than possibly ending up going * through the whole file */ if (avi->abort_buffering) { avi->abort_buffering = FALSE; if (size) { gst_adapter_flush (avi->adapter, 8); return GST_FLOW_OK; } } else { return GST_FLOW_OK; } } GST_DEBUG ("chunk ID %" GST_FOURCC_FORMAT ", size %u", GST_FOURCC_ARGS (tag), size); stream_nr = CHUNKID_TO_STREAMNR (tag); if (G_UNLIKELY (stream_nr < 0 || stream_nr >= avi->num_streams)) { /* recoverable */ GST_WARNING ("Invalid stream ID %d (%" GST_FOURCC_FORMAT ")", stream_nr, GST_FOURCC_ARGS (tag)); avi->offset += 8 + GST_ROUND_UP_2 (size); gst_adapter_flush (avi->adapter, 8 + GST_ROUND_UP_2 (size)); } else { GstAviStream *stream; GstClockTime next_ts = 0; GstBuffer *buf = NULL; #if 0 guint64 offset; #endif 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; } 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_resize (buf, 0, 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)); } #if 0 offset = avi->offset; #endif avi->offset += 8 + GST_ROUND_UP_2 (size); stream = &avi->stream[stream_nr]; /* set delay (if any) if (stream->strh->init_frames == stream->current_frame && stream->delay == 0) stream->delay = next_ts; */ /* parsing of corresponding header may have failed */ if (G_UNLIKELY (!stream->pad)) { GST_WARNING_OBJECT (avi, "no pad for stream ID %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (tag)); if (buf) gst_buffer_unref (buf); } else { /* get time of this buffer */ gst_pad_query_position (stream->pad, GST_FORMAT_TIME, (gint64 *) & next_ts); #if 0 gst_avi_demux_add_assoc (avi, stream, next_ts, offset, FALSE); #endif /* increment our positions */ stream->current_entry++; stream->current_total += size; /* update current position in the segment */ avi->segment.position = next_ts; if (saw_desired_kf && buf) { GstClockTime dur_ts = 0; /* invert the picture if needed */ buf = gst_avi_demux_invert (stream, buf); gst_pad_query_position (stream->pad, GST_FORMAT_TIME, (gint64 *) & dur_ts); GST_BUFFER_DTS (buf) = next_ts; GST_BUFFER_PTS (buf) = GST_CLOCK_TIME_NONE; 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; } 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; } } } } } return res; } /* * Send pending tags. */ static void push_tag_lists (GstAviDemux * avi) { guint i; GstTagList *tags; if (!avi->got_tags) return; GST_DEBUG_OBJECT (avi, "Pushing pending tag lists"); for (i = 0; i < avi->num_streams; i++) { GstAviStream *stream = &avi->stream[i]; GstPad *pad = stream->pad; tags = stream->taglist; if (pad && tags) { GST_DEBUG_OBJECT (pad, "Tags: %" GST_PTR_FORMAT, tags); gst_pad_push_event (pad, gst_event_new_tag (tags)); stream->taglist = NULL; } } if (!(tags = avi->globaltags)) tags = gst_tag_list_new_empty (); gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_CONTAINER_FORMAT, "AVI", NULL); GST_DEBUG_OBJECT (avi, "Global tags: %" GST_PTR_FORMAT, tags); gst_avi_demux_push_event (avi, gst_event_new_tag (tags)); avi->globaltags = NULL; avi->got_tags = FALSE; } static void gst_avi_demux_loop (GstPad * pad) { GstFlowReturn res; GstAviDemux *avi = GST_AVI_DEMUX (GST_PAD_PARENT (pad)); switch (avi->state) { case GST_AVI_DEMUX_START: res = gst_avi_demux_stream_init_pull (avi); if (G_UNLIKELY (res != GST_FLOW_OK)) { GST_WARNING ("stream_init flow: %s", gst_flow_get_name (res)); goto pause; } avi->state = GST_AVI_DEMUX_HEADER; /* fall-through */ case GST_AVI_DEMUX_HEADER: res = gst_avi_demux_stream_header_pull (avi); if (G_UNLIKELY (res != GST_FLOW_OK)) { GST_WARNING ("stream_header flow: %s", gst_flow_get_name (res)); goto pause; } avi->state = GST_AVI_DEMUX_MOVI; break; case GST_AVI_DEMUX_MOVI: 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); } /* process each index entry in turn */ res = gst_avi_demux_loop_data (avi); /* pause when error */ if (G_UNLIKELY (res != GST_FLOW_OK)) { GST_INFO ("stream_movi flow: %s", gst_flow_get_name (res)); goto pause; } break; default: GST_ERROR_OBJECT (avi, "unknown state %d", avi->state); res = GST_FLOW_ERROR; goto pause; } return; /* ERRORS */ pause:{ gboolean push_eos = FALSE; GST_LOG_OBJECT (avi, "pausing task, reason %s", gst_flow_get_name (res)); gst_pad_pause_task (avi->sinkpad); if (res == GST_FLOW_EOS) { /* handle end-of-stream/segment */ /* so align our position with the end of it, if there is one * this ensures a subsequent will arrive at correct base/acc time */ if (avi->segment.rate > 0.0 && GST_CLOCK_TIME_IS_VALID (avi->segment.stop)) avi->segment.position = avi->segment.stop; else if (avi->segment.rate < 0.0) avi->segment.position = avi->segment.start; if (avi->segment.flags & GST_SEEK_FLAG_SEGMENT) { gint64 stop; if ((stop = avi->segment.stop) == -1) stop = avi->segment.duration; GST_INFO_OBJECT (avi, "sending segment_done"); gst_element_post_message (GST_ELEMENT_CAST (avi), gst_message_new_segment_done (GST_OBJECT_CAST (avi), GST_FORMAT_TIME, stop)); } else { push_eos = TRUE; } } else if (res == GST_FLOW_NOT_LINKED || res < GST_FLOW_EOS) { /* 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"); if (!gst_avi_demux_push_event (avi, gst_event_new_eos ()) && (res == GST_FLOW_EOS)) { GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("got eos but no streams (yet)")); } } } } static GstFlowReturn gst_avi_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buf) { GstFlowReturn res; GstAviDemux *avi = GST_AVI_DEMUX (parent); 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 %" G_GSIZE_FORMAT " bytes in adapter", gst_buffer_get_size (buf)); gst_adapter_push (avi->adapter, buf); switch (avi->state) { case GST_AVI_DEMUX_START: if ((res = gst_avi_demux_stream_init_push (avi)) != GST_FLOW_OK) { GST_WARNING ("stream_init flow: %s", gst_flow_get_name (res)); break; } break; case GST_AVI_DEMUX_HEADER: if ((res = gst_avi_demux_stream_header_push (avi)) != GST_FLOW_OK) { GST_WARNING ("stream_header flow: %s", gst_flow_get_name (res)); break; } break; case GST_AVI_DEMUX_MOVI: 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")); res = GST_FLOW_ERROR; break; } GST_DEBUG_OBJECT (avi, "state: %d res:%s", avi->state, gst_flow_get_name (res)); 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; GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("unhandled buffer size")); return GST_FLOW_ERROR; } } static gboolean gst_avi_demux_sink_activate (GstPad * sinkpad, GstObject * parent) { GstQuery *query; gboolean pull_mode; query = gst_query_new_scheduling (); if (!gst_pad_peer_query (sinkpad, query)) { gst_query_unref (query); goto activate_push; } pull_mode = gst_query_has_scheduling_mode (query, GST_PAD_MODE_PULL); gst_query_unref (query); if (!pull_mode) goto activate_push; GST_DEBUG_OBJECT (sinkpad, "activating pull"); return gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PULL, TRUE); activate_push: { GST_DEBUG_OBJECT (sinkpad, "activating push"); return gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PUSH, TRUE); } } static gboolean gst_avi_demux_sink_activate_mode (GstPad * sinkpad, GstObject * parent, GstPadMode mode, gboolean active) { gboolean res; GstAviDemux *avi = GST_AVI_DEMUX (parent); switch (mode) { case GST_PAD_MODE_PULL: if (active) { avi->streaming = FALSE; res = gst_pad_start_task (sinkpad, (GstTaskFunction) gst_avi_demux_loop, sinkpad); } else { res = gst_pad_stop_task (sinkpad); } break; case GST_PAD_MODE_PUSH: if (active) { GST_DEBUG ("avi: activating push/chain function"); avi->streaming = TRUE; } else { GST_DEBUG ("avi: deactivating push/chain function"); } res = TRUE; break; default: res = FALSE; break; } return res; } #if 0 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; } #endif static GstStateChangeReturn gst_avi_demux_change_state (GstElement * element, GstStateChange transition) { GstStateChangeReturn ret; GstAviDemux *avi = GST_AVI_DEMUX (element); switch (transition) { case GST_STATE_CHANGE_READY_TO_PAUSED: avi->streaming = FALSE; gst_segment_init (&avi->segment, GST_FORMAT_TIME); break; default: break; } ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); if (ret == GST_STATE_CHANGE_FAILURE) goto done; switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY: avi->have_index = FALSE; gst_avi_demux_reset (avi); break; default: break; } done: return ret; }