qtmux: improve support for sparse streams
authorThiago Santos <ts.santos@sisa.samsung.com>
Fri, 7 Feb 2014 04:49:26 +0000 (01:49 -0300)
committerThiago Santos <ts.santos@sisa.samsung.com>
Fri, 7 Feb 2014 16:10:24 +0000 (13:10 -0300)
Do not try to use subsequent buffer timestamps to calculate
sparse streams durations because the stream is sparse and
the buffers might not be 'time adjacent'. So rely on the
duration and give the option to the pad to provide
custom 'empty' buffers to represent the gaps in the
stream, this can vary on how the data is represented.

Right now, the only sparse stream supported is tx3g subtitles.

gst/isomp4/gstqtmux.c
gst/isomp4/gstqtmux.h

index 424f951b73119c2dcdc435c85f8dc2b8bf2a1761..89401dcda4c8a4531e7aed0e8483762a732af8d1 100644 (file)
@@ -378,10 +378,12 @@ gst_qt_mux_pad_reset (GstQTPad * qtpad)
   qtpad->last_dts = 0;
   qtpad->first_ts = GST_CLOCK_TIME_NONE;
   qtpad->prepare_buf_func = NULL;
+  qtpad->create_empty_buffer = NULL;
   qtpad->avg_bitrate = 0;
   qtpad->max_bitrate = 0;
   qtpad->total_duration = 0;
   qtpad->total_bytes = 0;
+  qtpad->sparse = FALSE;
 
   qtpad->buf_head = 0;
   qtpad->buf_tail = 0;
@@ -583,6 +585,17 @@ gst_qt_mux_prepare_tx3g_buffer (GstQTPad * qtpad, GstBuffer * buf,
   return newbuf;
 }
 
+static GstBuffer *
+gst_qt_mux_create_empty_tx3g_buffer (GstQTPad * qtpad, gint64 duration)
+{
+  guint8 *data;
+
+  data = g_malloc (2);
+  GST_WRITE_UINT16_BE (data, 0);
+
+  return gst_buffer_new_wrapped (data, 2);;
+}
+
 static void
 gst_qt_mux_add_mp4_tag (GstQTMux * qtmux, const GstTagList * list,
     const char *tag, const char *tag2, guint32 fourcc)
@@ -2185,6 +2198,41 @@ check_and_subtract_ts (GstQTMux * qtmux, GstClockTime * ts_a, GstClockTime ts_b)
   }
 }
 
+
+static GstFlowReturn
+gst_qt_mux_register_and_push_sample (GstQTMux * qtmux, GstQTPad * pad,
+    GstBuffer * buffer, gboolean is_last_buffer, guint nsamples,
+    gint64 last_dts, gint64 scaled_duration, guint sample_size,
+    guint chunk_offset, gboolean sync, gboolean do_pts, gint64 pts_offset)
+{
+  GstFlowReturn ret = GST_FLOW_OK;
+
+  /* note that a new chunk is started each time (not fancy but works) */
+  if (qtmux->moov_recov_file) {
+    if (!atoms_recov_write_trak_samples (qtmux->moov_recov_file, pad->trak,
+            nsamples, (gint32) scaled_duration, sample_size, chunk_offset, sync,
+            do_pts, pts_offset)) {
+      GST_WARNING_OBJECT (qtmux, "Failed to write sample information to "
+          "recovery file, disabling recovery");
+      fclose (qtmux->moov_recov_file);
+      qtmux->moov_recov_file = NULL;
+    }
+  }
+
+  if (qtmux->fragment_sequence) {
+    /* ensure that always sync samples are marked as such */
+    ret = gst_qt_mux_pad_fragment_add_buffer (qtmux, pad, buffer,
+        is_last_buffer, nsamples, last_dts, (gint32) scaled_duration,
+        sample_size, !pad->sync || sync, pts_offset);
+  } else {
+    atom_trak_add_samples (pad->trak, nsamples, (gint32) scaled_duration,
+        sample_size, chunk_offset, sync, pts_offset);
+    ret = gst_qt_mux_send_buffer (qtmux, buffer, &qtmux->mdat_size, TRUE);
+  }
+
+  return ret;
+}
+
 /*
  * Here we push the buffer and update the tables in the track atoms
  */
@@ -2304,14 +2352,17 @@ gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf)
    * the duration based on the difference in DTS or PTS, falling back
    * to DURATION if the other two don't exist, such as with the last
    * sample before EOS. */
-  if (last_buf && buf && GST_BUFFER_DTS_IS_VALID (buf)
-      && GST_BUFFER_DTS_IS_VALID (last_buf))
-    duration = GST_BUFFER_DTS (buf) - GST_BUFFER_DTS (last_buf);
-  else if (last_buf && buf && GST_BUFFER_PTS_IS_VALID (buf)
-      && GST_BUFFER_PTS_IS_VALID (last_buf))
-    duration = GST_BUFFER_PTS (buf) - GST_BUFFER_PTS (last_buf);
-  else
-    duration = GST_BUFFER_DURATION (last_buf);
+  duration = GST_BUFFER_DURATION (last_buf);
+  if (!pad->sparse) {
+    if (last_buf && buf && GST_BUFFER_DTS_IS_VALID (buf)
+        && GST_BUFFER_DTS_IS_VALID (last_buf))
+      duration = GST_BUFFER_DTS (buf) - GST_BUFFER_DTS (last_buf);
+    else if (last_buf && buf && GST_BUFFER_PTS_IS_VALID (buf)
+        && GST_BUFFER_PTS_IS_VALID (last_buf))
+      duration = GST_BUFFER_PTS (buf) - GST_BUFFER_PTS (last_buf);
+  }
+
+  gst_buffer_replace (&pad->last_buf, buf);
 
   /* for computing the avg bitrate */
   if (G_LIKELY (last_buf)) {
@@ -2319,8 +2370,6 @@ gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf)
     pad->total_duration += duration;
   }
 
-  gst_buffer_replace (&pad->last_buf, buf);
-
   last_dts = gst_util_uint64_scale_round (pad->last_dts,
       atom_trak_get_timescale (pad->trak), GST_SECOND);
 
@@ -2425,32 +2474,44 @@ gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf)
   }
 
   /* now we go and register this buffer/sample all over */
-  /* note that a new chunk is started each time (not fancy but works) */
-  if (qtmux->moov_recov_file) {
-    if (!atoms_recov_write_trak_samples (qtmux->moov_recov_file, pad->trak,
-            nsamples, (gint32) scaled_duration, sample_size, chunk_offset, sync,
-            do_pts, pts_offset)) {
-      GST_WARNING_OBJECT (qtmux, "Failed to write sample information to "
-          "recovery file, disabling recovery");
-      fclose (qtmux->moov_recov_file);
-      qtmux->moov_recov_file = NULL;
+  ret = gst_qt_mux_register_and_push_sample (qtmux, pad, last_buf,
+      buf == NULL, nsamples, last_dts, scaled_duration, sample_size,
+      chunk_offset, sync, do_pts, pts_offset);
+
+  /* if this is sparse and we have a next buffer, check if there is any gap
+   * between them to insert an empty sample */
+  if (pad->sparse && buf) {
+    if (pad->create_empty_buffer) {
+      GstBuffer *empty_buf;
+      gint64 empty_duration =
+          GST_BUFFER_TIMESTAMP (buf) - (GST_BUFFER_TIMESTAMP (last_buf) +
+          duration);
+      gint64 empty_duration_scaled;
+
+      empty_buf = pad->create_empty_buffer (pad, empty_duration);
+
+      empty_duration_scaled = gst_util_uint64_scale_round (empty_duration,
+          atom_trak_get_timescale (pad->trak), GST_SECOND);
+
+      pad->total_bytes += gst_buffer_get_size (empty_buf);
+      pad->total_duration += duration;
+
+      ret =
+          gst_qt_mux_register_and_push_sample (qtmux, pad, empty_buf, FALSE, 1,
+          last_dts + scaled_duration, empty_duration_scaled,
+          gst_buffer_get_size (empty_buf), qtmux->mdat_size, sync, do_pts, 0);
+    } else {
+      /* our only case currently is tx3g subtitles, so there is no reason to fill this yet */
+      g_assert_not_reached ();
+      GST_WARNING_OBJECT (qtmux,
+          "no empty buffer creation function found for pad %s",
+          GST_PAD_NAME (pad->collect.pad));
     }
   }
 
   if (buf)
     gst_buffer_unref (buf);
 
-  if (qtmux->fragment_sequence) {
-    /* ensure that always sync samples are marked as such */
-    ret = gst_qt_mux_pad_fragment_add_buffer (qtmux, pad, last_buf,
-        buf == NULL, nsamples, last_dts, (gint32) scaled_duration, sample_size,
-        !pad->sync || sync, pts_offset);
-  } else {
-    atom_trak_add_samples (pad->trak, nsamples, (gint32) scaled_duration,
-        sample_size, chunk_offset, sync, pts_offset);
-    ret = gst_qt_mux_send_buffer (qtmux, last_buf, &qtmux->mdat_size, TRUE);
-  }
-
 exit:
 
   return ret;
@@ -3233,6 +3294,7 @@ gst_qt_mux_subtitle_sink_set_caps (GstQTPad * qtpad, GstCaps * caps)
   subtitle_sample_entry_init (&entry);
   qtpad->is_out_of_order = FALSE;
   qtpad->sync = FALSE;
+  qtpad->sparse = TRUE;
   qtpad->prepare_buf_func = NULL;
 
   structure = gst_caps_get_structure (caps, 0);
@@ -3242,6 +3304,7 @@ gst_qt_mux_subtitle_sink_set_caps (GstQTPad * qtpad, GstCaps * caps)
     if (format && strcmp (format, "utf8") == 0) {
       entry.fourcc = FOURCC_tx3g;
       qtpad->prepare_buf_func = gst_qt_mux_prepare_tx3g_buffer;
+      qtpad->create_empty_buffer = gst_qt_mux_create_empty_tx3g_buffer;
     }
   }
 
index 277283ffca78136eaba412c5ef87da04efab6d42..336803bc3b63784af66c7170605c2c564f7abce5 100644 (file)
@@ -79,6 +79,7 @@ typedef GstBuffer * (*GstQTPadPrepareBufferFunc) (GstQTPad * pad,
     GstBuffer * buf, GstQTMux * qtmux);
 
 typedef gboolean (*GstQTPadSetCapsFunc) (GstQTPad * pad, GstCaps * caps);
+typedef GstBuffer * (*GstQTPadCreateEmptyBufferFunc) (GstQTPad * pad, gint64 duration);
 
 #define QTMUX_NO_OF_TS   10
 
@@ -96,6 +97,9 @@ struct _GstQTPad
   guint sample_size;
   /* make sync table entry */
   gboolean sync;
+  /* if it is a sparse stream
+   * (meaning we can't use PTS differences to compute duration) */
+  gboolean sparse;
   /* bitrates */
   guint32 avg_bitrate, max_bitrate;
 
@@ -129,6 +133,7 @@ struct _GstQTPad
   /* if nothing is set, it won't be called */
   GstQTPadPrepareBufferFunc prepare_buf_func;
   GstQTPadSetCapsFunc set_caps;
+  GstQTPadCreateEmptyBufferFunc create_empty_buffer;
 };
 
 typedef enum _GstQTMuxState