diff options
Diffstat (limited to 'src/cubeb_mixer.cpp')
-rw-r--r-- | src/cubeb_mixer.cpp | 1034 |
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); } |