664 lines
21 KiB
C++
664 lines
21 KiB
C++
|
/*
|
||
|
* Copyright © 2016 Mozilla Foundation
|
||
|
*
|
||
|
* 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>
|
||
|
#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) */
|
||
|
|
||
|
#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
|
||
|
|
||
|
static cubeb_channel_layout
|
||
|
cubeb_channel_layout_check(cubeb_channel_layout l, uint32_t c)
|
||
|
{
|
||
|
if (l == CUBEB_LAYOUT_UNDEFINED) {
|
||
|
switch (c) {
|
||
|
case 1: return CUBEB_LAYOUT_MONO;
|
||
|
case 2: return CUBEB_LAYOUT_STEREO;
|
||
|
}
|
||
|
}
|
||
|
return l;
|
||
|
}
|
||
|
|
||
|
unsigned int cubeb_channel_layout_nb_channels(cubeb_channel_layout x)
|
||
|
{
|
||
|
#if __GNUC__ || __clang__
|
||
|
return __builtin_popcount (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(cubeb_channel_layout_check(in, in_channels))
|
||
|
, _out_ch_layout(cubeb_channel_layout_check(out, out_channels))
|
||
|
, _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;
|
||
|
}
|
||
|
_valid = init() >= 0;
|
||
|
}
|
||
|
|
||
|
static bool even(cubeb_channel_layout layout)
|
||
|
{
|
||
|
if (!layout) {
|
||
|
return true;
|
||
|
}
|
||
|
if (layout & (layout - 1)) {
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// 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;
|
||
|
}
|
||
|
|
||
|
return layout;
|
||
|
}
|
||
|
|
||
|
static bool sane_layout(cubeb_channel_layout layout)
|
||
|
{
|
||
|
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.
|
||
|
};
|
||
|
|
||
|
int MixerContext::auto_matrix()
|
||
|
{
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cubeb_channel_layout unaccounted = in_ch_layout & ~out_ch_layout;
|
||
|
|
||
|
// 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.
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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) {
|
||
|
matrix[FRONT_LEFT][BACK_CENTER] += _surround_mix_level * M_SQRT1_2;
|
||
|
matrix[FRONT_RIGHT][BACK_CENTER] += _surround_mix_level * M_SQRT1_2;
|
||
|
} else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
|
||
|
matrix[FRONT_CENTER][BACK_CENTER] +=
|
||
|
_surround_mix_level * M_SQRT1_2;
|
||
|
}
|
||
|
}
|
||
|
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;
|
||
|
matrix[FRONT_RIGHT][BACK_RIGHT] += _surround_mix_level;
|
||
|
} 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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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 {
|
||
|
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;
|
||
|
matrix[FRONT_RIGHT][SIDE_RIGHT] += _surround_mix_level;
|
||
|
} 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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
/* 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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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 (_format == CUBEB_SAMPLE_S16NE) {
|
||
|
maxval = 1.0;
|
||
|
} else {
|
||
|
maxval = INT_MAX;
|
||
|
}
|
||
|
|
||
|
// 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];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int MixerContext::init()
|
||
|
{
|
||
|
int r = auto_matrix();
|
||
|
if (r) {
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
// 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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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;
|
||
|
}
|
||
|
|
||
|
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)
|
||
|
{
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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)
|
||
|
{
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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)
|
||
|
{
|
||
|
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
|
||
|
{
|
||
|
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>
|
||
|
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.
|
||
|
if (_context._in_ch_count == 1 && _context._out_ch_count >= 2) {
|
||
|
// Special case for upmixing mono input to stereo and more. We will
|
||
|
// duplicate the mono channel to the first two channels. On most system,
|
||
|
// the first two channels are for left and right. It is commonly
|
||
|
// expected that mono will on both left+right channels
|
||
|
for (uint32_t i = 0; i < frames; i++) {
|
||
|
output_buffer[0] = output_buffer[1] = *input_buffer;
|
||
|
PodZero(output_buffer + 2, _context._out_ch_count - 2);
|
||
|
output_buffer += _context._out_ch_count;
|
||
|
input_buffer++;
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int mix(size_t frames,
|
||
|
const void * input_buffer,
|
||
|
size_t input_buffer_size,
|
||
|
void * output_buffer,
|
||
|
size_t output_buffer_size) const
|
||
|
{
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
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<const int16_t*>(input_buffer),
|
||
|
reinterpret_cast<int16_t*>(output_buffer));
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
// Return false if any of the input or ouput layout were invalid.
|
||
|
bool valid() const { return _context._valid; }
|
||
|
|
||
|
virtual ~cubeb_mixer(){};
|
||
|
|
||
|
MixerContext _context;
|
||
|
};
|
||
|
|
||
|
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)
|
||
|
{
|
||
|
return new cubeb_mixer(
|
||
|
format, in_channels, in_layout, out_channels, out_layout);
|
||
|
}
|
||
|
|
||
|
void cubeb_mixer_destroy(cubeb_mixer * mixer)
|
||
|
{
|
||
|
delete mixer;
|
||
|
}
|
||
|
|
||
|
int cubeb_mixer_mix(cubeb_mixer * mixer,
|
||
|
size_t frames,
|
||
|
const void * input_buffer,
|
||
|
size_t input_buffer_size,
|
||
|
void * output_buffer,
|
||
|
size_t output_buffer_size)
|
||
|
{
|
||
|
return mixer->mix(
|
||
|
frames, input_buffer, input_buffer_size, output_buffer, output_buffer_size);
|
||
|
}
|