aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/cubeb_mixer.cpp
diff options
context:
space:
mode:
authorJean-Yves Avenard <[email protected]>2018-03-19 14:57:07 +0100
committerPaul Adenot <[email protected]>2018-03-19 14:57:07 +0100
commit789eaaa3b0d77b55e110353972d6713889f510ea (patch)
treefbeb48958ab83c329193f37c54133c9bc68aac4a /src/cubeb_mixer.cpp
parentc88a484e1aac878419946334c49d35d6948beec7 (diff)
downloadcubeb-789eaaa3b0d77b55e110353972d6713889f510ea.tar.gz
cubeb-789eaaa3b0d77b55e110353972d6713889f510ea.zip
Multi-channels support for windows/mac/linux (#426)
* Add QUAD and QUAD_LFE layouts. * Remove dual mono layout. It makes no sense to have a case for those as the data structure used (a bitmask) do not allow to represent this channel layout (a channel can only be present once). As such it was a non-functional layout * Fix up cubeb_pulse compilation using C++ keyword. * Remove the concept of preferred layout. Channel layout is derived by the content being played. The concept of preferred layout is meaningless. Either we have a layout defined, or we don't. There's no in-between. So we remove it. * Remove CHANNEL_MONO concept. * Add cubeb_sample_size convenience method. * Rework cubeb_mixer. This completely replace the existing remixer which had serious limitations: 1- Had no memory bound checks 2- Could only downmix 5.1 and 7.1 to stereo. This mixer allows to convert from any sane layout to any other and work directly on interleaved samples. This cubeb_mixer doesn't have an API compatible with the previous one. This commit is non-fonctional, and was split for ease of review. * Fix remixing on mac, windows and pulse backend. * Make cubeb_mixer creation infallible. Rather than ignore nonsensical layouts, we attempt to play it according to the stream channels count instead. The audio data will be played as-is, dropping the extra channels or inserting silence where needed. * User proper sample size when calculating offsets. Should the user data be of a different type to what the AudioUnit output is set to, we would have written outside the end of our allocated buffer. * Fix input mixing and clarify frames vs samples terminology * If a layout is unknown or invalid, always treat it as plain stereo or mono.
Diffstat (limited to 'src/cubeb_mixer.cpp')
-rw-r--r--src/cubeb_mixer.cpp1034
1 files changed, 562 insertions, 472 deletions
diff --git a/src/cubeb_mixer.cpp b/src/cubeb_mixer.cpp
index 62ce867..1a8cf99 100644
--- a/src/cubeb_mixer.cpp
+++ b/src/cubeb_mixer.cpp
@@ -3,555 +3,645 @@
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
+ *
+ * Adapted from code based on libswresample's rematrix.c
*/
+#define NOMINMAX
+
+#include <algorithm>
#include <cassert>
+#include <climits>
+#include <cmath>
+#include <cstdlib>
+#ifdef _MSC_VER
+#include <intrin.h>
+#endif
+#include <memory>
+#include <type_traits>
#include "cubeb-internal.h"
#include "cubeb_mixer.h"
+#include "cubeb_utils.h"
+
+#ifndef FF_ARRAY_ELEMS
+#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0]))
+#endif
+
+#define CHANNELS_MAX 32
+#define FRONT_LEFT 0
+#define FRONT_RIGHT 1
+#define FRONT_CENTER 2
+#define LOW_FREQUENCY 3
+#define BACK_LEFT 4
+#define BACK_RIGHT 5
+#define FRONT_LEFT_OF_CENTER 6
+#define FRONT_RIGHT_OF_CENTER 7
+#define BACK_CENTER 8
+#define SIDE_LEFT 9
+#define SIDE_RIGHT 10
+#define TOP_CENTER 11
+#define TOP_FRONT_LEFT 12
+#define TOP_FRONT_CENTER 13
+#define TOP_FRONT_RIGHT 14
+#define TOP_BACK_LEFT 15
+#define TOP_BACK_CENTER 16
+#define TOP_BACK_RIGHT 17
+#define NUM_NAMED_CHANNELS 18
+
+#ifndef M_SQRT1_2
+#define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */
+#endif
+#ifndef M_SQRT2
+#define M_SQRT2 1.41421356237309504880 /* sqrt(2) */
+#endif
+#define SQRT3_2 1.22474487139158904909 /* sqrt(3/2) */
-// DUAL_MONO(_LFE) is same as STEREO(_LFE).
-#define MASK_MONO (1 << CHANNEL_MONO)
-#define MASK_MONO_LFE (MASK_MONO | (1 << CHANNEL_LFE))
-#define MASK_STEREO ((1 << CHANNEL_LEFT) | (1 << CHANNEL_RIGHT))
-#define MASK_STEREO_LFE (MASK_STEREO | (1 << CHANNEL_LFE))
-#define MASK_3F (MASK_STEREO | (1 << CHANNEL_CENTER))
-#define MASK_3F_LFE (MASK_3F | (1 << CHANNEL_LFE))
-#define MASK_2F1 (MASK_STEREO | (1 << CHANNEL_RCENTER))
-#define MASK_2F1_LFE (MASK_2F1 | (1 << CHANNEL_LFE))
-#define MASK_3F1 (MASK_3F | (1 << CHANNEL_RCENTER))
-#define MASK_3F1_LFE (MASK_3F1 | (1 << CHANNEL_LFE))
-#define MASK_2F2 (MASK_STEREO | (1 << CHANNEL_LS) | (1 << CHANNEL_RS))
-#define MASK_2F2_LFE (MASK_2F2 | (1 << CHANNEL_LFE))
-#define MASK_3F2 (MASK_2F2 | (1 << CHANNEL_CENTER))
-#define MASK_3F2_LFE (MASK_3F2 | (1 << CHANNEL_LFE))
-#define MASK_3F3R_LFE (MASK_3F2_LFE | (1 << CHANNEL_RCENTER))
-#define MASK_3F4_LFE (MASK_3F2_LFE | (1 << CHANNEL_RLS) | (1 << CHANNEL_RRS))
-
-cubeb_channel_layout cubeb_channel_map_to_layout(cubeb_channel_map const * channel_map)
+#define C30DB M_SQRT2
+#define C15DB 1.189207115
+#define C__0DB 1.0
+#define C_15DB 0.840896415
+#define C_30DB M_SQRT1_2
+#define C_45DB 0.594603558
+#define C_60DB 0.5
+
+unsigned int cubeb_channel_layout_nb_channels(cubeb_channel_layout x)
{
- uint32_t channel_mask = 0;
- for (uint8_t i = 0 ; i < channel_map->channels ; ++i) {
- if (channel_map->map[i] == CHANNEL_INVALID ||
- channel_map->map[i] == CHANNEL_UNMAPPED) {
- return CUBEB_LAYOUT_UNDEFINED;
+#if __GNUC__ || __clang__
+ return __builtin_popcount (x);
+#elif _MSC_VER
+ return __popcnt(x);
+#else
+ x -= (x >> 1) & 0x55555555;
+ x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
+ x = (x + (x >> 4)) & 0x0F0F0F0F;
+ x += x >> 8;
+ return (x + (x >> 16)) & 0x3F;
+#endif
+}
+struct MixerContext {
+ MixerContext(cubeb_sample_format f,
+ uint32_t in_channels,
+ cubeb_channel_layout in,
+ uint32_t out_channels,
+ cubeb_channel_layout out)
+ : _format(f)
+ , _in_ch_layout(in == CUBEB_LAYOUT_UNDEFINED
+ ? (in_channels == 1
+ ? CUBEB_LAYOUT_MONO
+ : (in_channels == 2 ? CUBEB_LAYOUT_STEREO
+ : CUBEB_LAYOUT_UNDEFINED))
+ : in)
+ , _out_ch_layout(
+ (out == CUBEB_LAYOUT_UNDEFINED
+ ? (out_channels == 1 ? CUBEB_LAYOUT_MONO
+ : (out_channels == 2 ? CUBEB_LAYOUT_STEREO
+ : CUBEB_LAYOUT_UNDEFINED))
+ : out))
+ , _in_ch_count(in_channels)
+ , _out_ch_count(out_channels)
+ {
+ if (in_channels != cubeb_channel_layout_nb_channels(in) ||
+ out_channels != cubeb_channel_layout_nb_channels(out)) {
+ // Mismatch between channels and layout, aborting.
+ return;
}
- channel_mask |= 1 << channel_map->map[i];
+ _valid = init() >= 0;
}
- switch(channel_mask) {
- case MASK_MONO: return CUBEB_LAYOUT_MONO;
- case MASK_MONO_LFE: return CUBEB_LAYOUT_MONO_LFE;
- case MASK_STEREO: return CUBEB_LAYOUT_STEREO;
- case MASK_STEREO_LFE: return CUBEB_LAYOUT_STEREO_LFE;
- case MASK_3F: return CUBEB_LAYOUT_3F;
- case MASK_3F_LFE: return CUBEB_LAYOUT_3F_LFE;
- case MASK_2F1: return CUBEB_LAYOUT_2F1;
- case MASK_2F1_LFE: return CUBEB_LAYOUT_2F1_LFE;
- case MASK_3F1: return CUBEB_LAYOUT_3F1;
- case MASK_3F1_LFE: return CUBEB_LAYOUT_3F1_LFE;
- case MASK_2F2: return CUBEB_LAYOUT_2F2;
- case MASK_2F2_LFE: return CUBEB_LAYOUT_2F2_LFE;
- case MASK_3F2: return CUBEB_LAYOUT_3F2;
- case MASK_3F2_LFE: return CUBEB_LAYOUT_3F2_LFE;
- case MASK_3F3R_LFE: return CUBEB_LAYOUT_3F3R_LFE;
- case MASK_3F4_LFE: return CUBEB_LAYOUT_3F4_LFE;
- default: return CUBEB_LAYOUT_UNDEFINED;
+ static bool even(cubeb_channel_layout layout)
+ {
+ if (!layout) {
+ return true;
+ }
+ if (layout & (layout - 1)) {
+ return true;
+ }
+ return false;
}
-}
-cubeb_layout_map const CUBEB_CHANNEL_LAYOUT_MAPS[CUBEB_LAYOUT_MAX] = {
- { "undefined", 0, CUBEB_LAYOUT_UNDEFINED },
- { "dual mono", 2, CUBEB_LAYOUT_DUAL_MONO },
- { "dual mono lfe", 3, CUBEB_LAYOUT_DUAL_MONO_LFE },
- { "mono", 1, CUBEB_LAYOUT_MONO },
- { "mono lfe", 2, CUBEB_LAYOUT_MONO_LFE },
- { "stereo", 2, CUBEB_LAYOUT_STEREO },
- { "stereo lfe", 3, CUBEB_LAYOUT_STEREO_LFE },
- { "3f", 3, CUBEB_LAYOUT_3F },
- { "3f lfe", 4, CUBEB_LAYOUT_3F_LFE },
- { "2f1", 3, CUBEB_LAYOUT_2F1 },
- { "2f1 lfe", 4, CUBEB_LAYOUT_2F1_LFE },
- { "3f1", 4, CUBEB_LAYOUT_3F1 },
- { "3f1 lfe", 5, CUBEB_LAYOUT_3F1_LFE },
- { "2f2", 4, CUBEB_LAYOUT_2F2 },
- { "2f2 lfe", 5, CUBEB_LAYOUT_2F2_LFE },
- { "3f2", 5, CUBEB_LAYOUT_3F2 },
- { "3f2 lfe", 6, CUBEB_LAYOUT_3F2_LFE },
- { "3f3r lfe", 7, CUBEB_LAYOUT_3F3R_LFE },
- { "3f4 lfe", 8, CUBEB_LAYOUT_3F4_LFE }
-};
+ // Ensure that the layout is sane (that is have symmetrical left/right
+ // channels), if not, layout will be treated as mono.
+ static cubeb_channel_layout clean_layout(cubeb_channel_layout layout)
+ {
+ if (layout && layout != CHANNEL_FRONT_LEFT && !(layout & (layout - 1))) {
+ LOG("Treating layout as mono");
+ return CHANNEL_FRONT_CENTER;
+ }
-static int const CHANNEL_ORDER_TO_INDEX[CUBEB_LAYOUT_MAX][CHANNEL_MAX] = {
-// M | L | R | C | LS | RS | RLS | RC | RRS | LFE
- { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // UNDEFINED
- { -1, 0, 1, -1, -1, -1, -1, -1, -1, -1 }, // DUAL_MONO
- { -1, 0, 1, -1, -1, -1, -1, -1, -1, 2 }, // DUAL_MONO_LFE
- { 0, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // MONO
- { 0, -1, -1, -1, -1, -1, -1, -1, -1, 1 }, // MONO_LFE
- { -1, 0, 1, -1, -1, -1, -1, -1, -1, -1 }, // STEREO
- { -1, 0, 1, -1, -1, -1, -1, -1, -1, 2 }, // STEREO_LFE
- { -1, 0, 1, 2, -1, -1, -1, -1, -1, -1 }, // 3F
- { -1, 0, 1, 2, -1, -1, -1, -1, -1, 3 }, // 3F_LFE
- { -1, 0, 1, -1, -1, -1, -1, 2, -1, -1 }, // 2F1
- { -1, 0, 1, -1, -1, -1, -1, 3, -1, 2 }, // 2F1_LFE
- { -1, 0, 1, 2, -1, -1, -1, 3, -1, -1 }, // 3F1
- { -1, 0, 1, 2, -1, -1, -1, 4, -1, 3 }, // 3F1_LFE
- { -1, 0, 1, -1, 2, 3, -1, -1, -1, -1 }, // 2F2
- { -1, 0, 1, -1, 3, 4, -1, -1, -1, 2 }, // 2F2_LFE
- { -1, 0, 1, 2, 3, 4, -1, -1, -1, -1 }, // 3F2
- { -1, 0, 1, 2, 4, 5, -1, -1, -1, 3 }, // 3F2_LFE
- { -1, 0, 1, 2, 5, 6, -1, 4, -1, 3 }, // 3F3R_LFE
- { -1, 0, 1, 2, 6, 7, 4, -1, 5, 3 }, // 3F4_LFE
-};
+ return layout;
+ }
-// The downmix matrix from TABLE 2 in the ITU-R BS.775-3[1] defines a way to
-// convert 3F2 input data to 1F, 2F, 3F, 2F1, 3F1, 2F2 output data. We extend it
-// to convert 3F2-LFE input data to 1F, 2F, 3F, 2F1, 3F1, 2F2 and their LFEs
-// output data.
-// [1] https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.775-3-201208-I!!PDF-E.pdf
-
-// Number of converted layouts: 1F, 2F, 3F, 2F1, 3F1, 2F2 and their LFEs.
-unsigned int const SUPPORTED_LAYOUT_NUM = 12;
-// Number of input channel for downmix conversion.
-unsigned int const INPUT_CHANNEL_NUM = 6; // 3F2-LFE
-// Max number of possible output channels.
-unsigned int const MAX_OUTPUT_CHANNEL_NUM = 5; // 2F2-LFE or 3F1-LFE
-float const INV_SQRT_2 = 0.707106f; // 1/sqrt(2)
-// Each array contains coefficients that will be multiplied with
-// { L, R, C, LFE, LS, RS } channels respectively.
-static float const DOWNMIX_MATRIX_3F2_LFE[SUPPORTED_LAYOUT_NUM][MAX_OUTPUT_CHANNEL_NUM][INPUT_CHANNEL_NUM] =
-{
-// 1F Mono
- {
- { INV_SQRT_2, INV_SQRT_2, 1, 0, 0.5, 0.5 }, // M
- },
-// 1F Mono-LFE
- {
- { INV_SQRT_2, INV_SQRT_2, 1, 0, 0.5, 0.5 }, // M
- { 0, 0, 0, 1, 0, 0 } // LFE
- },
-// 2F Stereo
- {
- { 1, 0, INV_SQRT_2, 0, INV_SQRT_2, 0 }, // L
- { 0, 1, INV_SQRT_2, 0, 0, INV_SQRT_2 } // R
- },
-// 2F Stereo-LFE
- {
- { 1, 0, INV_SQRT_2, 0, INV_SQRT_2, 0 }, // L
- { 0, 1, INV_SQRT_2, 0, 0, INV_SQRT_2 }, // R
- { 0, 0, 0, 1, 0, 0 } // LFE
- },
-// 3F
- {
- { 1, 0, 0, 0, INV_SQRT_2, 0 }, // L
- { 0, 1, 0, 0, 0, INV_SQRT_2 }, // R
- { 0, 0, 1, 0, 0, 0 } // C
- },
-// 3F-LFE
- {
- { 1, 0, 0, 0, INV_SQRT_2, 0 }, // L
- { 0, 1, 0, 0, 0, INV_SQRT_2 }, // R
- { 0, 0, 1, 0, 0, 0 }, // C
- { 0, 0, 0, 1, 0, 0 } // LFE
- },
-// 2F1
- {
- { 1, 0, INV_SQRT_2, 0, 0, 0 }, // L
- { 0, 1, INV_SQRT_2, 0, 0, 0 }, // R
- { 0, 0, 0, 0, INV_SQRT_2, INV_SQRT_2 } // S
- },
-// 2F1-LFE
+ static bool sane_layout(cubeb_channel_layout layout)
{
- { 1, 0, INV_SQRT_2, 0, 0, 0 }, // L
- { 0, 1, INV_SQRT_2, 0, 0, 0 }, // R
- { 0, 0, 0, 1, 0, 0 }, // LFE
- { 0, 0, 0, 0, INV_SQRT_2, INV_SQRT_2 } // S
- },
-// 3F1
- {
- { 1, 0, 0, 0, 0, 0 }, // L
- { 0, 1, 0, 0, 0, 0 }, // R
- { 0, 0, 1, 0, 0, 0 }, // C
- { 0, 0, 0, 0, INV_SQRT_2, INV_SQRT_2 } // S
- },
-// 3F1-LFE
- {
- { 1, 0, 0, 0, 0, 0 }, // L
- { 0, 1, 0, 0, 0, 0 }, // R
- { 0, 0, 1, 0, 0, 0 }, // C
- { 0, 0, 0, 1, 0, 0 }, // LFE
- { 0, 0, 0, 0, INV_SQRT_2, INV_SQRT_2 } // S
- },
-// 2F2
- {
- { 1, 0, INV_SQRT_2, 0, 0, 0 }, // L
- { 0, 1, INV_SQRT_2, 0, 0, 0 }, // R
- { 0, 0, 0, 0, 1, 0 }, // LS
- { 0, 0, 0, 0, 0, 1 } // RS
- },
-// 2F2-LFE
- {
- { 1, 0, INV_SQRT_2, 0, 0, 0 }, // L
- { 0, 1, INV_SQRT_2, 0, 0, 0 }, // R
- { 0, 0, 0, 1, 0, 0 }, // LFE
- { 0, 0, 0, 0, 1, 0 }, // LS
- { 0, 0, 0, 0, 0, 1 } // RS
+ if (!(layout & CUBEB_LAYOUT_3F)) { // at least 1 front speaker
+ return false;
+ }
+ if (!even(layout & (CHANNEL_FRONT_LEFT |
+ CHANNEL_FRONT_RIGHT))) { // no asymetric front
+ return false;
+ }
+ if (!even(layout &
+ (CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT))) { // no asymetric side
+ return false;
+ }
+ if (!even(layout & (CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT))) {
+ return false;
+ }
+ if (!even(layout &
+ (CHANNEL_FRONT_LEFT_OF_CENTER | CHANNEL_FRONT_RIGHT_OF_CENTER))) {
+ return false;
+ }
+ if (cubeb_channel_layout_nb_channels(layout) >= CHANNELS_MAX) {
+ return false;
+ }
+ return true;
}
+
+ int auto_matrix();
+ int init();
+
+ const cubeb_sample_format _format;
+ const cubeb_channel_layout _in_ch_layout; ///< input channel layout
+ const cubeb_channel_layout _out_ch_layout; ///< output channel layout
+ const uint32_t _in_ch_count; ///< input channel count
+ const uint32_t _out_ch_count; ///< output channel count
+ const float _surround_mix_level = C_30DB; ///< surround mixing level
+ const float _center_mix_level = C_30DB; ///< center mixing level
+ const float _lfe_mix_level = 1; ///< LFE mixing level
+ double _matrix[CHANNELS_MAX][CHANNELS_MAX] = {{ 0 }}; ///< floating point rematrixing coefficients
+ float _matrix_flt[CHANNELS_MAX][CHANNELS_MAX] = {{ 0 }}; ///< single precision floating point rematrixing coefficients
+ int32_t _matrix32[CHANNELS_MAX][CHANNELS_MAX] = {{ 0 }}; ///< 17.15 fixed point rematrixing coefficients
+ uint8_t _matrix_ch[CHANNELS_MAX][CHANNELS_MAX+1] = {{ 0 }}; ///< Lists of input channels per output channel that have non zero rematrixing coefficients
+ bool _clipping = false; ///< Set to true if clipping detection is required
+ bool _valid = false; ///< Set to true if context is valid.
};
-// Convert audio data from 3F2(-LFE) to 1F, 2F, 3F, 2F1, 3F1, 2F2 and their LFEs.
-//
-// ITU-R BS.775-3[1] provides spec for downmixing 3F2 data to 1F, 2F, 3F, 2F1,
-// 3F1, 2F2 data. We simply add LFE to its defined matrix. If both the input
-// and output have LFE channel, then we pass it's data. If only input or output
-// has LFE, then we either drop it or append 0 to the LFE channel.
-//
-// Fig. 1:
-// |<-------------- 1 -------------->|<-------------- 2 -------------->|
-// +----+----+----+------+-----+-----+----+----+----+------+-----+-----+
-// | L0 | R0 | C0 | LFE0 | LS0 | RS0 | L1 | R1 | C1 | LFE1 | LS1 | RS1 | ...
-// +----+----+----+------+-----+-----+----+----+----+------+-----+-----+
-//
-// Fig. 2:
-// |<-- 1 -->|<-- 2 -->|
-// +----+----+----+----+
-// | L0 | R0 | L1 | R1 | ...
-// +----+----+----+----+
-//
-// The figures above shows an example for downmixing from 3F2-LFE(Fig. 1) to
-// to stereo(Fig. 2), where L0 = L0 + 0.707 * (C0 + LS0),
-// R0 = R0 + 0.707 * (C0 + RS0), L1 = L1 + 0.707 * (C1 + LS1),
-// R1 = R1 + 0.707 * (C1 + RS1), ...
-//
-// Nevertheless, the downmixing method is a little bit different on OSX.
-// The audio rendering mechanism on OS X will drop the extra channels beyond
-// the channels that audio device can provide. The trick here is that OSX allows
-// us to set the layout containing other channels that the output device can
-// NOT provide. For example, setting 3F2-LFE layout to a stereo device is fine.
-// Therefore, OSX expects we fill the buffer for playing sound by the defined
-// layout, so there are some will-be-dropped data in the buffer:
-//
-// +---+---+---+-----+----+----+
-// | L | R | C | LFE | LS | RS | ...
-// +---+---+---+-----+----+----+
-// ^ ^ ^ ^
-// The data for these four channels will be dropped!
-//
-// To keep all the information, we need to downmix the data before it's dropped.
-// The figure below shows an example for downmixing from 3F2-LFE(Fig. 1)
-// to stereo(Fig. 3) on OSX, where the LO, R0, L1, R0 are same as above.
-//
-// Fig. 3:
-// |<---------- 1 ---------->|<---------- 2 ---------->|
-// +----+----+---+---+---+---+----+----+---+---+---+---+
-// | L0 | R0 | x | x | x | x | L1 | R1 | x | x | x | x | ...
-// +----+----+---+---+---+---+----+----+---+---+---+---+
-// |<-- dummy -->| |<-- dummy -->|
-template<typename T>
-bool
-downmix_3f2(unsigned long inframes,
- T const * const in, unsigned long in_len,
- T * out, unsigned long out_len,
- cubeb_channel_layout in_layout, cubeb_channel_layout out_layout)
+int MixerContext::auto_matrix()
{
- if ((in_layout != CUBEB_LAYOUT_3F2 && in_layout != CUBEB_LAYOUT_3F2_LFE) ||
- out_layout < CUBEB_LAYOUT_MONO || out_layout > CUBEB_LAYOUT_2F2_LFE) {
- return false;
+ double matrix[NUM_NAMED_CHANNELS][NUM_NAMED_CHANNELS] = { { 0 } };
+ double maxcoef = 0;
+ float maxval;
+
+ cubeb_channel_layout in_ch_layout = clean_layout(_in_ch_layout);
+ cubeb_channel_layout out_ch_layout = clean_layout(_out_ch_layout);
+
+ if (!sane_layout(in_ch_layout)) {
+ // Channel Not Supported
+ LOG("Input Layout %x is not supported", _in_ch_layout);
+ return -1;
}
- unsigned int in_channels = CUBEB_CHANNEL_LAYOUT_MAPS[in_layout].channels;
- unsigned int out_channels = CUBEB_CHANNEL_LAYOUT_MAPS[out_layout].channels;
-
- // Conversion from 3F2 to 2F2-LFE or 3F1-LFE is allowed, so we use '<=' instead of '<'.
- assert(out_channels <= in_channels);
-
- auto & downmix_matrix = DOWNMIX_MATRIX_3F2_LFE[out_layout - CUBEB_LAYOUT_MONO]; // The matrix is started from mono.
- unsigned long out_index = 0;
- for (unsigned long i = 0 ; i < inframes * in_channels; i += in_channels) {
- for (unsigned int j = 0; j < out_channels; ++j) {
- T sample = 0;
- for (unsigned int k = 0 ; k < INPUT_CHANNEL_NUM ; ++k) {
- // 3F2-LFE has 6 channels: L, R, C, LFE, LS, RS, while 3F2 has only 5
- // channels: L, R, C, LS, RS. Thus, we need to append 0 to LFE(index 3)
- // to simulate a 3F2-LFE data when input layout is 3F2.
- assert((in_layout == CUBEB_LAYOUT_3F2_LFE || k < 3) ? (i + k < in_len) : (k == 3) ? true : (i + k - 1 < in_len));
- T data = (in_layout == CUBEB_LAYOUT_3F2_LFE) ? in[i + k] : (k == 3) ? 0 : in[i + ((k < 3) ? k : k - 1)];
- sample += downmix_matrix[j][k] * data;
- }
- assert(out_index + j < out_len);
- out[out_index + j] = sample;
+ if (!sane_layout(out_ch_layout)) {
+ LOG("Output Layout %x is not supported", _out_ch_layout);
+ return -1;
+ }
+
+ for (uint32_t i = 0; i < FF_ARRAY_ELEMS(matrix); i++) {
+ if (in_ch_layout & out_ch_layout & (1U << i)) {
+ matrix[i][i] = 1.0;
}
-#if defined(USE_AUDIOUNIT)
- out_index += in_channels;
-#else
- out_index += out_channels;
-#endif
}
- return true;
-}
+ cubeb_channel_layout unaccounted = in_ch_layout & ~out_ch_layout;
-/* Map the audio data by channel name. */
-template<class T>
-bool
-mix_remap(long inframes,
- T const * const in, unsigned long in_len,
- T * out, unsigned long out_len,
- cubeb_channel_layout in_layout, cubeb_channel_layout out_layout)
-{
- assert(in_layout != out_layout && inframes >= 0);
+ // Rematrixing is done via a matrix of coefficient that should be applied to
+ // all channels. Channels are treated as pair and must be symmetrical (if a
+ // left channel exists, the corresponding right should exist too) unless the
+ // output layout has similar layout. Channels are then mixed toward the front
+ // center or back center if they exist with a slight bias toward the front.
- // We might overwrite the data before we copied them to the mapped index
- // (e.g. upmixing from stereo to 2F1 or mapping [L, R] to [R, L])
- if (in == out) {
- return false;
+ if (unaccounted & CHANNEL_FRONT_CENTER) {
+ if ((out_ch_layout & CUBEB_LAYOUT_STEREO) == CUBEB_LAYOUT_STEREO) {
+ if (in_ch_layout & CUBEB_LAYOUT_STEREO) {
+ matrix[FRONT_LEFT][FRONT_CENTER] += _center_mix_level;
+ matrix[FRONT_RIGHT][FRONT_CENTER] += _center_mix_level;
+ } else {
+ matrix[FRONT_LEFT][FRONT_CENTER] += M_SQRT1_2;
+ matrix[FRONT_RIGHT][FRONT_CENTER] += M_SQRT1_2;
+ }
+ }
}
-
- unsigned int in_channels = CUBEB_CHANNEL_LAYOUT_MAPS[in_layout].channels;
- unsigned int out_channels = CUBEB_CHANNEL_LAYOUT_MAPS[out_layout].channels;
-
- uint32_t in_layout_mask = 0;
- for (unsigned int i = 0 ; i < in_channels ; ++i) {
- in_layout_mask |= 1 << CHANNEL_INDEX_TO_ORDER[in_layout][i];
+ if (unaccounted & CUBEB_LAYOUT_STEREO) {
+ if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][FRONT_LEFT] += M_SQRT1_2;
+ matrix[FRONT_CENTER][FRONT_RIGHT] += M_SQRT1_2;
+ if (in_ch_layout & CHANNEL_FRONT_CENTER)
+ matrix[FRONT_CENTER][FRONT_CENTER] = _center_mix_level * M_SQRT2;
+ }
}
- uint32_t out_layout_mask = 0;
- for (unsigned int i = 0 ; i < out_channels ; ++i) {
- out_layout_mask |= 1 << CHANNEL_INDEX_TO_ORDER[out_layout][i];
+ if (unaccounted & CHANNEL_BACK_CENTER) {
+ if (out_ch_layout & CHANNEL_BACK_LEFT) {
+ matrix[BACK_LEFT][BACK_CENTER] += M_SQRT1_2;
+ matrix[BACK_RIGHT][BACK_CENTER] += M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_SIDE_LEFT) {
+ matrix[SIDE_LEFT][BACK_CENTER] += M_SQRT1_2;
+ matrix[SIDE_RIGHT][BACK_CENTER] += M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ if (unaccounted & (CHANNEL_BACK_LEFT | CHANNEL_SIDE_LEFT)) {
+ matrix[FRONT_LEFT][BACK_CENTER] -= _surround_mix_level * M_SQRT1_2;
+ matrix[FRONT_RIGHT][BACK_CENTER] += _surround_mix_level * M_SQRT1_2;
+ } else {
+ matrix[FRONT_LEFT][BACK_CENTER] -= _surround_mix_level;
+ matrix[FRONT_RIGHT][BACK_CENTER] += _surround_mix_level;
+ }
+ } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][BACK_CENTER] +=
+ _surround_mix_level * M_SQRT1_2;
+ }
}
-
- // If there is no matched channel, then do nothing.
- if (!(out_layout_mask & in_layout_mask)) {
- return false;
+ if (unaccounted & CHANNEL_BACK_LEFT) {
+ if (out_ch_layout & CHANNEL_BACK_CENTER) {
+ matrix[BACK_CENTER][BACK_LEFT] += M_SQRT1_2;
+ matrix[BACK_CENTER][BACK_RIGHT] += M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_SIDE_LEFT) {
+ if (in_ch_layout & CHANNEL_SIDE_LEFT) {
+ matrix[SIDE_LEFT][BACK_LEFT] += M_SQRT1_2;
+ matrix[SIDE_RIGHT][BACK_RIGHT] += M_SQRT1_2;
+ } else {
+ matrix[SIDE_LEFT][BACK_LEFT] += 1.0;
+ matrix[SIDE_RIGHT][BACK_RIGHT] += 1.0;
+ }
+ } else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ matrix[FRONT_LEFT][BACK_LEFT] -= _surround_mix_level * M_SQRT1_2;
+ matrix[FRONT_LEFT][BACK_RIGHT] -= _surround_mix_level * M_SQRT1_2;
+ matrix[FRONT_RIGHT][BACK_LEFT] += _surround_mix_level * M_SQRT1_2;
+ matrix[FRONT_RIGHT][BACK_RIGHT] += _surround_mix_level * M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][BACK_LEFT] += _surround_mix_level * M_SQRT1_2;
+ matrix[FRONT_CENTER][BACK_RIGHT] += _surround_mix_level * M_SQRT1_2;
+ }
}
- for (unsigned long i = 0, out_index = 0; i < (unsigned long)inframes * in_channels; i += in_channels, out_index += out_channels) {
- for (unsigned int j = 0; j < out_channels; ++j) {
- cubeb_channel channel = CHANNEL_INDEX_TO_ORDER[out_layout][j];
- uint32_t channel_mask = 1 << channel;
- int channel_index = CHANNEL_ORDER_TO_INDEX[in_layout][channel];
- assert(channel_index >= -1);
- assert(out_index + j < out_len);
- if (in_layout_mask & channel_mask) {
- assert(channel_index != -1);
- assert(i + channel_index < in_len);
- out[out_index + j] = in[i + channel_index];
+ if (unaccounted & CHANNEL_SIDE_LEFT) {
+ if (out_ch_layout & CHANNEL_BACK_LEFT) {
+ /* if back channels do not exist in the input, just copy side
+ channels to back channels, otherwise mix side into back */
+ if (in_ch_layout & CHANNEL_BACK_LEFT) {
+ matrix[BACK_LEFT][SIDE_LEFT] += M_SQRT1_2;
+ matrix[BACK_RIGHT][SIDE_RIGHT] += M_SQRT1_2;
} else {
- assert(channel_index == -1);
- out[out_index + j] = 0;
+ matrix[BACK_LEFT][SIDE_LEFT] += 1.0;
+ matrix[BACK_RIGHT][SIDE_RIGHT] += 1.0;
}
+ } else if (out_ch_layout & CHANNEL_BACK_CENTER) {
+ matrix[BACK_CENTER][SIDE_LEFT] += M_SQRT1_2;
+ matrix[BACK_CENTER][SIDE_RIGHT] += M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ matrix[FRONT_LEFT][SIDE_LEFT] -= _surround_mix_level * M_SQRT1_2;
+ matrix[FRONT_LEFT][SIDE_RIGHT] -= _surround_mix_level * M_SQRT1_2;
+ matrix[FRONT_RIGHT][SIDE_LEFT] += _surround_mix_level * M_SQRT1_2;
+ matrix[FRONT_RIGHT][SIDE_RIGHT] += _surround_mix_level * M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][SIDE_LEFT] += _surround_mix_level * M_SQRT1_2;
+ matrix[FRONT_CENTER][SIDE_RIGHT] += _surround_mix_level * M_SQRT1_2;
}
}
- return true;
-}
-
-/* Drop the extra channels beyond the provided output channels. */
-template<typename T>
-void
-downmix_fallback(long inframes,
- T const * const in, unsigned long in_len,
- T * out, unsigned long out_len,
- unsigned int in_channels, unsigned int out_channels)
-{
- assert(in_channels >= out_channels && inframes >= 0);
-
- if (in_channels == out_channels && in == out) {
- return;
+ if (unaccounted & CHANNEL_FRONT_LEFT_OF_CENTER) {
+ if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ matrix[FRONT_LEFT][FRONT_LEFT_OF_CENTER] += 1.0;
+ matrix[FRONT_RIGHT][FRONT_RIGHT_OF_CENTER] += 1.0;
+ } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][FRONT_LEFT_OF_CENTER] += M_SQRT1_2;
+ matrix[FRONT_CENTER][FRONT_RIGHT_OF_CENTER] += M_SQRT1_2;
+ }
}
-
- for (unsigned long i = 0, out_index = 0; i < (unsigned long)inframes * in_channels; i += in_channels, out_index += out_channels) {
- for (unsigned int j = 0; j < out_channels; ++j) {
- assert(i + j < in_len && out_index + j < out_len);
- out[out_index + j] = in[i + j];
+ /* mix LFE into front left/right or center */
+ if (unaccounted & CHANNEL_LOW_FREQUENCY) {
+ if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][LOW_FREQUENCY] += _lfe_mix_level;
+ } else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ matrix[FRONT_LEFT][LOW_FREQUENCY] += _lfe_mix_level * M_SQRT1_2;
+ matrix[FRONT_RIGHT][LOW_FREQUENCY] += _lfe_mix_level * M_SQRT1_2;
}
}
-}
-
-template<typename T>
-void
-cubeb_downmix(long inframes,
- T const * const in, unsigned long in_len,
- T * out, unsigned long out_len,
- cubeb_stream_params const * stream_params,
- cubeb_stream_params const * mixer_params)
-{
- assert(in && out);
- assert(inframes);
- assert(stream_params->channels >= mixer_params->channels &&
- mixer_params->channels > 0);
- assert(stream_params->layout != CUBEB_LAYOUT_UNDEFINED);
-
- unsigned int in_channels = stream_params->channels;
- cubeb_channel_layout in_layout = stream_params->layout;
-
- unsigned int out_channels = mixer_params->channels;
- cubeb_channel_layout out_layout = mixer_params->layout;
-
- // If the channel number is different from the layout's setting,
- // then we use fallback downmix mechanism.
- if (out_channels == CUBEB_CHANNEL_LAYOUT_MAPS[out_layout].channels &&
- in_channels == CUBEB_CHANNEL_LAYOUT_MAPS[in_layout].channels) {
- if (downmix_3f2(inframes, in, in_len, out, out_len, in_layout, out_layout)) {
- return;
+ // Normalize the conversion matrix.
+ for (uint32_t out_i = 0, i = 0; i < CHANNELS_MAX; i++) {
+ double sum = 0;
+ int in_i = 0;
+ if ((out_ch_layout & (1U << i)) == 0) {
+ continue;
}
+ for (uint32_t j = 0; j < CHANNELS_MAX; j++) {
+ if ((in_ch_layout & (1U << j)) == 0) {
+ continue;
+ }
+ if (i < FF_ARRAY_ELEMS(matrix) && j < FF_ARRAY_ELEMS(matrix[0])) {
+ _matrix[out_i][in_i] = matrix[i][j];
+ } else {
+ _matrix[out_i][in_i] =
+ i == j && (in_ch_layout & out_ch_layout & (1U << i));
+ }
+ sum += fabs(_matrix[out_i][in_i]);
+ in_i++;
+ }
+ maxcoef = std::max(maxcoef, sum);
+ out_i++;
+ }
-#if defined(USE_AUDIOUNIT)
- // We only support downmix for audio 5.1 on OS X currently.
- return;
-#endif
+ if (_format == CUBEB_SAMPLE_S16NE) {
+ maxval = 1.0;
+ } else {
+ maxval = INT_MAX;
+ }
- if (mix_remap(inframes, in, in_len, out, out_len, in_layout, out_layout)) {
- return;
+ // Normalize matrix if needed.
+ if (maxcoef > maxval) {
+ maxcoef /= maxval;
+ for (uint32_t i = 0; i < CHANNELS_MAX; i++)
+ for (uint32_t j = 0; j < CHANNELS_MAX; j++) {
+ _matrix[i][j] /= maxcoef;
+ }
+ }
+
+ if (_format == CUBEB_SAMPLE_FLOAT32NE) {
+ for (uint32_t i = 0; i < FF_ARRAY_ELEMS(_matrix); i++) {
+ for (uint32_t j = 0; j < FF_ARRAY_ELEMS(_matrix[0]); j++) {
+ _matrix_flt[i][j] = _matrix[i][j];
+ }
}
}
- downmix_fallback(inframes, in, in_len, out, out_len, in_channels, out_channels);
+ return 0;
}
-/* Upmix function, copies a mono channel into L and R. */
-template<typename T>
-void
-mono_to_stereo(long insamples, T const * in, unsigned long in_len,
- T * out, unsigned long out_len, unsigned int out_channels)
+int MixerContext::init()
{
- for (long i = 0, j = 0; i < insamples; ++i, j += out_channels) {
- assert((unsigned long)i < in_len && (unsigned long)j + 1 < out_len);
- out[j] = out[j + 1] = in[i];
+ int r = auto_matrix();
+ if (r) {
+ return r;
}
-}
-template<typename T>
-void
-cubeb_upmix(long inframes,
- T const * const in, unsigned long in_len,
- T * out, unsigned long out_len,
- cubeb_stream_params const * stream_params,
- cubeb_stream_params const * mixer_params)
-{
- assert(in && out);
- assert(inframes);
- assert(mixer_params->channels >= stream_params->channels &&
- stream_params->channels > 0);
-
- unsigned int in_channels = stream_params->channels;
- unsigned int out_channels = mixer_params->channels;
-
- /* Either way, if we have 2 or more channels, the first two are L and R. */
- /* If we are playing a mono stream over stereo speakers, copy the data over. */
- if (in_channels == 1 && out_channels >= 2) {
- mono_to_stereo(inframes, in, in_len, out, out_len, out_channels);
- } else {
- /* Copy through. */
- for (unsigned int i = 0, o = 0; i < inframes * in_channels;
- i += in_channels, o += out_channels) {
- for (unsigned int j = 0; j < in_channels; ++j) {
- assert(i + j < in_len && o + j < out_len);
- out[o + j] = in[i + j];
+ // Determine if matrix operation would overflow
+ if (_format == CUBEB_SAMPLE_S16NE) {
+ int maxsum = 0;
+ for (uint32_t i = 0; i < _out_ch_count; i++) {
+ double rem = 0;
+ int sum = 0;
+
+ for (uint32_t j = 0; j < _in_ch_count; j++) {
+ double target = _matrix[i][j] * 32768 + rem;
+ int value = lrintf(target);
+ rem += target - value;
+ sum += std::abs(value);
}
+ maxsum = std::max(maxsum, sum);
+ }
+ if (maxsum > 32768) {
+ _clipping = true;
}
}
- /* Check if more channels. */
- if (out_channels <= 2) {
- return;
- }
-
- /* Put silence in remaining channels. */
- for (long i = 0, o = 0; i < inframes; ++i, o += out_channels) {
- for (unsigned int j = in_channels; j < out_channels; ++j) {
- assert((unsigned long)o + j < out_len);
- out[o + j] = 0.0;
+ // FIXME quantize for integers
+ for (uint32_t i = 0; i < CHANNELS_MAX; i++) {
+ int ch_in = 0;
+ for (uint32_t j = 0; j < CHANNELS_MAX; j++) {
+ _matrix32[i][j] = lrintf(_matrix[i][j] * 32768);
+ if (_matrix[i][j]) {
+ _matrix_ch[i][++ch_in] = j;
+ }
}
+ _matrix_ch[i][0] = ch_in;
}
+
+ return 0;
}
-bool
-cubeb_should_upmix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer)
+template<typename TYPE_SAMPLE, typename TYPE_COEFF, typename F>
+void
+sum2(TYPE_SAMPLE * out,
+ uint32_t stride_out,
+ const TYPE_SAMPLE * in1,
+ const TYPE_SAMPLE * in2,
+ uint32_t stride_in,
+ TYPE_COEFF coeff1,
+ TYPE_COEFF coeff2,
+ F&& operand,
+ uint32_t frames)
{
- return mixer->channels > stream->channels;
+ static_assert(
+ std::is_same<TYPE_COEFF,
+ typename std::result_of<F(TYPE_COEFF)>::type>::value,
+ "function must return the same type as used by matrix_coeff");
+ for (uint32_t i = 0; i < frames; i++) {
+ *out = operand(coeff1 * *in1 + coeff2 * *in2);
+ out += stride_out;
+ in1 += stride_in;
+ in2 += stride_in;
+ }
}
-bool
-cubeb_should_downmix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer)
+template<typename TYPE_SAMPLE, typename TYPE_COEFF, typename F>
+void
+copy(TYPE_SAMPLE * out,
+ uint32_t stride_out,
+ const TYPE_SAMPLE * in,
+ uint32_t stride_in,
+ TYPE_COEFF coeff,
+ F&& operand,
+ uint32_t frames)
{
- if (mixer->channels > stream->channels || mixer->layout == stream->layout) {
- return false;
+ static_assert(
+ std::is_same<TYPE_COEFF,
+ typename std::result_of<F(TYPE_COEFF)>::type>::value,
+ "function must return the same type as used by matrix_coeff");
+ for (uint32_t i = 0; i < frames; i++) {
+ *out = operand(coeff * *in);
+ out += stride_out;
+ in += stride_in;
}
-
- return mixer->channels < stream->channels ||
- // When mixer.channels == stream.channels
- mixer->layout == CUBEB_LAYOUT_UNDEFINED || // fallback downmix
- (stream->layout == CUBEB_LAYOUT_3F2 && // 3f2 downmix
- (mixer->layout == CUBEB_LAYOUT_2F2_LFE ||
- mixer->layout == CUBEB_LAYOUT_3F1_LFE));
}
-bool
-cubeb_should_mix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer)
+template <typename TYPE, typename TYPE_COEFF, size_t COLS, typename F>
+static int rematrix(const MixerContext * s, TYPE * aOut, const TYPE * aIn,
+ const TYPE_COEFF (&matrix_coeff)[COLS][COLS],
+ F&& aF, uint32_t frames)
{
- return stream->layout != CUBEB_LAYOUT_UNDEFINED &&
- (cubeb_should_upmix(stream, mixer) || cubeb_should_downmix(stream, mixer));
+ static_assert(
+ std::is_same<TYPE_COEFF,
+ typename std::result_of<F(TYPE_COEFF)>::type>::value,
+ "function must return the same type as used by matrix_coeff");
+
+ for (uint32_t out_i = 0; out_i < s->_out_ch_count; out_i++) {
+ TYPE* out = aOut + out_i;
+ switch (s->_matrix_ch[out_i][0]) {
+ case 0:
+ for (uint32_t i = 0; i < frames; i++) {
+ out[i * s->_out_ch_count] = 0;
+ }
+ break;
+ case 1: {
+ int in_i = s->_matrix_ch[out_i][1];
+ copy(out,
+ s->_out_ch_count,
+ aIn + in_i,
+ s->_in_ch_count,
+ matrix_coeff[out_i][in_i],
+ aF,
+ frames);
+ } break;
+ case 2:
+ sum2(out,
+ s->_out_ch_count,
+ aIn + s->_matrix_ch[out_i][1],
+ aIn + s->_matrix_ch[out_i][2],
+ s->_in_ch_count,
+ matrix_coeff[out_i][s->_matrix_ch[out_i][1]],
+ matrix_coeff[out_i][s->_matrix_ch[out_i][2]],
+ aF,
+ frames);
+ break;
+ default:
+ for (uint32_t i = 0; i < frames; i++) {
+ TYPE_COEFF v = 0;
+ for (uint32_t j = 0; j < s->_matrix_ch[out_i][0]; j++) {
+ uint32_t in_i = s->_matrix_ch[out_i][1 + j];
+ v +=
+ *(aIn + in_i + i * s->_in_ch_count) * matrix_coeff[out_i][in_i];
+ }
+ out[i * s->_out_ch_count] = aF(v);
+ }
+ break;
+ }
+ }
+ return 0;
}
-struct cubeb_mixer {
- virtual void mix(long frames,
- void * input_buffer, unsigned long input_buffer_length,
- void * output_buffer, unsigned long output_buffer_length,
- cubeb_stream_params const * stream_params,
- cubeb_stream_params const * mixer_params) = 0;
- virtual ~cubeb_mixer() {};
-};
+struct cubeb_mixer
+{
+ cubeb_mixer(cubeb_sample_format format,
+ uint32_t in_channels,
+ cubeb_channel_layout in_layout,
+ uint32_t out_channels,
+ cubeb_channel_layout out_layout)
+ : _context(format, in_channels, in_layout, out_channels, out_layout)
+ {
+ }
-template<typename T>
-struct cubeb_mixer_impl : public cubeb_mixer {
- explicit cubeb_mixer_impl(unsigned int d)
- : direction(d)
+ template<typename T>
+ void copy_and_trunc(size_t frames,
+ const T * input_buffer,
+ T * output_buffer) const
{
+ if (_context._in_ch_count <= _context._out_ch_count) {
+ // Not enough channels to copy, fill the gaps with silence.
+ for (uint32_t i = 0; i < frames; i++) {
+ PodCopy(output_buffer, input_buffer, _context._in_ch_count);
+ output_buffer += _context._in_ch_count;
+ input_buffer += _context._in_ch_count;
+ PodZero(output_buffer, _context._out_ch_count - _context._in_ch_count);
+ output_buffer += _context._out_ch_count - _context._in_ch_count;
+ }
+ } else {
+ for (uint32_t i = 0; i < frames; i++) {
+ PodCopy(output_buffer, input_buffer, _context._out_ch_count);
+ output_buffer += _context._out_ch_count;
+ input_buffer += _context._in_ch_count;
+ }
+ }
}
- void mix(long frames,
- void * input_buffer, unsigned long input_buffer_length,
- void * output_buffer, unsigned long output_buffer_length,
- cubeb_stream_params const * stream_params,
- cubeb_stream_params const * mixer_params)
+ int mix(size_t frames,
+ void * input_buffer,
+ size_t input_buffer_size,
+ void * output_buffer,
+ size_t output_buffer_size) const
{
- if (frames <= 0) {
- return;
+ if (frames <= 0 || _context._out_ch_count == 0) {
+ return 0;
+ }
+
+ // Check if output buffer is of sufficient size.
+ size_t size_read_needed =
+ frames * _context._in_ch_count * cubeb_sample_size(_context._format);
+ if (input_buffer_size < size_read_needed) {
+ // We don't have enough data to read!
+ return -1;
+ }
+ if (output_buffer_size * _context._in_ch_count <
+ size_read_needed * _context._out_ch_count) {
+ return -1;
}
- T * in = static_cast<T*>(input_buffer);
- T * out = static_cast<T*>(output_buffer);
+ if (!valid()) {
+ // The channel layouts were invalid or unsupported, instead we will simply
+ // either drop the extra channels, or fill with silence the missing ones
+ if (_context._format == CUBEB_SAMPLE_FLOAT32NE) {
+ copy_and_trunc(frames,
+ static_cast<const float*>(input_buffer),
+ static_cast<float*>(output_buffer));
+ } else {
+ assert(_context._format == CUBEB_SAMPLE_S16NE);
+ copy_and_trunc(frames,
+ static_cast<int16_t*>(input_buffer),
+ reinterpret_cast<int16_t*>(output_buffer));
+ }
+ return 0;
+ }
- if ((direction & CUBEB_MIXER_DIRECTION_DOWNMIX) &&
- cubeb_should_downmix(stream_params, mixer_params)) {
- cubeb_downmix(frames, in, input_buffer_length, out, output_buffer_length, stream_params, mixer_params);
- } else if ((direction & CUBEB_MIXER_DIRECTION_UPMIX) &&
- cubeb_should_upmix(stream_params, mixer_params)) {
- cubeb_upmix(frames, in, input_buffer_length, out, output_buffer_length, stream_params, mixer_params);
+ switch (_context._format)
+ {
+ case CUBEB_SAMPLE_FLOAT32NE: {
+ auto f = [](float x) { return x; };
+ return rematrix(&_context,
+ static_cast<float*>(output_buffer),
+ static_cast<const float*>(input_buffer),
+ _context._matrix_flt,
+ f,
+ frames);
+ }
+ case CUBEB_SAMPLE_S16NE:
+ if (_context._clipping) {
+ auto f = [](int x) {
+ int y = (x + 16384) >> 15;
+ // clip the signed integer value into the -32768,32767 range.
+ if ((y + 0x8000U) & ~0xFFFF) {
+ return (y >> 31) ^ 0x7FFF;
+ }
+ return y;
+ };
+ return rematrix(&_context,
+ static_cast<int16_t*>(output_buffer),
+ static_cast<const int16_t*>(input_buffer),
+ _context._matrix32,
+ f,
+ frames);
+ } else {
+ auto f = [](int x) { return (x + 16384) >> 15; };
+ return rematrix(&_context,
+ static_cast<int16_t*>(output_buffer),
+ static_cast<const int16_t*>(input_buffer),
+ _context._matrix32,
+ f,
+ frames);
+ }
+ break;
+ default:
+ assert(false);
+ break;
}
+
+ return -1;
}
- ~cubeb_mixer_impl() {};
+ // Return false if any of the input or ouput layout were invalid.
+ bool valid() const { return _context._valid; }
- unsigned char const direction;
+ virtual ~cubeb_mixer(){};
+
+ MixerContext _context;
};
-cubeb_mixer * cubeb_mixer_create(cubeb_sample_format format,
- unsigned char direction)
+cubeb_mixer* cubeb_mixer_create(cubeb_sample_format format,
+ uint32_t in_channels,
+ cubeb_channel_layout in_layout,
+ uint32_t out_channels,
+ cubeb_channel_layout out_layout)
{
- assert(direction & CUBEB_MIXER_DIRECTION_DOWNMIX ||
- direction & CUBEB_MIXER_DIRECTION_UPMIX);
- switch(format) {
- case CUBEB_SAMPLE_S16NE:
- return new cubeb_mixer_impl<short>(direction);
- case CUBEB_SAMPLE_FLOAT32NE:
- return new cubeb_mixer_impl<float>(direction);
- default:
- assert(false);
- return nullptr;
- }
+ return new cubeb_mixer(
+ format, in_channels, in_layout, out_channels, out_layout);
}
void cubeb_mixer_destroy(cubeb_mixer * mixer)
@@ -559,13 +649,13 @@ void cubeb_mixer_destroy(cubeb_mixer * mixer)
delete mixer;
}
-void cubeb_mixer_mix(cubeb_mixer * mixer, long frames,
- void * input_buffer, unsigned long input_buffer_length,
- void * output_buffer, unsigned long output_buffer_length,
- cubeb_stream_params const * stream_params,
- cubeb_stream_params const * mixer_params)
+int cubeb_mixer_mix(cubeb_mixer * mixer,
+ size_t frames,
+ void* input_buffer,
+ size_t input_buffer_size,
+ void* output_buffer,
+ size_t output_buffer_size)
{
- assert(mixer);
- mixer->mix(frames, input_buffer, input_buffer_length, output_buffer, output_buffer_length,
- stream_params, mixer_params);
+ return mixer->mix(
+ frames, input_buffer, input_buffer_size, output_buffer, output_buffer_size);
}