diff mbox

[FFmpeg-devel] Support for Ambisonics and OpusProjection* API.

Message ID CABQ9Dcsmbq+BLwr8U=CCbF6d_3OKLLf8cg188HkVunQfXWerYQ@mail.gmail.com
State Superseded
Headers show

Commit Message

Drew Allen March 28, 2018, 9:59 p.m. UTC
Hello all,

My name is Andrew Allen and I'm a contributor to Opus, supporting new
channel mappings 2 and 3 for ambisonics compression. I've attached a patch
to support the new OpusProjectionEncoder and OpusProjectionDecoder APIs for
handling the new channel mapping 3 in OPUS.

Please let me know of any changes I should make or if there are any
questions I can help answer.

Cheers,
Drew

Comments

Drew Allen April 2, 2018, 4:13 p.m. UTC | #1
Friendly ping to allow this to push to the member list, please.

On Wed, Mar 28, 2018 at 2:58 PM Drew Allen <bitllama@google.com> wrote:

> Hello all,
>
> My name is Andrew Allen and I'm a contributor to Opus, supporting new
> channel mappings 2 and 3 for ambisonics compression. I've attached a patch
> to support the new OpusProjectionEncoder and OpusProjectionDecoder APIs for
> handling the new channel mapping 3 in OPUS.
>
> Please let me know of any changes I should make or if there are any
> questions I can help answer.
>
> Cheers,
> Drew
>
Drew Allen April 9, 2018, 5:57 p.m. UTC | #2
Friendly weekly ping about this patch.

Can someone please let me know if I need to do anything more to submit this
patch? Thanks!

On Mon, Apr 2, 2018 at 9:13 AM Drew Allen <bitllama@google.com> wrote:

> Friendly ping to allow this to push to the member list, please.
>
> On Wed, Mar 28, 2018 at 2:58 PM Drew Allen <bitllama@google.com> wrote:
>
>> Hello all,
>>
>> My name is Andrew Allen and I'm a contributor to Opus, supporting new
>> channel mappings 2 and 3 for ambisonics compression. I've attached a patch
>> to support the new OpusProjectionEncoder and OpusProjectionDecoder APIs for
>> handling the new channel mapping 3 in OPUS.
>>
>> Please let me know of any changes I should make or if there are any
>> questions I can help answer.
>>
>> Cheers,
>> Drew
>>
>
Michael Niedermayer April 11, 2018, 1:10 a.m. UTC | #3
On Wed, Mar 28, 2018 at 09:59:00PM +0000, Drew Allen wrote:
> Hello all,
> 
> My name is Andrew Allen and I'm a contributor to Opus, supporting new
> channel mappings 2 and 3 for ambisonics compression. I've attached a patch
> to support the new OpusProjectionEncoder and OpusProjectionDecoder APIs for
> handling the new channel mapping 3 in OPUS.
> 
> Please let me know of any changes I should make or if there are any
> questions I can help answer.
> 
> Cheers,
> Drew

>  libopusdec.c |  160 ++++++++++++++++++++++++++++++------
>  libopusenc.c |  257 ++++++++++++++++++++++++++++++++++++++++++++++++++---------
>  opus.c       |   18 +---
>  3 files changed, 358 insertions(+), 77 deletions(-)
> 1f1fae1e93478880b50cd5432bb7479bc816f659  ffmpeg-Support-Ambisonics.patch
> From a897b4d9b1ebe9031b98a9e507c28355ef9a44ba Mon Sep 17 00:00:00 2001
> From: Andrew Allen <bitllama@google.com>
> Date: Wed, 28 Mar 2018 14:48:46 -0700
> Subject: [PATCH] Support for Ambisonics and OpusProjection* API.

> 
> ---
>  libavcodec/libopusdec.c | 160 ++++++++++++++++++++-----
>  libavcodec/libopusenc.c | 257 ++++++++++++++++++++++++++++++++++------
>  libavcodec/opus.c       |  18 ++-
>  3 files changed, 358 insertions(+), 77 deletions(-)
> 
> diff --git a/libavcodec/libopusdec.c b/libavcodec/libopusdec.c
> index 3d2ee5b61b..d4b5a459b9 100644
> --- a/libavcodec/libopusdec.c
> +++ b/libavcodec/libopusdec.c
> @@ -21,6 +21,9 @@
>  
>  #include <opus.h>
>  #include <opus_multistream.h>
> +#ifdef OPUS_HAVE_OPUS_PROJECTION_H
> +#include <opus_projection.h>
> +#endif
>  
>  #include "libavutil/internal.h"
>  #include "libavutil/intreadwrite.h"
> @@ -33,9 +36,93 @@
>  #include "mathops.h"
>  #include "libopus.h"
>  
> +typedef struct OpusGenericDecoder {
> +#ifdef OPUS_HAVE_OPUS_PROJECTION_H
> +    OpusProjectionDecoder *pr;
> +#endif
> +    OpusMSDecoder *ms;
> +} OpusGenericDecoder;
> +
> +static int libopus_generic_decoder_init(OpusGenericDecoder *st, int Fs,
> +                                        int channels, int nb_streams,
> +                                        int nb_coupled, uint8_t *mapping,
> +                                        uint8_t *dmatrix) {
> +    int err;
> +    if (dmatrix != NULL) {
> +#ifdef OPUS_HAVE_OPUS_PROJECTION_H
> +        opus_int32 size;
> +        size = 2 * channels * (nb_streams + nb_coupled);
> +        st->pr = opus_projection_decoder_create(Fs, channels, nb_streams,
> +            nb_coupled, dmatrix, size, &err);
> +#else
> +        err = OPUS_UNIMPLEMENTED;
> +#endif
> +        st->ms = NULL;
> +        return err;
> +    }
> +#ifdef OPUS_HAVE_OPUS_PROJECTION_H
> +    st->pr = NULL;
> +#endif
> +    st->ms = opus_multistream_decoder_create(Fs, channels, nb_streams,
> +        nb_coupled, mapping, &err);
> +    return err;
> +}
> +
> +static void libopus_generic_decoder_cleanup(OpusGenericDecoder *st)
> +{

> +#ifdef OPUS_HAVE_OPUS_PROJECTION_H
> +    if (st->pr) opus_projection_decoder_destroy(st->pr);
> +#endif
> +    if (st->ms) opus_multistream_decoder_destroy(st->ms);
> +}
> +
> +static int libopus_generic_decode(OpusGenericDecoder *st,
> +        const unsigned char *data, opus_int32 len, opus_int16 *pcm,
> +        int frame_size, int decode_fec) {
> +    int ret;
> +
> +#if defined(OPUS_HAVE_OPUS_PROJECTION_H)

inconsistent comapred to previous #ifdef


> +    if (st->pr){
> +        ret=opus_projection_decode(st->pr, data, len, pcm, frame_size,
> +            decode_fec);
> +        return ret;
> +    }
> +#endif
> +    ret=opus_multistream_decode(st->ms, data, len, pcm, frame_size,
> +        decode_fec);
> +    return ret;
> +}
> +
> +static int libopus_generic_decode_float(OpusGenericDecoder *st,
> +        const unsigned char *data, opus_int32 len, float *pcm, int frame_size,
> +        int decode_fec) {
> +    int ret;
> +
> +#if defined(OPUS_HAVE_OPUS_PROJECTION_H)
> +    if (st->pr){
> +        ret=opus_projection_decode_float(st->pr, data, len, pcm, frame_size,
> +            decode_fec);
> +        return ret;
> +    }
> +#endif
> +    ret=opus_multistream_decode_float(st->ms, data, len, pcm, frame_size,
> +        decode_fec);
> +    return ret;
> +}
> +
> +#ifdef OPUS_HAVE_OPUS_PROJECTION_H
> +# define libopus_generic_decoder_ctl(st, request) \
> +    ((st)->pr != NULL ? \
> +    opus_projection_decoder_ctl((st)->pr, request) : \
> +    opus_multistream_decoder_ctl((st)->ms, request))
> +#else
> +# define libopus_generic_decoder_ctl(st, request) \
> +    opus_multistream_decoder_ctl((st)->ms, request)
> +#endif
> +
>  struct libopus_context {
>      AVClass *class;
> -    OpusMSDecoder *dec;
> +    OpusGenericDecoder dec;
>      int pre_skip;
>  #ifndef OPUS_SET_GAIN
>      union { int i; double d; } gain;
> @@ -46,12 +133,17 @@ struct libopus_context {
>  };
>  
>  #define OPUS_HEAD_SIZE 19
> +#ifdef OPUS_HAVE_OPUS_PROJECTION_H
> +# define OPUS_MAX_CHANNELS 18
> +#else
> +# define OPUS_MAX_CHANNELS 8
> +#endif
>  
>  static av_cold int libopus_decode_init(AVCodecContext *avc)
>  {
>      struct libopus_context *opus = avc->priv_data;
>      int ret, channel_map = 0, gain_db = 0, nb_streams, nb_coupled;
> -    uint8_t mapping_arr[8] = { 0, 1 }, *mapping;
> +    uint8_t mapping_arr[OPUS_MAX_CHANNELS] = { 0, 1 }, *mapping, *dmatrix = NULL;
>  
>      avc->channels = avc->extradata_size >= 10 ? avc->extradata[9] : (avc->channels == 1) ? 1 : 2;
>      if (avc->channels <= 0) {

> @@ -74,7 +166,21 @@ static av_cold int libopus_decode_init(AVCodecContext *avc)
>          nb_coupled = avc->extradata[OPUS_HEAD_SIZE + 1];
>          if (nb_streams + nb_coupled != avc->channels)
>              av_log(avc, AV_LOG_WARNING, "Inconsistent channel mapping.\n");
> -        mapping = avc->extradata + OPUS_HEAD_SIZE + 2;
> +        if (channel_map == 3) {
> +            int ch;
> +            if (avc->extradata_size >= OPUS_HEAD_SIZE + 2 + 2 * avc->channels * (nb_streams + nb_coupled)) {
> +                dmatrix =avc->extradata + OPUS_HEAD_SIZE + 2;
> +            } else {
> +                av_log(avc, AV_LOG_ERROR,
> +                    "Demixing matrix not present.\n");
> +                return AVERROR(EINVAL);

AVERROR_INVALIDDATA, extradata is part of the input bitstream and thats
the error type we generally use for it
unless iam missing something here

[...]
> diff --git a/libavcodec/libopusenc.c b/libavcodec/libopusenc.c
> index 4ae81b0bb2..729d86d2d7 100644
> --- a/libavcodec/libopusenc.c
> +++ b/libavcodec/libopusenc.c
> @@ -21,15 +21,139 @@
>  
>  #include <opus.h>
>  #include <opus_multistream.h>
> +#ifdef OPUS_HAVE_OPUS_PROJECTION_H
> +#include <opus_projection.h>
> +#endif
>  
>  #include "libavutil/opt.h"
>  #include "avcodec.h"
>  #include "bytestream.h"
>  #include "internal.h"
>  #include "libopus.h"
> +#include "mathops.h"
>  #include "vorbis.h"
>  #include "audio_frame_queue.h"
>  
> +typedef struct OpusGenericEncoder {
> +#ifdef OPUS_HAVE_OPUS_PROJECTION_H
> +    OpusProjectionEncoder *pr;
> +#endif
> +    OpusMSEncoder *ms;
> +} OpusGenericEncoder;
> +
> +static int libopus_generic_encoder_surround_init(OpusGenericEncoder *st, int Fs,
> +                                                 int channels,
> +                                                 int mapping_family,
> +                                                 int *nb_streams,
> +                                                 int *nb_coupled,
> +                                                 unsigned char *stream_map,
> +                                                 int application)
> +{
> +    int ret;
> +    if (mapping_family == 3) {
> +#ifdef OPUS_HAVE_OPUS_PROJECTION_H
> +        int ci;
> +        st->pr = opus_projection_ambisonics_encoder_create(
> +            Fs, channels, mapping_family, nb_streams, nb_coupled,
> +            application, &ret);

> +        for (ci = 0; ci < channels; ci++) stream_map[ci] = ci;

this is easier to read with a \n


> +#else
> +        ret = OPUS_UNIMPLEMENTED;
> +#endif
> +        st->ms = NULL;
> +        return ret;

the NULL init may be unneeded. The struct is memset 0 at the begin
so unless there is a patch that could set this to something else
it can be assumed to be still NULL

I would also suggest to use a differnt context name from
"st", as that is very commonly used for AVStream so it could
confuse people

[...]
> +
>  typedef struct LibopusEncOpts {
>      int vbr;
>      int application;
> @@ -46,7 +170,7 @@ typedef struct LibopusEncOpts {
>  
>  typedef struct LibopusEncContext {
>      AVClass *class;
> -    OpusMSEncoder *enc;
> +    OpusGenericEncoder enc;
>      int stream_count;
>      uint8_t *samples;
>      LibopusEncOpts opts;
> @@ -85,28 +209,68 @@ 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)
> +                                 const uint8_t *channel_mapping,
> +                                 OpusGenericEncoder *enc)
>  {
>      uint8_t *p   = avctx->extradata;
>      int channels = avctx->channels;
> +    int gain = 0;
>  
>      bytestream_put_buffer(&p, "OpusHead", 8);
>      bytestream_put_byte(&p, 1); /* Version */
>      bytestream_put_byte(&p, channels);
>      bytestream_put_le16(&p, avctx->initial_padding); /* Lookahead samples at 48kHz */
>      bytestream_put_le32(&p, avctx->sample_rate); /* Original sample rate */
> -    bytestream_put_le16(&p, 0); /* Gain of 0dB is recommended. */
> +    if (mapping_family == 3) {
> +#ifdef OPUS_HAVE_OPUS_PROJECTION_H
> +        int ret;
> +        ret = libopus_generic_encoder_ctl(enc,
> +                                          OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN(&gain));
> +        if (ret != OPUS_OK) {
> +            av_log(avctx, AV_LOG_ERROR,
> +                   "Unable to write header, demixing matrix gain not found.\n");
> +            return;
> +        }
> +#endif
> +    }
> +    bytestream_put_le16(&p, gain); /* Gain of 0dB is recommended. */
>  
>      /* Channel mapping */
>      bytestream_put_byte(&p, mapping_family);
> -    if (mapping_family != 0) {
> +    if (mapping_family == 3) {
> +        int ret;
> +        int32_t size;
> +        size = 2 * channels * (stream_count + coupled_stream_count);
> +        bytestream_put_byte(&p, stream_count);
> +        bytestream_put_byte(&p, coupled_stream_count);
> +        bytestream_put_byte(&p, stream_count);
> +#ifdef OPUS_HAVE_OPUS_PROJECTION_H
> +        ret = libopus_generic_encoder_ctl(enc,
> +                                          OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE(&size));
> +        if (ret != OPUS_OK) {
> +            av_log(avctx, AV_LOG_ERROR,
> +                   "Unable to write header, demixing matrix size not found.\n");
> +            return;
> +        }
> +        ret = libopus_generic_encoder_ctl(enc,
> +                                          OPUS_PROJECTION_GET_DEMIXING_MATRIX(p, size));
> +        if (ret != OPUS_OK) {
> +            av_log(avctx, AV_LOG_ERROR,
> +                   "Unable to write header, demixing matrix not found.\n");
> +            return;
> +        }
> +        (*(&p)) += size;

I think with the increasing complexity of writing extradata
it would make sense to before or after this patch switch to 
bytestream2_put* or something else that does check writes
against the output buffer bounds.
(this change should be in a seperate patch though)

thx

[...]
Rostislav Pehlivanov April 11, 2018, 1:01 p.m. UTC | #4
On 28 March 2018 at 22:59, Drew Allen <bitllama-at-google.com@ffmpeg.org>
wrote:

> Hello all,
>
> My name is Andrew Allen and I'm a contributor to Opus, supporting new
> channel mappings 2 and 3 for ambisonics compression. I've attached a patch
> to support the new OpusProjectionEncoder and OpusProjectionDecoder APIs for
> handling the new channel mapping 3 in OPUS.
>
> Please let me know of any changes I should make or if there are any
> questions I can help answer.
>
> Cheers,
> Drew
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
>
You don't set the channel layout to anything. We have support for some
ambisonics formats. Use them. How else would users know what is what? Check
the number of channels?
Also what about mapping families 240(?) -> 254?
I thought they were reserved for ambisonics (very recently too, though they
don't define any specific layouts :/).

IMO this might need a new channel layout API with support for ambisonics
because things are messy there as-is.
wm4 April 12, 2018, 2:33 p.m. UTC | #5
On Wed, 11 Apr 2018 14:01:08 +0100
Rostislav Pehlivanov <atomnuker@gmail.com> wrote:

> On 28 March 2018 at 22:59, Drew Allen <bitllama-at-google.com@ffmpeg.org>
> wrote:
> 
> > Hello all,
> >
> > My name is Andrew Allen and I'm a contributor to Opus, supporting new
> > channel mappings 2 and 3 for ambisonics compression. I've attached a patch
> > to support the new OpusProjectionEncoder and OpusProjectionDecoder APIs for
> > handling the new channel mapping 3 in OPUS.
> >
> > Please let me know of any changes I should make or if there are any
> > questions I can help answer.
> >
> > Cheers,
> > Drew
> >
> > _______________________________________________
> > ffmpeg-devel mailing list
> > ffmpeg-devel@ffmpeg.org
> > http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
> >
> >  
> You don't set the channel layout to anything. We have support for some
> ambisonics formats. Use them. How else would users know what is what? Check
> the number of channels?
> Also what about mapping families 240(?) -> 254?
> I thought they were reserved for ambisonics (very recently too, though they
> don't define any specific layouts :/).
> 
> IMO this might need a new channel layout API with support for ambisonics
> because things are messy there as-is.

Libav has a patchset, but it's stalled.
diff mbox

Patch

From a897b4d9b1ebe9031b98a9e507c28355ef9a44ba Mon Sep 17 00:00:00 2001
From: Andrew Allen <bitllama@google.com>
Date: Wed, 28 Mar 2018 14:48:46 -0700
Subject: [PATCH] Support for Ambisonics and OpusProjection* API.

---
 libavcodec/libopusdec.c | 160 ++++++++++++++++++++-----
 libavcodec/libopusenc.c | 257 ++++++++++++++++++++++++++++++++++------
 libavcodec/opus.c       |  18 ++-
 3 files changed, 358 insertions(+), 77 deletions(-)

diff --git a/libavcodec/libopusdec.c b/libavcodec/libopusdec.c
index 3d2ee5b61b..d4b5a459b9 100644
--- a/libavcodec/libopusdec.c
+++ b/libavcodec/libopusdec.c
@@ -21,6 +21,9 @@ 
 
 #include <opus.h>
 #include <opus_multistream.h>
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+#include <opus_projection.h>
+#endif
 
 #include "libavutil/internal.h"
 #include "libavutil/intreadwrite.h"
@@ -33,9 +36,93 @@ 
 #include "mathops.h"
 #include "libopus.h"
 
+typedef struct OpusGenericDecoder {
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+    OpusProjectionDecoder *pr;
+#endif
+    OpusMSDecoder *ms;
+} OpusGenericDecoder;
+
+static int libopus_generic_decoder_init(OpusGenericDecoder *st, int Fs,
+                                        int channels, int nb_streams,
+                                        int nb_coupled, uint8_t *mapping,
+                                        uint8_t *dmatrix) {
+    int err;
+    if (dmatrix != NULL) {
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+        opus_int32 size;
+        size = 2 * channels * (nb_streams + nb_coupled);
+        st->pr = opus_projection_decoder_create(Fs, channels, nb_streams,
+            nb_coupled, dmatrix, size, &err);
+#else
+        err = OPUS_UNIMPLEMENTED;
+#endif
+        st->ms = NULL;
+        return err;
+    }
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+    st->pr = NULL;
+#endif
+    st->ms = opus_multistream_decoder_create(Fs, channels, nb_streams,
+        nb_coupled, mapping, &err);
+    return err;
+}
+
+static void libopus_generic_decoder_cleanup(OpusGenericDecoder *st)
+{
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+    if (st->pr) opus_projection_decoder_destroy(st->pr);
+#endif
+    if (st->ms) opus_multistream_decoder_destroy(st->ms);
+}
+
+static int libopus_generic_decode(OpusGenericDecoder *st,
+        const unsigned char *data, opus_int32 len, opus_int16 *pcm,
+        int frame_size, int decode_fec) {
+    int ret;
+
+#if defined(OPUS_HAVE_OPUS_PROJECTION_H)
+    if (st->pr){
+        ret=opus_projection_decode(st->pr, data, len, pcm, frame_size,
+            decode_fec);
+        return ret;
+    }
+#endif
+    ret=opus_multistream_decode(st->ms, data, len, pcm, frame_size,
+        decode_fec);
+    return ret;
+}
+
+static int libopus_generic_decode_float(OpusGenericDecoder *st,
+        const unsigned char *data, opus_int32 len, float *pcm, int frame_size,
+        int decode_fec) {
+    int ret;
+
+#if defined(OPUS_HAVE_OPUS_PROJECTION_H)
+    if (st->pr){
+        ret=opus_projection_decode_float(st->pr, data, len, pcm, frame_size,
+            decode_fec);
+        return ret;
+    }
+#endif
+    ret=opus_multistream_decode_float(st->ms, data, len, pcm, frame_size,
+        decode_fec);
+    return ret;
+}
+
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+# define libopus_generic_decoder_ctl(st, request) \
+    ((st)->pr != NULL ? \
+    opus_projection_decoder_ctl((st)->pr, request) : \
+    opus_multistream_decoder_ctl((st)->ms, request))
+#else
+# define libopus_generic_decoder_ctl(st, request) \
+    opus_multistream_decoder_ctl((st)->ms, request)
+#endif
+
 struct libopus_context {
     AVClass *class;
-    OpusMSDecoder *dec;
+    OpusGenericDecoder dec;
     int pre_skip;
 #ifndef OPUS_SET_GAIN
     union { int i; double d; } gain;
@@ -46,12 +133,17 @@  struct libopus_context {
 };
 
 #define OPUS_HEAD_SIZE 19
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+# define OPUS_MAX_CHANNELS 18
+#else
+# define OPUS_MAX_CHANNELS 8
+#endif
 
 static av_cold int libopus_decode_init(AVCodecContext *avc)
 {
     struct libopus_context *opus = avc->priv_data;
     int ret, channel_map = 0, gain_db = 0, nb_streams, nb_coupled;
-    uint8_t mapping_arr[8] = { 0, 1 }, *mapping;
+    uint8_t mapping_arr[OPUS_MAX_CHANNELS] = { 0, 1 }, *mapping, *dmatrix = NULL;
 
     avc->channels = avc->extradata_size >= 10 ? avc->extradata[9] : (avc->channels == 1) ? 1 : 2;
     if (avc->channels <= 0) {
@@ -74,7 +166,21 @@  static av_cold int libopus_decode_init(AVCodecContext *avc)
         nb_coupled = avc->extradata[OPUS_HEAD_SIZE + 1];
         if (nb_streams + nb_coupled != avc->channels)
             av_log(avc, AV_LOG_WARNING, "Inconsistent channel mapping.\n");
-        mapping = avc->extradata + OPUS_HEAD_SIZE + 2;
+        if (channel_map == 3) {
+            int ch;
+            if (avc->extradata_size >= OPUS_HEAD_SIZE + 2 + 2 * avc->channels * (nb_streams + nb_coupled)) {
+                dmatrix =avc->extradata + OPUS_HEAD_SIZE + 2;
+            } else {
+                av_log(avc, AV_LOG_ERROR,
+                    "Demixing matrix not present.\n");
+                return AVERROR(EINVAL);
+            }
+            for (ch = 0; ch < avc->channels; ch++)
+                mapping_arr[ch] = ch;
+            mapping = mapping_arr;
+        } else {
+            mapping = avc->extradata + OPUS_HEAD_SIZE + 2;
+        }
     } else {
         if (avc->channels > 2 || channel_map) {
             av_log(avc, AV_LOG_ERROR,
@@ -98,18 +204,15 @@  static av_cold int libopus_decode_init(AVCodecContext *avc)
                 mapping_arr[ch] = mapping[vorbis_offset[ch]];
             mapping = mapping_arr;
         }
-    } else if (channel_map == 2) {
-        int ambisonic_order = ff_sqrt(avc->channels) - 1;
-        if (avc->channels != (ambisonic_order + 1) * (ambisonic_order + 1) &&
-            avc->channels != (ambisonic_order + 1) * (ambisonic_order + 1) + 2) {
+    } else if (channel_map == 2 || channel_map == 3) {
+        int order_plus_one = ff_sqrt(avc->channels);
+        int nondiegetic_channels = avc->channels - order_plus_one * order_plus_one;
+        if (order_plus_one < 1 || order_plus_one > 15 ||
+           (nondiegetic_channels != 0 && nondiegetic_channels != 2)) {
             av_log(avc, AV_LOG_ERROR,
-                   "Channel mapping 2 is only specified for channel counts"
-                   " which can be written as (n + 1)^2 or (n + 2)^2 + 2"
-                   " for nonnegative integer n\n");
-            return AVERROR_INVALIDDATA;
-        }
-        if (avc->channels > 227) {
-            av_log(avc, AV_LOG_ERROR, "Too many channels\n");
+                "This channel mapping is only specified for channel counts"
+                " which can be written as (n + 1)^2 + 2j, where n is a"
+                " non-negative integar from 0 to 14 and j is either 0 or 1.\n");
             return AVERROR_INVALIDDATA;
         }
         avc->channel_layout = 0;
@@ -117,17 +220,16 @@  static av_cold int libopus_decode_init(AVCodecContext *avc)
         avc->channel_layout = 0;
     }
 
-    opus->dec = opus_multistream_decoder_create(avc->sample_rate, avc->channels,
-                                                nb_streams, nb_coupled,
-                                                mapping, &ret);
-    if (!opus->dec) {
+    ret = libopus_generic_decoder_init(&opus->dec, avc->sample_rate, avc->channels,
+                                       nb_streams, nb_coupled, mapping, dmatrix);
+    if (ret != OPUS_OK) {
         av_log(avc, AV_LOG_ERROR, "Unable to create decoder: %s\n",
                opus_strerror(ret));
         return ff_opus_error_to_averror(ret);
     }
 
 #ifdef OPUS_SET_GAIN
-    ret = opus_multistream_decoder_ctl(opus->dec, OPUS_SET_GAIN(gain_db));
+    ret = libopus_generic_decoder_ctl(&opus->dec, OPUS_SET_GAIN(gain_db));
     if (ret != OPUS_OK)
         av_log(avc, AV_LOG_WARNING, "Failed to set gain: %s\n",
                opus_strerror(ret));
@@ -142,8 +244,8 @@  static av_cold int libopus_decode_init(AVCodecContext *avc)
 #endif
 
 #ifdef OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST
-    ret = opus_multistream_decoder_ctl(opus->dec,
-                                       OPUS_SET_PHASE_INVERSION_DISABLED(!opus->apply_phase_inv));
+    ret = libopus_generic_decoder_ctl(&opus->dec,
+                                      OPUS_SET_PHASE_INVERSION_DISABLED(!opus->apply_phase_inv));
     if (ret != OPUS_OK)
         av_log(avc, AV_LOG_WARNING,
                "Unable to set phase inversion: %s\n",
@@ -160,7 +262,7 @@  static av_cold int libopus_decode_close(AVCodecContext *avc)
 {
     struct libopus_context *opus = avc->priv_data;
 
-    opus_multistream_decoder_destroy(opus->dec);
+    libopus_generic_decoder_cleanup(&opus->dec);
     return 0;
 }
 
@@ -178,13 +280,13 @@  static int libopus_decode(AVCodecContext *avc, void *data,
         return ret;
 
     if (avc->sample_fmt == AV_SAMPLE_FMT_S16)
-        nb_samples = opus_multistream_decode(opus->dec, pkt->data, pkt->size,
-                                             (opus_int16 *)frame->data[0],
-                                             frame->nb_samples, 0);
+        nb_samples = libopus_generic_decode(&opus->dec, pkt->data, pkt->size,
+                                            (opus_int16 *)frame->data[0],
+                                            frame->nb_samples, 0);
     else
-        nb_samples = opus_multistream_decode_float(opus->dec, pkt->data, pkt->size,
-                                                   (float *)frame->data[0],
-                                                   frame->nb_samples, 0);
+        nb_samples = libopus_generic_decode_float(&opus->dec, pkt->data, pkt->size,
+                                                  (float *)frame->data[0],
+                                                  frame->nb_samples, 0);
 
     if (nb_samples < 0) {
         av_log(avc, AV_LOG_ERROR, "Decoding error: %s\n",
@@ -217,7 +319,7 @@  static void libopus_flush(AVCodecContext *avc)
 {
     struct libopus_context *opus = avc->priv_data;
 
-    opus_multistream_decoder_ctl(opus->dec, OPUS_RESET_STATE);
+    libopus_generic_decoder_ctl(&opus->dec, OPUS_RESET_STATE);
     /* The stream can have been extracted by a tool that is not Opus-aware.
        Therefore, any packet can become the first of the stream. */
     avc->internal->skip_samples = opus->pre_skip;
diff --git a/libavcodec/libopusenc.c b/libavcodec/libopusenc.c
index 4ae81b0bb2..729d86d2d7 100644
--- a/libavcodec/libopusenc.c
+++ b/libavcodec/libopusenc.c
@@ -21,15 +21,139 @@ 
 
 #include <opus.h>
 #include <opus_multistream.h>
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+#include <opus_projection.h>
+#endif
 
 #include "libavutil/opt.h"
 #include "avcodec.h"
 #include "bytestream.h"
 #include "internal.h"
 #include "libopus.h"
+#include "mathops.h"
 #include "vorbis.h"
 #include "audio_frame_queue.h"
 
+typedef struct OpusGenericEncoder {
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+    OpusProjectionEncoder *pr;
+#endif
+    OpusMSEncoder *ms;
+} OpusGenericEncoder;
+
+static int libopus_generic_encoder_surround_init(OpusGenericEncoder *st, int Fs,
+                                                 int channels,
+                                                 int mapping_family,
+                                                 int *nb_streams,
+                                                 int *nb_coupled,
+                                                 unsigned char *stream_map,
+                                                 int application)
+{
+    int ret;
+    if (mapping_family == 3) {
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+        int ci;
+        st->pr = opus_projection_ambisonics_encoder_create(
+            Fs, channels, mapping_family, nb_streams, nb_coupled,
+            application, &ret);
+        for (ci = 0; ci < channels; ci++) stream_map[ci] = ci;
+#else
+        ret = OPUS_UNIMPLEMENTED;
+#endif
+        st->ms = NULL;
+        return ret;
+    }
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+    st->pr = NULL;
+#endif
+    st->ms = opus_multistream_surround_encoder_create(
+        Fs, channels, mapping_family, nb_streams, nb_coupled, stream_map,
+        application, &ret);
+    return ret;
+}
+
+static int libopus_generic_encoder_init(OpusGenericEncoder *st, int Fs,
+                                        int channels, int streams,
+                                        int coupled_streams,
+                                        const unsigned char *mapping,
+                                        int application)
+{
+    int ret;
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+    st->pr = NULL;
+#endif
+    st->ms = opus_multistream_encoder_create(Fs, channels, streams,
+        coupled_streams, mapping, application, &ret);
+    return ret;
+}
+
+static int libopus_generic_encode(OpusGenericEncoder *st, const opus_int16 *pcm,
+                                  int frame_size, unsigned char *data,
+                                  opus_int32 max_data_bytes)
+{
+    int ret;
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+    if (st->pr) {
+        ret = opus_projection_encode(st->pr, pcm, frame_size, data,
+            max_data_bytes);
+        return ret;
+    }
+#endif
+    ret = opus_multistream_encode(st->ms, pcm, frame_size, data,
+        max_data_bytes);
+    return ret;
+}
+
+static int libopus_generic_encode_float(OpusGenericEncoder *st,
+                                        const float *pcm,
+                                        int frame_size, unsigned char *data,
+                                        opus_int32 max_data_bytes)
+{
+    int ret;
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+    if (st->pr) {
+        ret = opus_projection_encode_float(st->pr, pcm, frame_size, data,
+                                           max_data_bytes);
+        return ret;
+    }
+#endif
+    ret = opus_multistream_encode_float(st->ms, pcm, frame_size, data,
+                                        max_data_bytes);
+    return ret;
+}
+
+static void libous_generic_encoder_cleanup(OpusGenericEncoder *st)
+{
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+    if (st->pr) opus_projection_encoder_destroy(st->pr);
+#endif
+    if (st->ms) opus_multistream_encoder_destroy(st->ms);
+}
+
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+# define libopus_generic_encoder_ctl(st, request) \
+    ((st)->pr != NULL ? \
+    opus_projection_encoder_ctl((st)->pr, request) : \
+    opus_multistream_encoder_ctl((st)->ms, request))
+#else
+# define libopus_generic_encoder_ctl(st, request) \
+    opus_multistream_encoder_ctl((st)->ms, request)
+#endif
+
+static int libopus_generic_encoder_get_header_size(int mapping_family,
+                                                   int channels, int streams,
+                                                   int coupled_streams)
+{
+    int size = 19;
+    if (mapping_family == 1 || mapping_family == 2 || mapping_family == 255) {
+        return size + 2 + channels;
+    }
+    else if (mapping_family == 3) {
+        return size + 2 + 2 * channels * (streams + coupled_streams);
+    }
+    return size;
+}
+
 typedef struct LibopusEncOpts {
     int vbr;
     int application;
@@ -46,7 +170,7 @@  typedef struct LibopusEncOpts {
 
 typedef struct LibopusEncContext {
     AVClass *class;
-    OpusMSEncoder *enc;
+    OpusGenericEncoder enc;
     int stream_count;
     uint8_t *samples;
     LibopusEncOpts opts;
@@ -85,28 +209,68 @@  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)
+                                 const uint8_t *channel_mapping,
+                                 OpusGenericEncoder *enc)
 {
     uint8_t *p   = avctx->extradata;
     int channels = avctx->channels;
+    int gain = 0;
 
     bytestream_put_buffer(&p, "OpusHead", 8);
     bytestream_put_byte(&p, 1); /* Version */
     bytestream_put_byte(&p, channels);
     bytestream_put_le16(&p, avctx->initial_padding); /* Lookahead samples at 48kHz */
     bytestream_put_le32(&p, avctx->sample_rate); /* Original sample rate */
-    bytestream_put_le16(&p, 0); /* Gain of 0dB is recommended. */
+    if (mapping_family == 3) {
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+        int ret;
+        ret = libopus_generic_encoder_ctl(enc,
+                                          OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN(&gain));
+        if (ret != OPUS_OK) {
+            av_log(avctx, AV_LOG_ERROR,
+                   "Unable to write header, demixing matrix gain not found.\n");
+            return;
+        }
+#endif
+    }
+    bytestream_put_le16(&p, gain); /* Gain of 0dB is recommended. */
 
     /* Channel mapping */
     bytestream_put_byte(&p, mapping_family);
-    if (mapping_family != 0) {
+    if (mapping_family == 3) {
+        int ret;
+        int32_t size;
+        size = 2 * channels * (stream_count + coupled_stream_count);
+        bytestream_put_byte(&p, stream_count);
+        bytestream_put_byte(&p, coupled_stream_count);
+        bytestream_put_byte(&p, stream_count);
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+        ret = libopus_generic_encoder_ctl(enc,
+                                          OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE(&size));
+        if (ret != OPUS_OK) {
+            av_log(avctx, AV_LOG_ERROR,
+                   "Unable to write header, demixing matrix size not found.\n");
+            return;
+        }
+        ret = libopus_generic_encoder_ctl(enc,
+                                          OPUS_PROJECTION_GET_DEMIXING_MATRIX(p, size));
+        if (ret != OPUS_OK) {
+            av_log(avctx, AV_LOG_ERROR,
+                   "Unable to write header, demixing matrix not found.\n");
+            return;
+        }
+        (*(&p)) += size;
+#endif
+    }
+    else if (mapping_family != 0) {
         bytestream_put_byte(&p, stream_count);
         bytestream_put_byte(&p, coupled_stream_count);
         bytestream_put_buffer(&p, channel_mapping, channels);
     }
 }
 
-static int libopus_configure_encoder(AVCodecContext *avctx, OpusMSEncoder *enc,
+static int libopus_configure_encoder(AVCodecContext *avctx,
+                                     OpusGenericEncoder *enc,
                                      LibopusEncOpts *opts)
 {
     int ret;
@@ -118,48 +282,48 @@  static int libopus_configure_encoder(AVCodecContext *avctx, OpusMSEncoder *enc,
         return AVERROR(EINVAL);
     }
 
-    ret = opus_multistream_encoder_ctl(enc, OPUS_SET_BITRATE(avctx->bit_rate));
+    ret = libopus_generic_encoder_ctl(enc, OPUS_SET_BITRATE(avctx->bit_rate));
     if (ret != OPUS_OK) {
         av_log(avctx, AV_LOG_ERROR,
                "Failed to set bitrate: %s\n", opus_strerror(ret));
         return ret;
     }
 
-    ret = opus_multistream_encoder_ctl(enc,
-                                       OPUS_SET_COMPLEXITY(opts->complexity));
+    ret = libopus_generic_encoder_ctl(enc,
+                                      OPUS_SET_COMPLEXITY(opts->complexity));
     if (ret != OPUS_OK)
         av_log(avctx, AV_LOG_WARNING,
                "Unable to set complexity: %s\n", opus_strerror(ret));
 
-    ret = opus_multistream_encoder_ctl(enc, OPUS_SET_VBR(!!opts->vbr));
+    ret = libopus_generic_encoder_ctl(enc, OPUS_SET_VBR(!!opts->vbr));
     if (ret != OPUS_OK)
         av_log(avctx, AV_LOG_WARNING,
                "Unable to set VBR: %s\n", opus_strerror(ret));
 
-    ret = opus_multistream_encoder_ctl(enc,
-                                       OPUS_SET_VBR_CONSTRAINT(opts->vbr == 2));
+    ret = libopus_generic_encoder_ctl(enc,
+                                      OPUS_SET_VBR_CONSTRAINT(opts->vbr == 2));
     if (ret != OPUS_OK)
         av_log(avctx, AV_LOG_WARNING,
                "Unable to set constrained VBR: %s\n", opus_strerror(ret));
 
-    ret = opus_multistream_encoder_ctl(enc,
-                                       OPUS_SET_PACKET_LOSS_PERC(opts->packet_loss));
+    ret = libopus_generic_encoder_ctl(enc,
+                                      OPUS_SET_PACKET_LOSS_PERC(opts->packet_loss));
     if (ret != OPUS_OK)
         av_log(avctx, AV_LOG_WARNING,
                "Unable to set expected packet loss percentage: %s\n",
                opus_strerror(ret));
 
     if (avctx->cutoff) {
-        ret = opus_multistream_encoder_ctl(enc,
-                                           OPUS_SET_MAX_BANDWIDTH(opts->max_bandwidth));
+        ret = libopus_generic_encoder_ctl(enc,
+                                          OPUS_SET_MAX_BANDWIDTH(opts->max_bandwidth));
         if (ret != OPUS_OK)
             av_log(avctx, AV_LOG_WARNING,
                    "Unable to set maximum bandwidth: %s\n", opus_strerror(ret));
     }
 
 #ifdef OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST
-    ret = opus_multistream_encoder_ctl(enc,
-                                       OPUS_SET_PHASE_INVERSION_DISABLED(!opts->apply_phase_inv));
+    ret = libopus_generic_encoder_ctl(enc,
+                                      OPUS_SET_PHASE_INVERSION_DISABLED(!opts->apply_phase_inv));
     if (ret != OPUS_OK)
         av_log(avctx, AV_LOG_WARNING,
                "Unable to set phase inversion: %s\n",
@@ -207,6 +371,8 @@  static int libopus_validate_layout_and_get_channel_map(
 {
     const uint8_t * channel_map = NULL;
     int ret;
+    int order_plus_one;
+    int nondiegetic_channels;
 
     switch (mapping_family) {
     case -1:
@@ -231,6 +397,23 @@  static int libopus_validate_layout_and_get_channel_map(
             channel_map = ff_vorbis_channel_layout_offsets[avctx->channels - 1];
         }
         break;
+    case 2:
+    case 3:
+        order_plus_one = ff_sqrt(avctx->channels);
+        nondiegetic_channels = avctx->channels - order_plus_one * order_plus_one;
+        if (order_plus_one < 1 || order_plus_one > 15 ||
+           (nondiegetic_channels != 0 && nondiegetic_channels != 2)) {
+            av_log(avctx, AV_LOG_ERROR,
+                   "This channel mapping is only specified for channel counts"
+                   " which can be written as (n + 1)^2 + 2j, where n is a"
+                   " non-negative integar from 0 to 14 and j is either 0 or 1.\n");
+            ret = AVERROR_INVALIDDATA;
+        } else {
+            ret = 0;
+        }
+
+        /* Channels do not need to be reordered. */
+        break;
     case 255:
         ret = libopus_check_max_channels(avctx, 254);
         break;
@@ -248,7 +431,7 @@  static int libopus_validate_layout_and_get_channel_map(
 static av_cold int libopus_encode_init(AVCodecContext *avctx)
 {
     LibopusEncContext *opus = avctx->priv_data;
-    OpusMSEncoder *enc;
+    OpusGenericEncoder *enc = &opus->enc;
     uint8_t libopus_channel_mapping[255];
     int ret = OPUS_OK;
     int av_ret;
@@ -335,20 +518,20 @@  static av_cold int libopus_encode_init(AVCodecContext *avctx)
                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,
+        ret = libopus_generic_encoder_init(
+            enc, avctx->sample_rate, avctx->channels, opus->stream_count,
             coupled_stream_count,
             libavcodec_libopus_channel_map[avctx->channels - 1],
-            opus->opts.application, &ret);
+            opus->opts.application);
     } 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,
+        ret = libopus_generic_encoder_surround_init(
+            enc, avctx->sample_rate, avctx->channels, mapping_family,
             &opus->stream_count, &coupled_stream_count, libopus_channel_mapping,
-            opus->opts.application, &ret);
+            opus->opts.application);
     }
 
     if (ret != OPUS_OK) {
@@ -380,7 +563,9 @@  static av_cold int libopus_encode_init(AVCodecContext *avctx)
     }
 
     /* Header includes channel mapping table if and only if mapping family is NOT 0 */
-    header_size = 19 + (mapping_family == 0 ? 0 : 2 + avctx->channels);
+    header_size = libopus_generic_encoder_get_header_size(
+        mapping_family, avctx->channels, opus->stream_count,
+        coupled_stream_count);
     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");
@@ -397,23 +582,21 @@  static av_cold int libopus_encode_init(AVCodecContext *avctx)
         goto fail;
     }
 
-    ret = opus_multistream_encoder_ctl(enc, OPUS_GET_LOOKAHEAD(&avctx->initial_padding));
+    ret = libopus_generic_encoder_ctl(enc, OPUS_GET_LOOKAHEAD(&avctx->initial_padding));
     if (ret != OPUS_OK)
         av_log(avctx, AV_LOG_WARNING,
                "Unable to get number of lookahead samples: %s\n",
                opus_strerror(ret));
 
     libopus_write_header(avctx, opus->stream_count, coupled_stream_count,
-                         mapping_family, libopus_channel_mapping);
+                         mapping_family, libopus_channel_mapping, enc);
 
     ff_af_queue_init(avctx, &opus->afq);
 
-    opus->enc = enc;
-
     return 0;
 
 fail:
-    opus_multistream_encoder_destroy(enc);
+    libous_generic_encoder_cleanup(enc);
     av_freep(&avctx->extradata);
     return ret;
 }
@@ -470,13 +653,13 @@  static int libopus_encode(AVCodecContext *avctx, AVPacket *avpkt,
         return ret;
 
     if (avctx->sample_fmt == AV_SAMPLE_FMT_FLT)
-        ret = opus_multistream_encode_float(opus->enc, (float *)audio,
-                                            opus->opts.packet_size,
-                                            avpkt->data, avpkt->size);
+        ret = libopus_generic_encode_float(&opus->enc, (float *)audio,
+                                           opus->opts.packet_size,
+                                           avpkt->data, avpkt->size);
     else
-        ret = opus_multistream_encode(opus->enc, (opus_int16 *)audio,
-                                      opus->opts.packet_size,
-                                      avpkt->data, avpkt->size);
+        ret = libopus_generic_encode(&opus->enc, (opus_int16 *)audio,
+                                     opus->opts.packet_size,
+                                     avpkt->data, avpkt->size);
 
     if (ret < 0) {
         av_log(avctx, AV_LOG_ERROR,
@@ -517,7 +700,7 @@  static av_cold int libopus_encode_close(AVCodecContext *avctx)
 {
     LibopusEncContext *opus = avctx->priv_data;
 
-    opus_multistream_encoder_destroy(opus->enc);
+    libous_generic_encoder_cleanup(&opus->enc);
 
     ff_af_queue_close(&opus->afq);
 
diff --git a/libavcodec/opus.c b/libavcodec/opus.c
index aa827b604c..f8877233af 100644
--- a/libavcodec/opus.c
+++ b/libavcodec/opus.c
@@ -373,18 +373,14 @@  av_cold int ff_opus_parse_extradata(AVCodecContext *avctx,
             layout = ff_vorbis_channel_layouts[channels - 1];
             channel_reorder = channel_reorder_vorbis;
         } else if (map_type == 2) {
-            int ambisonic_order = ff_sqrt(channels) - 1;
-            if (channels != ((ambisonic_order + 1) * (ambisonic_order + 1)) &&
-                channels != ((ambisonic_order + 1) * (ambisonic_order + 1) + 2)) {
+            int order_plus_one = ff_sqrt(channels);
+            int nondiegetic_channels = channels - order_plus_one * order_plus_one;
+            if (order_plus_one < 1 || order_plus_one > 15 ||
+               (nondiegetic_channels != 0 && nondiegetic_channels != 2)) {
                 av_log(avctx, AV_LOG_ERROR,
-                       "Channel mapping 2 is only specified for channel counts"
-                       " which can be written as (n + 1)^2 or (n + 1)^2 + 2"
-                       " for nonnegative integer n\n");
-                return AVERROR_INVALIDDATA;
-            }
-            if (channels > 227) {
-                av_log(avctx, AV_LOG_ERROR, "Too many channels\n");
-                return AVERROR_INVALIDDATA;
+                    "This channel mapping is only specified for channel counts"
+                    " which can be written as (n + 1)^2 + 2j, where n is a"
+                    " non-negative integar from 0 to 14 and j is either 0 or 1.\n");
             }
             layout = 0;
         } else
-- 
2.17.0.rc1.321.gba9d0f2565-goog