Merge branch 'upstream/1.16' into tizen_gst_1.16.2
[platform/upstream/gst-plugins-good.git] / gst / isomp4 / gstqtmux.c
index e8b19ab..94a1314 100644 (file)
@@ -397,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 */
@@ -420,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);
 
@@ -454,6 +459,227 @@ 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)
 {
@@ -524,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."
 
@@ -647,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);
@@ -769,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;
@@ -776,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;
@@ -787,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
@@ -818,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 =
@@ -874,7 +1127,7 @@ gst_qt_mux_prepare_jpc_buffer (GstQTPad * qtpad, GstBuffer * buf,
 }
 
 static gsize
-extract_608_field_from_cc_data (const guint8 * ccdata, gsize ccdata_size,
+extract_608_field_from_s334_1a (const guint8 * ccdata, gsize ccdata_size,
     guint field, guint8 ** res)
 {
   guint8 *storage;
@@ -886,8 +1139,8 @@ extract_608_field_from_cc_data (const guint8 * ccdata, gsize ccdata_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] == 0xfc) ||
-        (field == 2 && ccdata[i * 3] == 0xfd)) {
+    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) {
@@ -940,9 +1193,9 @@ gst_qt_mux_prepare_caption_buffer (GstQTPad * qtpad, GstBuffer * buf,
       gsize write_offs = 0;
 
       cdat_size =
-          extract_608_field_from_cc_data (inmap.data, inmap.size, 1, &cdat);
+          extract_608_field_from_s334_1a (inmap.data, inmap.size, 1, &cdat);
       cdt2_size =
-          extract_608_field_from_cc_data (inmap.data, inmap.size, 2, &cdt2);
+          extract_608_field_from_s334_1a (inmap.data, inmap.size, 2, &cdt2);
 
       if (cdat_size)
         total_size += cdat_size + 8;
@@ -2500,8 +2753,9 @@ prefill_get_sample_size (GstQTMux * qtmux, GstQTPad * qpad)
       /* We always write both cdat and cdt2 atom in prefill mode */
       return 20;
     case FOURCC_c708:
-      /* We're cheating a bit by always allocating 100bytes even if we use less  */
-      return 100;
+      /* 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;
@@ -2791,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);
@@ -2967,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:
@@ -3028,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);
@@ -3449,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));
@@ -3588,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;
@@ -3615,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;
@@ -3645,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 */
@@ -3658,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);
           }
         }
 
@@ -4273,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;
 
@@ -4724,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;
@@ -6037,7 +6361,7 @@ gst_qt_mux_caption_sink_set_caps (GstQTPad * qtpad, GstCaps * caps)
 
   structure = gst_caps_get_structure (caps, 0);
 
-  /* We know we only handle 608,format=cc_data and 708,format=cdp */
+  /* 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")) {
@@ -6045,7 +6369,8 @@ gst_qt_mux_caption_sink_set_caps (GstQTPad * qtpad, GstCaps * caps)
   } else
     goto refuse_caps;
 
-  /* FIXME: Get the timescale from the video track ? */
+  /* 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;
@@ -6057,6 +6382,11 @@ gst_qt_mux_caption_sink_set_caps (GstQTPad * qtpad, GstCaps * caps)
       (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;
 
@@ -6142,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);
@@ -6372,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;
@@ -6463,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;