[FFmpeg-devel] [PATCH] Opus in MP4 support
James Almer
jamrial at gmail.com
Mon Mar 7 21:07:21 CET 2016
On 3/7/2016 1:29 AM, Matthew Gregan wrote:
> Hi,
>
> The attached patch adds basic mux/demux support for the Opus audio codec in MP4.
>
> Mozilla have expressed interest in shipping support for this in:
> https://groups.google.com/d/msg/mozilla.dev.platform/mdDZ-nBr_jM/MaLi2BDOAgAJ
>
> Comments welcome!
>
>
> Opus-in-MP4.patch
>
>
> Basic Opus in MP4 mux/demux support based on the draft spec.
>
> Draft spec: https://www.opus-codec.org/docs/opus_in_isobmff.html
Check section 4.3.4 and use AV_PKT_DATA_SKIP_SAMPLES in the demuxer to export
the amount of samples that need to be discarded from the last packet.
Take a look at Matroska and OggOpus for two examples of this.
>
> Signed-off-by: Matthew Gregan <kinetik at flim.org>
> ---
> libavformat/isom.c | 2 ++
> libavformat/mov.c | 38 ++++++++++++++++++++++++
> libavformat/movenc.c | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++--
> 3 files changed, 119 insertions(+), 3 deletions(-)
>
> diff --git a/libavformat/isom.c b/libavformat/isom.c
> index 2ca1265..6d92a93 100644
> --- a/libavformat/isom.c
> +++ b/libavformat/isom.c
> @@ -61,6 +61,7 @@ const AVCodecTag ff_mp4_obj_type[] = {
> { AV_CODEC_ID_DTS , 0xA9 }, /* mp4ra.org */
> { AV_CODEC_ID_TSCC2 , 0xD0 }, /* non standard, camtasia uses it */
> { AV_CODEC_ID_VORBIS , 0xDD }, /* non standard, gpac uses it */
> + { AV_CODEC_ID_OPUS , 0xDE }, /* non standard */
> { AV_CODEC_ID_DVD_SUBTITLE, 0xE0 }, /* non standard, see unsupported-embedded-subs-2.mp4 */
> { AV_CODEC_ID_QCELP , 0xE1 },
> { AV_CODEC_ID_MPEG4SYSTEMS, 0x01 },
> @@ -323,6 +324,7 @@ const AVCodecTag ff_codec_movaudio_tags[] = {
> { AV_CODEC_ID_WMAV2, MKTAG('W', 'M', 'A', '2') },
> { AV_CODEC_ID_EVRC, MKTAG('s', 'e', 'v', 'c') }, /* 3GPP2 */
> { AV_CODEC_ID_SMV, MKTAG('s', 's', 'm', 'v') }, /* 3GPP2 */
> + { AV_CODEC_ID_OPUS, MKTAG('O', 'p', 'u', 's') }, /* non-standard */
> { AV_CODEC_ID_NONE, 0 },
> };
>
> diff --git a/libavformat/mov.c b/libavformat/mov.c
> index 752bc12..a6b896e 100644
> --- a/libavformat/mov.c
> +++ b/libavformat/mov.c
> @@ -1534,6 +1534,43 @@ static int mov_read_svq3(MOVContext *c, AVIOContext *pb, MOVAtom atom)
> return mov_read_extradata(c, pb, atom, AV_CODEC_ID_SVQ3);
> }
>
> +static int mov_read_dops(MOVContext *c, AVIOContext *pb, MOVAtom atom)
> +{
> + AVStream *st;
> + size_t size;
> +
> + if (c->fc->nb_streams < 1)
> + return 0;
> + st = c->fc->streams[c->fc->nb_streams-1];
> +
> + if ((uint64_t)atom.size > (1<<30) || atom.size < 11)
> + return AVERROR_INVALIDDATA;
> +
> + // Check OpusSpecificBox version.
> + if (avio_r8(pb) != 0)
> + return AVERROR_INVALIDDATA;
> +
> + // OpusSpecificBox size plus magic header for OggOpus header.
> + size = atom.size + 16;
> +
> + st->codec->extradata = av_mallocz(size + AV_INPUT_BUFFER_PADDING_SIZE);
> + if (!st->codec->extradata)
> + return AVERROR(ENOMEM);
> + st->codec->extradata_size = size;
Use ff_alloc_extradata(st->codec, size).
> +
> + AV_WL32(st->codec->extradata, MKTAG('O','p','u','s'));
> + AV_WL32(st->codec->extradata + 4, MKTAG('H','e','a','d'));
> + AV_WB8(st->codec->extradata + 8, 1); // OggOpus version
> + avio_read(pb, st->codec->extradata + 9, size - 17);
> +
> + // OpusSpecificBox is stored in big-endian, but OpusHead is
> + // little-endian; they're otherwise identical.
> + AV_WL16(st->codec->extradata + 10, AV_RB16(st->codec->extradata + 10));
Shouldn't you fill st->codec->delay with this value as well?
> + AV_WL32(st->codec->extradata + 12, AV_RB32(st->codec->extradata + 12));
> + AV_WL16(st->codec->extradata + 16, AV_RB16(st->codec->extradata + 16));
> + return 0;
> +}
> +
> static int mov_read_wave(MOVContext *c, AVIOContext *pb, MOVAtom atom)
> {
> AVStream *st;
> @@ -4308,6 +4345,7 @@ static const MOVParseTableEntry mov_default_parse_table[] = {
> { MKTAG('f','r','m','a'), mov_read_frma },
> { MKTAG('s','e','n','c'), mov_read_senc },
> { MKTAG('s','a','i','z'), mov_read_saiz },
> +{ MKTAG('d','O','p','s'), mov_read_dops },
> { 0, NULL }
> };
>
> diff --git a/libavformat/movenc.c b/libavformat/movenc.c
> index cb125d8..e5698b4 100644
> --- a/libavformat/movenc.c
> +++ b/libavformat/movenc.c
> @@ -649,6 +649,25 @@ static int mov_write_wfex_tag(AVIOContext *pb, MOVTrack *track)
> return update_size(pb, pos);
> }
>
> +static int mov_write_dops_tag(AVIOContext *pb, MOVTrack *track)
> +{
> + int64_t pos = avio_tell(pb);
> + avio_wb32(pb, 0);
> + ffio_wfourcc(pb, "dOps");
> + avio_w8(pb, 0); // OpusSpecificBox version.
> + if (track->enc->extradata_size < 19)
> + return AVERROR_INVALIDDATA;
> + // Write the part of a OggOpus header matching the OpusSpecificBox layout.
> + // Skipping OggOpus magic (8 bytes) and version (1 byte).
> + avio_w8(pb, AV_RB8(track->enc->extradata + 9)); // OuputChannelCount
> + avio_wb16(pb, AV_RL16(track->enc->extradata + 10)); // PreSkip
> + avio_wb32(pb, AV_RL32(track->enc->extradata + 12)); // InputSampleRate
> + avio_wb16(pb, AV_RL16(track->enc->extradata + 16)); // OutputGain
> + // Write the rest of the header out as-is.
> + avio_write(pb, track->enc->extradata + 18, track->enc->extradata_size - 18);
> + return update_size(pb, pos);
> +}
> +
> static int mov_write_chan_tag(AVIOContext *pb, MOVTrack *track)
> {
> uint32_t layout_tag, bitmap;
> @@ -958,14 +977,20 @@ static int mov_write_audio_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *tr
> avio_wb16(pb, 16);
> avio_wb16(pb, track->audio_vbr ? -2 : 0); /* compression ID */
> } else { /* reserved for mp4/3gp */
> - avio_wb16(pb, 2);
> + if (track->enc->codec_id == AV_CODEC_ID_OPUS)
> + avio_wb16(pb, track->enc->channels);
> + else
> + avio_wb16(pb, 2);
> avio_wb16(pb, 16);
> avio_wb16(pb, 0);
> }
>
> avio_wb16(pb, 0); /* packet size (= 0) */
> - avio_wb16(pb, track->enc->sample_rate <= UINT16_MAX ?
> - track->enc->sample_rate : 0);
> + if (track->enc->codec_id == AV_CODEC_ID_OPUS)
> + avio_wb16(pb, 48000);
> + else
> + avio_wb16(pb, track->enc->sample_rate <= UINT16_MAX ?
> + track->enc->sample_rate : 0);
> avio_wb16(pb, 0); /* Reserved */
> }
>
> @@ -1004,6 +1029,8 @@ static int mov_write_audio_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *tr
> mov_write_extradata_tag(pb, track);
> else if (track->enc->codec_id == AV_CODEC_ID_WMAPRO)
> mov_write_wfex_tag(pb, track);
> + else if (track->enc->codec_id == AV_CODEC_ID_OPUS)
> + mov_write_dops_tag(pb, track);
> else if (track->vos_len > 0)
> mov_write_glbl_tag(pb, track);
>
> @@ -1148,6 +1175,7 @@ static int mp4_get_codec_tag(AVFormatContext *s, MOVTrack *track)
> else if (track->enc->codec_id == AV_CODEC_ID_DIRAC) tag = MKTAG('d','r','a','c');
> else if (track->enc->codec_id == AV_CODEC_ID_MOV_TEXT) tag = MKTAG('t','x','3','g');
> else if (track->enc->codec_id == AV_CODEC_ID_VC1) tag = MKTAG('v','c','-','1');
> + else if (track->enc->codec_id == AV_CODEC_ID_OPUS) tag = MKTAG('O','p','u','s');
> else if (track->enc->codec_type == AVMEDIA_TYPE_VIDEO) tag = MKTAG('m','p','4','v');
> else if (track->enc->codec_type == AVMEDIA_TYPE_AUDIO) tag = MKTAG('m','p','4','a');
> else if (track->enc->codec_id == AV_CODEC_ID_DVD_SUBTITLE) tag = MKTAG('m','p','4','s');
> @@ -1999,6 +2027,48 @@ static int mov_write_dref_tag(AVIOContext *pb)
> return 28;
> }
>
> +static int mov_write_sgpd_tag(AVIOContext *pb, MOVTrack *track)
> +{
> + int64_t pos = avio_tell(pb);
> +
> + // Same as OpusHead preskip except must be in track timescale (which we
> + // force to 48000Hz anyway, so the values are equal).
> + int16_t roll_distance = AV_RL16(track->enc->extradata + 10);
> +
> + av_assert0(track->enc->codec_id == AV_CODEC_ID_OPUS);
> +
> + avio_wb32(pb, 0); /* size */
> + ffio_wfourcc(pb, "sgpd");
> + avio_wb32(pb, 1 << 24); /* fullbox */
> + ffio_wfourcc(pb, "roll");
> + avio_wb32(pb, 2); // default_length
> + avio_wb32(pb, 1); // entry_count
> + avio_wb16(pb, roll_distance); // roll_distance
> + return update_size(pb, pos);
> +}
> +
> +static int mov_write_sbgp_tag(AVIOContext *pb, MOVTrack *track)
> +{
> + int64_t pos = avio_tell(pb);
> +
> + int entries = 0;
> +
> + av_assert0(track->enc->codec_id == AV_CODEC_ID_OPUS);
> +
> + for (int i = 0; i < track->entry; ++i) {
> + entries += track->cluster[i].entries;
> + }
> +
> + avio_wb32(pb, 0); /* size */
> + ffio_wfourcc(pb, "sbgp");
> + avio_wb32(pb, 0); /* fullbox */
> + ffio_wfourcc(pb, "roll");
> + avio_wb32(pb, 1); // entry_count
> + avio_wb32(pb, entries); // sample_count
> + avio_wb32(pb, 1); // group_description_index
> + return update_size(pb, pos);
> +}
> +
> static int mov_write_stbl_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track)
> {
> int64_t pos = avio_tell(pb);
> @@ -2026,6 +2096,12 @@ static int mov_write_stbl_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *tra
> if (mov->encryption_scheme == MOV_ENC_CENC_AES_CTR) {
> ff_mov_cenc_write_stbl_atoms(&track->cenc, pb);
> }
> + // XXX rather than hardcoding for Opus, write this for preroll (if it
> + // generalizes for other codecs)
Please see AVCodecContext->seek_preroll. Mainly for the demuxer, where you will
need to use it to export the value you're storing in the sgpd atom.
> + if (track->enc->codec_id == AV_CODEC_ID_OPUS) {
> + mov_write_sgpd_tag(pb, track);
> + mov_write_sbgp_tag(pb, track);
> + }
> return update_size(pb, pos);
> }
>
> -- 2.7.0
>
>
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
More information about the ffmpeg-devel
mailing list