qtmux: Added support for writing timecode track
authorVivia Nikolaidou <vivia@toolsonair.com>
Fri, 24 Jun 2016 13:32:37 +0000 (16:32 +0300)
committerSebastian Dröge <sebastian@centricular.com>
Wed, 17 Aug 2016 06:03:52 +0000 (09:03 +0300)
https://bugzilla.gnome.org/show_bug.cgi?id=767950

gst/isomp4/atoms.c
gst/isomp4/atoms.h
gst/isomp4/fourcc.h
gst/isomp4/gstqtmux.c
gst/isomp4/gstqtmux.h

index e0ec513..b2f7f74 100644 (file)
@@ -398,6 +398,96 @@ atom_edts_free (AtomEDTS * edts)
 }
 
 static void
+atom_tcmi_init (AtomTCMI * tcmi)
+{
+  guint8 flags[3] = { 0, 0, 0 };
+
+  atom_full_init (&tcmi->header, FOURCC_tcmi, 0, 0, 0, flags);
+}
+
+static void
+atom_tcmi_clear (AtomTCMI * tcmi)
+{
+  atom_full_clear (&tcmi->header);
+  tcmi->text_font = 0;
+  tcmi->text_face = 0;
+  tcmi->text_size = 0;
+  tcmi->text_color[0] = 0;
+  tcmi->text_color[1] = 0;
+  tcmi->text_color[2] = 0;
+  tcmi->bg_color[0] = 0;
+  tcmi->bg_color[1] = 0;
+  tcmi->bg_color[2] = 0;
+  g_free (tcmi->font_name);
+  tcmi->font_name = NULL;
+}
+
+static void
+atom_tmcd_init (AtomTMCD * tmcd)
+{
+  atom_header_set (&tmcd->header, FOURCC_tmcd, 0, 0);
+  atom_tcmi_init (&tmcd->tcmi);
+}
+
+static void
+atom_tmcd_clear (AtomTMCD * tmcd)
+{
+  atom_clear (&tmcd->header);
+  atom_tcmi_clear (&tmcd->tcmi);
+}
+
+static void
+atom_gmin_init (AtomGMIN * gmin)
+{
+  guint8 flags[3] = { 0, 0, 0 };
+
+  atom_full_init (&gmin->header, FOURCC_gmin, 0, 0, 0, flags);
+}
+
+static void
+atom_gmin_clear (AtomGMIN * gmin)
+{
+  atom_full_clear (&gmin->header);
+  gmin->graphics_mode = 0;
+  gmin->opcolor[0] = 0;
+  gmin->opcolor[1] = 0;
+  gmin->opcolor[2] = 0;
+  gmin->balance = 0;
+  gmin->reserved = 0;
+}
+
+static void
+atom_gmhd_init (AtomGMHD * gmhd)
+{
+  atom_header_set (&gmhd->header, FOURCC_gmhd, 0, 0);
+  atom_gmin_init (&gmhd->gmin);
+  atom_tmcd_init (&gmhd->tmcd);
+}
+
+static void
+atom_gmhd_clear (AtomGMHD * gmhd)
+{
+  atom_clear (&gmhd->header);
+  atom_gmin_clear (&gmhd->gmin);
+  atom_tmcd_clear (&gmhd->tmcd);
+}
+
+static AtomGMHD *
+atom_gmhd_new (void)
+{
+  AtomGMHD *gmhd = g_new0 (AtomGMHD, 1);
+  atom_gmhd_init (gmhd);
+  return gmhd;
+}
+
+static void
+atom_gmhd_free (AtomGMHD * gmhd)
+{
+  atom_gmhd_clear (gmhd);
+  g_free (gmhd);
+}
+
+static void
 atom_sample_entry_init (SampleTableEntry * se, guint32 type)
 {
   atom_header_set (&se->header, type, 0, 0);
@@ -452,6 +542,30 @@ sample_entry_mp4a_free (SampleTableEntryMP4A * mp4a)
 }
 
 static void
+sample_entry_tmcd_init (SampleTableEntryTMCD * tmcd)
+{
+  atom_sample_entry_init (&tmcd->se, FOURCC_tmcd);
+
+  tmcd->tc_flags = 0;
+  tmcd->timescale = 0;
+  tmcd->frame_duration = 0;
+  tmcd->n_frames = 0;
+
+  tmcd->name.language_code = 0;
+  g_free (tmcd->name.name);
+  tmcd->name.name = NULL;
+}
+
+static SampleTableEntryTMCD *
+sample_entry_tmcd_new (void)
+{
+  SampleTableEntryTMCD *tmcd = g_new0 (SampleTableEntryTMCD, 1);
+
+  sample_entry_tmcd_init (tmcd);
+  return tmcd;
+}
+
+static void
 sample_entry_mp4v_init (SampleTableEntryMP4V * mp4v, AtomsContext * context)
 {
   atom_sample_entry_init (&mp4v->se, FOURCC_mp4v);
@@ -934,6 +1048,7 @@ atom_minf_init (AtomMINF * minf, AtomsContext * context)
   minf->vmhd = NULL;
   minf->smhd = NULL;
   minf->hmhd = NULL;
+  minf->gmhd = NULL;
 
   if (context->flavor == ATOMS_TREE_FLAVOR_MOV) {
     minf->hdlr = atom_hdlr_new (context);
@@ -961,6 +1076,10 @@ atom_minf_clear_handlers (AtomMINF * minf)
     atom_hmhd_free (minf->hmhd);
     minf->hmhd = NULL;
   }
+  if (minf->gmhd) {
+    atom_gmhd_free (minf->gmhd);
+    minf->gmhd = NULL;
+  }
 }
 
 static void
@@ -1140,6 +1259,40 @@ atom_udta_clear (AtomUDTA * udta)
     atom_info_list_free (udta->entries);
 }
 
+static void
+atom_tref_init (AtomTREF * tref, guint32 reftype)
+{
+  atom_header_set (&tref->header, FOURCC_tref, 0, 0);
+  tref->reftype = reftype;
+  atom_array_init (&tref->entries, 128);
+}
+
+static void
+atom_tref_clear (AtomTREF * tref)
+{
+  atom_clear (&tref->header);
+  tref->reftype = 0;
+  atom_array_clear (&tref->entries);
+}
+
+AtomTREF *
+atom_tref_new (guint32 reftype)
+{
+  AtomTREF *tref;
+
+  tref = g_new0 (AtomTREF, 1);
+  atom_tref_init (tref, reftype);
+
+  return tref;
+}
+
+static void
+atom_tref_free (AtomTREF * tref)
+{
+  atom_tref_clear (tref);
+  g_free (tref);
+}
+
 /* Clear added tags, but keep the context/flavor the same */
 void
 atom_udta_clear_tags (AtomUDTA * udta)
@@ -1255,6 +1408,7 @@ atom_trak_init (AtomTRAK * trak, AtomsContext * context)
   atom_udta_init (&trak->udta, context);
   trak->edts = NULL;
   atom_mdia_init (&trak->mdia, context);
+  trak->tref = NULL;
 }
 
 AtomTRAK *
@@ -1275,6 +1429,8 @@ atom_trak_clear (AtomTRAK * trak)
     atom_edts_free (trak->edts);
   atom_udta_clear (&trak->udta);
   atom_mdia_clear (&trak->mdia);
+  if (trak->tref)
+    atom_tref_free (trak->tref);
 }
 
 static void
@@ -1651,6 +1807,91 @@ atom_hmhd_copy_data (AtomHMHD * hmhd, guint8 ** buffer, guint64 * size,
   return original_offset - *offset;
 }
 
+static guint64
+atom_tcmi_copy_data (AtomTCMI * tcmi, guint8 ** buffer, guint64 * size,
+    guint64 * offset)
+{
+  guint64 original_offset = *offset;
+
+  if (!atom_full_copy_data (&tcmi->header, buffer, size, offset)) {
+    return 0;
+  }
+  prop_copy_uint16 (tcmi->text_font, buffer, size, offset);
+  prop_copy_uint16 (tcmi->text_face, buffer, size, offset);
+  prop_copy_uint16 (tcmi->text_size, buffer, size, offset);
+  prop_copy_uint16 (tcmi->text_color[0], buffer, size, offset);
+  prop_copy_uint16 (tcmi->text_color[1], buffer, size, offset);
+  prop_copy_uint16 (tcmi->text_color[2], buffer, size, offset);
+  prop_copy_uint16 (tcmi->bg_color[0], buffer, size, offset);
+  prop_copy_uint16 (tcmi->bg_color[1], buffer, size, offset);
+  prop_copy_uint16 (tcmi->bg_color[2], buffer, size, offset);
+  /* reserved */
+  prop_copy_uint16 (0, buffer, size, offset);
+  prop_copy_size_string ((guint8 *) tcmi->font_name, strlen (tcmi->font_name),
+      buffer, size, offset);
+
+  atom_write_size (buffer, size, offset, original_offset);
+  return original_offset - *offset;
+}
+
+static guint64
+atom_tmcd_copy_data (AtomTMCD * tmcd, guint8 ** buffer, guint64 * size,
+    guint64 * offset)
+{
+  guint64 original_offset = *offset;
+
+  if (!atom_copy_data (&tmcd->header, buffer, size, offset)) {
+    return 0;
+  }
+  if (!atom_tcmi_copy_data (&tmcd->tcmi, buffer, size, offset)) {
+    return 0;
+  }
+
+  atom_write_size (buffer, size, offset, original_offset);
+  return original_offset - *offset;
+}
+
+static guint64
+atom_gmin_copy_data (AtomGMIN * gmin, guint8 ** buffer, guint64 * size,
+    guint64 * offset)
+{
+  guint64 original_offset = *offset;
+
+  if (!atom_full_copy_data (&gmin->header, buffer, size, offset)) {
+    return 0;
+  }
+  prop_copy_uint16 (gmin->graphics_mode, buffer, size, offset);
+  prop_copy_uint16 (gmin->opcolor[0], buffer, size, offset);
+  prop_copy_uint16 (gmin->opcolor[1], buffer, size, offset);
+  prop_copy_uint16 (gmin->opcolor[2], buffer, size, offset);
+  prop_copy_uint8 (gmin->balance, buffer, size, offset);
+  /* reserved */
+  prop_copy_uint8 (0, buffer, size, offset);
+
+  atom_write_size (buffer, size, offset, original_offset);
+  return original_offset - *offset;
+}
+
+static guint64
+atom_gmhd_copy_data (AtomGMHD * gmhd, guint8 ** buffer, guint64 * size,
+    guint64 * offset)
+{
+  guint64 original_offset = *offset;
+
+  if (!atom_copy_data (&gmhd->header, buffer, size, offset)) {
+    return 0;
+  }
+  if (!atom_gmin_copy_data (&gmhd->gmin, buffer, size, offset)) {
+    return 0;
+  }
+  if (!atom_tmcd_copy_data (&gmhd->tmcd, buffer, size, offset)) {
+    return 0;
+  }
+
+  atom_write_size (buffer, size, offset, original_offset);
+  return original_offset - *offset;
+}
+
 static gboolean
 atom_url_same_file_flag (AtomURL * url)
 {
@@ -1884,6 +2125,44 @@ sample_entry_tx3g_copy_data (SampleTableEntryTX3G * tx3g, guint8 ** buffer,
   return *offset - original_offset;
 }
 
+static guint64
+sample_entry_tmcd_copy_data (SampleTableEntryTMCD * tmcd, guint8 ** buffer,
+    guint64 * size, guint64 * offset)
+{
+  guint64 original_offset = *offset;
+
+  if (!atom_sample_entry_copy_data (&tmcd->se, buffer, size, offset)) {
+    return 0;
+  }
+
+  /* reserved */
+  prop_copy_uint32 (0, buffer, size, offset);
+
+  prop_copy_uint32 (tmcd->tc_flags, buffer, size, offset);
+  prop_copy_uint32 (tmcd->timescale, buffer, size, offset);
+  prop_copy_uint32 (tmcd->frame_duration, buffer, size, offset);
+  prop_copy_uint8 (tmcd->n_frames, buffer, size, offset);
+
+  /* reserved */
+  prop_copy_uint8 (0, buffer, size, offset);
+  {
+    Atom atom;
+    guint64 name_offset = *offset;
+
+    atom_header_set (&atom, FOURCC_name, 0, 0);
+    atom_copy_data (&atom, buffer, size, offset);
+    prop_copy_uint16 (strlen (tmcd->name.name), buffer, size, offset);
+    prop_copy_uint16 (tmcd->name.language_code, buffer, size, offset);
+    prop_copy_fixed_size_string ((guint8 *) tmcd->name.name,
+        strlen (tmcd->name.name), buffer, size, offset);
+
+    atom_write_size (buffer, size, offset, name_offset);
+  }
+
+  atom_write_size (buffer, size, offset, original_offset);
+  return *offset - original_offset;
+}
+
 guint64
 atom_stsz_copy_data (AtomSTSZ * stsz, guint8 ** buffer, guint64 * size,
     guint64 * offset)
@@ -2074,6 +2353,11 @@ atom_stsd_copy_data (AtomSTSD * stsd, guint8 ** buffer, guint64 * size,
                   walker->data, buffer, size, offset)) {
             return 0;
           }
+        } else if (se->kind == TIMECODE) {
+          if (!sample_entry_tmcd_copy_data ((SampleTableEntryTMCD *)
+                  walker->data, buffer, size, offset)) {
+            return 0;
+          }
         } else {
           if (!atom_hint_sample_entry_copy_data (
                   (AtomHintSampleEntry *) walker->data, buffer, size, offset)) {
@@ -2205,6 +2489,10 @@ atom_minf_copy_data (AtomMINF * minf, guint8 ** buffer, guint64 * size,
     if (!atom_hmhd_copy_data (minf->hmhd, buffer, size, offset)) {
       return 0;
     }
+  } else if (minf->gmhd) {
+    if (!atom_gmhd_copy_data (minf->gmhd, buffer, size, offset)) {
+      return 0;
+    }
   }
 
   if (minf->hdlr) {
@@ -2294,6 +2582,34 @@ atom_elst_copy_data (AtomELST * elst, guint8 ** buffer, guint64 * size,
 }
 
 static guint64
+atom_tref_copy_data (AtomTREF * tref, guint8 ** buffer, guint64 * size,
+    guint64 * offset)
+{
+  guint64 original_offset = *offset;
+  guint i;
+
+  g_assert (atom_array_get_len (&tref->entries) > 0);
+
+  if (!atom_copy_data (&tref->header, buffer, size, offset)) {
+    return 0;
+  }
+
+  prop_copy_uint32 (8 + 4 * atom_array_get_len (&tref->entries), buffer, size,
+      offset);
+  prop_copy_fourcc (tref->reftype, buffer, size, offset);
+  /* minimize realloc */
+  prop_copy_ensure_buffer (buffer, size, offset,
+      4 * atom_array_get_len (&tref->entries));
+  for (i = 0; i < atom_array_get_len (&tref->entries); i++) {
+    prop_copy_uint32 (atom_array_index (&tref->entries, i), buffer, size,
+        offset);
+  }
+
+  atom_write_size (buffer, size, offset, original_offset);
+  return *offset - original_offset;
+}
+
+static guint64
 atom_edts_copy_data (AtomEDTS * edts, guint8 ** buffer, guint64 * size,
     guint64 * offset)
 {
@@ -2489,6 +2805,14 @@ atom_trak_copy_data (AtomTRAK * trak, guint8 ** buffer, guint64 * size,
       return 0;
     }
   }
+  if (trak->tref) {
+    /* Make sure we need this atom (there is a referenced track */
+    if (atom_array_get_len (&trak->tref->entries) > 0) {
+      if (!atom_tref_copy_data (trak->tref, buffer, size, offset)) {
+        return 0;
+      }
+    }
+  }
 
   if (!atom_mdia_copy_data (&trak->mdia, buffer, size, offset)) {
     return 0;
@@ -2628,6 +2952,12 @@ atom_stco64_add_entry (AtomSTCO64 * stco64, guint64 entry)
     stco64->header.header.type = FOURCC_co64;
 }
 
+void
+atom_tref_add_entry (AtomTREF * tref, guint32 sample)
+{
+  atom_array_append (&tref->entries, sample, 512);
+}
+
 static void
 atom_stss_add_entry (AtomSTSS * stss, guint32 sample)
 {
@@ -2789,6 +3119,36 @@ atom_trak_update_duration (AtomTRAK * trak, guint64 moov_timescale)
   }
 }
 
+static void
+timecode_atom_trak_set_duration (AtomTRAK * trak, guint64 duration,
+    guint64 timescale)
+{
+  STTSEntry *entry;
+  GList *iter;
+
+  /* Sanity checks to ensure we have a timecode */
+  g_assert (trak->mdia.minf.gmhd != NULL);
+  g_assert (atom_array_get_len (&trak->mdia.minf.stbl.stts.entries) == 1);
+
+  trak->tkhd.duration = duration;
+  trak->mdia.mdhd.time_info.duration = duration;
+  trak->mdia.mdhd.time_info.timescale = timescale;
+
+  entry = &atom_array_index (&trak->mdia.minf.stbl.stts.entries, 0);
+  entry->sample_delta = duration;
+
+  for (iter = trak->mdia.minf.stbl.stsd.entries; iter;
+      iter = g_list_next (iter)) {
+    SampleTableEntry *entry = iter->data;
+    if (entry->kind == TIMECODE) {
+      SampleTableEntryTMCD *tmcd = (SampleTableEntryTMCD *) entry;
+
+      tmcd->frame_duration = tmcd->frame_duration * timescale / tmcd->timescale;
+      tmcd->timescale = timescale;
+    }
+  }
+}
+
 static guint32
 atom_moov_get_timescale (AtomMOOV * moov)
 {
@@ -2810,10 +3170,23 @@ atom_moov_update_duration (AtomMOOV * moov)
   while (traks) {
     AtomTRAK *trak = (AtomTRAK *) traks->data;
 
-    atom_trak_update_duration (trak, atom_moov_get_timescale (moov));
-    dur = atom_trak_get_duration (trak);
-    if (dur > duration)
-      duration = dur;
+    /* Skip timecodes for now: they have a placeholder duration */
+    if (trak->mdia.minf.gmhd == NULL) {
+      atom_trak_update_duration (trak, atom_moov_get_timescale (moov));
+      dur = atom_trak_get_duration (trak);
+      if (dur > duration)
+        duration = dur;
+    }
+    traks = g_list_next (traks);
+  }
+  /* Now update the duration of the timecodes */
+  traks = moov->traks;
+  while (traks) {
+    AtomTRAK *trak = (AtomTRAK *) traks->data;
+
+    if (trak->mdia.minf.gmhd != NULL)
+      timecode_atom_trak_set_duration (trak, duration,
+          atom_moov_get_timescale (moov));
     traks = g_list_next (traks);
   }
   moov->mvhd.time_info.duration = duration;
@@ -3323,6 +3696,34 @@ atom_trak_add_audio_entry (AtomTRAK * trak, AtomsContext * context,
   return mp4a;
 }
 
+static SampleTableEntryTMCD *
+atom_trak_add_timecode_entry (AtomTRAK * trak, AtomsContext * context,
+    GstVideoTimeCode * tc)
+{
+  AtomSTSD *stsd = &trak->mdia.minf.stbl.stsd;
+  SampleTableEntryTMCD *tmcd = sample_entry_tmcd_new ();
+
+  trak->mdia.hdlr.component_type = FOURCC_mhlr;
+  trak->mdia.hdlr.handler_type = FOURCC_tmcd;
+  trak->mdia.hdlr.name = g_strdup ("Time Code Media Handler");
+  trak->mdia.mdhd.time_info.timescale = tc->config.fps_n / tc->config.fps_d;
+
+  tmcd->se.kind = TIMECODE;
+  tmcd->se.data_reference_index = 1;
+  tmcd->tc_flags = TC_24H_MAX;
+  if (tc->config.flags &= GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME)
+    tmcd->tc_flags |= TC_DROP_FRAME;
+  tmcd->name.language_code = 0;
+  tmcd->name.name = g_strdup ("Tape");
+  tmcd->timescale = tc->config.fps_n;
+  tmcd->frame_duration = tc->config.fps_d;
+  tmcd->n_frames = tc->config.fps_n / tc->config.fps_d;
+
+  stsd->entries = g_list_prepend (stsd->entries, tmcd);
+  stsd->n_entries++;
+  return tmcd;
+}
+
 static SampleTableEntryMP4V *
 atom_trak_add_video_entry (AtomTRAK * trak, AtomsContext * context,
     guint32 type)
@@ -3467,6 +3868,34 @@ atom_trak_set_audio_type (AtomTRAK * trak, AtomsContext * context,
   return ste;
 }
 
+SampleTableEntryTMCD *
+atom_trak_set_timecode_type (AtomTRAK * trak, AtomsContext * context,
+    GstVideoTimeCode * tc)
+{
+  SampleTableEntryTMCD *ste;
+  AtomGMHD *gmhd = trak->mdia.minf.gmhd;
+
+  if (context->flavor != ATOMS_TREE_FLAVOR_MOV) {
+    return NULL;
+  }
+
+  ste = atom_trak_add_timecode_entry (trak, context, tc);
+
+  gmhd = atom_gmhd_new ();
+  gmhd->gmin.graphics_mode = 0x0040;
+  gmhd->gmin.opcolor[0] = 0x8000;
+  gmhd->gmin.opcolor[1] = 0x8000;
+  gmhd->gmin.opcolor[2] = 0x8000;
+  gmhd->tmcd.tcmi.text_size = 12;
+  gmhd->tmcd.tcmi.font_name = g_strdup ("Chicago");     /* Pascal string */
+
+  trak->mdia.minf.gmhd = gmhd;
+  trak->is_video = FALSE;
+  trak->is_h264 = FALSE;
+
+  return ste;
+}
+
 static AtomInfo *
 build_pasp_extension (gint par_width, gint par_height)
 {
index b105a4d..283d9cf 100644 (file)
@@ -45,6 +45,7 @@
 
 #include <glib.h>
 #include <string.h>
+#include <gst/video/video.h>
 
 #include "descriptors.h"
 #include "properties.h"
@@ -281,6 +282,45 @@ typedef struct _AtomHMHD
   guint32 sliding_avg_bitrate;
 } AtomHMHD;
 
+typedef struct _AtomTCMI
+{
+  AtomFull header;
+
+  guint16 text_font;
+  guint16 text_face;
+  guint16 text_size;
+  guint16 text_color[3];
+  guint16 bg_color[3];
+  gchar *font_name;
+} AtomTCMI;
+
+typedef struct _AtomTMCD
+{
+  Atom header;
+
+  AtomTCMI tcmi;
+} AtomTMCD;
+
+typedef struct _AtomGMIN
+{
+  AtomFull header;
+
+  guint16 graphics_mode;
+  guint16 opcolor[3];
+  guint8 balance;
+  guint8 reserved;
+
+} AtomGMIN;
+
+typedef struct _AtomGMHD
+{
+  Atom header;
+
+  AtomGMIN gmin;
+  AtomTMCD tmcd;
+
+} AtomGMHD;
+
 typedef struct _AtomURL
 {
   AtomFull header;
@@ -342,6 +382,7 @@ typedef enum _SampleEntryKind
   AUDIO,
   VIDEO,
   SUBTITLE,
+  TIMECODE
 } SampleEntryKind;
 
 typedef struct _SampleTableEntry
@@ -415,6 +456,27 @@ typedef struct _SampleTableEntryMP4A
   GList *extension_atoms;
 } SampleTableEntryMP4A;
 
+typedef struct _AtomNAME
+{
+  Atom header;
+
+  guint8 language_code;
+  gchar *name;
+} AtomNAME;
+
+typedef struct _SampleTableEntryTMCD
+{
+  SampleTableEntry se;
+
+  guint32 tc_flags;
+  guint32 timescale;
+  guint32 frame_duration;
+  guint8 n_frames;
+
+  AtomNAME name;
+
+} SampleTableEntryTMCD;
+
 typedef struct _SampleTableEntryTX3G
 {
   SampleTableEntry se;
@@ -463,6 +525,14 @@ typedef struct _AtomSTSC
   ATOM_ARRAY (STSCEntry) entries;
 } AtomSTSC;
 
+/* FIXME: this can support multiple tracks */
+typedef struct _AtomTREF
+{
+  Atom header;
+
+  guint32 reftype;
+  ATOM_ARRAY (guint32) entries;
+} AtomTREF;
 
 /*
  * used for both STCO and CO64
@@ -514,6 +584,7 @@ typedef struct _AtomMINF
   AtomVMHD *vmhd;
   AtomSMHD *smhd;
   AtomHMHD *hmhd;
+  AtomGMHD *gmhd;
 
   AtomHDLR *hdlr;
   AtomDINF dinf;
@@ -616,6 +687,15 @@ enum TfFlags
   TF_DEFAULT_BASE_IS_MOOF     = 0x020000  /* default-base-is-moof */
 };
 
+/* Timecode flags */
+enum TcFlags
+{
+  TC_DROP_FRAME = 0x0001,   /* Drop-frame timecode */
+  TC_24H_MAX = 0x0002,      /* Whether the timecode wraps after 24 hours */
+  TC_NEGATIVE_OK = 0x0004,  /* Whether negative time values are OK */
+  TC_COUNTER = 0x0008       /* Whether the time value corresponds to a tape counter value */
+};
+
 typedef struct _AtomTRAK
 {
   Atom header;
@@ -624,6 +704,7 @@ typedef struct _AtomTRAK
   AtomEDTS *edts;
   AtomMDIA mdia;
   AtomUDTA udta;
+  AtomTREF *tref;
 
   /* some helper info for structural conformity checks */
   gboolean is_video;
@@ -937,6 +1018,9 @@ SampleTableEntryMP4V * atom_trak_set_video_type (AtomTRAK * trak, AtomsContext *
 SampleTableEntryTX3G * atom_trak_set_subtitle_type (AtomTRAK * trak, AtomsContext * context,
                                SubtitleSampleEntry * entry);
 
+SampleTableEntryTMCD *
+atom_trak_set_timecode_type (AtomTRAK * trak, AtomsContext * context, GstVideoTimeCode * tc);
+
 void atom_trak_update_bitrates (AtomTRAK * trak, guint32 avg_bitrate,
                                 guint32 max_bitrate);
 
@@ -997,6 +1081,9 @@ void atom_udta_add_3gp_tag           (AtomUDTA *udta, guint32 fourcc, guint8 * d
 
 void atom_udta_add_xmp_tags          (AtomUDTA *udta, GstBuffer * xmp);
 
+AtomTREF * atom_tref_new (guint32 reftype);
+void atom_tref_add_entry (AtomTREF * tref, guint32 sample);
+
 #define GST_QT_MUX_DEFAULT_TAG_LANGUAGE   "und" /* undefined/unknown */
 guint16  language_code               (const char * lang);
 
index 5bb43e9..9e7408b 100644 (file)
@@ -215,7 +215,9 @@ G_BEGIN_DECLS
 #define FOURCC_subp     GST_MAKE_FOURCC('s','u','b','p')
 #define FOURCC_subt     GST_MAKE_FOURCC('s','u','b','t')
 #define FOURCC_text     GST_MAKE_FOURCC('t','e','x','t')
+#define FOURCC_tcmi     GST_MAKE_FOURCC('t','c','m','i')
 #define FOURCC_tkhd     GST_MAKE_FOURCC('t','k','h','d')
+#define FOURCC_tmcd     GST_MAKE_FOURCC('t','m','c','d')
 #define FOURCC_tmpo     GST_MAKE_FOURCC('t','m','p','o')
 #define FOURCC_trak     GST_MAKE_FOURCC('t','r','a','k')
 #define FOURCC_tref     GST_MAKE_FOURCC('t','r','e','f')
index 5cabb8f..cf6633d 100644 (file)
@@ -504,6 +504,7 @@ gst_qt_mux_pad_reset (GstQTPad * qtpad)
   qtpad->total_duration = 0;
   qtpad->total_bytes = 0;
   qtpad->sparse = FALSE;
+  qtpad->tc_trak = NULL;
 
   qtpad->buf_head = 0;
   qtpad->buf_tail = 0;
@@ -604,6 +605,8 @@ 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;
+  qtmux->first_pts = GST_CLOCK_TIME_NONE;
+  qtmux->tc_pos = -1;
 }
 
 static void
@@ -2498,6 +2501,10 @@ gst_qt_mux_update_edit_lists (GstQTMux * qtmux)
       duration += lateness;
 
       qtpad->trak->tkhd.duration = duration;
+      if (qtpad->tc_trak) {
+        qtpad->tc_trak->tkhd.duration = duration;
+        qtpad->tc_trak->mdia.mdhd.time_info.duration = duration;
+      }
 
       /* And possibly grow the moov duration */
       if (duration > qtmux->moov->mvhd.time_info.duration) {
@@ -2509,6 +2516,33 @@ gst_qt_mux_update_edit_lists (GstQTMux * qtmux)
 }
 
 static GstFlowReturn
+gst_qt_mux_update_timecode (GstQTMux * qtmux)
+{
+  GstSegment segment;
+  GstBuffer *buf;
+  GstMapInfo map;
+  guint64 offset = qtmux->tc_pos;
+
+  g_assert (qtmux->tc_pos != -1);
+
+  gst_segment_init (&segment, GST_FORMAT_BYTES);
+  segment.start = offset;
+  gst_pad_push_event (qtmux->srcpad, gst_event_new_segment (&segment));
+
+  buf = gst_buffer_new_and_alloc (4);
+  gst_buffer_map (buf, &map, GST_MAP_WRITE);
+
+  GST_WRITE_UINT32_BE (map.data,
+      gst_video_time_code_frames_since_daily_jam (qtmux->first_tc));
+  gst_buffer_unmap (buf, &map);
+
+  /* Reset this value, so the timecode won't be re-rewritten */
+  qtmux->tc_pos = -1;
+
+  return gst_qt_mux_send_buffer (qtmux, buf, &offset, FALSE);
+}
+
+static GstFlowReturn
 gst_qt_mux_stop_file (GstQTMux * qtmux)
 {
   gboolean ret = GST_FLOW_OK;
@@ -2528,6 +2562,13 @@ gst_qt_mux_stop_file (GstQTMux * qtmux)
   }
 
   gst_qt_mux_update_global_statistics (qtmux);
+  if (qtmux->tc_pos != -1) {
+    /* File is being stopped and timecode hasn't been updated. Update it now
+     * with whatever we have */
+    ret = gst_qt_mux_update_timecode (qtmux);
+    if (ret != GST_FLOW_OK)
+      return ret;
+  }
 
   switch (qtmux->mux_mode) {
     case GST_QT_MUX_MODE_FRAGMENTED:{
@@ -2979,6 +3020,84 @@ gst_qt_mux_register_and_push_sample (GstQTMux * qtmux, GstQTPad * pad,
   return ret;
 }
 
+static GstFlowReturn
+gst_qt_mux_check_and_update_timecode (GstQTMux * qtmux, GstQTPad * pad,
+    GstBuffer * buf, GstFlowReturn ret)
+{
+  if (buf != NULL && (pad->tc_trak == NULL || qtmux->tc_pos != -1)) {
+    GstVideoTimeCodeMeta *tc_meta = gst_buffer_get_video_time_code_meta (buf);
+    if (tc_meta) {
+      GstVideoTimeCode *tc = &tc_meta->tc;
+      GstBuffer *tc_buf;
+      gsize szret;
+      guint32 frames_since_daily_jam;
+
+      /* This means we never got a timecode before */
+      if (qtmux->first_tc == NULL) {
+#ifndef GST_DISABLE_GST_DEBUG
+        gchar *tc_str = gst_video_time_code_to_string (tc);
+        GST_DEBUG_OBJECT (qtmux, "Found first timecode %s", tc_str);
+        g_free (tc_str);
+#endif
+        g_assert (pad->tc_trak == NULL);
+        tc_buf = gst_buffer_new_allocate (NULL, 4, NULL);
+        qtmux->first_tc = gst_video_time_code_copy (tc);
+        /* If frames are out of order, the frame we're currently getting might
+         * not be the first one. Just write a 0 timecode for now and wait
+         * until we receive a timecode that's lower than the current one */
+        if (pad->is_out_of_order) {
+          qtmux->first_pts = GST_BUFFER_PTS (buf);
+          frames_since_daily_jam = 0;
+          /* Position to rewrite */
+          qtmux->tc_pos = qtmux->mdat_size;
+        } else {
+          frames_since_daily_jam =
+              gst_video_time_code_frames_since_daily_jam (qtmux->first_tc);
+          frames_since_daily_jam = GUINT32_TO_BE (frames_since_daily_jam);
+        }
+        /* Write the timecode trak now */
+        pad->tc_trak = atom_trak_new (qtmux->context);
+        atom_moov_add_trak (qtmux->moov, pad->tc_trak);
+
+        pad->trak->tref = atom_tref_new (FOURCC_tmcd);
+        atom_tref_add_entry (pad->trak->tref, pad->tc_trak->tkhd.track_ID);
+
+        atom_trak_set_timecode_type (pad->tc_trak, qtmux->context,
+            qtmux->first_tc);
+
+        szret = gst_buffer_fill (tc_buf, 0, &frames_since_daily_jam, 4);
+        g_assert (szret == 4);
+
+        atom_trak_add_samples (pad->tc_trak, 1, 1, 4, qtmux->mdat_size, FALSE,
+            0);
+        ret = gst_qt_mux_send_buffer (qtmux, tc_buf, &qtmux->mdat_size, TRUE);
+      } else if (pad->is_out_of_order) {
+        /* Check for a lower timecode than the one stored */
+        g_assert (pad->tc_trak != NULL);
+        if (GST_BUFFER_DTS (buf) <= qtmux->first_pts) {
+          if (gst_video_time_code_compare (tc, qtmux->first_tc) == -1) {
+            gst_video_time_code_free (qtmux->first_tc);
+            qtmux->first_tc = gst_video_time_code_copy (tc);
+          }
+        } else {
+          guint64 bk_size = qtmux->mdat_size;
+          GstSegment segment;
+          /* If this frame's DTS is after the first PTS received, it means
+           * we've already received the first frame to be presented. Otherwise
+           * the decoder would need to go back in time */
+          gst_qt_mux_update_timecode (qtmux);
+
+          /* Reset writing position */
+          gst_segment_init (&segment, GST_FORMAT_BYTES);
+          segment.start = bk_size;
+          gst_pad_push_event (qtmux->srcpad, gst_event_new_segment (&segment));
+        }
+      }
+    }
+  }
+  return ret;
+}
+
 /*
  * Here we push the buffer and update the tables in the track atoms
  */
@@ -3025,6 +3144,8 @@ gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf)
     }
   }
 
+  ret = gst_qt_mux_check_and_update_timecode (qtmux, pad, buf, ret);
+
   if (last_buf && !buf && !GST_BUFFER_DURATION_IS_VALID (last_buf)) {
     /* this is last buffer; there is no next buffer so we need valid number as duration */
     last_buf = gst_buffer_make_writable (last_buf);
@@ -4567,6 +4688,8 @@ gst_qt_mux_change_state (GstElement * element, GstStateChange transition)
     case GST_STATE_CHANGE_READY_TO_PAUSED:
       gst_collect_pads_start (qtmux->collect);
       qtmux->state = GST_QT_MUX_STATE_STARTED;
+      qtmux->first_tc = NULL;
+      qtmux->tc_pos = -1;
       break;
     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
       break;
index 81a23fa..5fd7df9 100644 (file)
@@ -123,6 +123,7 @@ struct _GstQTPad
   /* all the atom and chunk book-keeping is delegated here
    * unowned/uncounted reference, parent MOOV owns */
   AtomTRAK *trak;
+  AtomTRAK *tc_trak;
   SampleTableEntry *trak_ste;
   /* fragmented support */
   /* meta data book-keeping delegated here */
@@ -205,6 +206,10 @@ struct _GstQTMux
   /* Set when tags are received, cleared when written to moov */
   gboolean tags_changed;
 
+  /* SMPTE timecode */
+  GstVideoTimeCode *first_tc;
+  GstClockTime first_pts;
+  guint64 tc_pos;
 
   /* fragmented file index */
   AtomMFRA *mfra;