[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