[FFmpeg-cvslog] libopusenc: Add channel mapping family argument

Michael Graczyk git at videolan.org
Sat Jul 2 23:53:02 CEST 2016


ffmpeg | branch: master | Michael Graczyk <mgraczyk at google.com> | Tue Jun 14 18:33:15 2016 -0700| [37941878f193a2316c514bd5ba55bfe9d2dfdfcf] | committer: Michael Niedermayer

libopusenc: Add channel mapping family argument

The default value of -1 indicates that ffmpeg should determine the channel
mapping automatically, which was the behavior before this commit.

Unless the -mapping_family argument is provided, behavior is unchanged.

Signed-off-by: Michael Niedermayer <michael at niedermayer.cc>

> http://git.videolan.org/gitweb.cgi/ffmpeg.git/?a=commit;h=37941878f193a2316c514bd5ba55bfe9d2dfdfcf
---

 doc/encoders.texi       |   11 ++++
 libavcodec/libopusenc.c |  165 +++++++++++++++++++++++++++++++++++++++--------
 2 files changed, 150 insertions(+), 26 deletions(-)

diff --git a/doc/encoders.texi b/doc/encoders.texi
index f38cad3..d0caa77 100644
--- a/doc/encoders.texi
+++ b/doc/encoders.texi
@@ -1184,6 +1184,17 @@ following: 4000, 6000, 8000, 12000, or 20000, corresponding to
 narrowband, mediumband, wideband, super wideband, and fullband
 respectively. The default is 0 (cutoff disabled).
 
+ at item mapping_family (@emph{mapping_family})
+Set channel mapping family to be used by the encoder. The default value of -1
+uses mapping family 0 for mono and stereo inputs, and mapping family 1
+otherwise. The default also disables the surround masking and LFE bandwidth
+optimzations in libopus, and requires that the input contains 8 channels or
+fewer.
+
+Other values include 0 for mono and stereo, 1 for surround sound with masking
+and LFE bandwidth optimizations, and 255 for independent streams with an
+unspecified channel layout.
+
 @end table
 
 @section libvorbis
diff --git a/libavcodec/libopusenc.c b/libavcodec/libopusenc.c
index c9b96ce..c40fcde 100644
--- a/libavcodec/libopusenc.c
+++ b/libavcodec/libopusenc.c
@@ -38,6 +38,7 @@ typedef struct LibopusEncOpts {
     float frame_duration;
     int packet_size;
     int max_bandwidth;
+    int mapping_family;
 } LibopusEncOpts;
 
 typedef struct LibopusEncContext {
@@ -47,6 +48,7 @@ typedef struct LibopusEncContext {
     uint8_t *samples;
     LibopusEncOpts opts;
     AudioFrameQueue afq;
+    const uint8_t *encoder_channel_map;
 } LibopusEncContext;
 
 static const uint8_t opus_coupled_streams[8] = {
@@ -93,13 +95,11 @@ static void libopus_write_header(AVCodecContext *avctx, int stream_count,
     bytestream_put_le16(&p, 0); /* Gain of 0dB is recommended. */
 
     /* Channel mapping */
-    if (channels > 2) {
-        bytestream_put_byte(&p, mapping_family);
+    bytestream_put_byte(&p, mapping_family);
+    if (mapping_family != 0) {
         bytestream_put_byte(&p, stream_count);
         bytestream_put_byte(&p, coupled_stream_count);
         bytestream_put_buffer(&p, channel_mapping, channels);
-    } else {
-        bytestream_put_byte(&p, 0);
     }
 }
 
@@ -157,13 +157,92 @@ static int libopus_configure_encoder(AVCodecContext *avctx, OpusMSEncoder *enc,
     return OPUS_OK;
 }
 
+static int libopus_check_max_channels(AVCodecContext *avctx,
+                                      int max_channels) {
+    if (avctx->channels > max_channels) {
+        av_log(avctx, AV_LOG_ERROR, "Opus mapping family undefined for %d channels.\n",
+               avctx->channels);
+        return AVERROR(EINVAL);
+    }
+
+    return 0;
+}
+
+static int libopus_check_vorbis_layout(AVCodecContext *avctx, int mapping_family) {
+    av_assert2(avctx->channels < FF_ARRAY_ELEMS(ff_vorbis_channel_layouts));
+
+    if (!avctx->channel_layout) {
+        av_log(avctx, AV_LOG_WARNING,
+               "No channel layout specified. Opus encoder will use Vorbis "
+               "channel layout for %d channels.\n", avctx->channels);
+    } else if (avctx->channel_layout != ff_vorbis_channel_layouts[avctx->channels - 1]) {
+        char name[32];
+        av_get_channel_layout_string(name, sizeof(name), avctx->channels,
+                                     avctx->channel_layout);
+        av_log(avctx, AV_LOG_ERROR,
+               "Invalid channel layout %s for specified mapping family %d.\n",
+               name, mapping_family);
+
+        return AVERROR(EINVAL);
+    }
+
+    return 0;
+}
+
+static int libopus_validate_layout_and_get_channel_map(
+        AVCodecContext *avctx,
+        int mapping_family,
+        const uint8_t ** channel_map_result)
+{
+    const uint8_t * channel_map = NULL;
+    int ret;
+
+    switch (mapping_family) {
+    case -1:
+        ret = libopus_check_max_channels(avctx, 8);
+        if (ret == 0) {
+            ret = libopus_check_vorbis_layout(avctx, mapping_family);
+            /* Channels do not need to be reordered. */
+        }
+
+        break;
+    case 0:
+        ret = libopus_check_max_channels(avctx, 2);
+        if (ret == 0) {
+            ret = libopus_check_vorbis_layout(avctx, mapping_family);
+        }
+        break;
+    case 1:
+        /* Opus expects channels to be in Vorbis order. */
+        ret = libopus_check_max_channels(avctx, 8);
+        if (ret == 0) {
+            ret = libopus_check_vorbis_layout(avctx, mapping_family);
+            channel_map = ff_vorbis_channel_layout_offsets[avctx->channels - 1];
+        }
+        break;
+    case 255:
+        ret = libopus_check_max_channels(avctx, 254);
+        break;
+    default:
+        av_log(avctx, AV_LOG_WARNING,
+               "Unknown channel mapping family %d. Output channel layout may be invalid.\n",
+               mapping_family);
+        ret = 0;
+    }
+
+    *channel_map_result = channel_map;
+    return ret;
+}
+
 static av_cold int libopus_encode_init(AVCodecContext *avctx)
 {
     LibopusEncContext *opus = avctx->priv_data;
     OpusMSEncoder *enc;
     uint8_t libopus_channel_mapping[255];
     int ret = OPUS_OK;
+    int av_ret;
     int coupled_stream_count, header_size, frame_size;
+    int mapping_family;
 
     frame_size = opus->opts.frame_duration * 48000 / 1000;
     switch (frame_size) {
@@ -226,26 +305,40 @@ static av_cold int libopus_encode_init(AVCodecContext *avctx)
         }
     }
 
-    /* FIXME: Opus can handle up to 255 channels. However, the mapping for
-     * anything greater than 8 is undefined. */
-    if (avctx->channels > 8) {
-        av_log(avctx, AV_LOG_ERROR,
-               "Channel layout undefined for %d channels.\n", avctx->channels);
-        return AVERROR_PATCHWELCOME;
+    /* Channels may need to be reordered to match opus mapping. */
+    av_ret = libopus_validate_layout_and_get_channel_map(avctx, opus->opts.mapping_family,
+                                                         &opus->encoder_channel_map);
+    if (av_ret) {
+        return av_ret;
     }
 
-    coupled_stream_count = opus_coupled_streams[avctx->channels - 1];
-    opus->stream_count   = avctx->channels - coupled_stream_count;
-
-    memcpy(libopus_channel_mapping,
-           opus_vorbis_channel_map[avctx->channels - 1],
-           avctx->channels * sizeof(*libopus_channel_mapping));
-
-    enc = opus_multistream_encoder_create(
-        avctx->sample_rate, avctx->channels, opus->stream_count,
-        coupled_stream_count,
-        libavcodec_libopus_channel_map[avctx->channels - 1],
-        opus->opts.application, &ret);
+    if (opus->opts.mapping_family == -1) {
+        /* By default, use mapping family 1 for the header but use the older
+         * libopus multistream API to avoid surround masking. */
+
+        /* Set the mapping family so that the value is correct in the header */
+        mapping_family = avctx->channels > 2 ? 1 : 0;
+        coupled_stream_count = opus_coupled_streams[avctx->channels - 1];
+        opus->stream_count   = avctx->channels - coupled_stream_count;
+        memcpy(libopus_channel_mapping,
+               opus_vorbis_channel_map[avctx->channels - 1],
+               avctx->channels * sizeof(*libopus_channel_mapping));
+
+        enc = opus_multistream_encoder_create(
+            avctx->sample_rate, avctx->channels, opus->stream_count,
+            coupled_stream_count,
+            libavcodec_libopus_channel_map[avctx->channels - 1],
+            opus->opts.application, &ret);
+    } else {
+        /* Use the newer multistream API. The encoder will set the channel
+         * mapping and coupled stream counts to its internal defaults and will
+         * use surround masking analysis to save bits. */
+        mapping_family = opus->opts.mapping_family;
+        enc = opus_multistream_surround_encoder_create(
+            avctx->sample_rate, avctx->channels, mapping_family,
+            &opus->stream_count, &coupled_stream_count, libopus_channel_mapping,
+            opus->opts.application, &ret);
+    }
 
     if (ret != OPUS_OK) {
         av_log(avctx, AV_LOG_ERROR,
@@ -275,7 +368,8 @@ static av_cold int libopus_encode_init(AVCodecContext *avctx)
         goto fail;
     }
 
-    header_size = 19 + (avctx->channels > 2 ? 2 + avctx->channels : 0);
+    /* Header includes channel mapping table if and only if mapping family is 0 */
+    header_size = 19 + (mapping_family == 0 ? 0 : 2 + avctx->channels);
     avctx->extradata = av_malloc(header_size + AV_INPUT_BUFFER_PADDING_SIZE);
     if (!avctx->extradata) {
         av_log(avctx, AV_LOG_ERROR, "Failed to allocate extradata.\n");
@@ -299,7 +393,7 @@ static av_cold int libopus_encode_init(AVCodecContext *avctx)
                opus_strerror(ret));
 
     libopus_write_header(avctx, opus->stream_count, coupled_stream_count,
-                         avctx->channels <= 8 ? 1 : 255, libopus_channel_mapping);
+                         mapping_family, libopus_channel_mapping);
 
     ff_af_queue_init(avctx, &opus->afq);
 
@@ -313,6 +407,20 @@ fail:
     return ret;
 }
 
+static void libopus_copy_samples_with_channel_map(
+    uint8_t *dst, const uint8_t *src, const uint8_t *channel_map,
+    int nb_channels, int nb_samples, int bytes_per_sample) {
+    int sample, channel;
+    for (sample = 0; sample < nb_samples; ++sample) {
+        for (channel = 0; channel < nb_channels; ++channel) {
+            const size_t src_pos = bytes_per_sample * (nb_channels * sample + channel);
+            const size_t dst_pos = bytes_per_sample * (nb_channels * sample + channel_map[channel]);
+
+            memcpy(&dst[dst_pos], &src[src_pos], bytes_per_sample);
+        }
+    }
+}
+
 static int libopus_encode(AVCodecContext *avctx, AVPacket *avpkt,
                           const AVFrame *frame, int *got_packet_ptr)
 {
@@ -327,7 +435,12 @@ static int libopus_encode(AVCodecContext *avctx, AVPacket *avpkt,
         ret = ff_af_queue_add(&opus->afq, frame);
         if (ret < 0)
             return ret;
-        if (frame->nb_samples < opus->opts.packet_size) {
+        if (opus->encoder_channel_map != NULL) {
+            audio = opus->samples;
+            libopus_copy_samples_with_channel_map(
+                audio, frame->data[0], opus->encoder_channel_map,
+                avctx->channels, frame->nb_samples, bytes_per_sample);
+        } else if (frame->nb_samples < opus->opts.packet_size) {
             audio = opus->samples;
             memcpy(audio, frame->data[0], frame->nb_samples * sample_size);
         } else
@@ -416,6 +529,7 @@ static const AVOption libopus_options[] = {
         { "off",            "Use constant bit rate", 0, AV_OPT_TYPE_CONST, { .i64 = 0 }, 0, 0, FLAGS, "vbr" },
         { "on",             "Use variable bit rate", 0, AV_OPT_TYPE_CONST, { .i64 = 1 }, 0, 0, FLAGS, "vbr" },
         { "constrained",    "Use constrained VBR",   0, AV_OPT_TYPE_CONST, { .i64 = 2 }, 0, 0, FLAGS, "vbr" },
+    { "mapping_family", "Channel Mapping Family",              OFFSET(mapping_family), AV_OPT_TYPE_INT,   { .i64 = -1 },   -1,  255,  FLAGS, "mapping_family" },
     { NULL },
 };
 
@@ -449,7 +563,6 @@ AVCodec ff_libopus_encoder = {
     .sample_fmts     = (const enum AVSampleFormat[]){ AV_SAMPLE_FMT_S16,
                                                       AV_SAMPLE_FMT_FLT,
                                                       AV_SAMPLE_FMT_NONE },
-    .channel_layouts = ff_vorbis_channel_layouts,
     .supported_samplerates = libopus_sample_rates,
     .priv_class      = &libopus_class,
     .defaults        = libopus_defaults,



More information about the ffmpeg-cvslog mailing list