From ab18f5035c19b38b848a2acfb50dd792fe07ffcd Mon Sep 17 00:00:00 2001 From: Thiago Santos Date: Wed, 10 Jun 2015 22:27:27 -0300 Subject: [PATCH] qtmux: add AC-3 muxing support 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 | 61 ++++++++++++++++++++++- gst/isomp4/atoms.h | 11 +++-- gst/isomp4/fourcc.h | 2 + gst/isomp4/gstqtmux.c | 103 +++++++++++++++++++++++++++++++++++++-- gst/isomp4/gstqtmux.h | 1 + gst/isomp4/gstqtmuxmap.c | 8 ++- 6 files changed, 175 insertions(+), 11 deletions(-) diff --git a/gst/isomp4/atoms.c b/gst/isomp4/atoms.c index 70794f4ffe..bffd1f32f5 100644 --- a/gst/isomp4/atoms.c +++ b/gst/isomp4/atoms.c @@ -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) { diff --git a/gst/isomp4/atoms.h b/gst/isomp4/atoms.h index 871c7133e8..b2073c6934 100644 --- a/gst/isomp4/atoms.h +++ b/gst/isomp4/atoms.h @@ -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); diff --git a/gst/isomp4/fourcc.h b/gst/isomp4/fourcc.h index 70e327da08..774a26608a 100644 --- a/gst/isomp4/fourcc.h +++ b/gst/isomp4/fourcc.h @@ -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') diff --git a/gst/isomp4/gstqtmux.c b/gst/isomp4/gstqtmux.c index aabae624f0..9690eb5fdf 100644 --- a/gst/isomp4/gstqtmux.c +++ b/gst/isomp4/gstqtmux.c @@ -117,6 +117,8 @@ #include #include +#include +#include #include #include #include @@ -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; diff --git a/gst/isomp4/gstqtmux.h b/gst/isomp4/gstqtmux.h index 3f2896e35d..b3b0261fb6 100644 --- a/gst/isomp4/gstqtmux.h +++ b/gst/isomp4/gstqtmux.h @@ -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; diff --git a/gst/isomp4/gstqtmuxmap.c b/gst/isomp4/gstqtmuxmap.c index bc0f101c0d..e088a424a5 100644 --- a/gst/isomp4/gstqtmuxmap.c +++ b/gst/isomp4/gstqtmuxmap.c @@ -115,6 +115,10 @@ "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) */