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

Reply via email to