flvmux: Preallocate index space and fill it after finishing output
authorJan Urbański <wulczer@wulczer.org>
Sun, 14 Mar 2010 00:38:21 +0000 (01:38 +0100)
committerSebastian Dröge <sebastian.droege@collabora.co.uk>
Mon, 15 Mar 2010 12:52:03 +0000 (13:52 +0100)
Make the index appear at the beginning of the file, which is what most
players are expecting.

Fixes #601236.

gst/flv/gstflvmux.c

index de2b5ca..2bc568d 100644 (file)
@@ -50,6 +50,7 @@ enum
 };
 
 #define DEFAULT_IS_LIVE FALSE
+#define MAX_INDEX_ENTRIES 128
 
 static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src",
     GST_PAD_SRC,
@@ -566,6 +567,36 @@ gst_flv_mux_push (GstFlvMux * mux, GstBuffer * buffer)
   return gst_pad_push (mux->srcpad, buffer);
 }
 
+static GstBuffer *
+gst_flv_mux_preallocate_index (GstFlvMux * mux)
+{
+  GstBuffer *tmp;
+  guint8 *data;
+  gint preallocate_size;
+
+  /* preallocate index of size:
+   *  - 'keyframes' ECMA array key: 2 + 9 = 11 bytes
+   *  - nested ECMA array header, length and end marker: 8 bytes
+   *  - 'times' and 'filepositions' keys: 22 bytes
+   *  - two strict arrays headers and lengths: 10 bytes
+   *  - each index entry: 18 bytes
+   */
+  preallocate_size = 11 + 8 + 22 + 10 + MAX_INDEX_ENTRIES * 18;
+  GST_DEBUG_OBJECT (mux, "preallocating %d bytes for the index",
+      preallocate_size);
+
+  tmp = gst_buffer_new_and_alloc (preallocate_size);
+  data = GST_BUFFER_DATA (tmp);
+
+  /* prefill the space with a gstfiller: <spaces> script tag variable */
+  GST_WRITE_UINT16_BE (data, 9);        /* 9 characters */
+  memcpy (data + 2, "gstfiller", 9);
+  GST_WRITE_UINT8 (data + 11, 2);       /* a string value */
+  GST_WRITE_UINT16_BE (data + 12, preallocate_size - 14);
+  memset (data + 14, ' ', preallocate_size - 14);       /* the rest is spaces */
+  return tmp;
+}
+
 static GstFlowReturn
 gst_flv_mux_write_metadata (GstFlvMux * mux)
 {
@@ -612,6 +643,13 @@ gst_flv_mux_write_metadata (GstFlvMux * mux)
   GST_WRITE_UINT32_BE (data + 1, n_tags);
   script_tag = gst_buffer_join (script_tag, tmp);
 
+  if (!mux->is_live) {
+    tmp = gst_flv_mux_preallocate_index (mux);
+    script_tag = gst_buffer_join (script_tag, tmp);
+  } else {
+    GST_DEBUG_OBJECT (mux, "not preallocating index, stream is live");
+  }
+
   for (i = 0; tags && i < n_tags; i++) {
     const gchar *tag_name =
         gst_structure_nth_field_name ((const GstStructure *) tags, i);
@@ -1010,113 +1048,110 @@ static GstFlowReturn
 gst_flv_mux_write_index (GstFlvMux * mux)
 {
   GstFlowReturn ret = GST_FLOW_OK;
-  GstBuffer *script_tag, *tmp;
+  GstBuffer *index;
+  GstEvent *event;
   guint8 *data;
   GList *l;
-  guint32 index_len;
+  guint32 index_len, allocate_size;
   guint32 i, index_skip;
 
   if (!mux->index)
     return GST_FLOW_OK;
 
-  script_tag = gst_buffer_new_and_alloc (11);
-  data = GST_BUFFER_DATA (script_tag);
-
-  data[0] = 18;
-
-  /* Data size, unknown for now */
-  data[1] = 0;
-  data[2] = 0;
-  data[3] = 0;
-
-  /* Timestamp */
-  data[4] = data[5] = data[6] = data[7] = 0;
-
-  /* Stream ID */
-  data[8] = data[9] = data[10] = 0;
-
-  tmp = gst_buffer_new_and_alloc (13);
-  data = GST_BUFFER_DATA (tmp);
-  data[0] = 2;                  /* string */
-  data[1] = 0;
-  data[2] = 0x0a;               /* length 10 */
-  memcpy (&data[3], "onMetaData", 10);
-
-  script_tag = gst_buffer_join (script_tag, tmp);
+  if (mux->is_live)
+    return GST_FLOW_OK;
 
-  tmp = gst_buffer_new_and_alloc (5);
-  data = GST_BUFFER_DATA (tmp);
-  data[0] = 8;                  /* ECMA array */
-  GST_WRITE_UINT32_BE (data + 1, 2);
-  script_tag = gst_buffer_join (script_tag, tmp);
+  /* seek back to the preallocated index space */
+  event = gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES,
+      42, GST_CLOCK_TIME_NONE, 42);
+  if (!gst_pad_push_event (mux->srcpad, event)) {
+    GST_WARNING_OBJECT (mux, "Seek to rewrite index failed");
+    return GST_FLOW_OK;
+  }
 
   mux->index = g_list_reverse (mux->index);
   index_len = g_list_length (mux->index);
 
-  /* We write at most 128 elements */
-  index_skip = (index_len > 128) ? 1 + index_len / 128 : 1;
-  index_len =
-      (index_len <=
-      128) ? index_len : (index_len + index_skip - 1) / index_skip;
-
-  tmp = gst_buffer_new_and_alloc (2 + 5 + 1 + 4 + index_len * (1 + 8));
-  data = GST_BUFFER_DATA (tmp);
-  data[0] = 0;                  /* 5 bytes name */
-  data[1] = 5;
-  memcpy (&data[2], "times", 5);
-  data[7] = 10;                 /* array */
-  GST_WRITE_UINT32_BE (&data[8], index_len);
-  data += 12;
+  /* We write at most MAX_INDEX_ENTRIES elements */
+  if (index_len > MAX_INDEX_ENTRIES) {
+    index_skip = 1 + index_len / MAX_INDEX_ENTRIES;
+    index_len = (index_len + index_skip - 1) / index_skip;
+  } else {
+    index_skip = 1;
+  }
 
+  GST_DEBUG_OBJECT (mux, "Index length is %d", index_len);
+  /* see size calculation in gst_flv_mux_preallocate_index */
+  allocate_size = 11 + 8 + 22 + 10 + index_len * 18;
+  GST_DEBUG_OBJECT (mux, "Allocating %d bytes for index", allocate_size);
+  index = gst_buffer_new_and_alloc (allocate_size);
+  data = GST_BUFFER_DATA (index);
+
+  GST_WRITE_UINT16_BE (data, 9);        /* the 'keyframes' key */
+  memcpy (data + 2, "keyframes", 9);
+  GST_WRITE_UINT8 (data + 11, 8);       /* nested ECMA array */
+  GST_WRITE_UINT32_BE (data + 12, 2);   /* two elements */
+  GST_WRITE_UINT16_BE (data + 16, 5);   /* first string key: 'times' */
+  memcpy (data + 18, "times", 5);
+  GST_WRITE_UINT8 (data + 23, 10);      /* strict array */
+  GST_WRITE_UINT32_BE (data + 24, index_len);
+  data += 28;
+
+  /* the keyframes' times */
   for (i = 0, l = mux->index; l; l = l->next, i++) {
     GstFlvMuxIndexEntry *entry = l->data;
 
     if (i % index_skip != 0)
       continue;
-
-    data[0] = 0;
-    GST_WRITE_DOUBLE_BE (&data[1], entry->time);
+    GST_WRITE_UINT8 (data, 0);  /* numeric (aka double) */
+    GST_WRITE_DOUBLE_BE (data + 1, entry->time);
     data += 9;
   }
-  script_tag = gst_buffer_join (script_tag, tmp);
 
-  tmp = gst_buffer_new_and_alloc (2 + 13 + 1 + 4 + index_len * (1 + 8));
-  data = GST_BUFFER_DATA (tmp);
-  data[0] = 0;                  /* 13 bytes name */
-  data[1] = 13;
-  memcpy (&data[2], "filepositions", 13);
-  data[15] = 10;                /* array */
-  GST_WRITE_UINT32_BE (&data[16], index_len);
+  GST_WRITE_UINT16_BE (data, 13);       /* second string key: 'filepositions' */
+  memcpy (data + 2, "filepositions", 13);
+  GST_WRITE_UINT8 (data + 15, 10);      /* strict array */
+  GST_WRITE_UINT32_BE (data + 16, index_len);
   data += 20;
 
+  /* the keyframes' file positions */
   for (i = 0, l = mux->index; l; l = l->next, i++) {
     GstFlvMuxIndexEntry *entry = l->data;
 
     if (i % index_skip != 0)
       continue;
-    data[0] = 0;
-    GST_WRITE_DOUBLE_BE (&data[1], entry->position);
+    GST_WRITE_UINT8 (data, 0);
+    GST_WRITE_DOUBLE_BE (data + 1, entry->position);
     data += 9;
   }
-  script_tag = gst_buffer_join (script_tag, tmp);
 
-  tmp = gst_buffer_new_and_alloc (3);
-  data = GST_BUFFER_DATA (tmp);
   GST_WRITE_UINT24_BE (data, 9);        /* finish the ECMA array */
-  script_tag = gst_buffer_join (script_tag, tmp);
 
-  tmp = gst_buffer_new_and_alloc (4);
-  data = GST_BUFFER_DATA (tmp);
-  GST_WRITE_UINT32_BE (data, GST_BUFFER_SIZE (script_tag));
-  script_tag = gst_buffer_join (script_tag, tmp);
+  /* If there is space left in the prefilled area, reinsert the filler.
+     There is at least 18  bytes free, so it will always fit. */
+  if (index_len < MAX_INDEX_ENTRIES) {
+    GstBuffer *tmp;
+    guint8 *data;
+    guint32 remaining_filler_size;
 
-  data = GST_BUFFER_DATA (script_tag);
-  data[1] = ((GST_BUFFER_SIZE (script_tag) - 11 - 4) >> 16) & 0xff;
-  data[2] = ((GST_BUFFER_SIZE (script_tag) - 11 - 4) >> 8) & 0xff;
-  data[3] = ((GST_BUFFER_SIZE (script_tag) - 11 - 4) >> 0) & 0xff;
+    tmp = gst_buffer_new_and_alloc (14);
+    data = GST_BUFFER_DATA (tmp);
+    GST_WRITE_UINT16_BE (data, 9);
+    memcpy (data + 2, "gstfiller", 9);
+    GST_WRITE_UINT8 (data + 11, 2);     /* string */
+
+    /* There is 18 bytes per remaining index entry minus what is used for
+     * the'gstfiller' key. The rest is already filled with spaces, so just need
+     * to update length. */
+    remaining_filler_size = (MAX_INDEX_ENTRIES - index_len) * 18 - 14;
+    GST_DEBUG_OBJECT (mux, "Remaining filler size is %d bytes",
+        remaining_filler_size);
+    GST_WRITE_UINT16_BE (data + 12, remaining_filler_size);
+    index = gst_buffer_join (index, tmp);
+  }
 
-  gst_buffer_set_caps (script_tag, GST_PAD_CAPS (mux->srcpad));
-  ret = gst_flv_mux_push (mux, script_tag);
+  gst_buffer_set_caps (index, GST_PAD_CAPS (mux->srcpad));
+  ret = gst_flv_mux_push (mux, index);
 
   return ret;
 }