[FFmpeg-devel] [PATCH] libopusenc: Add channel mapping family argument

Michael Graczyk mgraczyk at google.com
Wed Jun 15 03:49:13 CEST 2016


Michael,

Thanks for the input. I have refactored the patch into two patches. The
first refactors to make the second patch simpler. I have attached both new
patches.

As for the approach, I can see why this may not be an ideal approach. I
based this patch on a discussion I had on #ffmpeg-devel a month or so ago.
It seemed that the consensus then was that I should add a mapping_family
command line parameter as I have done in this patch. However, it was not
absolutely clear that this would be the best approach.

Do you have an alternative in mind? What do you mean by "passed through API
like side data"?

Thanks,
Michael
-------------- next part --------------
From 52c16ba12344f1c461a7e0b8484361020039e419 Mon Sep 17 00:00:00 2001
From: Michael Graczyk <mgraczyk at google.com>
Date: Tue, 14 Jun 2016 18:30:36 -0700
Subject: [PATCH 1/2] libopusenc: Refactor to simplify forthcoming
 mapping_family parameter

---
 libavcodec/libopusenc.c | 79 +++++++++++++++++++++++++++----------------------
 1 file changed, 43 insertions(+), 36 deletions(-)

diff --git a/libavcodec/libopusenc.c b/libavcodec/libopusenc.c
index 3f3e80d..c9b96ce 100644
--- a/libavcodec/libopusenc.c
+++ b/libavcodec/libopusenc.c
@@ -79,6 +79,7 @@ static const uint8_t libavcodec_libopus_channel_map[8][8] = {
 
 static void libopus_write_header(AVCodecContext *avctx, int stream_count,
                                  int coupled_stream_count,
+                                 int mapping_family,
                                  const uint8_t *channel_mapping)
 {
     uint8_t *p   = avctx->extradata;
@@ -93,7 +94,7 @@ static void libopus_write_header(AVCodecContext *avctx, int stream_count,
 
     /* Channel mapping */
     if (channels > 2) {
-        bytestream_put_byte(&p, channels <= 8 ? 1 : 255);
+        bytestream_put_byte(&p, mapping_family);
         bytestream_put_byte(&p, stream_count);
         bytestream_put_byte(&p, coupled_stream_count);
         bytestream_put_buffer(&p, channel_mapping, channels);
@@ -159,37 +160,11 @@ static int libopus_configure_encoder(AVCodecContext *avctx, OpusMSEncoder *enc,
 static av_cold int libopus_encode_init(AVCodecContext *avctx)
 {
     LibopusEncContext *opus = avctx->priv_data;
-    const uint8_t *channel_mapping;
     OpusMSEncoder *enc;
+    uint8_t libopus_channel_mapping[255];
     int ret = OPUS_OK;
     int coupled_stream_count, header_size, frame_size;
 
-    coupled_stream_count = opus_coupled_streams[avctx->channels - 1];
-    opus->stream_count   = avctx->channels - coupled_stream_count;
-    channel_mapping      = libavcodec_libopus_channel_map[avctx->channels - 1];
-
-    /* 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;
-    }
-    if (!avctx->bit_rate) {
-        /* Sane default copied from opusenc */
-        avctx->bit_rate = 64000 * opus->stream_count +
-                          32000 * coupled_stream_count;
-        av_log(avctx, AV_LOG_WARNING,
-               "No bit rate set. Defaulting to %"PRId64" bps.\n", (int64_t)avctx->bit_rate);
-    }
-
-    if (avctx->bit_rate < 500 || avctx->bit_rate > 256000 * avctx->channels) {
-        av_log(avctx, AV_LOG_ERROR, "The bit rate %"PRId64" bps is unsupported. "
-               "Please choose a value between 500 and %d.\n", (int64_t)avctx->bit_rate,
-               256000 * avctx->channels);
-        return AVERROR(EINVAL);
-    }
-
     frame_size = opus->opts.frame_duration * 48000 / 1000;
     switch (frame_size) {
     case 120:
@@ -251,17 +226,49 @@ static av_cold int libopus_encode_init(AVCodecContext *avctx)
         }
     }
 
-    enc = opus_multistream_encoder_create(avctx->sample_rate, avctx->channels,
-                                          opus->stream_count,
-                                          coupled_stream_count,
-                                          channel_mapping,
-                                          opus->opts.application, &ret);
+    /* 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;
+    }
+
+    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 (ret != OPUS_OK) {
         av_log(avctx, AV_LOG_ERROR,
                "Failed to create encoder: %s\n", opus_strerror(ret));
         return ff_opus_error_to_averror(ret);
     }
 
+    if (!avctx->bit_rate) {
+        /* Sane default copied from opusenc */
+        avctx->bit_rate = 64000 * opus->stream_count +
+                          32000 * coupled_stream_count;
+        av_log(avctx, AV_LOG_WARNING,
+               "No bit rate set. Defaulting to %"PRId64" bps.\n", (int64_t)avctx->bit_rate);
+    }
+
+    if (avctx->bit_rate < 500 || avctx->bit_rate > 256000 * avctx->channels) {
+        av_log(avctx, AV_LOG_ERROR, "The bit rate %"PRId64" bps is unsupported. "
+               "Please choose a value between 500 and %d.\n", (int64_t)avctx->bit_rate,
+               256000 * avctx->channels);
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
     ret = libopus_configure_encoder(avctx, enc, &opus->opts);
     if (ret != OPUS_OK) {
         ret = ff_opus_error_to_averror(ret);
@@ -292,7 +299,7 @@ static av_cold int libopus_encode_init(AVCodecContext *avctx)
                opus_strerror(ret));
 
     libopus_write_header(avctx, opus->stream_count, coupled_stream_count,
-                         opus_vorbis_channel_map[avctx->channels - 1]);
+                         avctx->channels <= 8 ? 1 : 255, libopus_channel_mapping);
 
     ff_af_queue_init(avctx, &opus->afq);
 
@@ -310,8 +317,8 @@ static int libopus_encode(AVCodecContext *avctx, AVPacket *avpkt,
                           const AVFrame *frame, int *got_packet_ptr)
 {
     LibopusEncContext *opus = avctx->priv_data;
-    const int sample_size   = avctx->channels *
-                              av_get_bytes_per_sample(avctx->sample_fmt);
+    const int bytes_per_sample = av_get_bytes_per_sample(avctx->sample_fmt);
+    const int sample_size      = avctx->channels * bytes_per_sample;
     uint8_t *audio;
     int ret;
     int discard_padding;
-- 
2.8.0.rc3.226.g39d4020

-------------- next part --------------
From e15dec10973668818c14b16c5030a43e1873952d Mon Sep 17 00:00:00 2001
From: Michael Graczyk <mgraczyk at google.com>
Date: Tue, 14 Jun 2016 18:33:15 -0700
Subject: [PATCH 2/2] 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 should be unchanged.
---
 doc/encoders.texi       |  6 ++++
 libavcodec/libopusenc.c | 83 ++++++++++++++++++++++++++++++++++++-------------
 2 files changed, 68 insertions(+), 21 deletions(-)

diff --git a/doc/encoders.texi b/doc/encoders.texi
index f38cad3..51ccb66 100644
--- a/doc/encoders.texi
+++ b/doc/encoders.texi
@@ -1184,6 +1184,12 @@ 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 is -1
+(determine channel mapping and layout from channel count). Other values include
+0 for stereo, 1 for surround sound, 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..dcb5af4 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] = {
@@ -226,26 +228,46 @@ 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;
-    }
-
-    coupled_stream_count = opus_coupled_streams[avctx->channels - 1];
-    opus->stream_count   = avctx->channels - coupled_stream_count;
+    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. */
 
-    memcpy(libopus_channel_mapping,
-           opus_vorbis_channel_map[avctx->channels - 1],
-           avctx->channels * sizeof(*libopus_channel_mapping));
+        /* FIXME: Opus can handle up to 255 channels. However, the default
+         * 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;
+        }
 
-    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);
+        /* Set the mapping family so that the value is correct in the header */
+        opus->opts.mapping_family = 1;
+        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);
+
+        /* Channels do not need to be reordered. */
+        opus->encoder_channel_map = NULL;
+    } else {
+        /* Use the newer multistream API. The encoder will set the channel
+         * mapping and coupled stream counts to is internal defaults and will
+         * use surround masking analysis to save bits. */
+        enc = opus_multistream_surround_encoder_create(
+            avctx->sample_rate, avctx->channels, opus->opts.mapping_family,
+            &opus->stream_count, &coupled_stream_count, libopus_channel_mapping,
+            opus->opts.application, &ret);
+
+        /* Channels must be reordered to match opus mapping. */
+        opus->encoder_channel_map = ff_vorbis_channel_layout_offsets[avctx->channels - 1];
+    }
 
     if (ret != OPUS_OK) {
         av_log(avctx, AV_LOG_ERROR,
@@ -299,7 +321,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);
+                         opus->opts.mapping_family, libopus_channel_mapping);
 
     ff_af_queue_init(avctx, &opus->afq);
 
@@ -313,6 +335,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 +363,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 +457,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 +491,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,
-- 
2.8.0.rc3.226.g39d4020



More information about the ffmpeg-devel mailing list