flvdemux: Obtain the index from the end of an flv file in push mode
authorRobert Swain <robert.swain@collabora.co.uk>
Fri, 12 Feb 2010 15:06:45 +0000 (16:06 +0100)
committerRobert Swain <robert.swain@collabora.co.uk>
Fri, 12 Feb 2010 15:25:44 +0000 (16:25 +0100)
Allows for better support of seeking in flv files when in push mode

gst/flv/gstflvdemux.c
gst/flv/gstflvdemux.h
gst/flv/gstflvparse.c

index 4492d30bea6a82fee8085d3930364de43e5139e6..144042722b28a85b5b9d69fe9f5b1e66250fa618 100644 (file)
@@ -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)
 {
index 6c130d0552d7580363c1fa68a3696c19cf07dcf6..4c82e5ba91876fdb1523dd7a43716a8765d4a44d 100644 (file)
@@ -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
index b1e70a889eabea996a32607274dae1e037effce1..43dc1eb4bdda0ec55041e8ed2049701a2f667495 100644 (file)
@@ -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);