flvdemux: incrementally build index in pull mode
authorMark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>
Thu, 18 Feb 2010 11:42:31 +0000 (12:42 +0100)
committerMark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>
Wed, 10 Mar 2010 10:48:06 +0000 (11:48 +0100)
Scan for needed part upon a seek as opposed to doing a complete scan
at startup, which may take some time depending on file and/or platform.
Also accept index metadata in pull mode and peek for some metadata
at the end of the file when deemed appropriate.

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

index 1bb523d..70a00b8 100644 (file)
@@ -119,6 +119,9 @@ gst_flv_demux_cleanup (GstFLVDemux * demux)
   demux->indexed = FALSE;
   demux->file_size = 0;
 
+  demux->index_max_pos = 0;
+  demux->index_max_time = 0;
+
   demux->audio_start = demux->video_start = GST_CLOCK_TIME_NONE;
 
   demux->no_more_pads = FALSE;
@@ -583,35 +586,105 @@ gst_flv_demux_push_src_event (GstFLVDemux * demux, GstEvent * event)
 }
 
 static void
-gst_flv_demux_create_index (GstFLVDemux * demux)
+gst_flv_demux_create_index (GstFLVDemux * demux, gint64 pos, GstClockTime ts)
 {
   gint64 size;
   GstFormat fmt = GST_FORMAT_BYTES;
   size_t tag_size;
   guint64 old_offset;
   GstBuffer *buffer;
+  GstClockTime tag_time;
 
   if (G_UNLIKELY (!gst_pad_query_peer_duration (demux->sinkpad, &fmt, &size) ||
           fmt != GST_FORMAT_BYTES))
     return;
 
+  GST_DEBUG_OBJECT (demux, "building index at %" G_GINT64_FORMAT
+      " looking for time %" GST_TIME_FORMAT, pos, GST_TIME_ARGS (ts));
+
   old_offset = demux->offset;
+  demux->offset = pos;
 
   while (gst_flv_demux_pull_range (demux, demux->sinkpad, demux->offset, 12,
           &buffer) == GST_FLOW_OK) {
-    if (G_UNLIKELY (gst_flv_parse_tag_timestamp (demux, buffer,
-                &tag_size) == GST_CLOCK_TIME_NONE)) {
-      gst_buffer_unref (buffer);
-      break;
-    }
+    tag_time = gst_flv_parse_tag_timestamp (demux, TRUE, buffer, &tag_size);
 
     gst_buffer_unref (buffer);
+
+    if (G_UNLIKELY (tag_time == GST_CLOCK_TIME_NONE || tag_time > ts))
+      goto exit;
+
     demux->offset += tag_size;
   }
 
+  /* file ran out, so mark we have complete index */
+  demux->indexed = TRUE;
+
+exit:
   demux->offset = old_offset;
 }
 
+static gint64
+gst_flv_demux_get_metadata (GstFLVDemux * demux)
+{
+  gint64 ret, offset;
+  GstFormat fmt = GST_FORMAT_BYTES;
+  size_t tag_size, size;
+  GstBuffer *buffer = NULL;
+
+  if (G_UNLIKELY (!gst_pad_query_peer_duration (demux->sinkpad, &fmt, &offset)
+          || fmt != GST_FORMAT_BYTES))
+    goto exit;
+
+  ret = offset;
+  GST_DEBUG_OBJECT (demux, "upstream size: %" G_GINT64_FORMAT, offset);
+  if (G_UNLIKELY (offset < 4))
+    goto exit;
+
+  offset -= 4;
+  if (GST_FLOW_OK != gst_flv_demux_pull_range (demux, demux->sinkpad, offset,
+          4, &buffer))
+    goto exit;
+
+  tag_size = GST_READ_UINT32_BE (GST_BUFFER_DATA (buffer));
+  GST_DEBUG_OBJECT (demux, "last tag size: %d", tag_size);
+  gst_buffer_unref (buffer);
+  buffer = NULL;
+
+  offset -= tag_size;
+  if (GST_FLOW_OK != gst_flv_demux_pull_range (demux, demux->sinkpad, offset,
+          12, &buffer))
+    goto exit;
+
+  /* a consistency check */
+  size = GST_READ_UINT24_BE (GST_BUFFER_DATA (buffer) + 1);
+  if (size != tag_size - 11) {
+    GST_DEBUG_OBJECT (demux, "tag size %d, expected %d, ",
+        "corrupt or truncated file", size, tag_size - 11);
+    goto exit;
+  }
+
+  /* try to update duration with timestamp in any case */
+  gst_flv_parse_tag_timestamp (demux, FALSE, buffer, &size);
+
+  /* maybe get some more metadata */
+  if (GST_BUFFER_DATA (buffer)[0] == 18) {
+    gst_buffer_unref (buffer);
+    buffer = NULL;
+    GST_DEBUG_OBJECT (demux, "script tag, pulling it to parse");
+    offset += 4;
+    if (GST_FLOW_OK == gst_flv_demux_pull_range (demux, demux->sinkpad, offset,
+            tag_size, &buffer))
+      gst_flv_parse_tag_script (demux, buffer);
+  }
+
+exit:
+  if (buffer)
+    gst_buffer_unref (buffer);
+
+  return ret;
+}
+
 static void
 gst_flv_demux_loop (GstPad * pad)
 {
@@ -625,15 +698,21 @@ gst_flv_demux_loop (GstPad * pad)
     switch (demux->state) {
       case FLV_STATE_TAG_TYPE:
         ret = gst_flv_demux_pull_tag (pad, demux);
+        /* if we have seen real data, we probably passed a possible metadata
+         * header located at start.  So if we do not yet have an index,
+         * try to pick up metadata (index, duration) at the end */
+        if (G_UNLIKELY (!demux->file_size && !demux->indexed &&
+                (demux->has_video || demux->has_audio)))
+          demux->file_size = gst_flv_demux_get_metadata (demux);
         break;
       case FLV_STATE_DONE:
         ret = GST_FLOW_UNEXPECTED;
         break;
       default:
         ret = gst_flv_demux_pull_header (pad, demux);
-        if (ret == GST_FLOW_OK)
-          gst_flv_demux_create_index (demux);
-
+        /* index scans start after header */
+        demux->index_max_pos = demux->offset;
+        break;
     }
 
     /* pause if something went wrong */
@@ -663,7 +742,7 @@ gst_flv_demux_loop (GstPad * pad)
       default:
         ret = gst_flv_demux_pull_header (pad, demux);
         if (ret == GST_FLOW_OK)
-          gst_flv_demux_create_index (demux);
+          gst_flv_demux_create_index (demux, demux->offset, G_MAXINT64);
     }
 
     /* pause if something went wrong */
@@ -978,6 +1057,15 @@ gst_flv_demux_handle_seek_pull (GstFLVDemux * demux, GstEvent * event)
 
   if (flush || seeksegment.last_stop != demux->segment.last_stop) {
     /* Do the actual seeking */
+    /* index is reliable if it is complete or we do not go to far ahead */
+    if (!demux->indexed &&
+        seeksegment.last_stop > demux->index_max_time + 10 * GST_SECOND) {
+      /* scan and build index from current maximum offset to desired time */
+      /* NOTE this will _pull_range from seeking thread, but should be ok ... */
+      gst_flv_demux_create_index (demux, demux->index_max_pos,
+          seeksegment.last_stop);
+    }
+    /* now index should be as reliable as it can be for current purpose */
     demux->offset = gst_flv_demux_find_offset (demux, &seeksegment);
 
     /* Tell all the stream we moved to a different position (discont) */
index 4c82e5b..8027c6b 100644 (file)
@@ -125,6 +125,9 @@ struct _GstFLVDemux
   gboolean indexed; /* TRUE if index is completely built */
   gint64 file_size;
   GstEvent *seek_event;
+
+  GstClockTime index_max_time;
+  gint64 index_max_pos;
 };
 
 struct _GstFLVDemuxClass
index a02660b..eb2180b 100644 (file)
@@ -44,6 +44,11 @@ gst_flv_parse_add_index_entry (GstFLVDemux * demux, GstClockTime ts,
   gst_index_add_associationv (demux->index, demux->index_id,
       (keyframe) ? GST_ASSOCIATION_FLAG_KEY_UNIT : GST_ASSOCIATION_FLAG_NONE,
       2, (const GstIndexAssociation *) &associations);
+
+  if (pos > demux->index_max_pos)
+    demux->index_max_pos = pos;
+  if (ts > demux->index_max_time)
+    demux->index_max_time = ts;
 }
 
 static gchar *
@@ -440,11 +445,10 @@ gst_flv_parse_tag_script (GstFLVDemux * demux, GstBuffer * buffer)
 
     g_free (function_name);
 
-    if (demux->index && demux->times && demux->filepositions
-        && !demux->random_access) {
+    if (demux->index && demux->times && demux->filepositions) {
       guint num;
 
-      /* If an index was found and we're in push mode, insert associations */
+      /* If an index was found, insert associations */
       num = MIN (demux->times->len, demux->filepositions->len);
       for (i = 0; i < num; i++) {
         guint64 time, fileposition;
@@ -1182,8 +1186,8 @@ beach:
 }
 
 GstClockTime
-gst_flv_parse_tag_timestamp (GstFLVDemux * demux, GstBuffer * buffer,
-    size_t * tag_size)
+gst_flv_parse_tag_timestamp (GstFLVDemux * demux, gboolean index,
+    GstBuffer * buffer, size_t * tag_size)
 {
   guint32 pts = 0, pts_ext = 0;
   guint32 tag_data_size;
@@ -1239,7 +1243,7 @@ gst_flv_parse_tag_timestamp (GstFLVDemux * demux, GstBuffer * buffer,
   ret = pts * GST_MSECOND;
   GST_LOG_OBJECT (demux, "pts: %" GST_TIME_FORMAT, GST_TIME_ARGS (ret));
 
-  if (demux->index && !demux->indexed && (type == 9 || (type == 8
+  if (index && demux->index && !demux->indexed && (type == 9 || (type == 8
               && !demux->has_video))) {
     gst_flv_parse_add_index_entry (demux, ret, demux->offset, keyframe);
   }
index 203d30d..0875380 100644 (file)
@@ -36,7 +36,8 @@ GstFlowReturn gst_flv_parse_tag_type (GstFLVDemux * demux, GstBuffer *buffer);
 
 GstFlowReturn gst_flv_parse_header (GstFLVDemux * demux, GstBuffer *buffer);
 
-GstClockTime gst_flv_parse_tag_timestamp (GstFLVDemux *demux, GstBuffer *buffer, size_t *tag_data_size);
+GstClockTime gst_flv_parse_tag_timestamp (GstFLVDemux *demux, gboolean index,
+                                          GstBuffer *buffer, size_t *tag_data_size);
 
 G_END_DECLS
 #endif /* __FLV_PARSE_H__ */