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 <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; 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@ffmpeg.org > http://ffmpeg.org/mailman/listinfo/ffmpeg-devel > _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel