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

Michael Graczyk mgraczyk at google.com
Sat Jun 11 07:39:23 CEST 2016


The libopus encoder has a parameter called "channel mapping family" which
is used to specify inter-channel relationships for the purpose of encoding.
I have added a new command line argument which makes it possible to forward
the mapping family parameter along to libopus.

With this parameter, it is possible to choose between encoding multichannel
audio streams as surround sound (with interchannel masking and channel
coupling) versus independent channels (no interchannel masking nor channel
coupling).

Example usage:

$ wget https://samples.ffmpeg.org/A-codecs/wavpcm/8_Channel_ID.wav -O in.wav

# Use the old behavior. Header contains layout, but no masking
$ ./ffmpeg -y -i in.wav -c:a opus -mapping_family -1 out.ogg

# Use libopus surround mode. Masking + automatic channel coupling
$ ./ffmpeg -y -i in.wav -c:a opus -mapping_family 1 out.ogg

# Use libopus with independent channels. No header info, no masking, no
coupling
$ ./ffmpeg -y -i in.wav -c:a opus -mapping_family 255 out.ogg

This patch also makes it possible to encode up to 254 channels with opus
using channel mapping family 255.

$ head -c 1728000 /dev/urandom > noise.raw
$ ./ffmpeg -y -f s16le -ar 48000 -ac 18 -i noise.raw -c:a opus
-mapping_family 255 out.opus


Questions:

1. I had to remove .channel_layouts form ff_libopus_encoder to allow the
encoder to accept streams with more than 8 channels. Is that the right way
to extend the encoder? Based on a discussion on #ffmpeg it seemed like
removing the .channel_layouts field would be the only way to allow more
than 16 channels.
2. Am I using AVFrame.data correctly? I recall reading somewhere that
channels after the eighth would be stored in extended_data, but from the
documentation it seems like that is only the case for planar data.


Thanks for reading,
Michael Graczyk
-------------- next part --------------
From 439972c08894480d5cfde62dc1bccf8eff80d7c6 Mon Sep 17 00:00:00 2001
From: Michael Graczyk <mgraczyk at google.com>
Date: Mon, 23 May 2016 12:49:54 -0700
Subject: [PATCH] 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 | 125 +++++++++++++++++++++++++++++++++---------------
 2 files changed, 93 insertions(+), 38 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 3f3e80d..fdaf567 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] = {
@@ -77,8 +79,10 @@ static const uint8_t libavcodec_libopus_channel_map[8][8] = {
     { 0, 1, 6, 7, 4, 5, 2, 3 },
 };
 
+
 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 +97,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 +163,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 +229,69 @@ 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);
+    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. */
+
+        /* 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;
+        }
+
+        /* 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,
                "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 +322,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]);
+                         opus->opts.mapping_family, libopus_channel_mapping);
 
     ff_af_queue_init(avctx, &opus->afq);
 
@@ -306,12 +336,26 @@ 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)
 {
     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;
@@ -320,7 +364,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
@@ -409,6 +458,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 },
 };
 
@@ -442,7 +492,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