qtmux: add AC-3 muxing support
authorThiago Santos <thiagoss@osg.samsung.com>
Thu, 11 Jun 2015 01:27:27 +0000 (22:27 -0300)
committerThiago Santos <thiagoss@osg.samsung.com>
Thu, 11 Jun 2015 04:11:31 +0000 (01:11 -0300)
Adds AC-3 muxing support. It is defined for mp4 and 3gp formats.

One extra feature that was added was the ability to add extension
atoms after set_caps as the AC-3 extension atom needs some data
that has to be extracted from the stream itself and is not
present on caps.

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

index 70794f4ffe2cb62e22a43f11314d0d6e475a78a0..bffd1f32f590f56a40e69e8158144ebb1ab30a94 100644 (file)
@@ -3429,6 +3429,22 @@ atom_trak_set_subtitle_commons (AtomTRAK * trak, AtomsContext * context)
 }
 
 void
+sample_table_entry_add_ext_atom (SampleTableEntry * ste, AtomInfo * ext)
+{
+  GList **list = NULL;
+  if (ste->kind == AUDIO) {
+    list = &(((SampleTableEntryMP4A *) ste)->extension_atoms);
+  } else if (ste->kind == VIDEO) {
+    list = &(((SampleTableEntryMP4V *) ste)->extension_atoms);
+  } else {
+    g_assert_not_reached ();
+    return;
+  }
+
+  *list = g_list_prepend (*list, ext);
+}
+
+SampleTableEntryMP4A *
 atom_trak_set_audio_type (AtomTRAK * trak, AtomsContext * context,
     AudioSampleEntry * entry, guint32 scale, AtomInfo * ext, gint sample_size)
 {
@@ -3457,6 +3473,8 @@ atom_trak_set_audio_type (AtomTRAK * trak, AtomsContext * context,
 
   /* 0 size means variable size */
   atom_trak_set_constant_size_samples (trak, sample_size);
+
+  return ste;
 }
 
 static AtomInfo *
@@ -3479,7 +3497,7 @@ build_pasp_extension (AtomTRAK * trak, gint par_width, gint par_height)
       atom_data_free);
 }
 
-void
+SampleTableEntryMP4V *
 atom_trak_set_video_type (AtomTRAK * trak, AtomsContext * context,
     VisualSampleEntry * entry, guint32 scale, GList * ext_atoms_list)
 {
@@ -3528,6 +3546,8 @@ atom_trak_set_video_type (AtomTRAK * trak, AtomsContext * context,
     ste->extension_atoms = g_list_append (ste->extension_atoms,
         build_pasp_extension (trak, par_n, par_d));
   }
+
+  return ste;
 }
 
 void
@@ -3538,7 +3558,7 @@ subtitle_sample_entry_init (SubtitleSampleEntry * entry)
   entry->foreground_color_rgba = 0xFFFFFFFF;    /* all white, opaque */
 }
 
-void
+SampleTableEntryTX3G *
 atom_trak_set_subtitle_type (AtomTRAK * trak, AtomsContext * context,
     SubtitleSampleEntry * entry)
 {
@@ -3554,6 +3574,8 @@ atom_trak_set_subtitle_type (AtomTRAK * trak, AtomsContext * context,
 
   trak->is_video = FALSE;
   trak->is_h264 = FALSE;
+
+  return tx3g;
 }
 
 static void
@@ -4696,6 +4718,41 @@ build_ima_adpcm_extension (gint channels, gint rate, gint blocksize)
       atom_wave_free);
 }
 
+AtomInfo *
+build_ac3_extension (guint8 fscod, guint8 bsid, guint8 bsmod, guint8 acmod,
+    guint8 lfe_on, guint8 bitrate_code)
+{
+  AtomData *atom_data;
+  GstBuffer *buf;
+  guint8 *data;
+
+  data = g_malloc0 (3);
+
+  /* Bits from the spec
+   * fscod 2
+   * bsid  5
+   * bsmod 3
+   * acmod 3
+   * lfeon 1
+   * bit_rate_code 5
+   * reserved 5
+   */
+
+  /* Some bit manipulation magic. Need bitwriter */
+  data[0] = (fscod << 6) | (bsid << 1) | ((bsmod >> 2) & 1);
+  data[1] =
+      ((bsmod & 0x3) << 6) | (acmod << 3) | ((lfe_on & 1) << 2) | ((bitrate_code
+          >> 3) & 0x3);
+  data[2] = ((bitrate_code & 0x7) << 5);
+
+  buf = gst_buffer_new_wrapped (data, 3);
+  atom_data = atom_data_new_from_gst_buffer (FOURCC_dac3, buf);
+  gst_buffer_unref (buf);
+
+  return build_atom_info_wrapper ((Atom *) atom_data, atom_data_copy_data,
+      atom_data_free);
+}
+
 AtomInfo *
 build_uuid_xmp_atom (GstBuffer * xmp_data)
 {
index 871c7133e86737517f6dcdd55768174ee94a7105..b2073c6934d6960e190033875fddd95af889a585 100644 (file)
@@ -925,15 +925,15 @@ typedef struct
 
 void subtitle_sample_entry_init (SubtitleSampleEntry * entry);
 
-void atom_trak_set_audio_type (AtomTRAK * trak, AtomsContext * context,
+SampleTableEntryMP4A * atom_trak_set_audio_type (AtomTRAK * trak, AtomsContext * context,
                                AudioSampleEntry * entry, guint32 scale,
                                AtomInfo * ext, gint sample_size);
 
-void atom_trak_set_video_type (AtomTRAK * trak, AtomsContext * context,
+SampleTableEntryMP4V * atom_trak_set_video_type (AtomTRAK * trak, AtomsContext * context,
                                VisualSampleEntry * entry, guint32 rate,
                                GList * ext_atoms_list);
 
-void atom_trak_set_subtitle_type (AtomTRAK * trak, AtomsContext * context,
+SampleTableEntryTX3G * atom_trak_set_subtitle_type (AtomTRAK * trak, AtomsContext * context,
                                SubtitleSampleEntry * entry);
 
 void atom_trak_update_bitrates (AtomTRAK * trak, guint32 avg_bitrate,
@@ -942,6 +942,8 @@ void atom_trak_update_bitrates (AtomTRAK * trak, guint32 avg_bitrate,
 void atom_trak_tx3g_update_dimension (AtomTRAK * trak, guint32 width,
                                       guint32 height);
 
+void sample_table_entry_add_ext_atom (SampleTableEntry * ste, AtomInfo * ext);
+
 AtomInfo *   build_codec_data_extension  (guint32 fourcc, const GstBuffer * codec_data);
 AtomInfo *   build_mov_aac_extension     (AtomTRAK * trak, const GstBuffer * codec_data,
                                           guint32 avg_bitrate, guint32 max_bitrate);
@@ -958,6 +960,9 @@ AtomInfo *   build_jp2h_extension        (AtomTRAK * trak, gint width, gint heig
 
 AtomInfo *   build_jp2x_extension        (const GstBuffer * prefix);
 AtomInfo *   build_fiel_extension        (gint fields);
+AtomInfo *   build_ac3_extension         (guint8 fscod, guint8 bsid,
+                                          guint8 bsmod, guint8 acmod,
+                                          guint8 lfe_on, guint8 bitrate_code);
 AtomInfo *   build_amr_extension         (void);
 AtomInfo *   build_h263_extension        (void);
 AtomInfo *   build_gama_atom             (gdouble gamma);
index 70e327da0821b1382a3eb028dec20367b9674de1..774a26608a18e9a91e9901409590d9ee98512620 100644 (file)
@@ -83,6 +83,7 @@ G_BEGIN_DECLS
 #define FOURCC__too     GST_MAKE_FOURCC(0xa9,'t','o','o')
 #define FOURCC__wrt     GST_MAKE_FOURCC(0xa9,'w','r','t')
 #define FOURCC_aART     GST_MAKE_FOURCC('a','A','R','T')
+#define FOURCC_ac_3     GST_MAKE_FOURCC('a','c','-','3')
 #define FOURCC_agsm     GST_MAKE_FOURCC('a','g','s','m')
 #define FOURCC_alac     GST_MAKE_FOURCC('a','l','a','c')
 #define FOURCC_alaw     GST_MAKE_FOURCC('a','l','a','w')
@@ -101,6 +102,7 @@ G_BEGIN_DECLS
 #define FOURCC_crgn     GST_MAKE_FOURCC('c','r','g','n')
 #define FOURCC_ctab     GST_MAKE_FOURCC('c','t','a','b')
 #define FOURCC_ctts     GST_MAKE_FOURCC('c','t','t','s')
+#define FOURCC_dac3     GST_MAKE_FOURCC('d','a','c','3')
 #define FOURCC_data     GST_MAKE_FOURCC('d','a','t','a')
 #define FOURCC_dcom     GST_MAKE_FOURCC('d','c','o','m')
 #define FOURCC_desc     GST_MAKE_FOURCC('d','e','s','c')
index aabae624f06af554015e1aecff6af07afeae6279..9690eb5fdf665032f0533ce4a7e1fe4d54a1fc71 100644 (file)
 
 #include <gst/gst.h>
 #include <gst/base/gstcollectpads.h>
+#include <gst/base/gstbytereader.h>
+#include <gst/base/gstbitreader.h>
 #include <gst/audio/audio.h>
 #include <gst/video/video.h>
 #include <gst/tag/tag.h>
@@ -720,6 +722,82 @@ gst_qt_mux_prepare_tx3g_buffer (GstQTPad * qtpad, GstBuffer * buf,
   return newbuf;
 }
 
+static void
+gst_qt_mux_pad_add_ac3_extension (GstQTMux * qtmux, GstQTPad * qtpad,
+    guint8 fscod, guint8 frmsizcod, guint8 bsid, guint8 bsmod, guint8 acmod,
+    guint8 lfe_on)
+{
+  AtomInfo *ext;
+
+  g_return_if_fail (qtpad->trak_ste);
+
+  ext = build_ac3_extension (fscod, bsid, bsmod, acmod, lfe_on, frmsizcod >> 1);        /* bitrate_code is inside frmsizcod */
+
+  sample_table_entry_add_ext_atom (qtpad->trak_ste, ext);
+}
+
+static GstBuffer *
+gst_qt_mux_prepare_parse_ac3_frame (GstQTPad * qtpad, GstBuffer * buf,
+    GstQTMux * qtmux)
+{
+  GstMapInfo map;
+  GstByteReader reader;
+  guint off;
+
+  if (!gst_buffer_map (buf, &map, GST_MAP_READ)) {
+    GST_WARNING_OBJECT (qtpad->collect.pad, "Failed to map buffer");
+    return buf;
+  }
+
+  if (G_UNLIKELY (map.size < 8))
+    goto done;
+
+  gst_byte_reader_init (&reader, map.data, map.size);
+  off = gst_byte_reader_masked_scan_uint32 (&reader, 0xffff0000, 0x0b770000,
+      0, map.size);
+
+  if (off != -1) {
+    GstBitReader bits;
+    guint8 fscod, frmsizcod, bsid, bsmod, acmod, lfe_on;
+
+    GST_DEBUG_OBJECT (qtpad->collect.pad, "Found ac3 sync point at offset: %u",
+        off);
+
+    gst_bit_reader_init (&bits, map.data, map.size);
+
+    /* off + sync + crc */
+    gst_bit_reader_skip_unchecked (&bits, off * 8 + 16 + 16);
+
+    fscod = gst_bit_reader_get_bits_uint8_unchecked (&bits, 2);
+    frmsizcod = gst_bit_reader_get_bits_uint8_unchecked (&bits, 6);
+    bsid = gst_bit_reader_get_bits_uint8_unchecked (&bits, 5);
+    bsmod = gst_bit_reader_get_bits_uint8_unchecked (&bits, 3);
+    acmod = gst_bit_reader_get_bits_uint8_unchecked (&bits, 3);
+
+    if ((acmod & 0x1) && (acmod != 0x1))        /* 3 front channels */
+      gst_bit_reader_skip_unchecked (&bits, 2);
+    if ((acmod & 0x4))          /* if a surround channel exists */
+      gst_bit_reader_skip_unchecked (&bits, 2);
+    if (acmod == 0x2)           /* if in 2/0 mode */
+      gst_bit_reader_skip_unchecked (&bits, 2);
+
+    lfe_on = gst_bit_reader_get_bits_uint8_unchecked (&bits, 1);
+
+    gst_qt_mux_pad_add_ac3_extension (qtmux, qtpad, fscod, frmsizcod, bsid,
+        bsmod, acmod, lfe_on);
+
+    /* AC-3 spec says that those values should be constant for the
+     * whole stream when muxed in mp4. We trust the input follows it */
+    GST_DEBUG_OBJECT (qtpad->collect.pad, "Data parsed, removing "
+        "prepare buffer function");
+    qtpad->prepare_buf_func = NULL;
+  }
+
+done:
+  gst_buffer_unmap (buf, &map);
+  return buf;
+}
+
 static GstBuffer *
 gst_qt_mux_create_empty_tx3g_buffer (GstQTPad * qtpad, gint64 duration)
 {
@@ -3497,6 +3575,18 @@ gst_qt_mux_audio_sink_set_caps (GstQTPad * qtpad, GstCaps * caps)
     entry.samples_per_packet = GST_READ_UINT32_BE (map.data + 4);
     gst_buffer_unmap (codec_config, &map);
     gst_buffer_unref (codec_config);
+  } else if (strcmp (mimetype, "audio/x-ac3") == 0) {
+    entry.fourcc = FOURCC_ac_3;
+
+    /* Fixed values according to TS 102 366 but it also mentions that
+     * they should be ignored */
+    entry.channels = 2;
+    entry.sample_size = 16;
+
+    /* AC-3 needs an extension atom but its data can only be obtained from
+     * the stream itself. Abuse the prepare_buf_func so we parse a frame
+     * and get the needed data */
+    qtpad->prepare_buf_func = gst_qt_mux_prepare_parse_ac3_frame;
   }
 
   if (!entry.fourcc)
@@ -3505,7 +3595,9 @@ gst_qt_mux_audio_sink_set_caps (GstQTPad * qtpad, GstCaps * caps)
   /* ok, set the pad info accordingly */
   qtpad->fourcc = entry.fourcc;
   qtpad->sample_size = constant_size;
-  atom_trak_set_audio_type (qtpad->trak, qtmux->context, &entry,
+  qtpad->trak_ste =
+      (SampleTableEntry *) atom_trak_set_audio_type (qtpad->trak,
+      qtmux->context, &entry,
       qtmux->trak_timescale ? qtmux->trak_timescale : entry.sample_rate,
       ext_atom, constant_size);
 
@@ -3866,8 +3958,9 @@ gst_qt_mux_video_sink_set_caps (GstQTPad * qtpad, GstCaps * caps)
   /* ok, set the pad info accordingly */
   qtpad->fourcc = entry.fourcc;
   qtpad->sync = sync;
-  atom_trak_set_video_type (qtpad->trak, qtmux->context, &entry, rate,
-      ext_atom_list);
+  qtpad->trak_ste =
+      (SampleTableEntry *) atom_trak_set_video_type (qtpad->trak,
+      qtmux->context, &entry, rate, ext_atom_list);
 
   gst_object_unref (qtmux);
   return TRUE;
@@ -3942,7 +4035,9 @@ gst_qt_mux_subtitle_sink_set_caps (GstQTPad * qtpad, GstCaps * caps)
     goto refuse_caps;
 
   qtpad->fourcc = entry.fourcc;
-  atom_trak_set_subtitle_type (qtpad->trak, qtmux->context, &entry);
+  qtpad->trak_ste =
+      (SampleTableEntry *) atom_trak_set_subtitle_type (qtpad->trak,
+      qtmux->context, &entry);
 
   gst_object_unref (qtmux);
   return TRUE;
index 3f2896e35da4109a4426d9f7a975b02d5f7b8296..b3b0261fb6d6e231f9cffa5831c15a52e1fe0667 100644 (file)
@@ -118,6 +118,7 @@ struct _GstQTPad
   /* all the atom and chunk book-keeping is delegated here
    * unowned/uncounted reference, parent MOOV owns */
   AtomTRAK *trak;
+  SampleTableEntry *trak_ste;
   /* fragmented support */
   /* meta data book-keeping delegated here */
   AtomTRAF *traf;
index bc0f101c0df3d170b195f6db99e69f734adcf09c..e088a424a5372c2839d2b21c5fb76e3c6ae3693e 100644 (file)
   "stream-format = (string) raw, " \
   COMMON_AUDIO_CAPS (8, MAX)
 
+#define AC3_CAPS \
+  "audio/x-ac3, " \
+  COMMON_AUDIO_CAPS (6, MAX)
+
 #define AMR_CAPS \
   "audio/AMR, " \
   "rate = (int) 8000, " \
@@ -184,7 +188,7 @@ GstQTMuxFormatProp gst_qt_mux_format_list[] = {
         GST_STATIC_CAPS ("video/quicktime, variant = (string) iso"),
         GST_STATIC_CAPS (MPEG4V_CAPS "; " H264_CAPS ";"
             "video/x-mp4-part," COMMON_VIDEO_CAPS),
-        GST_STATIC_CAPS (MP3_CAPS "; " AAC_CAPS " ; " ALAC_CAPS),
+        GST_STATIC_CAPS (MP3_CAPS "; " AAC_CAPS " ; " AC3_CAPS " ; " ALAC_CAPS),
       GST_STATIC_CAPS (TEXT_UTF8)}
   ,
   /* Microsoft Smooth Streaming fmp4/isml */
@@ -210,7 +214,7 @@ GstQTMuxFormatProp gst_qt_mux_format_list[] = {
         "Gst3GPPMux",
         GST_STATIC_CAPS ("video/quicktime, variant = (string) 3gpp"),
         GST_STATIC_CAPS (H263_CAPS "; " MPEG4V_CAPS "; " H264_CAPS),
-        GST_STATIC_CAPS (AMR_CAPS "; " MP3_CAPS "; " AAC_CAPS),
+        GST_STATIC_CAPS (AMR_CAPS "; " MP3_CAPS "; " AAC_CAPS "; " AC3_CAPS),
       GST_STATIC_CAPS (TEXT_UTF8)}
   ,
   /* ISO 15444-3: Motion-JPEG-2000 (also ISO base media extension) */