qtmux: add mfra to fragmented file
authorMark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>
Thu, 18 Nov 2010 15:48:06 +0000 (16:48 +0100)
committerMark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>
Fri, 19 Nov 2010 18:21:37 +0000 (19:21 +0100)
Based on patch by Marc-AndrĂ© Lureau <mlureau@flumotion.com>

gst/qtmux/atoms.c
gst/qtmux/atoms.h
gst/qtmux/gstqtmux.c
gst/qtmux/gstqtmux.h

index 8ea269f..7556078 100644 (file)
@@ -3643,12 +3643,238 @@ atom_traf_add_samples (AtomTRAF * traf, guint32 delta, guint32 size,
       pts_offset);
 }
 
+guint32
+atom_traf_get_sample_num (AtomTRAF * traf)
+{
+  AtomTRUN *trun;
+
+  if (G_UNLIKELY (!traf->truns))
+    return 0;
+
+  trun = traf->truns->data;
+  return atom_array_get_len (&trun->entries);
+}
+
 void
 atom_moof_add_traf (AtomMOOF * moof, AtomTRAF * traf)
 {
   moof->trafs = g_list_append (moof->trafs, traf);
 }
 
+static void
+atom_tfra_free (AtomTFRA * tfra)
+{
+  atom_full_clear (&tfra->header);
+  atom_array_clear (&tfra->entries);
+  g_free (tfra);
+}
+
+AtomMFRA *
+atom_mfra_new (AtomsContext * context)
+{
+  AtomMFRA *mfra = g_new0 (AtomMFRA, 1);
+
+  atom_header_set (&mfra->header, FOURCC_mfra, 0, 0);
+  return mfra;
+}
+
+void
+atom_mfra_add_tfra (AtomMFRA * mfra, AtomTFRA * tfra)
+{
+  mfra->tfras = g_list_append (mfra->tfras, tfra);
+}
+
+void
+atom_mfra_free (AtomMFRA * mfra)
+{
+  GList *walker;
+
+  walker = mfra->tfras;
+  while (walker) {
+    atom_tfra_free ((AtomTFRA *) walker->data);
+    walker = g_list_next (walker);
+  }
+  g_list_free (mfra->tfras);
+  mfra->tfras = NULL;
+
+  atom_clear (&mfra->header);
+  g_free (mfra);
+}
+
+static void
+atom_tfra_init (AtomTFRA * tfra, guint32 track_ID)
+{
+  guint8 flags[3] = { 0, 0, 0 };
+
+  atom_full_init (&tfra->header, FOURCC_tfra, 0, 0, 0, flags);
+  tfra->track_ID = track_ID;
+  atom_array_init (&tfra->entries, 512);
+}
+
+AtomTFRA *
+atom_tfra_new (AtomsContext * context, guint32 track_ID)
+{
+  AtomTFRA *tfra = g_new0 (AtomTFRA, 1);
+
+  atom_tfra_init (tfra, track_ID);
+  return tfra;
+
+}
+
+static inline gint
+need_bytes (guint32 num)
+{
+  gint n = 0;
+
+  while (num >>= 8)
+    n++;
+
+  return n;
+}
+
+void
+atom_tfra_add_entry (AtomTFRA * tfra, guint64 dts, guint32 sample_num)
+{
+  TFRAEntry entry;
+
+  entry.time = dts;
+  /* fill in later */
+  entry.moof_offset = 0;
+  /* always write a single trun in a single traf */
+  entry.traf_number = 1;
+  entry.trun_number = 1;
+  entry.sample_number = sample_num;
+
+  /* auto-use 64 bits if needed */
+  if (dts > G_MAXUINT32)
+    tfra->header.version = 1;
+
+  /* 1 byte will always do for traf and trun number,
+   * check how much sample_num needs */
+  tfra->lengths = (tfra->lengths & 0xfc) ||
+      MAX (tfra->lengths, need_bytes (sample_num));
+
+  atom_array_append (&tfra->entries, entry, 256);
+}
+
+void
+atom_tfra_update_offset (AtomTFRA * tfra, guint64 offset)
+{
+  gint i;
+
+  /* auto-use 64 bits if needed */
+  if (offset > G_MAXUINT32)
+    tfra->header.version = 1;
+
+  for (i = atom_array_get_len (&tfra->entries) - 1; i >= 0; i--) {
+    TFRAEntry *entry = &atom_array_index (&tfra->entries, i);
+
+    if (entry->moof_offset)
+      break;
+    entry->moof_offset = offset;
+  }
+}
+
+static guint64
+atom_tfra_copy_data (AtomTFRA * tfra, guint8 ** buffer, guint64 * size,
+    guint64 * offset)
+{
+  guint64 original_offset = *offset;
+  guint32 i;
+  TFRAEntry *entry;
+  guint32 data;
+  guint bytes;
+  guint version;
+
+  if (!atom_full_copy_data (&tfra->header, buffer, size, offset)) {
+    return 0;
+  }
+
+  prop_copy_uint32 (tfra->track_ID, buffer, size, offset);
+  prop_copy_uint32 (tfra->lengths, buffer, size, offset);
+  prop_copy_uint32 (atom_array_get_len (&tfra->entries), buffer, size, offset);
+
+  version = tfra->header.version;
+  for (i = 0; i < atom_array_get_len (&tfra->entries); ++i) {
+    entry = &atom_array_index (&tfra->entries, i);
+    if (version) {
+      prop_copy_uint64 (entry->time, buffer, size, offset);
+      prop_copy_uint64 (entry->moof_offset, buffer, size, offset);
+    } else {
+      prop_copy_uint32 (entry->time, buffer, size, offset);
+      prop_copy_uint32 (entry->moof_offset, buffer, size, offset);
+    }
+
+    bytes = (tfra->lengths & (0x3 << 4)) + 1;
+    data = GUINT32_TO_BE (entry->traf_number);
+    prop_copy_fixed_size_string (((guint8 *) & data) + 4 - bytes, bytes,
+        buffer, size, offset);
+
+    bytes = (tfra->lengths & (0x3 << 2)) + 1;
+    data = GUINT32_TO_BE (entry->trun_number);
+    prop_copy_fixed_size_string (((guint8 *) & data) + 4 - bytes, bytes,
+        buffer, size, offset);
+
+    bytes = (tfra->lengths & (0x3)) + 1;
+    data = GUINT32_TO_BE (entry->sample_number);
+    prop_copy_fixed_size_string (((guint8 *) & data) + 4 - bytes, bytes,
+        buffer, size, offset);
+
+  }
+
+  atom_write_size (buffer, size, offset, original_offset);
+  return *offset - original_offset;
+}
+
+static guint64
+atom_mfro_copy_data (guint32 s, guint8 ** buffer, guint64 * size,
+    guint64 * offset)
+{
+  guint64 original_offset = *offset;
+  guint8 flags[3] = { 0, 0, 0 };
+  AtomFull mfro;
+
+  atom_full_init (&mfro, FOURCC_mfro, 0, 0, 0, flags);
+
+  if (!atom_full_copy_data (&mfro, buffer, size, offset)) {
+    return 0;
+  }
+
+  prop_copy_uint32 (s, buffer, size, offset);
+
+  atom_write_size (buffer, size, offset, original_offset);
+
+  return *offset - original_offset;
+}
+
+
+guint64
+atom_mfra_copy_data (AtomMFRA * mfra, guint8 ** buffer, guint64 * size,
+    guint64 * offset)
+{
+  guint64 original_offset = *offset;
+  GList *walker;
+
+  if (!atom_copy_data (&mfra->header, buffer, size, offset))
+    return 0;
+
+  walker = g_list_first (mfra->tfras);
+  while (walker != NULL) {
+    if (!atom_tfra_copy_data ((AtomTFRA *) walker->data, buffer, size, offset)) {
+      return 0;
+    }
+    walker = g_list_next (walker);
+  }
+
+  /* 16 is the size of the mfro atom */
+  if (!atom_mfro_copy_data (*offset - original_offset + 16, buffer,
+          size, offset))
+    return 0;
+
+  atom_write_size (buffer, size, offset, original_offset);
+  return *offset - original_offset;
+}
+
 /* some sample description construction helpers */
 
 AtomInfo *
index 980c897..8f48303 100644 (file)
@@ -729,6 +729,33 @@ typedef struct _AtomWAVE
   GList *extension_atoms;
 } AtomWAVE;
 
+typedef struct _TFRAEntry
+{
+  guint64 time;
+  guint64 moof_offset;
+  guint32 traf_number;
+  guint32 trun_number;
+  guint32 sample_number;
+} TFRAEntry;
+
+typedef struct _AtomTFRA
+{
+  AtomFull header;
+
+  guint32 track_ID;
+  guint32 lengths;
+  /* array of entries */
+  ATOM_ARRAY (TFRAEntry) entries;
+} AtomTFRA;
+
+typedef struct _AtomMFRA
+{
+  Atom header;
+
+  /* list of tfra */
+  GList *tfras;
+} AtomMFRA;
+
 /*
  * Function to serialize an atom
  */
@@ -812,8 +839,18 @@ void       atom_traf_free              (AtomTRAF * traf);
 void       atom_traf_add_samples       (AtomTRAF * traf, guint32 delta,
                                         guint32 size, gboolean sync,
                                         gboolean do_pts, gint64 pts_offset);
+guint32    atom_traf_get_sample_num    (AtomTRAF * traf);
 void       atom_moof_add_traf          (AtomMOOF *moof, AtomTRAF *traf);
 
+AtomMFRA*  atom_mfra_new               (AtomsContext *context);
+void       atom_mfra_free              (AtomMFRA *mfra);
+AtomTFRA*  atom_tfra_new               (AtomsContext *context, guint32 track_ID);
+void       atom_tfra_add_entry         (AtomTFRA *tfra, guint64 dts, guint32 sample_num);
+void       atom_tfra_update_offset     (AtomTFRA * tfra, guint64 offset);
+void       atom_mfra_add_tfra          (AtomMFRA *mfra, AtomTFRA *tfra);
+guint64    atom_mfra_copy_data         (AtomMFRA *mfra, guint8 **buffer, guint64 *size, guint64* offset);
+
+
 /* media sample description related helpers */
 
 typedef struct
index e00d25b..39fce4e 100644 (file)
@@ -283,6 +283,9 @@ gst_qt_mux_pad_reset (GstQTPad * qtpad)
     qtpad->traf = NULL;
   }
   atom_array_clear (&qtpad->fragment_buffers);
+
+  /* reference owned elsewhere */
+  qtpad->tfra = NULL;
 }
 
 /*
@@ -310,6 +313,10 @@ gst_qt_mux_reset (GstQTMux * qtmux, gboolean alloc)
     atom_moov_free (qtmux->moov);
     qtmux->moov = NULL;
   }
+  if (qtmux->mfra) {
+    atom_mfra_free (qtmux->mfra);
+    qtmux->mfra = NULL;
+  }
   if (qtmux->fast_start_file) {
     fclose (qtmux->fast_start_file);
     g_remove (qtmux->fast_start_file_path);
@@ -1532,13 +1539,16 @@ gst_qt_mux_start_file (GstQTMux * qtmux)
       /* prepare moov and/or tags */
       gst_qt_mux_configure_moov (qtmux, NULL);
       gst_qt_mux_setup_metadata (qtmux);
-      ret = gst_qt_mux_send_moov (qtmux, NULL, FALSE);
+      ret = gst_qt_mux_send_moov (qtmux, &qtmux->header_size, FALSE);
       if (ret != GST_FLOW_OK)
         return ret;
       /* extra atoms */
-      ret = gst_qt_mux_send_extra_atoms (qtmux, TRUE, NULL, FALSE);
+      ret =
+          gst_qt_mux_send_extra_atoms (qtmux, TRUE, &qtmux->header_size, FALSE);
       if (ret != GST_FLOW_OK)
         return ret;
+      /* prepare index */
+      qtmux->mfra = atom_mfra_new (qtmux->context);
     } else {
       /* extended to ensure some spare space */
       ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, 0, TRUE);
@@ -1595,6 +1605,20 @@ gst_qt_mux_stop_file (GstQTMux * qtmux)
   if (qtmux->fragment_sequence) {
     GstEvent *event;
 
+    if (qtmux->mfra) {
+      guint8 *data = NULL;
+      GstBuffer *buf;
+
+      size = offset = 0;
+      GST_DEBUG_OBJECT (qtmux, "adding mfra");
+      if (!atom_mfra_copy_data (qtmux->mfra, &data, &size, &offset))
+        goto serialize_error;
+      buf = _gst_buffer_new_take_data (data, offset);
+      ret = gst_qt_mux_send_buffer (qtmux, buf, NULL, FALSE);
+      if (ret != GST_FLOW_OK)
+        return ret;
+    }
+
     timescale = qtmux->timescale;
     /* only mvex duration is updated,
      * mvhd should be consistent with empty moov
@@ -1732,8 +1756,9 @@ ftyp_error:
 
 static GstFlowReturn
 gst_qt_mux_pad_fragment_add_buffer (GstQTMux * qtmux, GstQTPad * pad,
-    GstBuffer * buf, gboolean force, guint32 nsamples, guint32 delta,
-    guint32 size, gboolean sync, gboolean do_pts, gint64 pts_offset)
+    GstBuffer * buf, gboolean force, guint32 nsamples, gint64 dts,
+    guint32 delta, guint32 size, gboolean sync, gboolean do_pts,
+    gint64 pts_offset)
 {
   GstFlowReturn ret = GST_FLOW_OK;
 
@@ -1752,6 +1777,10 @@ flush:
     GstBuffer *buffer;
     guint i, total_size;
 
+    /* now we know where moof ends up, update offset in tfra */
+    if (pad->tfra)
+      atom_tfra_update_offset (pad->tfra, qtmux->header_size);
+
     moof = atom_moof_new (qtmux->context, qtmux->fragment_sequence);
     /* takes ownership */
     atom_moof_add_traf (moof, pad->traf);
@@ -1759,7 +1788,7 @@ flush:
     atom_moof_copy_data (moof, &data, &size, &offset);
     buffer = _gst_buffer_new_take_data (data, offset);
     GST_LOG_OBJECT (qtmux, "writing moof size %d", GST_BUFFER_SIZE (buffer));
-    ret = gst_qt_mux_send_buffer (qtmux, buffer, NULL, FALSE);
+    ret = gst_qt_mux_send_buffer (qtmux, buffer, &qtmux->header_size, FALSE);
 
     /* and actual data */
     total_size = 0;
@@ -1771,11 +1800,13 @@ flush:
     GST_LOG_OBJECT (qtmux, "writing %d buffers, total_size %d",
         atom_array_get_len (&pad->fragment_buffers), total_size);
     if (ret == GST_FLOW_OK)
-      ret = gst_qt_mux_send_mdat_header (qtmux, NULL, total_size, FALSE);
+      ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, total_size,
+          FALSE);
     for (i = 0; i < atom_array_get_len (&pad->fragment_buffers); i++) {
       if (G_LIKELY (ret == GST_FLOW_OK))
         ret = gst_qt_mux_send_buffer (qtmux,
-            atom_array_index (&pad->fragment_buffers, i), NULL, FALSE);
+            atom_array_index (&pad->fragment_buffers, i), &qtmux->header_size,
+            FALSE);
       else
         gst_buffer_unref (atom_array_index (&pad->fragment_buffers, i));
     }
@@ -1793,6 +1824,11 @@ init:
     atom_array_init (&pad->fragment_buffers, 512);
     pad->fragment_duration = gst_util_uint64_scale (qtmux->fragment_duration,
         atom_trak_get_timescale (pad->trak), 1000);
+
+    if (G_UNLIKELY (qtmux->mfra && !pad->tfra)) {
+      pad->tfra = atom_tfra_new (qtmux->context, atom_trak_get_id (pad->trak));
+      atom_mfra_add_tfra (qtmux->mfra, pad->tfra);
+    }
   }
 
   /* add buffer and metadata */
@@ -1800,6 +1836,13 @@ init:
   atom_array_append (&pad->fragment_buffers, buf, 256);
   pad->fragment_duration -= delta;
 
+  if (pad->tfra) {
+    guint32 sn = atom_traf_get_sample_num (pad->traf);
+
+    if ((sync && pad->sync) || (sn == 1 && !pad->sync))
+      atom_tfra_add_entry (pad->tfra, dts, sn);
+  }
+
   if (G_UNLIKELY (force))
     goto flush;
 
@@ -2049,8 +2092,8 @@ gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf)
   if (qtmux->fragment_sequence) {
     /* ensure that always sync samples are marked as such */
     return gst_qt_mux_pad_fragment_add_buffer (qtmux, pad, last_buf,
-        buf == NULL, nsamples, scaled_duration, sample_size, !pad->sync || sync,
-        do_pts, pts_offset);
+        buf == NULL, nsamples, last_dts, scaled_duration, sample_size,
+        !pad->sync || sync, do_pts, pts_offset);
   } else {
     atom_trak_add_samples (pad->trak, nsamples, scaled_duration, sample_size,
         chunk_offset, sync, do_pts, pts_offset);
index 9856d77..76f9d23 100644 (file)
@@ -113,6 +113,8 @@ struct _GstQTPad
   ATOM_ARRAY (GstBuffer *) fragment_buffers;
   /* running fragment duration */
   gint64 fragment_duration;
+  /* optional fragment index book-keeping */
+  AtomTFRA *tfra;
 
   /* if nothing is set, it won't be called */
   GstQTPadPrepareBufferFunc prepare_buf_func;
@@ -154,6 +156,9 @@ struct _GstQTMux
   GSList *extra_atoms; /* list of extra top-level atoms (e.g. UUID for xmp)
                         * Stored as AtomInfo structs */
 
+  /* fragmented file index */
+  AtomMFRA *mfra;
+
   /* fast start */
   FILE *fast_start_file;