Merge branch 'upstream/1.16' into tizen_gst_1.16.2
[platform/upstream/gst-plugins-good.git] / gst / isomp4 / gstqtmux.c
index 0dee96e..94a1314 100644 (file)
  * #GstQTMux::reserved-duration-remaining property to see how close to full
  * the reserved space is becoming.
  *
+ * Applications that wish to be able to use/edit a file while it is being
+ * written to by live content, can use the "Robust Prefill Muxing" mode. That
+ * mode is a variant of the "Robust Muxing" mode in that it will pre-allocate a
+ * completely valid header from the start for all tracks (i.e. it appears as
+ * though the file is "reserved-max-duration" long with all samples
+ * present). This mode can be enabled by setting the
+ * #GstQTMux::reserved-moov-update-period and #GstQTMux::reserved-prefill
+ * properties. Note that this mode is only possible with input streams that have
+ * a fixed sample size (such as raw audio and Prores Video) and that don't
+ * have reordered samples.
+ *
  * <refsect2>
  * <title>Example pipelines</title>
  * |[
@@ -386,6 +397,10 @@ enum
   PROP_INTERLEAVE_BYTES,
   PROP_INTERLEAVE_TIME,
   PROP_MAX_RAW_AUDIO_DRIFT,
+  PROP_START_GAP_THRESHOLD,
+#ifdef TIZEN_FEATURE_GST_MUX_ENHANCEMENT
+  PROP_EXPECTED_TRAILER_SIZE,
+#endif /* TIZEN_FEATURE_GST_MUX_ENHANCEMENT */  
 };
 
 /* some spare for header size as well */
@@ -409,6 +424,7 @@ enum
 #define DEFAULT_INTERLEAVE_BYTES 0
 #define DEFAULT_INTERLEAVE_TIME 250*GST_MSECOND
 #define DEFAULT_MAX_RAW_AUDIO_DRIFT 40 * GST_MSECOND
+#define DEFAULT_START_GAP_THRESHOLD 0
 
 static void gst_qt_mux_finalize (GObject * object);
 
@@ -443,13 +459,235 @@ static void gst_qt_mux_update_edit_lists (GstQTMux * qtmux);
 
 static GstElementClass *parent_class = NULL;
 
+#ifdef TIZEN_FEATURE_GST_MUX_ENHANCEMENT
+/*
+     [[ Metadata Size ]]
+     1. Common
+       free = 8
+       moov = 8
+         mvhd = 108
+       -------------
+       total : 124
+
+     2. Video
+       i. Video common
+         trak = 8
+           tkhd = 92
+           mdia = 8
+             mdhd = 32
+             hdlr = 45
+             minf = 8
+               vmhd = 20
+               dinf = 36 (8, dref : 16 , url : 12)
+               stbl = 8
+         ---------------
+         total : 257
+
+       ii. Variation in file format
+         - MP4
+             ftyp = 32
+             udta = 61
+         - 3GP
+             ftyp = 28
+             udta = 8
+
+       iii. Variation in codec
+         - MPEG4
+           stsd = 137(16, mp4v : 86, esds : 35)
+
+         - H.264 = 487(or 489) + (8*stts_count) + (8*frame) + (4*I-frame)
+           stsd = 134 (SPS 9, PPS 4) or 136 (SPS 111, PPS 4)
+
+         - H.263 = 470 + + (8*stts_count) + (8*frame) + (4*I-frame)
+           stsd = 102 -> different from H.264
+
+       iv. Variation in frame
+         stts = 16 + (8*stts_count)
+         stss = 16 + (4*I-frame)
+         stsc = 28
+         stsz = 20 + (4*frame)
+         stco = 16 + (4*frame)
+
+     3. Audio
+       i. Audio common
+         trak = 8
+           tkhd = 92
+           mdia = 8
+             mdhd = 32
+             hdlr = 45
+             minf = 8
+               smhd = 16
+               dinf = 36 (8, dref : 16, url : 12)
+               stbl = 8
+         ---------------
+         total : 253
+
+                 stts = 16
+                 stsz = 20
+                 stco = 16
+                 ------------
+                 total : 52
+
+       ii. Variation in file format
+         - MP4
+             udta = 61
+         - 3GP
+             udta = 8
+
+       iii. Variation in codec
+         - Common
+             stts = 16 + (8*stts_count)
+             stsc = 28
+             stsz = 20 + (4*frame)
+             stco = 16 + (4*frame)
+
+         - AAC
+             stsd = 94 (16, mp4a : 78(36 ,esds : 42))
+
+         - AMR
+             stsd = 69 (16, samr : 53(36, damr : 17))
+*/
+
+/* trailer entry size */
+#define ENTRY_SIZE_VIDEO_STTS         8
+#define ENTRY_SIZE_VIDEO_STSS         4
+#define ENTRY_SIZE_VIDEO_STSZ         4
+#define ENTRY_SIZE_VIDEO_STCO         4
+#define ENTRY_SIZE_AUDIO_STTS         8
+#define ENTRY_SIZE_AUDIO_STSZ         4
+#define ENTRY_SIZE_AUDIO_STCO         4
+
+#define ENTRY_SIZE_VIDEO_MPEG4_STSD   137
+#define ENTRY_SIZE_VIDEO_H263P_STSD   102
+#define ENTRY_SIZE_AUDIO_AAC_STSD     94
+#define ENTRY_SIZE_AUDIO_AMR_STSD     69
+
+#define ENTRY_SIZE_STSC               28
+#define ENTRY_SIZE_VIDEO_ST           68        /*atom size (stss + stts + stsc + stsz + stco ) * (size + atom + version + flags + sample count)+stsz(sample size) */
+#define ENTRY_SIZE_AUDIO_ST           52        /*atom size (stss + stsc + stsz + stco ) * (size + atom + version + flags + sample count)+stsz(sample size) */
+
+/* common */
+#define MUX_COMMON_SIZE_HEADER             124   /* free + moov + moov.mvhd*/
+
+#define MUX_COMMON_SIZE_VIDEO_HEADER       257
+#define MUX_COMMON_SIZE_AUDIO_HEADER       253
+
+#define MUX_COMMON_SIZE_MP4_FTYP           32
+#define MUX_COMMON_SIZE_3GP_FTYP           28
+
+#define MUX_COMMON_SIZE_MP4_UDTA           61
+#define MUX_COMMON_SIZE_3GP_UDTA           8
+
+static void
+gst_qt_mux_update_expected_trailer_size (GstQTMux *qtmux, GstQTPad *pad)
+{
+  guint nb_video_frames = 0;
+  guint nb_video_i_frames = 0;
+  guint nb_video_stts_entry = 0;
+  guint nb_audio_frames = 0;
+  guint nb_audio_stts_entry = 0;
+  gboolean video_stream = FALSE;
+  gboolean audio_stream = FALSE;
+  guint exp_size = 0;
+  GstQTMuxClass *qtmux_klass = NULL;
+
+  if (qtmux == NULL || pad == NULL) {
+    GST_ERROR_OBJECT (qtmux, "Invalid parameter");
+    return;
+  }
+
+  qtmux_klass = (GstQTMuxClass *)(G_OBJECT_GET_CLASS(qtmux));
+
+  if (!strncmp(GST_PAD_NAME(pad->collect.pad), "video", 5)) {
+    nb_video_frames += pad->trak->mdia.minf.stbl.stsz.table_size;
+    nb_video_i_frames += pad->trak->mdia.minf.stbl.stss.entries.len;
+    nb_video_stts_entry += pad->trak->mdia.minf.stbl.stts.entries.len;
+
+    video_stream = TRUE;
+  } else if (!strncmp(GST_PAD_NAME(pad->collect.pad), "audio", 5)) {
+    nb_audio_frames += pad->trak->mdia.minf.stbl.stsz.table_size;
+    nb_audio_stts_entry += pad->trak->mdia.minf.stbl.stts.entries.len;
+
+    audio_stream = TRUE;
+  }
+
+  /* free + moov + mvhd */
+  qtmux->expected_trailer_size = MUX_COMMON_SIZE_HEADER;
+
+  /* ftyp + udta * 3 (There is 3 udta fields and it's same size) */
+  switch (qtmux_klass->format) {
+  case GST_QT_MUX_FORMAT_MP4:
+    qtmux->expected_trailer_size += MUX_COMMON_SIZE_MP4_FTYP + MUX_COMMON_SIZE_MP4_UDTA * 3;
+    break;
+  case GST_QT_MUX_FORMAT_3GP:
+    qtmux->expected_trailer_size += MUX_COMMON_SIZE_3GP_FTYP + MUX_COMMON_SIZE_3GP_UDTA * 3;
+    break;
+  default:
+    break;
+  }
+
+  /* Calculate trailer size for video stream */
+  if (video_stream) {
+    switch (pad->fourcc) {
+    case FOURCC_h263:
+    case FOURCC_s263:
+      exp_size += MUX_COMMON_SIZE_VIDEO_HEADER + ENTRY_SIZE_VIDEO_H263P_STSD;
+      break;
+    case FOURCC_mp4v:
+    case FOURCC_MP4V:
+    case FOURCC_fmp4:
+    case FOURCC_FMP4:
+    case FOURCC_3gp4:
+    case FOURCC_3gp6:
+    case FOURCC_3gg6:
+      exp_size += MUX_COMMON_SIZE_VIDEO_HEADER + ENTRY_SIZE_VIDEO_MPEG4_STSD;
+      break;
+    default:
+      break;
+    }
+
+    /* frame related */
+    exp_size += ENTRY_SIZE_VIDEO_ST + (ENTRY_SIZE_VIDEO_STTS * nb_video_stts_entry) +
+                (ENTRY_SIZE_VIDEO_STSS * nb_video_i_frames) + (ENTRY_SIZE_STSC) +
+                ((ENTRY_SIZE_VIDEO_STSZ + ENTRY_SIZE_VIDEO_STCO) * nb_video_frames);
+
+    qtmux->video_expected_trailer_size = exp_size;
+  }
+
+  /* Calculate trailer size for audio stream */
+  if (audio_stream) {
+    exp_size += MUX_COMMON_SIZE_AUDIO_HEADER + ENTRY_SIZE_AUDIO_ST + (ENTRY_SIZE_AUDIO_STTS * nb_audio_stts_entry) +
+                (ENTRY_SIZE_STSC) + ((ENTRY_SIZE_AUDIO_STSZ + ENTRY_SIZE_AUDIO_STCO) * nb_audio_frames);
+
+    if (pad->fourcc == FOURCC_samr)
+      exp_size += ENTRY_SIZE_AUDIO_AMR_STSD;
+    else
+      exp_size += ENTRY_SIZE_AUDIO_AAC_STSD;
+
+    qtmux->audio_expected_trailer_size = exp_size;
+  }
+
+  qtmux->expected_trailer_size += qtmux->video_expected_trailer_size + qtmux->audio_expected_trailer_size;
+
+  /*
+  GST_INFO_OBJECT (qtmux, "pad type %s", GST_PAD_NAME(pad->collect.pad));
+  GST_INFO_OBJECT (qtmux, "VIDEO : stts-entry=[%d], i-frame=[%d], video-sample=[%d]", nb_video_stts_entry, nb_video_i_frames, nb_video_frames);
+  GST_INFO_OBJECT (qtmux, "AUDIO : stts-entry=[%d], audio-sample=[%d]", nb_audio_stts_entry, nb_audio_frames);
+  GST_INFO_OBJECT (qtmux, "expected trailer size %d", qtmux->expected_trailer_size);
+  */
+
+  return;
+}
+#endif /* TIZEN_FEATURE_GST_MUX_ENHANCEMENT */
+
 static void
 gst_qt_mux_base_init (gpointer g_class)
 {
   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
   GstQTMuxClass *klass = (GstQTMuxClass *) g_class;
   GstQTMuxClassParams *params;
-  GstPadTemplate *videosinktempl, *audiosinktempl, *subtitlesinktempl;
+  GstPadTemplate *videosinktempl, *audiosinktempl, *subtitlesinktempl,
+      *captionsinktempl;
   GstPadTemplate *srctempl;
   gchar *longname, *description;
 
@@ -494,6 +732,13 @@ gst_qt_mux_base_init (gpointer g_class)
     gst_element_class_add_pad_template (element_class, subtitlesinktempl);
   }
 
+  if (params->caption_sink_caps) {
+    captionsinktempl = gst_pad_template_new_with_gtype ("caption_%u",
+        GST_PAD_SINK, GST_PAD_REQUEST, params->caption_sink_caps,
+        GST_TYPE_QT_MUX_PAD);
+    gst_element_class_add_pad_template (element_class, captionsinktempl);
+  }
+
   klass->format = params->prop->format;
 }
 
@@ -505,6 +750,9 @@ gst_qt_mux_class_init (GstQTMuxClass * klass)
   GParamFlags streamable_flags;
   const gchar *streamable_desc;
   gboolean streamable;
+#ifdef TIZEN_FEATURE_GST_MUX_ENHANCEMENT
+  GParamSpec *tspec = NULL;
+#endif /* TIZEN_FEATURE_GST_MUX_ENHANCEMENT */
 #define STREAMABLE_DESC "If set to true, the output should be as if it is to "\
   "be streamed and hence no indexes written or duration written."
 
@@ -628,6 +876,21 @@ gst_qt_mux_class_init (GstQTMuxClass * klass)
           "Maximum allowed drift of raw audio samples vs. timestamps in nanoseconds",
           0, G_MAXUINT64, DEFAULT_MAX_RAW_AUDIO_DRIFT,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_START_GAP_THRESHOLD,
+      g_param_spec_uint64 ("start-gap-threshold", "Start Gap Threshold",
+          "Threshold for creating an edit list for gaps at the start in nanoseconds",
+          0, G_MAXUINT64, DEFAULT_START_GAP_THRESHOLD,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+#ifdef TIZEN_FEATURE_GST_MUX_ENHANCEMENT
+  tspec = g_param_spec_uint("expected-trailer-size", "Expected Trailer Size",
+    "Expected trailer size (bytes)",
+    0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  if (tspec)
+    g_object_class_install_property(gobject_class, PROP_EXPECTED_TRAILER_SIZE, tspec);
+  else
+    GST_ERROR("g_param_spec failed for \"expected-trailer-size\"");
+#endif /* TIZEN_FEATURE_GST_MUX_ENHANCEMENT */
 
   gstelement_class->request_new_pad =
       GST_DEBUG_FUNCPTR (gst_qt_mux_request_new_pad);
@@ -750,6 +1013,7 @@ gst_qt_mux_reset (GstQTMux * qtmux, gboolean alloc)
 
   if (alloc) {
     qtmux->moov = atom_moov_new (qtmux->context);
+#ifndef TIZEN_FEATURE_GST_MUX_ENHANCEMENT
     /* ensure all is as nice and fresh as request_new_pad would provide it */
     for (walk = qtmux->sinkpads; walk; walk = g_slist_next (walk)) {
       GstQTPad *qtpad = (GstQTPad *) walk->data;
@@ -757,6 +1021,7 @@ gst_qt_mux_reset (GstQTMux * qtmux, gboolean alloc)
       qtpad->trak = atom_trak_new (qtmux->context);
       atom_moov_add_trak (qtmux->moov, qtpad->trak);
     }
+#endif
   }
 
   qtmux->current_pad = NULL;
@@ -768,6 +1033,12 @@ gst_qt_mux_reset (GstQTMux * qtmux, gboolean alloc)
   qtmux->last_moov_update = GST_CLOCK_TIME_NONE;
   qtmux->muxed_since_last_update = 0;
   qtmux->reserved_duration_remaining = GST_CLOCK_TIME_NONE;
+
+#ifdef TIZEN_FEATURE_GST_MUX_ENHANCEMENT
+  qtmux->expected_trailer_size = 0;
+  qtmux->video_expected_trailer_size = 0;
+  qtmux->audio_expected_trailer_size = 0;
+#endif /* TIZEN_FEATURE_GST_MUX_ENHANCEMENT */
 }
 
 static void
@@ -799,6 +1070,7 @@ gst_qt_mux_init (GstQTMux * qtmux, GstQTMuxClass * qtmux_klass)
   qtmux->interleave_bytes = DEFAULT_INTERLEAVE_BYTES;
   qtmux->interleave_time = DEFAULT_INTERLEAVE_TIME;
   qtmux->max_raw_audio_drift = DEFAULT_MAX_RAW_AUDIO_DRIFT;
+  qtmux->start_gap_threshold = DEFAULT_START_GAP_THRESHOLD;
 
   /* always need this */
   qtmux->context =
@@ -854,6 +1126,159 @@ gst_qt_mux_prepare_jpc_buffer (GstQTPad * qtpad, GstBuffer * buf,
   return newbuf;
 }
 
+static gsize
+extract_608_field_from_s334_1a (const guint8 * ccdata, gsize ccdata_size,
+    guint field, guint8 ** res)
+{
+  guint8 *storage;
+  gsize storage_size = 128;
+  gsize i, res_size = 0;
+
+  storage = g_malloc0 (storage_size);
+
+  /* Iterate over the ccdata and put the corresponding tuples for the given field
+   * in the storage */
+  for (i = 0; i < ccdata_size; i += 3) {
+    if ((field == 1 && (ccdata[i * 3] & 0x80)) ||
+        (field == 2 && !(ccdata[i * 3] & 0x80))) {
+      GST_DEBUG ("Storing matching cc for field %d : 0x%02x 0x%02x", field,
+          ccdata[i * 3 + 1], ccdata[i * 3 + 2]);
+      if (res_size >= storage_size) {
+        storage_size += 128;
+        storage = g_realloc (storage, storage_size);
+      }
+      storage[res_size] = ccdata[i * 3 + 1];
+      storage[res_size + 1] = ccdata[i * 3 + 2];
+      res_size += 2;
+    }
+  }
+
+  if (res_size == 0) {
+    g_free (storage);
+    *res = NULL;
+    return 0;
+  }
+
+  *res = storage;
+  return res_size;
+}
+
+
+static GstBuffer *
+gst_qt_mux_prepare_caption_buffer (GstQTPad * qtpad, GstBuffer * buf,
+    GstQTMux * qtmux)
+{
+  GstBuffer *newbuf = NULL;
+  GstMapInfo map, inmap;
+  gsize size;
+  gboolean in_prefill;
+
+  if (buf == NULL)
+    return NULL;
+
+  in_prefill = (qtmux->mux_mode == GST_QT_MUX_MODE_ROBUST_RECORDING_PREFILL);
+
+  size = gst_buffer_get_size (buf);
+  gst_buffer_map (buf, &inmap, GST_MAP_READ);
+
+  GST_LOG_OBJECT (qtmux,
+      "Preparing caption buffer %" GST_FOURCC_FORMAT " size:%" G_GSIZE_FORMAT,
+      GST_FOURCC_ARGS (qtpad->fourcc), size);
+
+  switch (qtpad->fourcc) {
+    case FOURCC_c608:
+    {
+      guint8 *cdat, *cdt2;
+      gsize cdat_size, cdt2_size, total_size = 0;
+      gsize write_offs = 0;
+
+      cdat_size =
+          extract_608_field_from_s334_1a (inmap.data, inmap.size, 1, &cdat);
+      cdt2_size =
+          extract_608_field_from_s334_1a (inmap.data, inmap.size, 2, &cdt2);
+
+      if (cdat_size)
+        total_size += cdat_size + 8;
+      if (cdt2_size)
+        total_size += cdt2_size + 8;
+      if (total_size == 0) {
+        GST_DEBUG_OBJECT (qtmux, "No 608 data ?");
+        /* FIXME : We might want to *always* store something, even if
+         * it's "empty" CC (i.e. 0x80 0x80) */
+        break;
+      }
+
+      newbuf = gst_buffer_new_and_alloc (in_prefill ? 20 : total_size);
+      /* Let's copy over all metadata and not the memory */
+      gst_buffer_copy_into (newbuf, buf, GST_BUFFER_COPY_METADATA, 0, size);
+
+      gst_buffer_map (newbuf, &map, GST_MAP_WRITE);
+      if (cdat_size || in_prefill) {
+        GST_WRITE_UINT32_BE (map.data, in_prefill ? 10 : cdat_size + 8);
+        GST_WRITE_UINT32_LE (map.data + 4, FOURCC_cdat);
+        if (cdat_size)
+          memcpy (map.data + 8, cdat, in_prefill ? 2 : cdat_size);
+        else {
+          /* Write 'empty' CC */
+          map.data[8] = 0x80;
+          map.data[9] = 0x80;
+        }
+        write_offs = in_prefill ? 10 : cdat_size + 8;
+        if (cdat_size)
+          g_free (cdat);
+      }
+
+      if (cdt2_size || in_prefill) {
+        GST_WRITE_UINT32_BE (map.data + write_offs,
+            in_prefill ? 10 : cdt2_size + 8);
+        GST_WRITE_UINT32_LE (map.data + write_offs + 4, FOURCC_cdt2);
+        if (cdt2_size)
+          memcpy (map.data + write_offs + 8, cdt2, in_prefill ? 2 : cdt2_size);
+        else {
+          /* Write 'empty' CC */
+          map.data[write_offs + 8] = 0x80;
+          map.data[write_offs + 9] = 0x80;
+        }
+        if (cdt2_size)
+          g_free (cdt2);
+      }
+      gst_buffer_unmap (newbuf, &map);
+      break;
+    }
+      break;
+    case FOURCC_c708:
+    {
+      /* Take the whole CDP */
+      if (in_prefill && size > 256) {
+        GST_ERROR_OBJECT (qtmux, "Input C708 CDP too big for prefill mode !");
+        break;
+      }
+      newbuf = gst_buffer_new_and_alloc (in_prefill ? 256 + 8 : size + 8);
+
+      /* Let's copy over all metadata and not the memory */
+      gst_buffer_copy_into (newbuf, buf, GST_BUFFER_COPY_METADATA, 0, size);
+
+      gst_buffer_map (newbuf, &map, GST_MAP_WRITE);
+
+      GST_WRITE_UINT32_BE (map.data, size + 8);
+      GST_WRITE_UINT32_LE (map.data + 4, FOURCC_ccdp);
+      memcpy (map.data + 8, inmap.data, inmap.size);
+
+      gst_buffer_unmap (newbuf, &map);
+      break;
+    }
+    default:
+      /* theoretically this should never happen, but let's keep this here in case */
+      GST_WARNING_OBJECT (qtmux, "Unknown caption format");
+      break;
+  }
+
+  gst_buffer_unmap (buf, &inmap);
+  gst_buffer_unref (buf);
+
+  return newbuf;
+}
+
 static GstBuffer *
 gst_qt_mux_prepare_tx3g_buffer (GstQTPad * qtpad, GstBuffer * buf,
     GstQTMux * qtmux)
@@ -2254,6 +2679,8 @@ prefill_get_block_index (GstQTMux * qtmux, GstQTPad * qpad)
     case FOURCC_apco:
     case FOURCC_ap4h:
     case FOURCC_ap4x:
+    case FOURCC_c608:
+    case FOURCC_c708:
       return qpad->sample_offset;
     case FOURCC_sowt:
     case FOURCC_twos:
@@ -2322,6 +2749,13 @@ prefill_get_sample_size (GstQTMux * qtmux, GstQTPad * qpad)
         return 900000;
       }
       break;
+    case FOURCC_c608:
+      /* We always write both cdat and cdt2 atom in prefill mode */
+      return 20;
+    case FOURCC_c708:
+      /* We're cheating a bit by always allocating 256 bytes plus 8 bytes for the atom header
+       * even if we use less  */
+      return 256 + 8;
     case FOURCC_sowt:
     case FOURCC_twos:{
       guint64 block_idx;
@@ -2356,6 +2790,8 @@ prefill_get_next_timestamp (GstQTMux * qtmux, GstQTPad * qpad)
     case FOURCC_apco:
     case FOURCC_ap4h:
     case FOURCC_ap4x:
+    case FOURCC_c608:
+    case FOURCC_c708:
       return gst_util_uint64_scale (qpad->sample_offset + 1,
           qpad->expected_sample_duration_d * GST_SECOND,
           qpad->expected_sample_duration_n);
@@ -2452,6 +2888,33 @@ prefill_raw_audio_prepare_buf_func (GstQTPad * qtpad, GstBuffer * buf,
   return buf;
 }
 
+static void
+find_video_sample_duration (GstQTMux * qtmux, guint * dur_n, guint * dur_d)
+{
+  GSList *walk;
+
+  /* Find the (first) video track and assume that we have to output
+   * in that size */
+  for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
+    GstCollectData *cdata = (GstCollectData *) walk->data;
+    GstQTPad *tmp_qpad = (GstQTPad *) cdata;
+
+    if (tmp_qpad->trak->is_video) {
+      *dur_n = tmp_qpad->expected_sample_duration_n;
+      *dur_d = tmp_qpad->expected_sample_duration_d;
+      break;
+    }
+  }
+
+  if (walk == NULL) {
+    GST_INFO_OBJECT (qtmux,
+        "Found no video framerate, using 40ms audio buffers");
+    *dur_n = 25;
+    *dur_d = 1;
+  }
+}
+
+/* Called when all pads are prerolled to adjust and  */
 static gboolean
 prefill_update_sample_size (GstQTMux * qtmux, GstQTPad * qpad)
 {
@@ -2461,37 +2924,26 @@ prefill_update_sample_size (GstQTMux * qtmux, GstQTPad * qpad)
     case FOURCC_apcs:
     case FOURCC_apco:
     case FOURCC_ap4h:
-    case FOURCC_ap4x:{
+    case FOURCC_ap4x:
+    {
       guint sample_size = prefill_get_sample_size (qtmux, qpad);
       atom_trak_set_constant_size_samples (qpad->trak, sample_size);
       return TRUE;
     }
+    case FOURCC_c608:
+    case FOURCC_c708:
+    {
+      guint sample_size = prefill_get_sample_size (qtmux, qpad);
+      /* We need a "valid" duration */
+      find_video_sample_duration (qtmux, &qpad->expected_sample_duration_n,
+          &qpad->expected_sample_duration_d);
+      atom_trak_set_constant_size_samples (qpad->trak, sample_size);
+      return TRUE;
+    }
     case FOURCC_sowt:
     case FOURCC_twos:{
-      GSList *walk;
-
-      /* Find the (first) video track and assume that we have to output
-       * in that size */
-      for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
-        GstCollectData *cdata = (GstCollectData *) walk->data;
-        GstQTPad *tmp_qpad = (GstQTPad *) cdata;
-
-        if (tmp_qpad->trak->is_video) {
-          qpad->expected_sample_duration_n =
-              tmp_qpad->expected_sample_duration_n;
-          qpad->expected_sample_duration_d =
-              tmp_qpad->expected_sample_duration_d;
-          break;
-        }
-      }
-
-      if (walk == NULL) {
-        GST_INFO_OBJECT (qpad->collect.pad,
-            "Found no video framerate, using 40ms audio buffers");
-        qpad->expected_sample_duration_n = 25;
-        qpad->expected_sample_duration_d = 1;
-      }
-
+      find_video_sample_duration (qtmux, &qpad->expected_sample_duration_n,
+          &qpad->expected_sample_duration_d);
       /* Set a prepare_buf_func that ensures this */
       qpad->prepare_buf_func = prefill_raw_audio_prepare_buf_func;
       qpad->raw_audio_adapter = gst_adapter_new ();
@@ -2505,12 +2957,17 @@ prefill_update_sample_size (GstQTMux * qtmux, GstQTPad * qpad)
   }
 }
 
+/* Only called at startup when doing the "fake" iteration of all tracks in order
+ * to prefill the sample tables in the header.  */
 static GstQTPad *
-find_best_pad_prefill (GstQTMux * qtmux)
+find_best_pad_prefill_start (GstQTMux * qtmux)
 {
   GSList *walk;
   GstQTPad *best_pad = NULL;
 
+  /* If interleave limits have been specified and the current pad is within
+   * those interleave limits, pick that one, otherwise let's try to figure out
+   * the next best one. */
   if (qtmux->current_pad &&
       (qtmux->interleave_bytes != 0 || qtmux->interleave_time != 0) &&
       (qtmux->interleave_bytes == 0
@@ -2524,9 +2981,13 @@ find_best_pad_prefill (GstQTMux * qtmux)
       best_pad = qtmux->current_pad;
     }
   } else if (qtmux->collect->data->next) {
+    /* Attempt to try another pad if we have one. Otherwise use the only pad
+     * present */
     best_pad = qtmux->current_pad = NULL;
   }
 
+  /* The next best pad is the one which has the lowest timestamp and hasn't
+   * exceeded the reserved max duration */
   if (!best_pad) {
     GstClockTime best_time = GST_CLOCK_TIME_NONE;
 
@@ -2551,6 +3012,11 @@ find_best_pad_prefill (GstQTMux * qtmux)
   return best_pad;
 }
 
+/* Called when starting the file in prefill_mode to figure out all the entries
+ * of the header based on the input stream and reserved maximum duration.
+ *
+ * The _actual_ header (i.e. with the proper duration and trimmed sample tables)
+ * will be updated and written on EOS. */
 static gboolean
 gst_qt_mux_prefill_samples (GstQTMux * qtmux)
 {
@@ -2579,7 +3045,8 @@ gst_qt_mux_prefill_samples (GstQTMux * qtmux)
           gst_collect_pads_peek (qtmux->collect, (GstCollectData *) qpad);
       GstVideoTimeCodeMeta *tc_meta;
 
-      if (buffer && (tc_meta = gst_buffer_get_video_time_code_meta (buffer))) {
+      if (buffer && (tc_meta = gst_buffer_get_video_time_code_meta (buffer))
+          && qpad->trak->is_video) {
         GstVideoTimeCode *tc = &tc_meta->tc;
 
         qpad->tc_trak = atom_trak_new (qtmux->context);
@@ -2608,7 +3075,7 @@ gst_qt_mux_prefill_samples (GstQTMux * qtmux)
     }
   }
 
-  while ((qpad = find_best_pad_prefill (qtmux))) {
+  while ((qpad = find_best_pad_prefill_start (qtmux))) {
     GstClockTime timestamp, next_timestamp, duration;
     guint nsamples, sample_size;
     guint64 chunk_offset;
@@ -2755,6 +3222,10 @@ gst_qt_mux_start_file (GstQTMux * qtmux)
             (NULL));
         return GST_FLOW_ERROR;
       }
+      if (qtmux->reserved_moov_update_period == GST_CLOCK_TIME_NONE) {
+        GST_WARNING_OBJECT (qtmux,
+            "Robust muxing requires reserved-moov-update-period to be set");
+      }
       break;
     case GST_QT_MUX_MODE_FAST_START:
     case GST_QT_MUX_MODE_FRAGMENTED_STREAMABLE:
@@ -2816,6 +3287,50 @@ gst_qt_mux_start_file (GstQTMux * qtmux)
     qtmux->timescale = suggested_timescale;
   }
 
+  /* Set width/height/timescale of any closed caption tracks to that of the
+   * first video track */
+  {
+    guint video_width = 0, video_height = 0;
+    guint32 video_timescale = 0;
+    GSList *walk;
+
+    for (walk = qtmux->sinkpads; walk; walk = g_slist_next (walk)) {
+      GstCollectData *cdata = (GstCollectData *) walk->data;
+      GstQTPad *qpad = (GstQTPad *) cdata;
+
+      if (!qpad->trak)
+        continue;
+
+      /* Not closed caption */
+      if (qpad->trak->mdia.hdlr.handler_type != FOURCC_clcp)
+        continue;
+
+      if (video_width == 0 || video_height == 0 || video_timescale == 0) {
+        GSList *walk2;
+
+        for (walk2 = qtmux->sinkpads; walk2; walk2 = g_slist_next (walk2)) {
+          GstCollectData *cdata2 = (GstCollectData *) walk2->data;
+          GstQTPad *qpad2 = (GstQTPad *) cdata2;
+
+          if (!qpad2->trak)
+            continue;
+
+          /* not video */
+          if (!qpad2->trak->mdia.minf.vmhd)
+            continue;
+
+          video_width = qpad2->trak->tkhd.width;
+          video_height = qpad2->trak->tkhd.height;
+          video_timescale = qpad2->trak->mdia.mdhd.time_info.timescale;
+        }
+      }
+
+      qpad->trak->tkhd.width = video_width << 16;
+      qpad->trak->tkhd.height = video_height << 16;
+      qpad->trak->mdia.mdhd.time_info.timescale = video_timescale;
+    }
+  }
+
   /* initialize our moov recovery file */
   if (qtmux->moov_recov_file_path) {
     gst_qt_mux_prepare_moov_recovery (qtmux);
@@ -3237,13 +3752,19 @@ gst_qt_mux_update_edit_lists (GstQTMux * qtmux)
       has_gap = (qtpad->first_ts > (qtmux->first_ts + qtpad->dts_adjustment));
 
       if (has_gap) {
-        GstClockTime diff;
+        GstClockTime diff, trak_lateness;
 
         diff = qtpad->first_ts - (qtmux->first_ts + qtpad->dts_adjustment);
         lateness = gst_util_uint64_scale_round (diff,
             qtmux->timescale, GST_SECOND);
 
-        if (lateness > 0) {
+        /* Allow up to 1 trak timescale unit of lateness, Such a small
+         * timestamp/duration can't be represented by the trak-specific parts
+         * of the headers anyway, so it's irrelevantly small */
+        trak_lateness = gst_util_uint64_scale (diff,
+            atom_trak_get_timescale (qtpad->trak), GST_SECOND);
+
+        if (trak_lateness > 0 && diff > qtmux->start_gap_threshold) {
           GST_DEBUG_OBJECT (qtmux,
               "Pad %s is a late stream by %" GST_TIME_FORMAT,
               GST_PAD_NAME (qtpad->collect.pad), GST_TIME_ARGS (diff));
@@ -3376,9 +3897,11 @@ gst_qt_mux_stop_file (GstQTMux * qtmux)
        * mvhd should be consistent with empty moov
        * (but TODO maybe some clients do not handle that well ?) */
       qtmux->moov->mvex.mehd.fragment_duration =
-          gst_util_uint64_scale (qtmux->last_dts, qtmux->timescale, GST_SECOND);
-      GST_DEBUG_OBJECT (qtmux, "rewriting moov with mvex duration %"
-          GST_TIME_FORMAT, GST_TIME_ARGS (qtmux->last_dts));
+          gst_util_uint64_scale_round (qtmux->last_dts, qtmux->timescale,
+          GST_SECOND);
+      GST_DEBUG_OBJECT (qtmux,
+          "rewriting moov with mvex duration %" GST_TIME_FORMAT,
+          GST_TIME_ARGS (qtmux->last_dts));
       /* seek and rewrite the header */
       gst_segment_init (&segment, GST_FORMAT_BYTES);
       segment.start = qtmux->moov_pos;
@@ -3403,21 +3926,15 @@ gst_qt_mux_stop_file (GstQTMux * qtmux)
       for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
         GstCollectData *cdata = (GstCollectData *) walk->data;
         GstQTPad *qpad = (GstQTPad *) cdata;
-        const TrakBufferEntryInfo *sample_entry;
         guint64 block_idx;
         AtomSTBL *stbl = &qpad->trak->mdia.minf.stbl;
 
         /* Get the block index of the last sample we wrote, not of the next
          * sample we would write */
         block_idx = prefill_get_block_index (qtmux, qpad);
-        g_assert (block_idx > 0);
-        block_idx--;
-
-        sample_entry =
-            &g_array_index (qpad->samples, TrakBufferEntryInfo, block_idx);
 
         /* stts */
-        {
+        if (block_idx > 0) {
           STTSEntry *entry;
           guint64 nsamples = 0;
           gint i, n;
@@ -3433,6 +3950,8 @@ gst_qt_mux_stop_file (GstQTMux * qtmux)
             nsamples += entry->sample_count;
           }
           g_assert (i < n);
+        } else {
+          stbl->stts.entries.len = 0;
         }
 
         /* stsz */
@@ -3446,56 +3965,66 @@ gst_qt_mux_stop_file (GstQTMux * qtmux)
           gint i, n;
           guint64 nsamples = 0;
           gint chunk_index = 0;
-
-          n = stbl->stco64.entries.len;
-          for (i = 0; i < n; i++) {
-            guint64 *entry = &atom_array_index (&stbl->stco64.entries, i);
-
-            if (*entry == sample_entry->chunk_offset) {
-              stbl->stco64.entries.len = i + 1;
-              chunk_index = i + 1;
-              break;
+          const TrakBufferEntryInfo *sample_entry;
+
+          if (block_idx > 0) {
+            sample_entry =
+                &g_array_index (qpad->samples, TrakBufferEntryInfo,
+                block_idx - 1);
+
+            n = stbl->stco64.entries.len;
+            for (i = 0; i < n; i++) {
+              guint64 *entry = &atom_array_index (&stbl->stco64.entries, i);
+
+              if (*entry == sample_entry->chunk_offset) {
+                stbl->stco64.entries.len = i + 1;
+                chunk_index = i + 1;
+                break;
+              }
             }
-          }
-          g_assert (i < n);
-          g_assert (chunk_index > 0);
-
-          n = stbl->stsc.entries.len;
-          for (i = 0; i < n; i++) {
-            STSCEntry *entry = &atom_array_index (&stbl->stsc.entries, i);
-
-            if (entry->first_chunk >= chunk_index)
-              break;
+            g_assert (i < n);
+            g_assert (chunk_index > 0);
+
+            n = stbl->stsc.entries.len;
+            for (i = 0; i < n; i++) {
+              STSCEntry *entry = &atom_array_index (&stbl->stsc.entries, i);
+
+              if (entry->first_chunk >= chunk_index)
+                break;
+
+              if (i > 0) {
+                nsamples +=
+                    (entry->first_chunk - atom_array_index (&stbl->stsc.entries,
+                        i -
+                        1).first_chunk) * atom_array_index (&stbl->stsc.entries,
+                    i - 1).samples_per_chunk;
+              }
+            }
+            g_assert (i <= n);
 
             if (i > 0) {
+              STSCEntry *prev_entry =
+                  &atom_array_index (&stbl->stsc.entries, i - 1);
               nsamples +=
-                  (entry->first_chunk - atom_array_index (&stbl->stsc.entries,
-                      i -
-                      1).first_chunk) * atom_array_index (&stbl->stsc.entries,
-                  i - 1).samples_per_chunk;
-            }
-          }
-          g_assert (i <= n);
-
-          if (i > 0) {
-            STSCEntry *prev_entry =
-                &atom_array_index (&stbl->stsc.entries, i - 1);
-            nsamples +=
-                (chunk_index -
-                prev_entry->first_chunk) * prev_entry->samples_per_chunk;
-            if (qpad->sample_offset - nsamples > 0) {
-              stbl->stsc.entries.len = i;
-              atom_stsc_add_new_entry (&stbl->stsc, chunk_index,
-                  qpad->sample_offset - nsamples);
+                  (chunk_index -
+                  prev_entry->first_chunk) * prev_entry->samples_per_chunk;
+              if (qpad->sample_offset - nsamples > 0) {
+                stbl->stsc.entries.len = i;
+                atom_stsc_add_new_entry (&stbl->stsc, chunk_index,
+                    qpad->sample_offset - nsamples);
+              } else {
+                stbl->stsc.entries.len = i;
+                stbl->stco64.entries.len--;
+              }
             } else {
-              stbl->stsc.entries.len = i;
-              stbl->stco64.entries.len--;
+              /* Everything in a single chunk */
+              stbl->stsc.entries.len = 0;
+              atom_stsc_add_new_entry (&stbl->stsc, chunk_index,
+                  qpad->sample_offset);
             }
           } else {
-            /* Everything in a single chunk */
+            stbl->stco64.entries.len = 0;
             stbl->stsc.entries.len = 0;
-            atom_stsc_add_new_entry (&stbl->stsc, chunk_index,
-                qpad->sample_offset);
           }
         }
 
@@ -3988,6 +4517,21 @@ gst_qt_mux_register_and_push_sample (GstQTMux * qtmux, GstQTPad * pad,
         GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
             ("Unexpected values in sample %" G_GUINT64_FORMAT,
                 pad->sample_offset + 1));
+        GST_ERROR_OBJECT (qtmux, "Expected: samples %u, delta %u, size %u, "
+            "chunk offset %" G_GUINT64_FORMAT ", "
+            "pts offset %" G_GUINT64_FORMAT ", sync %d",
+            sample_entry->nsamples,
+            sample_entry->delta,
+            sample_entry->size,
+            sample_entry->chunk_offset,
+            sample_entry->pts_offset, sample_entry->sync);
+        GST_ERROR_OBJECT (qtmux, "Got: samples %u, delta %u, size %u, "
+            "chunk offset %" G_GUINT64_FORMAT ", "
+            "pts offset %" G_GUINT64_FORMAT ", sync %d",
+            nsamples,
+            (guint) scaled_duration,
+            sample_size, chunk_offset, pts_offset, sync);
+
         gst_buffer_unref (buffer);
         return GST_FLOW_ERROR;
       }
@@ -4046,6 +4590,9 @@ gst_qt_mux_check_and_update_timecode (GstQTMux * qtmux, GstQTPad * pad,
   guint32 frames_since_daily_jam;
   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
 
+  if (!pad->trak->is_video)
+    return ret;
+
   if (qtmux_klass->format != GST_QT_MUX_FORMAT_QT)
     return ret;
 
@@ -4486,8 +5033,10 @@ gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf)
           gst_qt_mux_register_and_push_sample (qtmux, pad, empty_buf, FALSE, 1,
           last_dts + scaled_duration, empty_duration_scaled,
           empty_size, chunk_offset, sync, TRUE, 0);
-    } else {
-      /* our only case currently is tx3g subtitles, so there is no reason to fill this yet */
+    } else if (pad->fourcc != FOURCC_c608 && pad->fourcc != FOURCC_c708) {
+      /* This assert is kept here to make sure implementors of new
+       * sparse input format decide whether there needs to be special
+       * gap handling or not */
       g_assert_not_reached ();
       GST_WARNING_OBJECT (qtmux,
           "no empty buffer creation function found for pad %s",
@@ -4495,6 +5044,10 @@ gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf)
     }
   }
 
+#ifdef TIZEN_FEATURE_GST_MUX_ENHANCEMENT
+  gst_qt_mux_update_expected_trailer_size(qtmux, pad);
+#endif /* TIZEN_FEATURE_GST_MUX_ENHANCEMENT */
+
 exit:
 
   return ret;
@@ -4797,6 +5350,7 @@ gst_qtmux_caps_is_subset_full (GstQTMux * qtmux, GstCaps * subset,
   return gst_structure_foreach (sub_s, check_field, sup_s);
 }
 
+/* will unref @qtmux */
 static gboolean
 gst_qt_mux_can_renegotiate (GstQTMux * qtmux, GstPad * pad, GstCaps * caps)
 {
@@ -4820,6 +5374,7 @@ gst_qt_mux_can_renegotiate (GstQTMux * qtmux, GstPad * pad, GstCaps * caps)
   GST_DEBUG_OBJECT (qtmux,
       "pad %s accepted renegotiation to %" GST_PTR_FORMAT " from %"
       GST_PTR_FORMAT, GST_PAD_NAME (pad), caps, current_caps);
+  gst_object_unref (qtmux);
   gst_caps_unref (current_caps);
 
   return TRUE;
@@ -5529,6 +6084,35 @@ gst_qt_mux_video_sink_set_caps (GstQTPad * qtpad, GstCaps * caps)
   } else if (strcmp (mimetype, "video/x-cineform") == 0) {
     entry.fourcc = FOURCC_cfhd;
     sync = FALSE;
+  } else if (strcmp (mimetype, "video/x-av1") == 0) {
+    gint presentation_delay;
+    guint8 presentation_delay_byte = 0;
+    GstBuffer *av1_codec_data;
+
+    if (gst_structure_get_int (structure, "presentation-delay",
+            &presentation_delay)) {
+      presentation_delay_byte = 1 << 5;
+      presentation_delay_byte |= MAX (0xF, presentation_delay & 0xF);
+    }
+
+
+    av1_codec_data = gst_buffer_new_allocate (NULL, 5, NULL);
+    /* Fill version and 3 bytes of flags to 0 */
+    gst_buffer_memset (av1_codec_data, 0, 0, 4);
+    gst_buffer_fill (av1_codec_data, 4, &presentation_delay_byte, 1);
+    if (codec_data)
+      av1_codec_data = gst_buffer_append (av1_codec_data,
+          gst_buffer_ref ((GstBuffer *) codec_data));
+
+    entry.fourcc = FOURCC_av01;
+
+    ext_atom = build_btrt_extension (0, qtpad->avg_bitrate, qtpad->max_bitrate);
+    if (ext_atom != NULL)
+      ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
+    ext_atom = build_codec_data_extension (FOURCC_av1C, av1_codec_data);
+    if (ext_atom != NULL)
+      ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
+    gst_buffer_unref (av1_codec_data);
   }
 
   if (!entry.fourcc)
@@ -5754,6 +6338,69 @@ refuse_caps:
 }
 
 static gboolean
+gst_qt_mux_caption_sink_set_caps (GstQTPad * qtpad, GstCaps * caps)
+{
+  GstPad *pad = qtpad->collect.pad;
+  GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
+  GstStructure *structure;
+  guint32 fourcc_entry;
+  guint32 timescale;
+
+  if (qtpad->fourcc)
+    return gst_qt_mux_can_renegotiate (qtmux, pad, caps);
+
+  GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
+      GST_DEBUG_PAD_NAME (pad), caps);
+
+  /* captions default */
+  qtpad->is_out_of_order = FALSE;
+  qtpad->sync = FALSE;
+  qtpad->sparse = TRUE;
+  /* Closed caption data are within atoms */
+  qtpad->prepare_buf_func = gst_qt_mux_prepare_caption_buffer;
+
+  structure = gst_caps_get_structure (caps, 0);
+
+  /* We know we only handle 608,format=s334-1a and 708,format=cdp */
+  if (gst_structure_has_name (structure, "closedcaption/x-cea-608")) {
+    fourcc_entry = FOURCC_c608;
+  } else if (gst_structure_has_name (structure, "closedcaption/x-cea-708")) {
+    fourcc_entry = FOURCC_c708;
+  } else
+    goto refuse_caps;
+
+  /* We set the real timescale later to the one from the video track when
+   * writing the headers */
+  timescale = gst_qt_mux_pad_get_timescale (GST_QT_MUX_PAD_CAST (pad));
+  if (!timescale && qtmux->trak_timescale)
+    timescale = qtmux->trak_timescale;
+  else if (!timescale)
+    timescale = 30000;
+
+  qtpad->fourcc = fourcc_entry;
+  qtpad->trak_ste =
+      (SampleTableEntry *) atom_trak_set_caption_type (qtpad->trak,
+      qtmux->context, timescale, fourcc_entry);
+
+  /* Initialize caption track language code to 0 unless something else is
+   * specified. Without this, Final Cut considers it "non-standard"
+   */
+  qtpad->trak->mdia.mdhd.language_code = 0;
+
+  gst_object_unref (qtmux);
+  return TRUE;
+
+  /* ERRORS */
+refuse_caps:
+  {
+    GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
+        GST_PAD_NAME (pad), caps);
+    gst_object_unref (qtmux);
+    return FALSE;
+  }
+}
+
+static gboolean
 gst_qt_mux_sink_event (GstCollectPads * pads, GstCollectData * data,
     GstEvent * event, gpointer user_data)
 {
@@ -5825,9 +6472,7 @@ gst_qt_mux_sink_event (GstCollectPads * pads, GstCollectData * data,
           g_assert (qtpad);
           if (qtpad->trak) {
             /* https://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap4/qtff4.html */
-            qtpad->trak->mdia.mdhd.language_code =
-                (iso_code[0] - 0x60) * 0x400 + (iso_code[1] - 0x60) * 0x20 +
-                (iso_code[2] - 0x60);
+            qtpad->trak->mdia.mdhd.language_code = language_code (iso_code);
           }
         }
         g_free (code);
@@ -5924,6 +6569,14 @@ gst_qt_mux_request_new_pad (GstElement * element,
       name = g_strdup_printf ("subtitle_%u", qtmux->subtitle_pads++);
     }
     lock = FALSE;
+  } else if (templ == gst_element_class_get_pad_template (klass, "caption_%u")) {
+    setcaps_func = gst_qt_mux_caption_sink_set_caps;
+    if (req_name != NULL && sscanf (req_name, "caption_%u", &pad_id) == 1) {
+      name = g_strdup (req_name);
+    } else {
+      name = g_strdup_printf ("caption_%u", qtmux->caption_pads++);
+    }
+    lock = FALSE;
   } else
     goto wrong_template;
 
@@ -6047,6 +6700,14 @@ gst_qt_mux_get_property (GObject * object,
     case PROP_MAX_RAW_AUDIO_DRIFT:
       g_value_set_uint64 (value, qtmux->max_raw_audio_drift);
       break;
+    case PROP_START_GAP_THRESHOLD:
+      g_value_set_uint64 (value, qtmux->start_gap_threshold);
+      break;
+#ifdef TIZEN_FEATURE_GST_MUX_ENHANCEMENT
+    case PROP_EXPECTED_TRAILER_SIZE:
+      g_value_set_uint(value, qtmux->expected_trailer_size);
+      break;
+#endif /* TIZEN_FEATURE_GST_MUX_ENHANCEMENT */   
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -6138,6 +6799,9 @@ gst_qt_mux_set_property (GObject * object,
     case PROP_MAX_RAW_AUDIO_DRIFT:
       qtmux->max_raw_audio_drift = g_value_get_uint64 (value);
       break;
+    case PROP_START_GAP_THRESHOLD:
+      qtmux->start_gap_threshold = g_value_get_uint64 (value);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -6218,7 +6882,7 @@ gst_qt_mux_register (GstPlugin * plugin)
 
   while (TRUE) {
     GstQTMuxFormatProp *prop;
-    GstCaps *subtitle_caps;
+    GstCaps *subtitle_caps, *caption_caps;
 
     prop = &gst_qt_mux_format_list[i];
     format = prop->format;
@@ -6237,6 +6901,12 @@ gst_qt_mux_register (GstPlugin * plugin)
     } else {
       gst_caps_unref (subtitle_caps);
     }
+    caption_caps = gst_static_caps_get (&prop->caption_sink_caps);
+    if (!gst_caps_is_equal (caption_caps, GST_CAPS_NONE)) {
+      params->caption_sink_caps = caption_caps;
+    } else {
+      gst_caps_unref (caption_caps);
+    }
 
     /* create the type now */
     type = g_type_register_static (GST_TYPE_ELEMENT, prop->type_name, &typeinfo,