mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-03-30 12:49:40 +00:00
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
This commit is contained in:
parent
dd23397b4f
commit
bf9d8dbbdc
3 changed files with 221 additions and 35 deletions
|
@ -39,6 +39,7 @@
|
|||
#include "gstflvmux.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <gst/base/gstbytereader.h>
|
||||
|
||||
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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue