summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Allen <bitllama@google.com>2017-11-07 13:26:23 -0800
committerJean-Marc Valin <jmvalin@jmvalin.ca>2017-11-07 17:44:36 -0500
commitf643c03fba45ad3547b0e54cc6c0f570361002bf (patch)
tree38e6605bf9b39151299c364731ec7dde3c92aefc
parente12df85a62598c7acbee84f13602f82eb6d39734 (diff)
downloadopus-f643c03fba45ad3547b0e54cc6c0f570361002bf.tar.gz
Support for Channel Mapping 253
OpusProjection* classes MixingMatrix class Projection tests Change-Id: I98644466abf4ffd36e48bdecad1204d69e1539b9 Signed-off-by: Jean-Marc Valin <jmvalin@jmvalin.ca>
-rw-r--r--.gitignore1
-rw-r--r--Makefile.am11
-rw-r--r--include/opus_projection.h568
-rw-r--r--opus_sources.mk5
-rw-r--r--src/mapping_matrix.c287
-rw-r--r--src/mapping_matrix.h115
-rw-r--r--src/opus_multistream_decoder.c26
-rw-r--r--src/opus_multistream_encoder.c41
-rw-r--r--src/opus_private.h33
-rw-r--r--src/opus_projection_decoder.c234
-rw-r--r--src/opus_projection_encoder.c412
-rw-r--r--tests/test_opus_projection.c431
12 files changed, 2116 insertions, 48 deletions
diff --git a/.gitignore b/.gitignore
index 9bcd28da..eccd0a2a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,6 +49,7 @@ tests/test_opus_api
tests/test_opus_decode
tests/test_opus_encode
tests/test_opus_padding
+tests/test_opus_projection
celt/arm/armopts.s
celt/dump_modes/dump_modes
celt/tests/test_unit_cwrs32
diff --git a/Makefile.am b/Makefile.am
index 5b57b337..f25a9505 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -82,7 +82,7 @@ if OPUS_ARM_EXTERNAL_ASM
libopus_la_LIBADD += libarmasm.la
endif
-pkginclude_HEADERS = include/opus.h include/opus_multistream.h include/opus_types.h include/opus_defines.h
+pkginclude_HEADERS = include/opus.h include/opus_multistream.h include/opus_types.h include/opus_defines.h include/opus_projection.h
noinst_HEADERS = $(OPUS_HEAD) $(SILK_HEAD) $(CELT_HEAD)
@@ -102,7 +102,8 @@ noinst_PROGRAMS = celt/tests/test_unit_cwrs32 \
tests/test_opus_api \
tests/test_opus_decode \
tests/test_opus_encode \
- tests/test_opus_padding
+ tests/test_opus_padding \
+ tests/test_opus_projection
TESTS = celt/tests/test_unit_cwrs32 \
celt/tests/test_unit_dft \
@@ -116,7 +117,8 @@ TESTS = celt/tests/test_unit_cwrs32 \
tests/test_opus_api \
tests/test_opus_decode \
tests/test_opus_encode \
- tests/test_opus_padding
+ tests/test_opus_padding \
+ tests/test_opus_projection
opus_demo_SOURCES = src/opus_demo.c
@@ -141,6 +143,9 @@ tests_test_opus_decode_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
tests_test_opus_padding_SOURCES = tests/test_opus_padding.c tests/test_opus_common.h
tests_test_opus_padding_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
+tests_test_opus_projection_SOURCES = tests/test_opus_projection.c tests/test_opus_common.h
+tests_test_opus_projection_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
+
CELT_OBJ = $(CELT_SOURCES:.c=.lo)
SILK_OBJ = $(SILK_SOURCES:.c=.lo)
diff --git a/include/opus_projection.h b/include/opus_projection.h
new file mode 100644
index 00000000..1e65610b
--- /dev/null
+++ b/include/opus_projection.h
@@ -0,0 +1,568 @@
+/* Copyright (c) 2017 Google Inc.
+ Written by Andrew Allen */
+/*
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ - Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ - Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/**
+ * @file opus_projection.h
+ * @brief Opus projection reference API
+ */
+
+#ifndef OPUS_PROJECTION_H
+#define OPUS_PROJECTION_H
+
+#include "opus_multistream.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @cond OPUS_INTERNAL_DOC */
+
+/** These are the actual encoder and decoder CTL ID numbers.
+ * They should not be used directly by applications.c
+ * In general, SETs should be even and GETs should be odd.*/
+/**@{*/
+#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN_REQUEST 6001
+#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST 6003
+#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST 6005
+/**@}*/
+
+
+/** @endcond */
+
+/** @defgroup opus_projection_ctls Projection specific encoder and decoder CTLs
+ *
+ * These are convenience macros that are specific to the
+ * opus_projection_encoder_ctl() and opus_projection_decoder_ctl()
+ * interface.
+ * The CTLs from @ref opus_genericctls, @ref opus_encoderctls,
+ * @ref opus_decoderctls, and @ref opus_multistream_ctls may be applied to a
+ * projection encoder or decoder as well.
+ */
+/**@{*/
+
+/** Gets the gain (in dB. S7.8-format) of the demixing matrix from the encoder.
+ * @param[out] x <tt>opus_int32 *</tt>: Returns the gain (in dB. S7.8-format)
+ * of the demixing matrix.
+ * @hideinitializer
+ */
+#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN(x) OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN_REQUEST, __opus_check_int_ptr(x)
+
+
+/** Gets the size in bytes of the demixing matrix from the encoder.
+ * @param[out] x <tt>opus_int32 *</tt>: Returns the size in bytes of the
+ * demixing matrix.
+ * @hideinitializer
+ */
+#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE(x) OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST, __opus_check_int_ptr(x)
+
+
+/** Copies the demixing matrix to the supplied pointer location.
+ * @param[out] x <tt>unsigned char *</tt>: Returns the demixing matrix to the
+ * supplied pointer location.
+ * @param y <tt>opus_in32</tt>: The size in bytes of the reserved memory at the
+ * pointer location.
+ * @hideinitializer
+ */
+#define OPUS_PROJECTION_GET_DEMIXING_MATRIX(x,y) OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST, x, __opus_check_int(y)
+
+
+/**@}*/
+
+/** Opus projection encoder state.
+ * This contains the complete state of a projection Opus encoder.
+ * It is position independent and can be freely copied.
+ * @see opus_projection_ambisonics_encoder_create
+ */
+typedef struct OpusProjectionEncoder OpusProjectionEncoder;
+
+
+/** Opus projection decoder state.
+ * This contains the complete state of a projection Opus decoder.
+ * It is position independent and can be freely copied.
+ * @see opus_projection_decoder_create
+ * @see opus_projection_decoder_init
+ */
+typedef struct OpusProjectionDecoder OpusProjectionDecoder;
+
+
+/**\name Projection encoder functions */
+/**@{*/
+
+/** Gets the size of an OpusProjectionEncoder structure.
+ * @param channels <tt>int</tt>: The total number of input channels to encode.
+ * This must be no more than 255.
+ * @param mapping_family <tt>int</tt>: The mapping family to use for selecting
+ * the appropriate projection.
+ * @returns The size in bytes on success, or a negative error code
+ * (see @ref opus_errorcodes) on error.
+ */
+OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_projection_ambisonics_encoder_get_size(
+ int channels,
+ int mapping_family
+);
+
+
+/** Allocates and initializes a projection encoder state.
+ * Call opus_projection_encoder_destroy() to release
+ * this object when finished.
+ * @param Fs <tt>opus_int32</tt>: Sampling rate of the input signal (in Hz).
+ * This must be one of 8000, 12000, 16000,
+ * 24000, or 48000.
+ * @param channels <tt>int</tt>: Number of channels in the input signal.
+ * This must be at most 255.
+ * It may be greater than the number of
+ * coded channels (<code>streams +
+ * coupled_streams</code>).
+ * @param mapping_family <tt>int</tt>: The mapping family to use for selecting
+ * the appropriate projection.
+ * @param[out] streams <tt>int *</tt>: The total number of streams that will
+ * be encoded from the input.
+ * @param[out] coupled_streams <tt>int *</tt>: Number of coupled (2 channel)
+ * streams that will be encoded from the input.
+ * @param application <tt>int</tt>: The target encoder application.
+ * This must be one of the following:
+ * <dl>
+ * <dt>#OPUS_APPLICATION_VOIP</dt>
+ * <dd>Process signal for improved speech intelligibility.</dd>
+ * <dt>#OPUS_APPLICATION_AUDIO</dt>
+ * <dd>Favor faithfulness to the original input.</dd>
+ * <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
+ * <dd>Configure the minimum possible coding delay by disabling certain modes
+ * of operation.</dd>
+ * </dl>
+ * @param[out] error <tt>int *</tt>: Returns #OPUS_OK on success, or an error
+ * code (see @ref opus_errorcodes) on
+ * failure.
+ */
+OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusProjectionEncoder *opus_projection_ambisonics_encoder_create(
+ opus_int32 Fs,
+ int channels,
+ int mapping_family,
+ int *streams,
+ int *coupled_streams,
+ int application,
+ int *error
+) OPUS_ARG_NONNULL(4) OPUS_ARG_NONNULL(5);
+
+
+/** Initialize a previously allocated projection encoder state.
+ * The memory pointed to by \a st must be at least the size returned by
+ * opus_projection_ambisonics_encoder_get_size().
+ * This is intended for applications which use their own allocator instead of
+ * malloc.
+ * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL.
+ * @see opus_projection_ambisonics_encoder_create
+ * @see opus_projection_ambisonics_encoder_get_size
+ * @param st <tt>OpusProjectionEncoder*</tt>: Projection encoder state to initialize.
+ * @param Fs <tt>opus_int32</tt>: Sampling rate of the input signal (in Hz).
+ * This must be one of 8000, 12000, 16000,
+ * 24000, or 48000.
+ * @param channels <tt>int</tt>: Number of channels in the input signal.
+ * This must be at most 255.
+ * It may be greater than the number of
+ * coded channels (<code>streams +
+ * coupled_streams</code>).
+ * @param streams <tt>int</tt>: The total number of streams to encode from the
+ * input.
+ * This must be no more than the number of channels.
+ * @param coupled_streams <tt>int</tt>: Number of coupled (2 channel) streams
+ * to encode.
+ * This must be no larger than the total
+ * number of streams.
+ * Additionally, The total number of
+ * encoded channels (<code>streams +
+ * coupled_streams</code>) must be no
+ * more than the number of input channels.
+ * @param application <tt>int</tt>: The target encoder application.
+ * This must be one of the following:
+ * <dl>
+ * <dt>#OPUS_APPLICATION_VOIP</dt>
+ * <dd>Process signal for improved speech intelligibility.</dd>
+ * <dt>#OPUS_APPLICATION_AUDIO</dt>
+ * <dd>Favor faithfulness to the original input.</dd>
+ * <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
+ * <dd>Configure the minimum possible coding delay by disabling certain modes
+ * of operation.</dd>
+ * </dl>
+ * @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes)
+ * on failure.
+ */
+OPUS_EXPORT int opus_projection_ambisonics_encoder_init(
+ OpusProjectionEncoder *st,
+ opus_int32 Fs,
+ int channels,
+ int mapping_family,
+ int *streams,
+ int *coupled_streams,
+ int application
+) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(5) OPUS_ARG_NONNULL(6);
+
+
+/** Encodes a projection Opus frame.
+ * @param st <tt>OpusProjectionEncoder*</tt>: Projection encoder state.
+ * @param[in] pcm <tt>const opus_int16*</tt>: The input signal as interleaved
+ * samples.
+ * This must contain
+ * <code>frame_size*channels</code>
+ * samples.
+ * @param frame_size <tt>int</tt>: Number of samples per channel in the input
+ * signal.
+ * This must be an Opus frame size for the
+ * encoder's sampling rate.
+ * For example, at 48 kHz the permitted values
+ * are 120, 240, 480, 960, 1920, and 2880.
+ * Passing in a duration of less than 10 ms
+ * (480 samples at 48 kHz) will prevent the
+ * encoder from using the LPC or hybrid modes.
+ * @param[out] data <tt>unsigned char*</tt>: Output payload.
+ * This must contain storage for at
+ * least \a max_data_bytes.
+ * @param [in] max_data_bytes <tt>opus_int32</tt>: Size of the allocated
+ * memory for the output
+ * payload. This may be
+ * used to impose an upper limit on
+ * the instant bitrate, but should
+ * not be used as the only bitrate
+ * control. Use #OPUS_SET_BITRATE to
+ * control the bitrate.
+ * @returns The length of the encoded packet (in bytes) on success or a
+ * negative error code (see @ref opus_errorcodes) on failure.
+ */
+OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_encode(
+ OpusProjectionEncoder *st,
+ const opus_int16 *pcm,
+ int frame_size,
+ unsigned char *data,
+ opus_int32 max_data_bytes
+) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4);
+
+
+/** Encodes a projection Opus frame from floating point input.
+ * @param st <tt>OpusProjectionEncoder*</tt>: Projection encoder state.
+ * @param[in] pcm <tt>const float*</tt>: The input signal as interleaved
+ * samples with a normal range of
+ * +/-1.0.
+ * Samples with a range beyond +/-1.0
+ * are supported but will be clipped by
+ * decoders using the integer API and
+ * should only be used if it is known
+ * that the far end supports extended
+ * dynamic range.
+ * This must contain
+ * <code>frame_size*channels</code>
+ * samples.
+ * @param frame_size <tt>int</tt>: Number of samples per channel in the input
+ * signal.
+ * This must be an Opus frame size for the
+ * encoder's sampling rate.
+ * For example, at 48 kHz the permitted values
+ * are 120, 240, 480, 960, 1920, and 2880.
+ * Passing in a duration of less than 10 ms
+ * (480 samples at 48 kHz) will prevent the
+ * encoder from using the LPC or hybrid modes.
+ * @param[out] data <tt>unsigned char*</tt>: Output payload.
+ * This must contain storage for at
+ * least \a max_data_bytes.
+ * @param [in] max_data_bytes <tt>opus_int32</tt>: Size of the allocated
+ * memory for the output
+ * payload. This may be
+ * used to impose an upper limit on
+ * the instant bitrate, but should
+ * not be used as the only bitrate
+ * control. Use #OPUS_SET_BITRATE to
+ * control the bitrate.
+ * @returns The length of the encoded packet (in bytes) on success or a
+ * negative error code (see @ref opus_errorcodes) on failure.
+ */
+OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_encode_float(
+ OpusProjectionEncoder *st,
+ const float *pcm,
+ int frame_size,
+ unsigned char *data,
+ opus_int32 max_data_bytes
+) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4);
+
+
+/** Frees an <code>OpusProjectionEncoder</code> allocated by
+ * opus_projection_ambisonics_encoder_create().
+ * @param st <tt>OpusProjectionEncoder*</tt>: Projection encoder state to be freed.
+ */
+OPUS_EXPORT void opus_projection_encoder_destroy(OpusProjectionEncoder *st);
+
+
+/** Perform a CTL function on a projection Opus encoder.
+ *
+ * Generally the request and subsequent arguments are generated by a
+ * convenience macro.
+ * @param st <tt>OpusProjectionEncoder*</tt>: Projection encoder state.
+ * @param request This and all remaining parameters should be replaced by one
+ * of the convenience macros in @ref opus_genericctls,
+ * @ref opus_encoderctls, @ref opus_multistream_ctls, or
+ * @ref opus_projection_ctls
+ * @see opus_genericctls
+ * @see opus_encoderctls
+ * @see opus_multistream_ctls
+ * @see opus_projection_ctls
+ */
+OPUS_EXPORT int opus_projection_encoder_ctl(OpusProjectionEncoder *st, int request, ...) OPUS_ARG_NONNULL(1);
+
+
+/**@}*/
+
+/**\name Projection decoder functions */
+/**@{*/
+
+/** Gets the size of an <code>OpusProjectionDecoder</code> structure.
+ * @param channels <tt>int</tt>: The total number of output channels.
+ * This must be no more than 255.
+ * @param streams <tt>int</tt>: The total number of streams coded in the
+ * input.
+ * This must be no more than 255.
+ * @param coupled_streams <tt>int</tt>: Number streams to decode as coupled
+ * (2 channel) streams.
+ * This must be no larger than the total
+ * number of streams.
+ * Additionally, The total number of
+ * coded channels (<code>streams +
+ * coupled_streams</code>) must be no
+ * more than 255.
+ * @returns The size in bytes on success, or a negative error code
+ * (see @ref opus_errorcodes) on error.
+ */
+OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_projection_decoder_get_size(
+ int channels,
+ int streams,
+ int coupled_streams
+);
+
+
+/** Allocates and initializes a projection decoder state.
+ * Call opus_projection_decoder_destroy() to release
+ * this object when finished.
+ * @param Fs <tt>opus_int32</tt>: Sampling rate to decode at (in Hz).
+ * This must be one of 8000, 12000, 16000,
+ * 24000, or 48000.
+ * @param channels <tt>int</tt>: Number of channels to output.
+ * This must be at most 255.
+ * It may be different from the number of coded
+ * channels (<code>streams +
+ * coupled_streams</code>).
+ * @param streams <tt>int</tt>: The total number of streams coded in the
+ * input.
+ * This must be no more than 255.
+ * @param coupled_streams <tt>int</tt>: Number of streams to decode as coupled
+ * (2 channel) streams.
+ * This must be no larger than the total
+ * number of streams.
+ * Additionally, The total number of
+ * coded channels (<code>streams +
+ * coupled_streams</code>) must be no
+ * more than 255.
+ * @param[in] demixing_matrix <tt>const unsigned char[demixing_matrix_size]</tt>: Demixing matrix
+ * that mapping from coded channels to output channels,
+ * as described in @ref opus_projection and
+ * @ref opus_projection_ctls.
+ * @param demixing_matrix_size <tt>opus_int32</tt>: The size in bytes of the
+ * demixing matrix, as
+ * described in @ref
+ * opus_projection_ctls.
+ * @param[out] error <tt>int *</tt>: Returns #OPUS_OK on success, or an error
+ * code (see @ref opus_errorcodes) on
+ * failure.
+ */
+OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusProjectionDecoder *opus_projection_decoder_create(
+ opus_int32 Fs,
+ int channels,
+ int streams,
+ int coupled_streams,
+ unsigned char *demixing_matrix,
+ opus_int32 demixing_matrix_size,
+ int *error
+) OPUS_ARG_NONNULL(5);
+
+
+/** Intialize a previously allocated projection decoder state object.
+ * The memory pointed to by \a st must be at least the size returned by
+ * opus_projection_decoder_get_size().
+ * This is intended for applications which use their own allocator instead of
+ * malloc.
+ * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL.
+ * @see opus_projection_decoder_create
+ * @see opus_projection_deocder_get_size
+ * @param st <tt>OpusProjectionDecoder*</tt>: Projection encoder state to initialize.
+ * @param Fs <tt>opus_int32</tt>: Sampling rate to decode at (in Hz).
+ * This must be one of 8000, 12000, 16000,
+ * 24000, or 48000.
+ * @param channels <tt>int</tt>: Number of channels to output.
+ * This must be at most 255.
+ * It may be different from the number of coded
+ * channels (<code>streams +
+ * coupled_streams</code>).
+ * @param streams <tt>int</tt>: The total number of streams coded in the
+ * input.
+ * This must be no more than 255.
+ * @param coupled_streams <tt>int</tt>: Number of streams to decode as coupled
+ * (2 channel) streams.
+ * This must be no larger than the total
+ * number of streams.
+ * Additionally, The total number of
+ * coded channels (<code>streams +
+ * coupled_streams</code>) must be no
+ * more than 255.
+ * @param[in] demixing_matrix <tt>const unsigned char[demixing_matrix_size]</tt>: Demixing matrix
+ * that mapping from coded channels to output channels,
+ * as described in @ref opus_projection and
+ * @ref opus_projection_ctls.
+ * @param demixing_matrix_size <tt>opus_int32</tt>: The size in bytes of the
+ * demixing matrix, as
+ * described in @ref
+ * opus_projection_ctls.
+ * @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes)
+ * on failure.
+ */
+OPUS_EXPORT int opus_projection_decoder_init(
+ OpusProjectionDecoder *st,
+ opus_int32 Fs,
+ int channels,
+ int streams,
+ int coupled_streams,
+ unsigned char *demixing_matrix,
+ opus_int32 demixing_matrix_size
+) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6);
+
+
+/** Decode a projection Opus packet.
+ * @param st <tt>OpusProjectionDecoder*</tt>: Projection decoder state.
+ * @param[in] data <tt>const unsigned char*</tt>: Input payload.
+ * Use a <code>NULL</code>
+ * pointer to indicate packet
+ * loss.
+ * @param len <tt>opus_int32</tt>: Number of bytes in payload.
+ * @param[out] pcm <tt>opus_int16*</tt>: Output signal, with interleaved
+ * samples.
+ * This must contain room for
+ * <code>frame_size*channels</code>
+ * samples.
+ * @param frame_size <tt>int</tt>: The number of samples per channel of
+ * available space in \a pcm.
+ * If this is less than the maximum packet duration
+ * (120 ms; 5760 for 48kHz), this function will not be capable
+ * of decoding some packets. In the case of PLC (data==NULL)
+ * or FEC (decode_fec=1), then frame_size needs to be exactly
+ * the duration of audio that is missing, otherwise the
+ * decoder will not be in the optimal state to decode the
+ * next incoming packet. For the PLC and FEC cases, frame_size
+ * <b>must</b> be a multiple of 2.5 ms.
+ * @param decode_fec <tt>int</tt>: Flag (0 or 1) to request that any in-band
+ * forward error correction data be decoded.
+ * If no such data is available, the frame is
+ * decoded as if it were lost.
+ * @returns Number of samples decoded on success or a negative error code
+ * (see @ref opus_errorcodes) on failure.
+ */
+OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_decode(
+ OpusProjectionDecoder *st,
+ const unsigned char *data,
+ opus_int32 len,
+ opus_int16 *pcm,
+ int frame_size,
+ int decode_fec
+) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
+
+
+/** Decode a projection Opus packet with floating point output.
+ * @param st <tt>OpusProjectionDecoder*</tt>: Projection decoder state.
+ * @param[in] data <tt>const unsigned char*</tt>: Input payload.
+ * Use a <code>NULL</code>
+ * pointer to indicate packet
+ * loss.
+ * @param len <tt>opus_int32</tt>: Number of bytes in payload.
+ * @param[out] pcm <tt>opus_int16*</tt>: Output signal, with interleaved
+ * samples.
+ * This must contain room for
+ * <code>frame_size*channels</code>
+ * samples.
+ * @param frame_size <tt>int</tt>: The number of samples per channel of
+ * available space in \a pcm.
+ * If this is less than the maximum packet duration
+ * (120 ms; 5760 for 48kHz), this function will not be capable
+ * of decoding some packets. In the case of PLC (data==NULL)
+ * or FEC (decode_fec=1), then frame_size needs to be exactly
+ * the duration of audio that is missing, otherwise the
+ * decoder will not be in the optimal state to decode the
+ * next incoming packet. For the PLC and FEC cases, frame_size
+ * <b>must</b> be a multiple of 2.5 ms.
+ * @param decode_fec <tt>int</tt>: Flag (0 or 1) to request that any in-band
+ * forward error correction data be decoded.
+ * If no such data is available, the frame is
+ * decoded as if it were lost.
+ * @returns Number of samples decoded on success or a negative error code
+ * (see @ref opus_errorcodes) on failure.
+ */
+OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_decode_float(
+ OpusProjectionDecoder *st,
+ const unsigned char *data,
+ opus_int32 len,
+ float *pcm,
+ int frame_size,
+ int decode_fec
+) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
+
+
+/** Perform a CTL function on a projection Opus decoder.
+ *
+ * Generally the request and subsequent arguments are generated by a
+ * convenience macro.
+ * @param st <tt>OpusProjectionDecoder*</tt>: Projection decoder state.
+ * @param request This and all remaining parameters should be replaced by one
+ * of the convenience macros in @ref opus_genericctls,
+ * @ref opus_decoderctls, @ref opus_multistream_ctls, or
+ * @ref opus_projection_ctls.
+ * @see opus_genericctls
+ * @see opus_decoderctls
+ * @see opus_multistream_ctls
+ * @see opus_projection_ctls
+ */
+OPUS_EXPORT int opus_projection_decoder_ctl(OpusProjectionDecoder *st, int request, ...) OPUS_ARG_NONNULL(1);
+
+
+/** Frees an <code>OpusProjectionDecoder</code> allocated by
+ * opus_projection_decoder_create().
+ * @param st <tt>OpusProjectionDecoder</tt>: Projection decoder state to be freed.
+ */
+OPUS_EXPORT void opus_projection_decoder_destroy(OpusProjectionDecoder *st);
+
+
+/**@}*/
+
+/**@}*/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* OPUS_PROJECTION_H */
diff --git a/opus_sources.mk b/opus_sources.mk
index e4eeb919..b0763f99 100644
--- a/opus_sources.mk
+++ b/opus_sources.mk
@@ -4,7 +4,10 @@ src/opus_encoder.c \
src/opus_multistream.c \
src/opus_multistream_encoder.c \
src/opus_multistream_decoder.c \
-src/repacketizer.c
+src/repacketizer.c \
+src/opus_projection_encoder.c \
+src/opus_projection_decoder.c \
+src/mapping_matrix.c
OPUS_SOURCES_FLOAT = \
src/analysis.c \
diff --git a/src/mapping_matrix.c b/src/mapping_matrix.c
new file mode 100644
index 00000000..5268bc6d
--- /dev/null
+++ b/src/mapping_matrix.c
@@ -0,0 +1,287 @@
+/* Copyright (c) 2017 Google Inc.
+ Written by Andrew Allen */
+/*
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ - Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ - Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "arch.h"
+#include "float_cast.h"
+#include "opus_private.h"
+#include "opus_defines.h"
+#include "mapping_matrix.h"
+
+#ifdef ENABLE_EXPERIMENTAL_AMBISONICS
+
+#define MATRIX_INDEX(nb_rows, row, col) (nb_rows * col + row)
+
+int mapping_matrix_get_size(int rows, int cols)
+{
+ return align(sizeof(MappingMatrix)) + rows * cols * sizeof(opus_int16);
+}
+
+opus_int16 *mapping_matrix_get_data(const MappingMatrix *matrix)
+{
+ return (opus_int16*)(void *)(matrix + align(sizeof(MappingMatrix)));
+}
+
+void mapping_matrix_init(MappingMatrix * const matrix,
+ int rows, int cols, int gain, const opus_int16 *data, opus_int32 data_size)
+{
+ int i;
+ opus_int16 *ptr;
+
+#if !defined(ENABLE_ASSERTIONS)
+ (void)data_size;
+#endif
+ celt_assert((opus_uint32)data_size == rows * cols * sizeof(opus_int16));
+
+ matrix->rows = rows;
+ matrix->cols = cols;
+ matrix->gain = gain;
+ ptr = mapping_matrix_get_data(matrix);
+ for (i = 0; i < rows * cols; i++)
+ {
+ ptr[i] = data[i];
+ }
+}
+
+#ifndef DISABLE_FLOAT_API
+void mapping_matrix_multiply_float(const MappingMatrix *matrix,
+ const float *input, int input_rows,
+ float *output, int output_rows,
+ int frame_size)
+{
+ /* Matrix data is ordered col-wise.
+ * Input (x) is [n x k], output (y) is [m x k], matrix (M) is [m x n]:
+ * y = M x
+ */
+ opus_int16* matrix_data;
+ int i, row, col;
+ float matrix_cell, input_sample;
+
+ celt_assert(input_rows <= matrix->cols && output_rows <= matrix->rows);
+
+ matrix_data = mapping_matrix_get_data(matrix);
+
+ for (i = 0; i < frame_size; i++)
+ {
+ for (row = 0; row < output_rows; row++)
+ {
+ output[MATRIX_INDEX(output_rows, row, i)] = 0;
+ for (col = 0; col < input_rows; col++)
+ {
+ matrix_cell = (0.000030518f)*(float)matrix_data[MATRIX_INDEX(matrix->rows, row, col)];
+ input_sample = input[MATRIX_INDEX(input_rows, col, i)];
+ output[MATRIX_INDEX(output_rows, row, i)] += matrix_cell * input_sample;
+ }
+ }
+ }
+}
+#endif /* DISABLE_FLOAT_API */
+
+void mapping_matrix_multiply_short(const MappingMatrix *matrix,
+ const opus_int16 *input, int input_rows,
+ opus_int16 *output, int output_rows,
+ int frame_size)
+{
+ /* Matrix data is ordered col-wise.
+ * Input (x) is [n x k], output (y) is [m x k], matrix (M) is [m x n]:
+ * y = M x
+ */
+ opus_int16* matrix_data;
+ int i, row, col;
+
+ celt_assert(input_rows <= matrix->cols && output_rows <= matrix->rows);
+
+ matrix_data = mapping_matrix_get_data(matrix);
+
+ for (i = 0; i < frame_size; i++)
+ {
+ for (row = 0; row < output_rows; row++)
+ {
+ opus_int32 tmp = 0;
+ for (col = 0; col < input_rows; col++)
+ {
+ tmp +=
+ (matrix_data[MATRIX_INDEX(matrix->rows, row, col)] *
+ input[MATRIX_INDEX(input_rows, col, i)]) >> 8;
+ }
+ output[MATRIX_INDEX(output_rows, row, i)] = (tmp + 64)>>7;
+ }
+ }
+}
+
+const MappingMatrix mapping_matrix_foa_mixing = { 6, 6, 0 };
+const opus_int16 mapping_matrix_foa_mixing_data[36] = {
+ 16384, 0, -16384, 23170, 0, 0, 16384, 23170,
+ 16384, 0, 0, 0, 16384, 0, -16384, -23170,
+ 0, 0, 16384, -23170, 16384, 0, 0, 0,
+ 0, 0, 0, 0, 32767, 0, 0, 0,
+ 0, 0, 0, 32767
+};
+
+const MappingMatrix mapping_matrix_soa_mixing = { 11, 11, 0 };
+const opus_int16 mapping_matrix_soa_mixing_data[121] = {
+ 10923, 7723, 13377, -13377, 11585, 9459, 7723, -16384,
+ -6689, 0, 0, 10923, 7723, 13377, 13377, -11585,
+ 9459, 7723, 16384, -6689, 0, 0, 10923, -15447,
+ 13377, 0, 0, -18919, 7723, 0, 13377, 0,
+ 0, 10923, 7723, -13377, -13377, 11585, -9459, 7723,
+ 16384, -6689, 0, 0, 10923, -7723, 0, 13377,
+ -16384, 0, -15447, 0, 9459, 0, 0, 10923,
+ -7723, 0, -13377, 16384, 0, -15447, 0, 9459,
+ 0, 0, 10923, 15447, 0, 0, 0, 0,
+ -15447, 0, -18919, 0, 0, 10923, 7723, -13377,
+ 13377, -11585, -9459, 7723, -16384, -6689, 0, 0,
+ 10923, -15447, -13377, 0, 0, 18919, 7723, 0,
+ 13377, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 32767, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 32767
+};
+
+const MappingMatrix mapping_matrix_toa_mixing = { 18, 18, 0 };
+const opus_int16 mapping_matrix_toa_mixing_data[324] = {
+ 8208, 0, -881, 14369, 0, 0, -8192, -4163,
+ 13218, 0, 0, 0, 11095, -8836, -6218, 14833,
+ 0, 0, 8208, -10161, 881, 10161, -13218, -2944,
+ -8192, 2944, 0, -10488, -6218, 6248, -11095, -6248,
+ 0, -10488, 0, 0, 8208, 10161, 881, -10161,
+ -13218, 2944, -8192, -2944, 0, 10488, -6218, -6248,
+ -11095, 6248, 0, 10488, 0, 0, 8176, 5566,
+ -11552, 5566, 9681, -11205, 8192, -11205, 0, 4920,
+ -15158, 9756, -3334, 9756, 0, -4920, 0, 0,
+ 8176, 7871, 11552, 0, 0, 15846, 8192, 0,
+ -9681, -6958, 0, 13797, 3334, 0, -15158, 0,
+ 0, 0, 8176, 0, 11552, 7871, 0, 0,
+ 8192, 15846, 9681, 0, 0, 0, 3334, 13797,
+ 15158, 6958, 0, 0, 8176, 5566, -11552, -5566,
+ -9681, -11205, 8192, 11205, 0, 4920, 15158, 9756,
+ -3334, -9756, 0, 4920, 0, 0, 8208, 14369,
+ -881, 0, 0, -4163, -8192, 0, -13218, -14833,
+ 0, -8836, 11095, 0, 6218, 0, 0, 0,
+ 8208, 10161, 881, 10161, 13218, 2944, -8192, 2944,
+ 0, 10488, 6218, -6248, -11095, -6248, 0, -10488,
+ 0, 0, 8208, -14369, -881, 0, 0, 4163,
+ -8192, 0, -13218, 14833, 0, 8836, 11095, 0,
+ 6218, 0, 0, 0, 8208, 0, -881, -14369,
+ 0, 0, -8192, 4163, 13218, 0, 0, 0,
+ 11095, 8836, -6218, -14833, 0, 0, 8176, -5566,
+ -11552, 5566, -9681, 11205, 8192, -11205, 0, -4920,
+ 15158, -9756, -3334, 9756, 0, -4920, 0, 0,
+ 8176, 0, 11552, -7871, 0, 0, 8192, -15846,
+ 9681, 0, 0, 0, 3334, -13797, 15158, -6958,
+ 0, 0, 8176, -7871, 11552, 0, 0, -15846,
+ 8192, 0, -9681, 6958, 0, -13797, 3334, 0,
+ -15158, 0, 0, 0, 8176, -5566, -11552, -5566,
+ 9681, 11205, 8192, 11205, 0, -4920, -15158, -9756,
+ -3334, -9756, 0, 4920, 0, 0, 8208, -10161,
+ 881, -10161, 13218, -2944, -8192, -2944, 0, -10488,
+ 6218, 6248, -11095, 6248, 0, 10488, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 32767, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 32767
+};
+
+const MappingMatrix mapping_matrix_foa_demixing = { 6, 6, 0 };
+const opus_int16 mapping_matrix_foa_demixing_data[36] = {
+ 16384, 16384, 16384, 16384, 0, 0, 0, 23170,
+ 0, -23170, 0, 0, -16384, 16384, -16384, 16384,
+ 0, 0, 23170, 0, -23170, 0, 0, 0,
+ 0, 0, 0, 0, 32767, 0, 0, 0,
+ 0, 0, 0, 32767
+};
+
+const MappingMatrix mapping_matrix_soa_demixing = { 11, 11, 3050 };
+const opus_int16 mapping_matrix_soa_demixing_data[121] = {
+ 2771, 2771, 2771, 2771, 2771, 2771, 2771, 2771,
+ 2771, 0, 0, 10033, 10033, -20066, 10033, 14189,
+ 14189, -28378, 10033, -20066, 0, 0, 3393, 3393,
+ 3393, -3393, 0, 0, 0, -3393, -3393, 0,
+ 0, -17378, 17378, 0, -17378, -24576, 24576, 0,
+ 17378, 0, 0, 0, -14189, 14189, 0, -14189,
+ -28378, 28378, 0, 14189, 0, 0, 0, 2399,
+ 2399, -4799, -2399, 0, 0, 0, -2399, 4799,
+ 0, 0, 1959, 1959, 1959, 1959, -3918, -3918,
+ -3918, 1959, 1959, 0, 0, -4156, 4156, 0,
+ 4156, 0, 0, 0, -4156, 0, 0, 0,
+ 8192, 8192, -16384, 8192, 16384, 16384, -32768, 8192,
+ -16384, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 8312, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 8312
+};
+
+const MappingMatrix mapping_matrix_toa_demixing = { 18, 18, 0 };
+const opus_int16 mapping_matrix_toa_demixing_data[324] = {
+ 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192,
+ 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192,
+ 0, 0, 0, -9779, 9779, 6263, 8857, 0,
+ 6263, 13829, 9779, -13829, 0, -6263, 0, -8857,
+ -6263, -9779, 0, 0, -3413, 3413, 3413, -11359,
+ 11359, 11359, -11359, -3413, 3413, -3413, -3413, -11359,
+ 11359, 11359, -11359, 3413, 0, 0, 13829, 9779,
+ -9779, 6263, 0, 8857, -6263, 0, 9779, 0,
+ -13829, 6263, -8857, 0, -6263, -9779, 0, 0,
+ 0, -15617, -15617, 6406, 0, 0, -6406, 0,
+ 15617, 0, 0, -6406, 0, 0, 6406, 15617,
+ 0, 0, 0, -5003, 5003, -10664, 15081, 0,
+ -10664, -7075, 5003, 7075, 0, 10664, 0, -15081,
+ 10664, -5003, 0, 0, -8176, -8176, -8176, 8208,
+ 8208, 8208, 8208, -8176, -8176, -8176, -8176, 8208,
+ 8208, 8208, 8208, -8176, 0, 0, -7075, 5003,
+ -5003, -10664, 0, 15081, 10664, 0, 5003, 0,
+ 7075, -10664, -15081, 0, 10664, -5003, 0, 0,
+ 15617, 0, 0, 0, -6406, 6406, 0, -15617,
+ 0, -15617, 15617, 0, 6406, -6406, 0, 0,
+ 0, 0, 0, -11393, 11393, 2993, -4233, 0,
+ 2993, -16112, 11393, 16112, 0, -2993, 0, 4233,
+ -2993, -11393, 0, 0, 0, -9974, -9974, -13617,
+ 0, 0, 13617, 0, 9974, 0, 0, 13617,
+ 0, 0, -13617, 9974, 0, 0, 0, 5579,
+ -5579, 10185, 14403, 0, 10185, -7890, -5579, 7890,
+ 0, -10185, 0, -14403, -10185, 5579, 0, 0,
+ 11826, -11826, -11826, -901, 901, 901, -901, 11826,
+ -11826, 11826, 11826, -901, 901, 901, -901, -11826,
+ 0, 0, -7890, -5579, 5579, 10185, 0, 14403,
+ -10185, 0, -5579, 0, 7890, 10185, -14403, 0,
+ -10185, 5579, 0, 0, -9974, 0, 0, 0,
+ -13617, 13617, 0, 9974, 0, 9974, -9974, 0,
+ 13617, -13617, 0, 0, 0, 0, 16112, -11393,
+ 11393, -2993, 0, 4233, 2993, 0, -11393, 0,
+ -16112, -2993, -4233, 0, 2993, 11393, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 32767, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 32767
+};
+
+#endif /* ENABLE_EXPERIMENTAL_AMBISONICS */
diff --git a/src/mapping_matrix.h b/src/mapping_matrix.h
new file mode 100644
index 00000000..8fe82eab
--- /dev/null
+++ b/src/mapping_matrix.h
@@ -0,0 +1,115 @@
+/* Copyright (c) 2017 Google Inc.
+ Written by Andrew Allen */
+/*
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ - Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ - Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/**
+ * @file mapping_matrix.h
+ * @brief Opus reference implementation mapping matrix API
+ */
+
+#ifndef MAPPING_MATRIX_H
+#define MAPPING_MATRIX_H
+
+#ifdef ENABLE_EXPERIMENTAL_AMBISONICS
+
+#include "opus_types.h"
+#include "opus_projection.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct MappingMatrix
+{
+ int rows;
+ int cols;
+ int gain; /* in dB. S7.8-format. */
+ /* Matrix cell data goes here using col-wise ordering. */
+} MappingMatrix;
+
+int mapping_matrix_get_size(int rows, int cols);
+
+opus_int16 *mapping_matrix_get_data(const MappingMatrix *matrix);
+
+void mapping_matrix_init(
+ MappingMatrix * const st,
+ int rows,
+ int cols,
+ int gain,
+ const opus_int16 *data,
+ opus_int32 data_size
+);
+
+#ifndef DISABLE_FLOAT_API
+void mapping_matrix_multiply_float(
+ const MappingMatrix *matrix,
+ const float *input,
+ int input_rows,
+ float *output,
+ int output_rows,
+ int frame_size
+);
+#endif /* DISABLE_FLOAT_API */
+
+void mapping_matrix_multiply_short(
+ const MappingMatrix *matrix,
+ const opus_int16 *input,
+ int input_rows,
+ opus_int16 *output,
+ int output_rows,
+ int frame_size
+);
+
+/* Pre-computed mixing and demixing matrices for 1st to 3rd-order ambisonics.
+ * foa: first-order ambisonics
+ * soa: second-order ambisonics
+ * toa: third-order ambisonics
+ */
+extern const MappingMatrix mapping_matrix_foa_mixing;
+extern const opus_int16 mapping_matrix_foa_mixing_data[36];
+
+extern const MappingMatrix mapping_matrix_soa_mixing;
+extern const opus_int16 mapping_matrix_soa_mixing_data[121];
+
+extern const MappingMatrix mapping_matrix_toa_mixing;
+extern const opus_int16 mapping_matrix_toa_mixing_data[324];
+
+extern const MappingMatrix mapping_matrix_foa_demixing;
+extern const opus_int16 mapping_matrix_foa_demixing_data[36];
+
+extern const MappingMatrix mapping_matrix_soa_demixing;
+extern const opus_int16 mapping_matrix_soa_demixing_data[121];
+
+extern const MappingMatrix mapping_matrix_toa_demixing;
+extern const opus_int16 mapping_matrix_toa_demixing_data[324];
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ENABLE_EXPERIMENTAL_AMBISONICS */
+
+#endif /* MAPPING_MATRIX_H */
diff --git a/src/opus_multistream_decoder.c b/src/opus_multistream_decoder.c
index e421726f..6ab5d4c7 100644
--- a/src/opus_multistream_decoder.c
+++ b/src/opus_multistream_decoder.c
@@ -37,14 +37,6 @@
#include "float_cast.h"
#include "os_support.h"
-struct OpusMSDecoder {
- ChannelLayout layout;
- /* Decoder states go here */
-};
-
-
-
-
/* DECODER */
opus_int32 opus_multistream_decoder_get_size(int nb_streams, int nb_coupled_streams)
@@ -408,15 +400,13 @@ int opus_multistream_decode_float(
}
#endif
-int opus_multistream_decoder_ctl(OpusMSDecoder *st, int request, ...)
+int opus_multistream_decoder_ctl_va_list(OpusMSDecoder *st, int request,
+ va_list ap)
{
- va_list ap;
int coupled_size, mono_size;
char *ptr;
int ret = OPUS_OK;
- va_start(ap, request);
-
coupled_size = opus_decoder_get_size(2);
mono_size = opus_decoder_get_size(1);
ptr = (char*)st + align(sizeof(OpusMSDecoder));
@@ -525,14 +515,20 @@ int opus_multistream_decoder_ctl(OpusMSDecoder *st, int request, ...)
ret = OPUS_UNIMPLEMENTED;
break;
}
-
- va_end(ap);
return ret;
bad_arg:
- va_end(ap);
return OPUS_BAD_ARG;
}
+int opus_multistream_decoder_ctl(OpusMSDecoder *st, int request, ...)
+{
+ int ret;
+ va_list ap;
+ va_start(ap, request);
+ ret = opus_multistream_decoder_ctl_va_list(st, request, ap);
+ va_end(ap);
+ return ret;
+}
void opus_multistream_decoder_destroy(OpusMSDecoder *st)
{
diff --git a/src/opus_multistream_encoder.c b/src/opus_multistream_encoder.c
index 032fc003..07de6cce 100644
--- a/src/opus_multistream_encoder.c
+++ b/src/opus_multistream_encoder.c
@@ -70,28 +70,6 @@ typedef void (*opus_copy_channel_in_func)(
int frame_size
);
-typedef enum {
- MAPPING_TYPE_NONE,
- MAPPING_TYPE_SURROUND
-#ifdef ENABLE_EXPERIMENTAL_AMBISONICS
- , /* Do not include comma at end of enumerator list */
- MAPPING_TYPE_AMBISONICS
-#endif
-} MappingType;
-
-struct OpusMSEncoder {
- ChannelLayout layout;
- int arch;
- int lfe_stream;
- int application;
- int variable_duration;
- MappingType mapping_type;
- opus_int32 bitrate_bps;
- /* Encoder states go here */
- /* then opus_val32 window_mem[channels*120]; */
- /* then opus_val32 preemph_mem[channels]; */
-};
-
static opus_val32 *ms_get_preemph_mem(OpusMSEncoder *st)
{
int s;
@@ -1196,15 +1174,13 @@ int opus_multistream_encode(
}
#endif
-int opus_multistream_encoder_ctl(OpusMSEncoder *st, int request, ...)
+int opus_multistream_encoder_ctl_va_list(OpusMSEncoder *st, int request,
+ va_list ap)
{
- va_list ap;
int coupled_size, mono_size;
char *ptr;
int ret = OPUS_OK;
- va_start(ap, request);
-
coupled_size = opus_encoder_get_size(2);
mono_size = opus_encoder_get_size(1);
ptr = (char*)st + align(sizeof(OpusMSEncoder));
@@ -1392,14 +1368,21 @@ int opus_multistream_encoder_ctl(OpusMSEncoder *st, int request, ...)
ret = OPUS_UNIMPLEMENTED;
break;
}
-
- va_end(ap);
return ret;
bad_arg:
- va_end(ap);
return OPUS_BAD_ARG;
}
+int opus_multistream_encoder_ctl(OpusMSEncoder *st, int request, ...)
+{
+ int ret;
+ va_list ap;
+ va_start(ap, request);
+ ret = opus_multistream_encoder_ctl_va_list(st, request, ap);
+ va_end(ap);
+ return ret;
+}
+
void opus_multistream_encoder_destroy(OpusMSEncoder *st)
{
opus_free(st);
diff --git a/src/opus_private.h b/src/opus_private.h
index a731cc55..acbb0ae8 100644
--- a/src/opus_private.h
+++ b/src/opus_private.h
@@ -33,6 +33,7 @@
#include "opus.h"
#include "celt.h"
+#include <stdarg.h> /* va_list */
#include <stddef.h> /* offsetof */
struct OpusRepacketizer {
@@ -50,6 +51,38 @@ typedef struct ChannelLayout {
unsigned char mapping[256];
} ChannelLayout;
+typedef enum {
+ MAPPING_TYPE_NONE,
+ MAPPING_TYPE_SURROUND
+#ifdef ENABLE_EXPERIMENTAL_AMBISONICS
+ , /* Do not include comma at end of enumerator list */
+ MAPPING_TYPE_AMBISONICS
+#endif /* ENABLE_EXPERIMENTAL_AMBISONICS */
+} MappingType;
+
+struct OpusMSEncoder {
+ ChannelLayout layout;
+ int arch;
+ int lfe_stream;
+ int application;
+ int variable_duration;
+ MappingType mapping_type;
+ opus_int32 bitrate_bps;
+ /* Encoder states go here */
+ /* then opus_val32 window_mem[channels*120]; */
+ /* then opus_val32 preemph_mem[channels]; */
+};
+
+struct OpusMSDecoder {
+ ChannelLayout layout;
+ /* Decoder states go here */
+};
+
+int opus_multistream_encoder_ctl_va_list(struct OpusMSEncoder *st, int request,
+ va_list ap);
+int opus_multistream_decoder_ctl_va_list(struct OpusMSDecoder *st, int request,
+ va_list ap);
+
int validate_layout(const ChannelLayout *layout);
int get_left_channel(const ChannelLayout *layout, int stream_id, int prev);
int get_right_channel(const ChannelLayout *layout, int stream_id, int prev);
diff --git a/src/opus_projection_decoder.c b/src/opus_projection_decoder.c
new file mode 100644
index 00000000..a9b1d65c
--- /dev/null
+++ b/src/opus_projection_decoder.c
@@ -0,0 +1,234 @@
+/* Copyright (c) 2017 Google Inc.
+ Written by Andrew Allen */
+/*
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ - Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ - Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "mathops.h"
+#include "os_support.h"
+#include "opus_private.h"
+#include "opus_defines.h"
+#include "opus_projection.h"
+#include "opus_multistream.h"
+#include "mapping_matrix.h"
+#include "stack_alloc.h"
+
+#ifdef ENABLE_EXPERIMENTAL_AMBISONICS
+
+struct OpusProjectionDecoder
+{
+ int demixing_matrix_size_in_bytes;
+ /* Encoder states go here */
+};
+
+static MappingMatrix *get_demixing_matrix(OpusProjectionDecoder *st)
+{
+ return (MappingMatrix *)((char*)st + align(sizeof(OpusProjectionDecoder)));
+}
+
+static OpusMSDecoder *get_multistream_decoder(OpusProjectionDecoder *st)
+{
+ return (OpusMSDecoder *)((char*)st + align(sizeof(OpusProjectionDecoder) +
+ st->demixing_matrix_size_in_bytes));
+}
+
+opus_int32 opus_projection_decoder_get_size(int channels, int streams,
+ int coupled_streams)
+{
+ opus_int32 matrix_size;
+ opus_int32 decoder_size;
+
+ matrix_size =
+ mapping_matrix_get_size(streams + coupled_streams, channels);
+ decoder_size = opus_multistream_decoder_get_size(streams, coupled_streams);
+ if (!decoder_size)
+ return 0;
+
+ return align(sizeof(OpusProjectionDecoder) + matrix_size + decoder_size);
+}
+
+int opus_projection_decoder_init(OpusProjectionDecoder *st, opus_int32 Fs,
+ int channels, int streams, int coupled_streams,
+ unsigned char *demixing_matrix, opus_int32 demixing_matrix_size)
+{
+ int nb_input_streams;
+ opus_int32 expected_matrix_size;
+ int i, ret;
+ unsigned char mapping[255];
+ VARDECL(opus_int16, buf);
+ ALLOC_STACK;
+
+ /* Verify supplied matrix size. */
+ nb_input_streams = streams + coupled_streams;
+ expected_matrix_size = nb_input_streams * channels * sizeof(opus_int16);
+ if (expected_matrix_size != demixing_matrix_size)
+ {
+ RESTORE_STACK;
+ return OPUS_BAD_ARG;
+ }
+
+ /* Convert demixing matrix input into internal format. */
+ ALLOC(buf, demixing_matrix_size, opus_int16);
+ for (i = 0; i < nb_input_streams * channels; i++)
+ {
+ int s = demixing_matrix[2*i + 1] << 8 | demixing_matrix[2*i];
+ s = ((s & 0xFFFF) ^ 0x8000) - 0x8000;
+ buf[i] = (opus_int16)s;
+ }
+
+ /* Assign demixing matrix. */
+ st->demixing_matrix_size_in_bytes = expected_matrix_size;
+ mapping_matrix_init(get_demixing_matrix(st), nb_input_streams, channels, 0,
+ buf, demixing_matrix_size);
+
+ /* Set trivial mapping so each input channel pairs with a matrix column. */
+ for (i = 0; i < channels; i++)
+ {
+ mapping[i] = i;
+ }
+
+ ret = opus_multistream_decoder_init(
+ get_multistream_decoder(st), Fs, channels, streams, coupled_streams, mapping);
+ RESTORE_STACK;
+ return ret;
+}
+
+OpusProjectionDecoder *opus_projection_decoder_create(
+ opus_int32 Fs, int channels, int streams, int coupled_streams,
+ unsigned char *demixing_matrix, opus_int32 demixing_matrix_size, int *error)
+{
+ int size;
+ int ret;
+ OpusProjectionDecoder *st;
+
+ /* Allocate space for the projection decoder. */
+ size = opus_projection_decoder_get_size(channels, streams, coupled_streams);
+ if (!size) {
+ if (error)
+ *error = OPUS_ALLOC_FAIL;
+ return NULL;
+ }
+ st = (OpusProjectionDecoder *)opus_alloc(size);
+ if (!st)
+ {
+ if (error)
+ *error = OPUS_ALLOC_FAIL;
+ return NULL;
+ }
+
+ /* Initialize projection decoder with provided settings. */
+ ret = opus_projection_decoder_init(st, Fs, channels, streams, coupled_streams,
+ demixing_matrix, demixing_matrix_size);
+ if (ret != OPUS_OK)
+ {
+ opus_free(st);
+ st = NULL;
+ }
+ if (error)
+ *error = ret;
+ return st;
+}
+
+int opus_projection_decode(OpusProjectionDecoder *st, const unsigned char *data,
+ opus_int32 len, opus_int16 *pcm, int frame_size,
+ int decode_fec)
+{
+#ifdef NONTHREADSAFE_PSEUDOSTACK
+ celt_fatal("Unable to use opus_projection_decode() when NONTHREADSAFE_PSEUDOSTACK is defined.");
+#endif
+ MappingMatrix *matrix;
+ OpusMSDecoder *ms_decoder;
+ int ret;
+ VARDECL(opus_int16, buf);
+ ALLOC_STACK;
+
+ ms_decoder = get_multistream_decoder(st);
+ ALLOC(buf, (ms_decoder->layout.nb_streams + ms_decoder->layout.nb_coupled_streams) *
+ frame_size, opus_int16);
+ ret = opus_multistream_decode(ms_decoder, data, len, buf, frame_size,
+ decode_fec);
+ if (ret <= 0)
+ return ret;
+ frame_size = ret;
+ matrix = get_demixing_matrix(st);
+ mapping_matrix_multiply_short(matrix, buf,
+ ms_decoder->layout.nb_streams + ms_decoder->layout.nb_coupled_streams,
+ pcm, ms_decoder->layout.nb_channels, frame_size);
+ RESTORE_STACK;
+ return frame_size;
+}
+
+#ifndef DISABLE_FLOAT_API
+int opus_projection_decode_float(OpusProjectionDecoder *st, const unsigned char *data,
+ opus_int32 len, float *pcm,
+ int frame_size, int decode_fec)
+{
+#ifdef NONTHREADSAFE_PSEUDOSTACK
+ celt_fatal("Unable to use opus_projection_decode_float() when NONTHREADSAFE_PSEUDOSTACK is defined.");
+#endif
+ MappingMatrix *matrix;
+ OpusMSDecoder *ms_decoder;
+ int ret;
+ VARDECL(float, buf);
+ ALLOC_STACK;
+
+ ms_decoder = get_multistream_decoder(st);
+ ALLOC(buf, (ms_decoder->layout.nb_streams + ms_decoder->layout.nb_coupled_streams) *
+ frame_size, float);
+ ret = opus_multistream_decode_float(ms_decoder, data, len, buf,
+ frame_size, decode_fec);
+ if (ret <= 0)
+ return ret;
+ frame_size = ret;
+ matrix = get_demixing_matrix(st);
+ mapping_matrix_multiply_float(matrix, buf,
+ ms_decoder->layout.nb_streams + ms_decoder->layout.nb_coupled_streams,
+ pcm, ms_decoder->layout.nb_channels, frame_size);
+ RESTORE_STACK;
+ return frame_size;
+}
+#endif
+
+int opus_projection_decoder_ctl(OpusProjectionDecoder *st, int request, ...)
+{
+ va_list ap;
+ int ret = OPUS_OK;
+
+ va_start(ap, request);
+ ret = opus_multistream_decoder_ctl_va_list(get_multistream_decoder(st),
+ request, ap);
+ va_end(ap);
+ return ret;
+}
+
+void opus_projection_decoder_destroy(OpusProjectionDecoder *st)
+{
+ opus_free(st);
+}
+
+#endif /* ENABLE_EXPERIMENTAL_AMBISONICS */
diff --git a/src/opus_projection_encoder.c b/src/opus_projection_encoder.c
new file mode 100644
index 00000000..e34b0e57
--- /dev/null
+++ b/src/opus_projection_encoder.c
@@ -0,0 +1,412 @@
+/* Copyright (c) 2017 Google Inc.
+ Written by Andrew Allen */
+/*
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ - Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ - Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "mathops.h"
+#include "os_support.h"
+#include "opus_private.h"
+#include "opus_defines.h"
+#include "opus_projection.h"
+#include "opus_multistream.h"
+#include "stack_alloc.h"
+#include "mapping_matrix.h"
+
+#ifdef ENABLE_EXPERIMENTAL_AMBISONICS
+
+struct OpusProjectionEncoder
+{
+ int mixing_matrix_size_in_bytes;
+ int demixing_matrix_size_in_bytes;
+ /* Encoder states go here */
+};
+
+static int get_order_plus_one_from_channels(int channels, int *order_plus_one)
+{
+ int order_plus_one_;
+ int acn_channels;
+ int nondiegetic_channels;
+
+ /* Allowed numbers of channels:
+ * (1 + n)^2 + 2j, for n = 0...14 and j = 0 or 1.
+ */
+ order_plus_one_ = isqrt32(channels);
+ acn_channels = order_plus_one_ * order_plus_one_;
+ nondiegetic_channels = channels - acn_channels;
+ if (order_plus_one)
+ *order_plus_one = order_plus_one_;
+
+ if (order_plus_one_ < 1 || order_plus_one_ > 15 ||
+ (nondiegetic_channels != 0 && nondiegetic_channels != 2))
+ return OPUS_BAD_ARG;
+ return OPUS_OK;
+}
+
+static int get_streams_from_channels(int channels, int mapping_family,
+ int *streams, int *coupled_streams,
+ int *order_plus_one)
+{
+ if (mapping_family == 253)
+ {
+ if (get_order_plus_one_from_channels(channels, order_plus_one) != OPUS_OK)
+ return OPUS_BAD_ARG;
+ if (streams)
+ *streams = (channels + 1) / 2;
+ if (coupled_streams)
+ *coupled_streams = channels / 2;
+ return OPUS_OK;
+ }
+ return OPUS_BAD_ARG;
+}
+
+static MappingMatrix *get_mixing_matrix(OpusProjectionEncoder *st)
+{
+ return (MappingMatrix *)((char*)st + align(sizeof(OpusProjectionEncoder)));
+}
+
+static MappingMatrix *get_demixing_matrix(OpusProjectionEncoder *st)
+{
+ return (MappingMatrix *)((char*)st + align(sizeof(OpusProjectionEncoder) +
+ st->mixing_matrix_size_in_bytes));
+}
+
+static OpusMSEncoder *get_multistream_encoder(OpusProjectionEncoder *st)
+{
+ return (OpusMSEncoder *)((char*)st + align(sizeof(OpusProjectionEncoder) +
+ st->mixing_matrix_size_in_bytes + st->demixing_matrix_size_in_bytes));
+}
+
+opus_int32 opus_projection_ambisonics_encoder_get_size(int channels,
+ int mapping_family)
+{
+ int nb_streams;
+ int nb_coupled_streams;
+ int order_plus_one;
+ int matrix_rows;
+ opus_int32 matrix_size;
+ opus_int32 encoder_size;
+ int ret;
+
+ ret = get_streams_from_channels(channels, mapping_family, &nb_streams,
+ &nb_coupled_streams, &order_plus_one);
+ if (ret != OPUS_OK)
+ {
+ return 0;
+ }
+
+ matrix_rows = order_plus_one * order_plus_one + 2;
+ matrix_size = mapping_matrix_get_size(matrix_rows, matrix_rows);
+ encoder_size =
+ opus_multistream_encoder_get_size(nb_streams, nb_coupled_streams);
+ if (!encoder_size)
+ return 0;
+ return align(sizeof(OpusProjectionEncoder) + matrix_size + matrix_size + encoder_size);
+}
+
+int opus_projection_ambisonics_encoder_init(OpusProjectionEncoder *st, opus_int32 Fs,
+ int channels, int mapping_family,
+ int *streams, int *coupled_streams,
+ int application)
+{
+ MappingMatrix *mixing_matrix;
+ MappingMatrix *demixing_matrix;
+ OpusMSEncoder *ms_encoder;
+ int nb_streams;
+ int nb_coupled_streams;
+ int i;
+ int ret;
+ unsigned char mapping[255];
+
+ if (get_streams_from_channels(channels, mapping_family,
+ &nb_streams, &nb_coupled_streams, NULL)
+ != OPUS_OK)
+ return OPUS_BAD_ARG;
+
+ if (streams == NULL || coupled_streams == NULL) {
+ return OPUS_BAD_ARG;
+ }
+ *streams = nb_streams;
+ *coupled_streams = nb_coupled_streams;
+
+ if (mapping_family == 253)
+ {
+ int order_plus_one;
+ if (get_order_plus_one_from_channels(channels, &order_plus_one) != OPUS_OK)
+ return OPUS_BAD_ARG;
+
+ /* Assign mixing matrix based on available pre-computed matrices. */
+ mixing_matrix = get_mixing_matrix(st);
+ if (order_plus_one == 2)
+ {
+ mapping_matrix_init(mixing_matrix, mapping_matrix_foa_mixing.rows,
+ mapping_matrix_foa_mixing.cols, mapping_matrix_foa_mixing.gain,
+ mapping_matrix_foa_mixing_data, 36 * sizeof(opus_int16));
+ }
+ else if (order_plus_one == 3)
+ {
+ mapping_matrix_init(mixing_matrix, mapping_matrix_soa_mixing.rows,
+ mapping_matrix_soa_mixing.cols, mapping_matrix_soa_mixing.gain,
+ mapping_matrix_soa_mixing_data, 121 * sizeof(opus_int16));
+ }
+ else if (order_plus_one == 4)
+ {
+ mapping_matrix_init(mixing_matrix, mapping_matrix_toa_mixing.rows,
+ mapping_matrix_toa_mixing.cols, mapping_matrix_toa_mixing.gain,
+ mapping_matrix_toa_mixing_data, 324 * sizeof(opus_int16));
+ }
+ st->mixing_matrix_size_in_bytes = mapping_matrix_get_size(
+ mixing_matrix->rows, mixing_matrix->cols);
+
+ /* Assign demixing matrix based on available pre-computed matrices. */
+ demixing_matrix = get_demixing_matrix(st);
+ if (order_plus_one == 2)
+ {
+ mapping_matrix_init(demixing_matrix, mapping_matrix_foa_demixing.rows,
+ mapping_matrix_foa_demixing.cols, mapping_matrix_foa_demixing.gain,
+ mapping_matrix_foa_demixing_data, 36 * sizeof(opus_int16));
+ }
+ else if (order_plus_one == 3)
+ {
+ mapping_matrix_init(demixing_matrix, mapping_matrix_soa_demixing.rows,
+ mapping_matrix_soa_demixing.cols, mapping_matrix_soa_demixing.gain,
+ mapping_matrix_soa_demixing_data, 121 * sizeof(opus_int16));
+ }
+ else if (order_plus_one == 4)
+ {
+ mapping_matrix_init(demixing_matrix, mapping_matrix_toa_demixing.rows,
+ mapping_matrix_toa_demixing.cols, mapping_matrix_toa_demixing.gain,
+ mapping_matrix_toa_demixing_data, 324 * sizeof(opus_int16));
+ }
+ st->demixing_matrix_size_in_bytes = mapping_matrix_get_size(
+ demixing_matrix->rows, demixing_matrix->cols);
+ }
+ else
+ return OPUS_UNIMPLEMENTED;
+
+ /* Ensure matrices are large enough for desired coding scheme. */
+ if (nb_streams + nb_coupled_streams > mixing_matrix->rows ||
+ channels > mixing_matrix->cols ||
+ channels > demixing_matrix->rows ||
+ nb_streams + nb_coupled_streams > demixing_matrix->cols)
+ return OPUS_BAD_ARG;
+
+ /* Set trivial mapping so each input channel pairs with a matrix column. */
+ for (i = 0; i < channels; i++)
+ {
+ mapping[i] = i;
+ }
+
+ /* Initialize multistream encoder with provided settings. */
+ ms_encoder = get_multistream_encoder(st);
+ ret = opus_multistream_encoder_init(ms_encoder, Fs, channels, nb_streams,
+ nb_coupled_streams, mapping, application);
+ return ret;
+}
+
+OpusProjectionEncoder *opus_projection_ambisonics_encoder_create(
+ opus_int32 Fs, int channels, int mapping_family, int *streams,
+ int *coupled_streams, int application, int *error)
+{
+ int size;
+ int ret;
+ OpusProjectionEncoder *st;
+
+ /* Allocate space for the projection encoder. */
+ size = opus_projection_ambisonics_encoder_get_size(channels, mapping_family);
+ if (!size) {
+ if (error)
+ *error = OPUS_ALLOC_FAIL;
+ return NULL;
+ }
+ st = (OpusProjectionEncoder *)opus_alloc(size);
+ if (!st)
+ {
+ if (error)
+ *error = OPUS_ALLOC_FAIL;
+ return NULL;
+ }
+
+ /* Initialize projection encoder with provided settings. */
+ ret = opus_projection_ambisonics_encoder_init(st, Fs, channels,
+ mapping_family, streams, coupled_streams, application);
+ if (ret != OPUS_OK)
+ {
+ opus_free(st);
+ st = NULL;
+ }
+ if (error)
+ *error = ret;
+ return st;
+}
+
+int opus_projection_encode(OpusProjectionEncoder *st, const opus_int16 *pcm,
+ int frame_size, unsigned char *data,
+ opus_int32 max_data_bytes)
+{
+#ifdef NONTHREADSAFE_PSEUDOSTACK
+ celt_fatal("Unable to use opus_projection_encode() when NONTHREADSAFE_PSEUDOSTACK is defined.");
+#endif
+ MappingMatrix *matrix;
+ OpusMSEncoder *ms_encoder;
+ int ret;
+ VARDECL(opus_int16, buf);
+ ALLOC_STACK;
+
+ matrix = get_mixing_matrix(st);
+ ms_encoder = get_multistream_encoder(st);
+ ALLOC(buf, (ms_encoder->layout.nb_streams + ms_encoder->layout.nb_coupled_streams) *
+ frame_size, opus_int16);
+ mapping_matrix_multiply_short(matrix, pcm,
+ ms_encoder->layout.nb_channels, buf,
+ ms_encoder->layout.nb_streams + ms_encoder->layout.nb_coupled_streams,
+ frame_size);
+ ret = opus_multistream_encode(ms_encoder, buf, frame_size, data, max_data_bytes);
+ RESTORE_STACK;
+ return ret;
+}
+
+#ifndef DISABLE_FLOAT_API
+int opus_projection_encode_float(OpusProjectionEncoder *st, const float *pcm,
+ int frame_size, unsigned char *data,
+ opus_int32 max_data_bytes)
+{
+#ifdef NONTHREADSAFE_PSEUDOSTACK
+ celt_fatal("Unable to use opus_projection_encode_float() when NONTHREADSAFE_PSEUDOSTACK is defined.");
+#endif
+ MappingMatrix *matrix;
+ OpusMSEncoder *ms_encoder;
+ int ret;
+ VARDECL(float, buf);
+ ALLOC_STACK;
+
+ matrix = get_mixing_matrix(st);
+ ms_encoder = get_multistream_encoder(st);
+ ALLOC(buf, (ms_encoder->layout.nb_streams + ms_encoder->layout.nb_coupled_streams) *
+ frame_size, float);
+ mapping_matrix_multiply_float(matrix, pcm,
+ ms_encoder->layout.nb_channels, buf,
+ ms_encoder->layout.nb_streams + ms_encoder->layout.nb_coupled_streams,
+ frame_size);
+ ret = opus_multistream_encode_float(ms_encoder, buf, frame_size, data, max_data_bytes);
+ RESTORE_STACK;
+ return ret;
+}
+#endif
+
+void opus_projection_encoder_destroy(OpusProjectionEncoder *st)
+{
+ opus_free(st);
+}
+
+int opus_projection_encoder_ctl(OpusProjectionEncoder *st, int request, ...)
+{
+ MappingMatrix *demixing_matrix;
+ OpusMSEncoder *ms_encoder;
+ int ret = OPUS_OK;
+
+ ms_encoder = get_multistream_encoder(st);
+ demixing_matrix = get_demixing_matrix(st);
+
+ va_list ap;
+ va_start(ap, request);
+ switch(request)
+ {
+ case OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST:
+ {
+ opus_int32 *value = va_arg(ap, opus_int32*);
+ if (!value)
+ {
+ goto bad_arg;
+ }
+ *value =
+ ms_encoder->layout.nb_channels * (ms_encoder->layout.nb_streams
+ + ms_encoder->layout.nb_coupled_streams) * sizeof(opus_int16);
+ }
+ break;
+ case OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN_REQUEST:
+ {
+ opus_int32 *value = va_arg(ap, opus_int32*);
+ if (!value)
+ {
+ goto bad_arg;
+ }
+ *value = demixing_matrix->gain;
+ }
+ break;
+ case OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST:
+ {
+ int i;
+ int nb_input_streams;
+ int nb_output_streams;
+ unsigned char *external_char;
+ opus_int16 *internal_short;
+ opus_int32 external_size;
+ opus_int32 internal_size;
+
+ /* (I/O is in relation to the decoder's perspective). */
+ nb_input_streams = ms_encoder->layout.nb_streams +
+ ms_encoder->layout.nb_coupled_streams;
+ nb_output_streams = ms_encoder->layout.nb_channels;
+
+ external_char = va_arg(ap, unsigned char *);
+ external_size = va_arg(ap, opus_uint32);
+ if (!external_char)
+ {
+ goto bad_arg;
+ }
+ internal_short = mapping_matrix_get_data(demixing_matrix);
+ internal_size = nb_input_streams * nb_output_streams * sizeof(opus_int16);
+ if (external_size != internal_size)
+ {
+ goto bad_arg;
+ }
+
+ /* Copy demixing matrix subset to output destination. */
+ for (i = 0; i < nb_input_streams * nb_output_streams; i++)
+ {
+ external_char[2*i] = (unsigned char)internal_short[i];
+ external_char[2*i+1] = (unsigned char)(internal_short[i] >> 8);
+ }
+ }
+ break;
+ default:
+ {
+ ret = opus_multistream_encoder_ctl_va_list(ms_encoder, request, ap);
+ }
+ break;
+ }
+ va_end(ap);
+ return ret;
+
+bad_arg:
+ va_end(ap);
+ return OPUS_BAD_ARG;
+}
+
+#endif /* ENABLE_EXPERIMENTAL_AMBISONICS */
diff --git a/tests/test_opus_projection.c b/tests/test_opus_projection.c
new file mode 100644
index 00000000..4b11c129
--- /dev/null
+++ b/tests/test_opus_projection.c
@@ -0,0 +1,431 @@
+/* Copyright (c) 2017 Google Inc.
+ Written by Andrew Allen */
+/*
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ - Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ - Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include "float_cast.h"
+#include "opus.h"
+#include "test_opus_common.h"
+#include "opus_projection.h"
+#include "mathops.h"
+#include "../src/mapping_matrix.c"
+#include "mathops.c"
+
+#ifdef ENABLE_EXPERIMENTAL_AMBISONICS
+
+#define BUFFER_SIZE 960
+#define MAX_DATA_BYTES 32768
+#define MAX_FRAME_SAMPLES 5760
+
+#define INT16_TO_FLOAT(x) ((1/32768.f)*(float)x)
+
+void print_matrix_short(const opus_int16 *data, int rows, int cols)
+{
+ int i, j;
+ for (i = 0; i < rows; i++)
+ {
+ for (j = 0; j < cols; j++)
+ {
+ fprintf(stderr, "%8.5f ", (float)INT16_TO_FLOAT(data[j * rows + i]));
+ }
+ fprintf(stderr, "\n");
+ }
+ fprintf(stderr, "\n");
+}
+
+void print_matrix_float(const float *data, int rows, int cols)
+{
+ int i, j;
+ for (i = 0; i < rows; i++)
+ {
+ for (j = 0; j < cols; j++)
+ {
+ fprintf(stderr, "%8.5f ", data[j * rows + i]);
+ }
+ fprintf(stderr, "\n");
+ }
+ fprintf(stderr, "\n");
+}
+
+void print_matrix(MappingMatrix *matrix)
+{
+ opus_int16 *data;
+
+ fprintf(stderr, "%d x %d, gain: %d\n", matrix->rows, matrix->cols,
+ matrix->gain);
+
+ data = mapping_matrix_get_data(matrix);
+ print_matrix_short(data, matrix->rows, matrix->cols);
+}
+
+int assert_transform_short(
+ const opus_int16 *a, const opus_int16 *b, int size, opus_int16 tolerance)
+{
+ int i;
+ for (i = 0; i < size; i++)
+ {
+ if (abs(a[i] - b[i]) > tolerance)
+ {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+int assert_transform_float(
+ const float *a, const float *b, int size, float tolerance)
+{
+ int i;
+ for (i = 0; i < size; i++)
+ {
+ if (fabsf(a[i] - b[i]) > tolerance)
+ {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+void test_matrix_transform(void)
+{
+ /* Create testing mixing matrix (4 x 3), gain 0dB:
+ * [ 0 1 0 ]
+ * [ 1 0 0 ]
+ * [ 0 0 0 ]
+ * [ 0 0 1 ]
+ */
+ opus_int32 matrix_size;
+ MappingMatrix *testing_matrix;
+ const opus_int16 testing_matrix_data[12] = {
+ 0, 32767, 0, 0, 32767, 0, 0, 0, 0, 0, 0, 32767 };
+
+ const int frame_size = 10;
+ const opus_int16 input[30] = {
+ 32767, 0, -32768, 29491, -3277, -29491, 26214, -6554, -26214, 22938, -9830,
+ -22938, 19661, -13107, -19661, 16384, -16384, -16384, 13107, -19661, -13107,
+ 9830, -22938, -9830, 6554, -26214, -6554, 3277, -29491, -3277};
+ const opus_int16 expected_output[40] = {
+ 0, 32767, 0, -32768, -3277, 29491, 0, -29491, -6554, 26214, 0, -26214,
+ -9830, 22938, 0, -22938, -13107, 19661, 0, -19661, -16384, 16384, 0, -16384,
+ -19661, 13107, 0, -13107, -22938, 9830, 0, -9830, -26214, 6554, 0, -6554,
+ -29491, 3277, 0, -3277};
+ opus_int16 output[40] = {0};
+
+#ifndef DISABLE_FLOAT_API
+ int i;
+ /* Sample-accurate to -93.9794 dB */
+ float flt_tolerance = 2e-5f;
+ float input32[30] = {0};
+ float output32[40] = {0};
+ float expected_output32[40] = {0};
+
+ /* Convert short to float representations. */
+ for (i = 0; i < 30; i++)
+ {
+ input32[i] = INT16_TO_FLOAT(input[i]);
+ }
+ for (i = 0; i < 40; i++)
+ {
+ expected_output32[i] = INT16_TO_FLOAT(expected_output[i]);
+ }
+#endif /* DISABLE_FLOAT_API */
+
+ /* Create the matrix. */
+ matrix_size = mapping_matrix_get_size(4, 3);
+ testing_matrix = (MappingMatrix *)opus_alloc(matrix_size);
+ mapping_matrix_init(testing_matrix, 4, 3, 0, testing_matrix_data,
+ 12 * sizeof(opus_int16));
+
+ mapping_matrix_multiply_short(testing_matrix, input, testing_matrix->cols,
+ output, testing_matrix->rows, frame_size);
+ if (!assert_transform_short(output, expected_output, 40, 1))
+ {
+ fprintf(stderr, "Matrix:\n");
+ print_matrix(testing_matrix);
+
+ fprintf(stderr, "Input (short):\n");
+ print_matrix_short(input, testing_matrix->cols, frame_size);
+
+ fprintf(stderr, "Expected Output (short):\n");
+ print_matrix_short(expected_output, testing_matrix->rows, frame_size);
+
+ fprintf(stderr, "Output (short):\n");
+ print_matrix_short(output, testing_matrix->rows, frame_size);
+
+ goto bad_cleanup;
+ }
+
+#ifndef DISABLE_FLOAT_API
+ mapping_matrix_multiply_float(testing_matrix, input32, testing_matrix->cols,
+ output32, testing_matrix->rows, frame_size);
+ if (!assert_transform_float(output32, expected_output32, 40, flt_tolerance))
+ {
+ fprintf(stderr, "Matrix:\n");
+ print_matrix(testing_matrix);
+
+ fprintf(stderr, "Input (float):\n");
+ print_matrix_float(input32, testing_matrix->cols, frame_size);
+
+ fprintf(stderr, "Expected Output (float):\n");
+ print_matrix_float(expected_output32, testing_matrix->rows, frame_size);
+
+ fprintf(stderr, "Output (float):\n");
+ print_matrix_float(output32, testing_matrix->rows, frame_size);
+
+ goto bad_cleanup;
+ }
+#endif
+ opus_free(testing_matrix);
+ return;
+bad_cleanup:
+ opus_free(testing_matrix);
+ test_failed();
+}
+
+void test_creation_arguments(const int channels, const int mapping_family)
+{
+ int streams;
+ int coupled_streams;
+ int enc_error;
+ int dec_error;
+ int ret;
+ OpusProjectionEncoder *st_enc = NULL;
+ OpusProjectionDecoder *st_dec = NULL;
+
+ const opus_int32 Fs = 48000;
+ const int application = OPUS_APPLICATION_AUDIO;
+
+ int order_plus_one = (int)floor(sqrt((float)channels));
+ int nondiegetic_channels = channels - order_plus_one * order_plus_one;
+
+ int is_channels_valid = 0;
+ int is_projection_valid = 0;
+
+ st_enc = opus_projection_ambisonics_encoder_create(Fs, channels,
+ mapping_family, &streams, &coupled_streams, application, &enc_error);
+ if (st_enc != NULL)
+ {
+ opus_int32 matrix_size;
+ unsigned char *matrix;
+
+ ret = opus_projection_encoder_ctl(st_enc,
+ OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST, &matrix_size);
+ if (ret != OPUS_OK || !matrix_size)
+ test_failed();
+
+ matrix = (unsigned char *)opus_alloc(matrix_size);
+ ret = opus_projection_encoder_ctl(st_enc,
+ OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST, matrix, matrix_size);
+
+ opus_projection_encoder_destroy(st_enc);
+
+ st_dec = opus_projection_decoder_create(Fs, channels, streams,
+ coupled_streams, matrix, matrix_size, &dec_error);
+ if (st_dec != NULL)
+ {
+ opus_projection_decoder_destroy(st_dec);
+ }
+ opus_free(matrix);
+ }
+
+ is_channels_valid = (order_plus_one >= 2 && order_plus_one <= 4) &&
+ (nondiegetic_channels == 0 || nondiegetic_channels == 2);
+ is_projection_valid = (enc_error == OPUS_OK && dec_error == OPUS_OK);
+ if (is_channels_valid ^ is_projection_valid)
+ {
+ fprintf(stderr, "Channels: %d, Family: %d\n", channels, mapping_family);
+ fprintf(stderr, "Order+1: %d, Non-diegetic Channels: %d\n",
+ order_plus_one, nondiegetic_channels);
+ fprintf(stderr, "Streams: %d, Coupled Streams: %d\n",
+ streams, coupled_streams);
+ test_failed();
+ }
+}
+
+void generate_music(short *buf, opus_int32 len, opus_int32 channels)
+{
+ opus_int32 i,j,k;
+ opus_int32 *a,*b,*c,*d;
+ a = (opus_int32 *)malloc(sizeof(opus_int32) * channels);
+ b = (opus_int32 *)malloc(sizeof(opus_int32) * channels);
+ c = (opus_int32 *)malloc(sizeof(opus_int32) * channels);
+ d = (opus_int32 *)malloc(sizeof(opus_int32) * channels);
+ memset(a, 0, sizeof(opus_int32) * channels);
+ memset(b, 0, sizeof(opus_int32) * channels);
+ memset(c, 0, sizeof(opus_int32) * channels);
+ memset(d, 0, sizeof(opus_int32) * channels);
+ j=0;
+
+ for(i=0;i<len;i++)
+ {
+ for(k=0;k<channels;k++)
+ {
+ opus_uint32 r;
+ opus_int32 v;
+ v=(((j*((j>>12)^((j>>10|j>>12)&26&j>>7)))&128)+128)<<15;
+ r=fast_rand();v+=r&65535;v-=r>>16;
+ b[k]=v-a[k]+((b[k]*61+32)>>6);a[k]=v;
+ c[k]=(30*(c[k]+b[k]+d[k])+32)>>6;d[k]=b[k];
+ v=(c[k]+128)>>8;
+ buf[i*channels+k]=v>32767?32767:(v<-32768?-32768:v);
+ if(i%6==0)j++;
+ }
+ }
+
+ free(a);
+ free(b);
+ free(c);
+ free(d);
+}
+
+void test_encode_decode(opus_int32 bitrate, opus_int32 channels,
+ const int mapping_family)
+{
+ const opus_int32 Fs = 48000;
+ const int application = OPUS_APPLICATION_AUDIO;
+
+ OpusProjectionEncoder *st_enc;
+ OpusProjectionDecoder *st_dec;
+ int streams;
+ int coupled;
+ int error;
+ short *buffer_in;
+ short *buffer_out;
+ unsigned char data[MAX_DATA_BYTES] = { 0 };
+ int len;
+ int out_samples;
+ opus_int32 matrix_size = 0;
+ unsigned char *matrix = NULL;
+
+ buffer_in = (short *)malloc(sizeof(short) * BUFFER_SIZE * channels);
+ buffer_out = (short *)malloc(sizeof(short) * BUFFER_SIZE * channels);
+
+ st_enc = opus_projection_ambisonics_encoder_create(Fs, channels,
+ mapping_family, &streams, &coupled, application, &error);
+ if (error != OPUS_OK) {
+ fprintf(stderr,
+ "Couldn\'t create encoder with %d channels and mapping family %d.\n",
+ channels, mapping_family);
+ free(buffer_in);
+ free(buffer_out);
+ test_failed();
+ }
+
+ error = opus_projection_encoder_ctl(st_enc,
+ OPUS_SET_BITRATE(bitrate * 1000 * (streams + coupled)));
+ if (error != OPUS_OK)
+ {
+ goto bad_cleanup;
+ }
+
+ error = opus_projection_encoder_ctl(st_enc,
+ OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST, &matrix_size);
+ if (error != OPUS_OK || !matrix_size)
+ {
+ goto bad_cleanup;
+ }
+
+ matrix = (unsigned char *)opus_alloc(matrix_size);
+ error = opus_projection_encoder_ctl(st_enc,
+ OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST, matrix, matrix_size);
+
+ st_dec = opus_projection_decoder_create(Fs, channels, streams, coupled,
+ matrix, matrix_size, &error);
+ opus_free(matrix);
+
+ if (error != OPUS_OK) {
+ fprintf(stderr,
+ "Couldn\'t create decoder with %d channels, %d streams "
+ "and %d coupled streams.\n", channels, streams, coupled);
+ goto bad_cleanup;
+ }
+
+ generate_music(buffer_in, BUFFER_SIZE, channels);
+
+ len = opus_projection_encode(
+ st_enc, buffer_in, BUFFER_SIZE, data, MAX_DATA_BYTES);
+ if(len<0 || len>MAX_DATA_BYTES) {
+ fprintf(stderr,"opus_encode() returned %d\n", len);
+ goto bad_cleanup;
+ }
+
+ out_samples = opus_projection_decode(
+ st_dec, data, len, buffer_out, MAX_FRAME_SAMPLES, 0);
+ if(out_samples!=BUFFER_SIZE) {
+ fprintf(stderr,"opus_decode() returned %d\n", out_samples);
+ goto bad_cleanup;
+ }
+
+ free(buffer_in);
+ free(buffer_out);
+ return;
+bad_cleanup:
+ free(buffer_in);
+ free(buffer_out);
+ test_failed();
+}
+
+int main(int _argc, char **_argv)
+{
+ unsigned int i;
+
+ (void)_argc;
+ (void)_argv;
+
+ /* Test matrix creation/multiplication. */
+ test_matrix_transform();
+
+ /* Test full range of channels in creation arguments. */
+ for (i = 0; i < 255; i++)
+ test_creation_arguments(i, 253);
+
+ /* Test encode/decode pipeline. */
+ test_encode_decode(64 * 16, 16, 253);
+
+ fprintf(stderr, "All projection tests passed.\n");
+ return 0;
+}
+
+#else
+
+int main(int _argc, char **_argv)
+{
+ (void)_argc;
+ (void)_argv;
+ fprintf(stderr, "Projection tests are disabled. "
+ "Configure with --enable-ambisonics for support.\n");
+ return 0;
+}
+
+#endif /* ENABLE_EXPERIMENTAL_AMBISONICS */