From bf9d8dbbdc9499a3b97cc3e2a21a8743320396b3 Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Fri, 12 Feb 2010 16:06:45 +0100 Subject: [PATCH] flvdemux: Obtain the index from the end of an flv file in push mode Allows for better support of seeking in flv files when in push mode --- gst/flv/gstflvdemux.c | 179 +++++++++++++++++++++++++++++++++++++++++- gst/flv/gstflvdemux.h | 7 ++ gst/flv/gstflvparse.c | 70 +++++++++-------- 3 files changed, 221 insertions(+), 35 deletions(-) diff --git a/gst/flv/gstflvdemux.c b/gst/flv/gstflvdemux.c index 4492d30bea..144042722b 100644 --- a/gst/flv/gstflvdemux.c +++ b/gst/flv/gstflvdemux.c @@ -39,6 +39,7 @@ #include "gstflvmux.h" #include +#include static GstStaticPadTemplate flv_sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, @@ -68,6 +69,9 @@ GST_BOILERPLATE (GstFLVDemux, gst_flv_demux, GstElement, GST_TYPE_ELEMENT); /* 1 byte of tag type + 3 bytes of tag data size */ #define FLV_TAG_TYPE_SIZE 4 +static gboolean flv_demux_handle_seek_push (GstFLVDemux * demux, + GstEvent * event); + static void gst_flv_demux_flush (GstFLVDemux * demux, gboolean discont) { @@ -80,8 +84,8 @@ gst_flv_demux_flush (GstFLVDemux * demux, gboolean discont) demux->flushing = FALSE; - /* Only in push mode */ - if (!demux->random_access) { + /* Only in push mode and if we're not during a seek */ + if (!demux->random_access && demux->state != FLV_STATE_SEEK) { /* After a flush we expect a tag_type */ demux->state = FLV_STATE_TAG_TYPE; /* We reset the offset and will get one from first push */ @@ -171,6 +175,29 @@ gst_flv_demux_cleanup (GstFLVDemux * demux) } } +/* + * Create and push a flushing seek event upstream + */ +static gboolean +flv_demux_seek_to_offset (GstFLVDemux * 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; +} + static GstFlowReturn gst_flv_demux_chain (GstPad * pad, GstBuffer * buffer) { @@ -195,6 +222,13 @@ gst_flv_demux_chain (GstPad * pad, GstBuffer * buffer) gst_adapter_push (demux->adapter, buffer); + if (demux->seeking) { + demux->state = FLV_STATE_SEEK; + GST_OBJECT_LOCK (demux); + demux->seeking = FALSE; + GST_OBJECT_UNLOCK (demux); + } + parse: if (G_UNLIKELY (ret != GST_FLOW_OK)) { if (ret == GST_FLOW_NOT_LINKED && (demux->audio_linked @@ -246,6 +280,11 @@ parse: gst_buffer_unref (buffer); demux->offset += FLV_TAG_TYPE_SIZE; + /* last tag is not an index => no index/don't know where the index is + * seek back to the beginning */ + if (demux->seek_event && demux->state != FLV_STATE_TAG_SCRIPT) + goto no_index; + goto parse; } else { goto beach; @@ -300,11 +339,67 @@ parse: demux->offset += demux->tag_size; demux->state = FLV_STATE_TAG_TYPE; + + /* if there's a seek event we're here for the index so if we don't have it + * we seek back to the beginning */ + if (demux->seek_event) { + if (demux->indexed) + demux->state = FLV_STATE_SEEK; + else + goto no_index; + } + goto parse; } else { goto beach; } } + case FLV_STATE_SEEK: + { + GstEvent *event; + + ret = GST_FLOW_OK; + + if (!demux->indexed) { + if (demux->offset == demux->file_size - sizeof (guint32)) { + GstBuffer *buffer = + gst_adapter_take_buffer (demux->adapter, sizeof (guint32)); + GstByteReader *reader = gst_byte_reader_new_from_buffer (buffer); + guint64 seek_offset; + + if (!gst_adapter_available (demux->adapter) >= sizeof (guint32)) { + /* error */ + } + + seek_offset = + demux->file_size - sizeof (guint32) - + gst_byte_reader_peek_uint32_be_unchecked (reader); + gst_byte_reader_free (reader); + gst_buffer_unref (buffer); + + GST_INFO_OBJECT (demux, + "Seeking to beginning of last tag at %" G_GUINT64_FORMAT, + seek_offset); + demux->state = FLV_STATE_TAG_TYPE; + flv_demux_seek_to_offset (demux, seek_offset); + goto beach; + } else + goto no_index; + } + + GST_OBJECT_LOCK (demux); + event = demux->seek_event; + demux->seek_event = NULL; + GST_OBJECT_UNLOCK (demux); + + /* calculate and perform seek */ + if (!flv_demux_handle_seek_push (demux, event)) + goto seek_failed; + + gst_event_unref (event); + demux->state = FLV_STATE_TAG_TYPE; + goto beach; + } default: GST_DEBUG_OBJECT (demux, "unexpected demuxer state"); } @@ -320,6 +415,26 @@ beach: gst_object_unref (demux); return ret; + +/* ERRORS */ +no_index: + { + GST_OBJECT_LOCK (demux); + demux->seeking = FALSE; + gst_event_unref (demux->seek_event); + demux->seek_event = NULL; + GST_OBJECT_UNLOCK (demux); + GST_WARNING_OBJECT (demux, + "failed to find an index, seeking back to beginning"); + flv_demux_seek_to_offset (demux, 0); + return GST_FLOW_OK; + } +seek_failed: + { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), ("seek failed")); + return GST_FLOW_ERROR; + } + } static GstFlowReturn @@ -664,7 +779,7 @@ gst_flv_demux_find_offset (GstFLVDemux * demux, GstSegment * segment) } static gboolean -gst_flv_demux_handle_seek_push (GstFLVDemux * demux, GstEvent * event) +flv_demux_handle_seek_push (GstFLVDemux * demux, GstEvent * event) { GstFormat format; GstSeekFlags flags; @@ -746,6 +861,64 @@ wrong_format: } } +static gboolean +gst_flv_demux_handle_seek_push (GstFLVDemux * demux, GstEvent * event) +{ + if (!demux->indexed) { + guint64 seek_offset; + gboolean building_index; + GstFormat fmt; + + GST_OBJECT_LOCK (demux); + /* handle the seek in the chain function */ + demux->seeking = TRUE; + demux->state = FLV_STATE_SEEK; + + /* copy the event */ + if (demux->seek_event) + gst_event_unref (demux->seek_event); + demux->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 = demux->building_index; + if (!building_index) { + demux->building_index = TRUE; + fmt = GST_FORMAT_BYTES; + if (!demux->file_size + && !gst_pad_query_peer_duration (demux->sinkpad, &fmt, + &demux->file_size)) { + GST_WARNING_OBJECT (demux, + "Cannot obtain file size - %" G_GINT64_FORMAT ", format %u", + demux->file_size, fmt); + GST_OBJECT_UNLOCK (demux); + return FALSE; + } + + /* we hope the last tag is a scriptdataobject containing an index + * the size of the last tag is given in the last guint32 bits + * then we seek to the beginning of the tag, parse it and hopefully obtain an index */ + seek_offset = demux->file_size - sizeof (guint32); + GST_DEBUG_OBJECT (demux, + "File size obtained, seeking to %" G_GUINT64_FORMAT, seek_offset); + } + GST_OBJECT_UNLOCK (demux); + + if (!building_index) { + GST_INFO_OBJECT (demux, "Seeking to last 4 bytes at %" G_GUINT64_FORMAT, + seek_offset); + return flv_demux_seek_to_offset (demux, seek_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 flv_demux_handle_seek_push (demux, event); +} + static gboolean gst_flv_demux_handle_seek_pull (GstFLVDemux * demux, GstEvent * event) { diff --git a/gst/flv/gstflvdemux.h b/gst/flv/gstflvdemux.h index 6c130d0552..4c82e5ba91 100644 --- a/gst/flv/gstflvdemux.h +++ b/gst/flv/gstflvdemux.h @@ -44,6 +44,7 @@ typedef enum FLV_STATE_TAG_VIDEO, FLV_STATE_TAG_AUDIO, FLV_STATE_TAG_SCRIPT, + FLV_STATE_SEEK, FLV_STATE_DONE, FLV_STATE_NONE } GstFLVDemuxState; @@ -118,6 +119,12 @@ struct _GstFLVDemux gboolean flushing; gboolean no_more_pads; + + gboolean seeking; + gboolean building_index; + gboolean indexed; /* TRUE if index is completely built */ + gint64 file_size; + GstEvent *seek_event; }; struct _GstFLVDemuxClass diff --git a/gst/flv/gstflvparse.c b/gst/flv/gstflvparse.c index b1e70a889e..43dc1eb4bd 100644 --- a/gst/flv/gstflvparse.c +++ b/gst/flv/gstflvparse.c @@ -434,6 +434,7 @@ gst_flv_parse_tag_script (GstFLVDemux * demux, GstBuffer * buffer) GST_ASSOCIATION_FLAG_KEY_UNIT, GST_FORMAT_TIME, time, GST_FORMAT_BYTES, fileposition, NULL); } + demux->indexed = TRUE; } } @@ -741,16 +742,17 @@ gst_flv_parse_tag_audio (GstFLVDemux * demux, GstBuffer * buffer) demux->duration < GST_BUFFER_TIMESTAMP (outbuf)) demux->duration = GST_BUFFER_TIMESTAMP (outbuf); - /* Only add audio frames to the index if we have no video - * and if we don't have random access */ - if (!demux->has_video && demux->index && !demux->random_access) { - GST_LOG_OBJECT (demux, "adding association %" GST_TIME_FORMAT "-> %" - G_GUINT64_FORMAT, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)), - demux->cur_tag_offset); + /* Only add audio frames to the index if we have no video, if we don't have + * random access and if the index is not yet complete */ + if (!demux->has_video && demux->index && !demux->random_access + && !demux->indexed) { + GST_LOG_OBJECT (demux, + "adding association %" GST_TIME_FORMAT "-> %" G_GUINT64_FORMAT, + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)), demux->cur_tag_offset); gst_index_add_association (demux->index, demux->index_id, - GST_ASSOCIATION_FLAG_KEY_UNIT, - GST_FORMAT_TIME, GST_BUFFER_TIMESTAMP (outbuf), - GST_FORMAT_BYTES, demux->cur_tag_offset, NULL); + GST_ASSOCIATION_FLAG_KEY_UNIT, GST_FORMAT_TIME, + GST_BUFFER_TIMESTAMP (outbuf), GST_FORMAT_BYTES, demux->cur_tag_offset, + NULL); } if (G_UNLIKELY (demux->audio_need_discont)) { @@ -1079,26 +1081,28 @@ gst_flv_parse_tag_video (GstFLVDemux * demux, GstBuffer * buffer) demux->duration < GST_BUFFER_TIMESTAMP (outbuf)) demux->duration = GST_BUFFER_TIMESTAMP (outbuf); - if (!keyframe) { - GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DELTA_UNIT); - if (demux->index && !demux->random_access) { - GST_LOG_OBJECT (demux, "adding association %" GST_TIME_FORMAT "-> %" - G_GUINT64_FORMAT, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)), - demux->cur_tag_offset); - gst_index_add_association (demux->index, demux->index_id, - GST_ASSOCIATION_FLAG_NONE, - GST_FORMAT_TIME, GST_BUFFER_TIMESTAMP (outbuf), - GST_FORMAT_BYTES, demux->cur_tag_offset, NULL); - } - } else { - if (demux->index && !demux->random_access) { - GST_LOG_OBJECT (demux, "adding association %" GST_TIME_FORMAT "-> %" - G_GUINT64_FORMAT, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)), - demux->cur_tag_offset); - gst_index_add_association (demux->index, demux->index_id, - GST_ASSOCIATION_FLAG_KEY_UNIT, - GST_FORMAT_TIME, GST_BUFFER_TIMESTAMP (outbuf), - GST_FORMAT_BYTES, demux->cur_tag_offset, NULL); + if (!demux->indexed) { + if (!keyframe) { + GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DELTA_UNIT); + if (demux->index && !demux->random_access) { + GST_LOG_OBJECT (demux, "adding association %" GST_TIME_FORMAT "-> %" + G_GUINT64_FORMAT, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)), + demux->cur_tag_offset); + gst_index_add_association (demux->index, demux->index_id, + GST_ASSOCIATION_FLAG_NONE, + GST_FORMAT_TIME, GST_BUFFER_TIMESTAMP (outbuf), + GST_FORMAT_BYTES, demux->cur_tag_offset, NULL); + } + } else { + if (demux->index && !demux->random_access) { + GST_LOG_OBJECT (demux, "adding association %" GST_TIME_FORMAT "-> %" + G_GUINT64_FORMAT, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)), + demux->cur_tag_offset); + gst_index_add_association (demux->index, demux->index_id, + GST_ASSOCIATION_FLAG_KEY_UNIT, + GST_FORMAT_TIME, GST_BUFFER_TIMESTAMP (outbuf), + GST_FORMAT_BYTES, demux->cur_tag_offset, NULL); + } } } @@ -1231,9 +1235,11 @@ gst_flv_parse_tag_timestamp (GstFLVDemux * demux, GstBuffer * buffer, ret = pts * GST_MSECOND; - if (demux->index && (type == 9 || (type == 8 && !demux->has_video))) { - GST_LOG_OBJECT (demux, "adding association %" GST_TIME_FORMAT "-> %" - G_GUINT64_FORMAT, GST_TIME_ARGS (ret), demux->offset); + if (demux->index && !demux->indexed && (type == 9 || (type == 8 + && !demux->has_video))) { + GST_LOG_OBJECT (demux, + "adding association %" GST_TIME_FORMAT "-> %" G_GUINT64_FORMAT, + GST_TIME_ARGS (ret), demux->offset); gst_index_add_association (demux->index, demux->index_id, (keyframe) ? GST_ASSOCIATION_FLAG_KEY_UNIT : GST_ASSOCIATION_FLAG_NONE, GST_FORMAT_TIME, ret, GST_FORMAT_BYTES, demux->offset, NULL);