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!
Basic Opus in MP4 mux/demux support based on the draft spec. Draft spec: https://www.opus-codec.org/docs/opus_in_isobmff.html Signed-off-by: Matthew Gregan <kine...@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; + + 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)); + 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) + 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@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel