mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
Always apply filter based on dest layout, even when distributing, averaging or copying a signal.
586 lines
18 KiB
C
586 lines
18 KiB
C
/* Spa
|
|
*
|
|
* Copyright © 2018 Wim Taymans
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice (including the next
|
|
* paragraph) shall be included in all copies or substantial portions of the
|
|
* Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
|
|
#include <spa/param/audio/format-utils.h>
|
|
#include <spa/support/cpu.h>
|
|
#include <spa/support/log.h>
|
|
#include <spa/utils/defs.h>
|
|
|
|
#define VOLUME_MIN 0.0f
|
|
#define VOLUME_NORM 1.0f
|
|
|
|
#include "channelmix-ops.h"
|
|
#include "hilbert.h"
|
|
|
|
|
|
#define _M(ch) (1UL << SPA_AUDIO_CHANNEL_ ## ch)
|
|
#define MASK_MONO _M(FC)|_M(MONO)|_M(UNKNOWN)
|
|
#define MASK_STEREO _M(FL)|_M(FR)|_M(UNKNOWN)
|
|
#define MASK_QUAD _M(FL)|_M(FR)|_M(RL)|_M(RR)|_M(UNKNOWN)
|
|
#define MASK_3_1 _M(FL)|_M(FR)|_M(FC)|_M(LFE)
|
|
#define MASK_5_1 _M(FL)|_M(FR)|_M(FC)|_M(LFE)|_M(SL)|_M(SR)|_M(RL)|_M(RR)
|
|
#define MASK_7_1 _M(FL)|_M(FR)|_M(FC)|_M(LFE)|_M(SL)|_M(SR)|_M(RL)|_M(RR)
|
|
|
|
#define ANY ((uint32_t)-1)
|
|
#define EQ ((uint32_t)-2)
|
|
|
|
typedef void (*channelmix_func_t) (struct channelmix *mix, void * SPA_RESTRICT dst[],
|
|
const void * SPA_RESTRICT src[], uint32_t n_samples);
|
|
|
|
#define MAKE(sc,sm,dc,dm,func,...) \
|
|
{ sc, sm, dc, dm, func, #func, __VA_ARGS__ }
|
|
|
|
static const struct channelmix_info {
|
|
uint32_t src_chan;
|
|
uint64_t src_mask;
|
|
uint32_t dst_chan;
|
|
uint64_t dst_mask;
|
|
|
|
channelmix_func_t process;
|
|
const char *name;
|
|
|
|
uint32_t cpu_flags;
|
|
} channelmix_table[] =
|
|
{
|
|
#if defined (HAVE_SSE)
|
|
MAKE(2, MASK_MONO, 2, MASK_MONO, channelmix_copy_sse, SPA_CPU_FLAG_SSE),
|
|
MAKE(2, MASK_STEREO, 2, MASK_STEREO, channelmix_copy_sse, SPA_CPU_FLAG_SSE),
|
|
MAKE(EQ, 0, EQ, 0, channelmix_copy_sse, SPA_CPU_FLAG_SSE),
|
|
#endif
|
|
MAKE(2, MASK_MONO, 2, MASK_MONO, channelmix_copy_c),
|
|
MAKE(2, MASK_STEREO, 2, MASK_STEREO, channelmix_copy_c),
|
|
MAKE(EQ, 0, EQ, 0, channelmix_copy_c),
|
|
|
|
MAKE(1, MASK_MONO, 2, MASK_STEREO, channelmix_f32_1_2_c),
|
|
MAKE(2, MASK_STEREO, 1, MASK_MONO, channelmix_f32_2_1_c),
|
|
MAKE(4, MASK_QUAD, 1, MASK_MONO, channelmix_f32_4_1_c),
|
|
MAKE(4, MASK_3_1, 1, MASK_MONO, channelmix_f32_4_1_c),
|
|
MAKE(2, MASK_STEREO, 4, MASK_QUAD, channelmix_f32_2_4_c),
|
|
MAKE(2, MASK_STEREO, 4, MASK_3_1, channelmix_f32_2_3p1_c),
|
|
MAKE(2, MASK_STEREO, 6, MASK_5_1, channelmix_f32_2_5p1_c),
|
|
MAKE(2, MASK_STEREO, 8, MASK_7_1, channelmix_f32_2_7p1_c),
|
|
#if defined (HAVE_SSE)
|
|
MAKE(4, MASK_3_1, 2, MASK_STEREO, channelmix_f32_3p1_2_sse, SPA_CPU_FLAG_SSE),
|
|
#endif
|
|
MAKE(4, MASK_3_1, 2, MASK_STEREO, channelmix_f32_3p1_2_c),
|
|
#if defined (HAVE_SSE)
|
|
MAKE(6, MASK_5_1, 2, MASK_STEREO, channelmix_f32_5p1_2_sse, SPA_CPU_FLAG_SSE),
|
|
#endif
|
|
MAKE(6, MASK_5_1, 2, MASK_STEREO, channelmix_f32_5p1_2_c),
|
|
#if defined (HAVE_SSE)
|
|
MAKE(6, MASK_5_1, 4, MASK_QUAD, channelmix_f32_5p1_4_sse, SPA_CPU_FLAG_SSE),
|
|
#endif
|
|
MAKE(6, MASK_5_1, 4, MASK_QUAD, channelmix_f32_5p1_4_c),
|
|
|
|
#if defined (HAVE_SSE)
|
|
MAKE(6, MASK_5_1, 4, MASK_3_1, channelmix_f32_5p1_3p1_sse, SPA_CPU_FLAG_SSE),
|
|
#endif
|
|
MAKE(6, MASK_5_1, 4, MASK_3_1, channelmix_f32_5p1_3p1_c),
|
|
|
|
MAKE(8, MASK_7_1, 2, MASK_STEREO, channelmix_f32_7p1_2_c),
|
|
MAKE(8, MASK_7_1, 4, MASK_QUAD, channelmix_f32_7p1_4_c),
|
|
MAKE(8, MASK_7_1, 4, MASK_3_1, channelmix_f32_7p1_3p1_c),
|
|
|
|
MAKE(ANY, 0, ANY, 0, channelmix_f32_n_m_c),
|
|
};
|
|
#undef MAKE
|
|
|
|
#define MATCH_CHAN(a,b) ((a) == ANY || (a) == (b))
|
|
#define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a)
|
|
#define MATCH_MASK(a,b) ((a) == 0 || ((a) & (b)) == (b))
|
|
|
|
static const struct channelmix_info *find_channelmix_info(uint32_t src_chan, uint64_t src_mask,
|
|
uint32_t dst_chan, uint64_t dst_mask, uint32_t cpu_flags)
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < SPA_N_ELEMENTS(channelmix_table); i++) {
|
|
if (!MATCH_CPU_FLAGS(channelmix_table[i].cpu_flags, cpu_flags))
|
|
continue;
|
|
|
|
if (src_chan == dst_chan && src_mask == dst_mask)
|
|
return &channelmix_table[i];
|
|
|
|
if (MATCH_CHAN(channelmix_table[i].src_chan, src_chan) &&
|
|
MATCH_CHAN(channelmix_table[i].dst_chan, dst_chan) &&
|
|
MATCH_MASK(channelmix_table[i].src_mask, src_mask) &&
|
|
MATCH_MASK(channelmix_table[i].dst_mask, dst_mask))
|
|
return &channelmix_table[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
#define SQRT3_2 1.224744871f /* sqrt(3/2) */
|
|
#define SQRT1_2 0.707106781f
|
|
#define SQRT2 1.414213562f
|
|
|
|
#define MATRIX_NORMAL 0
|
|
#define MATRIX_DOLBY 1
|
|
#define MATRIX_DPLII 2
|
|
|
|
#define _CH(ch) ((SPA_AUDIO_CHANNEL_ ## ch)-3)
|
|
#define _MASK(ch) (1ULL << _CH(ch))
|
|
#define FRONT (_MASK(FC))
|
|
#define STEREO (_MASK(FL)|_MASK(FR))
|
|
#define REAR (_MASK(RL)|_MASK(RR))
|
|
#define SIDE (_MASK(SL)|_MASK(SR))
|
|
|
|
static int make_matrix(struct channelmix *mix)
|
|
{
|
|
float matrix[SPA_AUDIO_MAX_CHANNELS][SPA_AUDIO_MAX_CHANNELS] = {{ 0.0f }};
|
|
uint64_t src_mask = mix->src_mask;
|
|
uint64_t dst_mask = mix->dst_mask;
|
|
uint64_t unassigned, keep;
|
|
uint32_t i, j, ic, jc, matrix_encoding = MATRIX_NORMAL;
|
|
float clev = SQRT1_2;
|
|
float slev = SQRT1_2;
|
|
float llev = 0.5f;
|
|
float maxsum = 0.0f;
|
|
bool filter_fc = false, filter_lfe = false;
|
|
#define _MATRIX(s,d) matrix[_CH(s)][_CH(d)]
|
|
|
|
spa_log_debug(mix->log, "src-mask:%08"PRIx64" dst-mask:%08"PRIx64
|
|
" options:%08x", src_mask, dst_mask, mix->options);
|
|
|
|
/* move the MONO mask to FRONT so that the lower bits can be shifted
|
|
* away. */
|
|
if ((src_mask & (1Ull << SPA_AUDIO_CHANNEL_MONO)) != 0) {
|
|
if (mix->src_chan == 1)
|
|
src_mask = 0;
|
|
else
|
|
src_mask |= (1ULL << SPA_AUDIO_CHANNEL_FC);
|
|
}
|
|
if ((dst_mask & (1Ull << SPA_AUDIO_CHANNEL_MONO)) != 0)
|
|
dst_mask |= (1ULL << SPA_AUDIO_CHANNEL_FC);
|
|
|
|
/* shift so that bit 0 is FL */
|
|
src_mask >>= 3;
|
|
dst_mask >>= 3;
|
|
|
|
/* unknown channels or just 1 channel */
|
|
if (src_mask == 0 || dst_mask == 0) {
|
|
if (mix->src_chan == 1) {
|
|
/* one FC/MONO src goes everywhere */
|
|
spa_log_debug(mix->log, "distribute FC/MONO");
|
|
for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++)
|
|
matrix[i][0]= 1.0f;
|
|
} else if (mix->dst_chan == 1) {
|
|
/* one FC/MONO dst get average of everything */
|
|
spa_log_debug(mix->log, "average FC/MONO");
|
|
for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++)
|
|
matrix[0][i]= 1.0f / mix->src_chan;
|
|
} else {
|
|
/* just pair channels */
|
|
spa_log_debug(mix->log, "pairing channels");
|
|
for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++)
|
|
matrix[i][i]= 1.0f;
|
|
}
|
|
src_mask = dst_mask = ~0LU;
|
|
filter_fc = filter_lfe = true;
|
|
goto done;
|
|
} else {
|
|
spa_log_debug(mix->log, "matching channels");
|
|
for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) {
|
|
if ((src_mask & dst_mask & (1ULL << i))) {
|
|
spa_log_debug(mix->log, "matched %u", i);
|
|
matrix[i][i]= 1.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
unassigned = src_mask & ~dst_mask;
|
|
keep = dst_mask & ~src_mask;
|
|
|
|
if (!SPA_FLAG_IS_SET(mix->options, CHANNELMIX_OPTION_UPMIX)) {
|
|
keep = 0;
|
|
} else {
|
|
if (mix->upmix == CHANNELMIX_UPMIX_NONE)
|
|
keep = 0;
|
|
keep |= FRONT;
|
|
if (mix->lfe_cutoff > 0.0f)
|
|
keep |= _MASK(LFE);
|
|
else
|
|
keep &= ~_MASK(LFE);
|
|
}
|
|
|
|
spa_log_debug(mix->log, "unassigned downmix %08" PRIx64 " %08" PRIx64, unassigned, keep);
|
|
|
|
if (unassigned & FRONT){
|
|
if ((dst_mask & STEREO) == STEREO){
|
|
spa_log_debug(mix->log, "assign FC to STEREO");
|
|
if(src_mask & STEREO) {
|
|
_MATRIX(FL,FC) += clev;
|
|
_MATRIX(FR,FC) += clev;
|
|
} else {
|
|
_MATRIX(FL,FC) += SQRT1_2;
|
|
_MATRIX(FR,FC) += SQRT1_2;
|
|
}
|
|
} else {
|
|
spa_log_warn(mix->log, "can't assign FC");
|
|
}
|
|
}
|
|
|
|
if (unassigned & STEREO){
|
|
if (dst_mask & FRONT) {
|
|
spa_log_debug(mix->log, "assign STEREO to FC");
|
|
_MATRIX(FC,FL) += SQRT1_2;
|
|
_MATRIX(FC,FR) += SQRT1_2;
|
|
if (src_mask & FRONT)
|
|
_MATRIX(FC,FC) = clev * SQRT2;
|
|
keep &= ~FRONT;
|
|
} else {
|
|
spa_log_warn(mix->log, "can't assign STEREO");
|
|
}
|
|
}
|
|
|
|
if (unassigned & _MASK(RC)) {
|
|
if (dst_mask & REAR){
|
|
spa_log_debug(mix->log, "assign RC to RL+RR");
|
|
_MATRIX(RL,RC) += SQRT1_2;
|
|
_MATRIX(RR,RC) += SQRT1_2;
|
|
} else if (dst_mask & SIDE) {
|
|
spa_log_debug(mix->log, "assign RC to SL+SR");
|
|
_MATRIX(SL,RC) += SQRT1_2;
|
|
_MATRIX(SR,RC) += SQRT1_2;
|
|
} else if(dst_mask & STEREO) {
|
|
spa_log_debug(mix->log, "assign RC to FL+FR");
|
|
if (matrix_encoding == MATRIX_DOLBY ||
|
|
matrix_encoding == MATRIX_DPLII) {
|
|
if (unassigned & (_MASK(RL)|_MASK(RR))) {
|
|
_MATRIX(FL,RC) -= slev * SQRT1_2;
|
|
_MATRIX(FR,RC) += slev * SQRT1_2;
|
|
} else {
|
|
_MATRIX(FL,RC) -= slev;
|
|
_MATRIX(FR,RC) += slev;
|
|
}
|
|
} else {
|
|
_MATRIX(FL,RC) += slev * SQRT1_2;
|
|
_MATRIX(FR,RC) += slev * SQRT1_2;
|
|
}
|
|
} else if (dst_mask & FRONT) {
|
|
spa_log_debug(mix->log, "assign RC to FC");
|
|
_MATRIX(FC,RC) += slev * SQRT1_2;
|
|
} else {
|
|
spa_log_warn(mix->log, "can't assign RC");
|
|
}
|
|
}
|
|
|
|
if (unassigned & REAR) {
|
|
if (dst_mask & _MASK(RC)) {
|
|
spa_log_debug(mix->log, "assign RL+RR to RC");
|
|
_MATRIX(RC,RL) += SQRT1_2;
|
|
_MATRIX(RC,RR) += SQRT1_2;
|
|
} else if (dst_mask & SIDE) {
|
|
spa_log_debug(mix->log, "assign RL+RR to SL+SR");
|
|
if (src_mask & SIDE) {
|
|
_MATRIX(SL,RL) += SQRT1_2;
|
|
_MATRIX(SR,RR) += SQRT1_2;
|
|
} else {
|
|
_MATRIX(SL,RL) += 1.0f;
|
|
_MATRIX(SR,RR) += 1.0f;
|
|
}
|
|
} else if (dst_mask & STEREO) {
|
|
spa_log_debug(mix->log, "assign RL+RR to FL+FR %f", slev);
|
|
if (matrix_encoding == MATRIX_DOLBY) {
|
|
_MATRIX(FL,RL) -= slev * SQRT1_2;
|
|
_MATRIX(FL,RR) -= slev * SQRT1_2;
|
|
_MATRIX(FR,RL) += slev * SQRT1_2;
|
|
_MATRIX(FR,RR) += slev * SQRT1_2;
|
|
} else if (matrix_encoding == MATRIX_DPLII) {
|
|
_MATRIX(FL,RL) -= slev * SQRT3_2;
|
|
_MATRIX(FL,RR) -= slev * SQRT1_2;
|
|
_MATRIX(FR,RL) += slev * SQRT1_2;
|
|
_MATRIX(FR,RR) += slev * SQRT3_2;
|
|
} else {
|
|
_MATRIX(FL,RL) += slev;
|
|
_MATRIX(FR,RR) += slev;
|
|
}
|
|
} else if (dst_mask & FRONT) {
|
|
spa_log_debug(mix->log, "assign RL+RR to FC");
|
|
_MATRIX(FC,RL)+= slev * SQRT1_2;
|
|
_MATRIX(FC,RR)+= slev * SQRT1_2;
|
|
} else {
|
|
spa_log_warn(mix->log, "can't assign RL");
|
|
}
|
|
}
|
|
|
|
if (unassigned & SIDE) {
|
|
if (dst_mask & REAR) {
|
|
spa_log_debug(mix->log, "assign SL+SR to RL+RR");
|
|
if (src_mask & _MASK(RL)) {
|
|
_MATRIX(RL,SL) += SQRT1_2;
|
|
_MATRIX(RR,SR) += SQRT1_2;
|
|
} else {
|
|
_MATRIX(RL,SL) += 1.0f;
|
|
_MATRIX(RR,SR) += 1.0f;
|
|
}
|
|
} else if (dst_mask & _MASK(RC)) {
|
|
spa_log_debug(mix->log, "assign SL+SR to RC");
|
|
_MATRIX(RC,SL)+= SQRT1_2;
|
|
_MATRIX(RC,SR)+= SQRT1_2;
|
|
} else if (dst_mask & STEREO) {
|
|
spa_log_debug(mix->log, "assign SL+SR to FL+FR");
|
|
if (matrix_encoding == MATRIX_DOLBY) {
|
|
_MATRIX(FL,SL) -= slev * SQRT1_2;
|
|
_MATRIX(FL,SR) -= slev * SQRT1_2;
|
|
_MATRIX(FR,SL) += slev * SQRT1_2;
|
|
_MATRIX(FR,SR) += slev * SQRT1_2;
|
|
} else if (matrix_encoding == MATRIX_DPLII) {
|
|
_MATRIX(FL,SL) -= slev * SQRT3_2;
|
|
_MATRIX(FL,SR) -= slev * SQRT1_2;
|
|
_MATRIX(FR,SL) += slev * SQRT1_2;
|
|
_MATRIX(FR,SR) += slev * SQRT3_2;
|
|
} else {
|
|
_MATRIX(FL,SL) += slev;
|
|
_MATRIX(FR,SR) += slev;
|
|
}
|
|
} else if (dst_mask & FRONT) {
|
|
spa_log_debug(mix->log, "assign SL+SR to FC");
|
|
_MATRIX(FC,SL) += slev * SQRT1_2;
|
|
_MATRIX(FC,SR) += slev * SQRT1_2;
|
|
} else {
|
|
spa_log_warn(mix->log, "can't assign SL");
|
|
}
|
|
}
|
|
|
|
if (unassigned & _MASK(FLC)) {
|
|
if (dst_mask & STEREO) {
|
|
spa_log_debug(mix->log, "assign FLC+FRC to FL+FR");
|
|
_MATRIX(FL,FLC)+= 1.0f;
|
|
_MATRIX(FR,FRC)+= 1.0f;
|
|
} else if(dst_mask & FRONT) {
|
|
spa_log_debug(mix->log, "assign FLC+FRC to FC");
|
|
_MATRIX(FC,FLC)+= SQRT1_2;
|
|
_MATRIX(FC,FRC)+= SQRT1_2;
|
|
} else {
|
|
spa_log_warn(mix->log, "can't assign FLC");
|
|
}
|
|
}
|
|
if (unassigned & _MASK(LFE) &&
|
|
SPA_FLAG_IS_SET(mix->options, CHANNELMIX_OPTION_MIX_LFE)) {
|
|
if (dst_mask & FRONT) {
|
|
spa_log_debug(mix->log, "assign LFE to FC");
|
|
_MATRIX(FC,LFE) += llev;
|
|
} else if (dst_mask & STEREO) {
|
|
spa_log_debug(mix->log, "assign LFE to FL+FR");
|
|
_MATRIX(FL,LFE) += llev * SQRT1_2;
|
|
_MATRIX(FR,LFE) += llev * SQRT1_2;
|
|
} else {
|
|
spa_log_warn(mix->log, "can't assign LFE");
|
|
}
|
|
}
|
|
|
|
unassigned = dst_mask & ~src_mask & keep;
|
|
|
|
spa_log_debug(mix->log, "unassigned upmix %08"PRIx64" lfe:%f",
|
|
unassigned, mix->lfe_cutoff);
|
|
|
|
if (unassigned & FRONT) {
|
|
if ((src_mask & STEREO) == STEREO) {
|
|
spa_log_debug(mix->log, "produce FC from STEREO");
|
|
_MATRIX(FC,FL) += clev;
|
|
_MATRIX(FC,FR) += clev;
|
|
filter_fc = true;
|
|
} else {
|
|
spa_log_warn(mix->log, "can't produce FC");
|
|
}
|
|
}
|
|
if (unassigned & _MASK(LFE)) {
|
|
if ((src_mask & STEREO) == STEREO) {
|
|
spa_log_debug(mix->log, "produce LFE from STEREO");
|
|
_MATRIX(LFE,FL) += llev;
|
|
_MATRIX(LFE,FR) += llev;
|
|
filter_lfe = true;
|
|
} else if ((src_mask & FRONT) == FRONT) {
|
|
spa_log_debug(mix->log, "produce LFE from FC");
|
|
_MATRIX(LFE,FC) += llev;
|
|
filter_lfe = true;
|
|
} else {
|
|
spa_log_warn(mix->log, "can't produce LFE");
|
|
}
|
|
}
|
|
if (unassigned & SIDE) {
|
|
if ((src_mask & REAR) == REAR) {
|
|
spa_log_debug(mix->log, "produce SIDE from REAR");
|
|
_MATRIX(SL,RL) += 1.0f;
|
|
_MATRIX(SR,RR) += 1.0f;
|
|
} else if ((src_mask & STEREO) == STEREO) {
|
|
spa_log_debug(mix->log, "produce SIDE from STEREO");
|
|
_MATRIX(SL,FL) += slev;
|
|
_MATRIX(SR,FR) += slev;
|
|
} else if ((src_mask & FRONT) == FRONT) {
|
|
spa_log_debug(mix->log, "produce SIDE from FC");
|
|
_MATRIX(SL,FC) += clev;
|
|
_MATRIX(SR,FC) += clev;
|
|
}
|
|
}
|
|
if (unassigned & REAR) {
|
|
if ((src_mask & SIDE) == SIDE) {
|
|
spa_log_debug(mix->log, "produce REAR from SIDE");
|
|
_MATRIX(RL,SL) += 1.0f;
|
|
_MATRIX(RR,SR) += 1.0f;
|
|
} else if ((src_mask & STEREO) == STEREO) {
|
|
spa_log_debug(mix->log, "produce REAR from STEREO");
|
|
_MATRIX(RL,FL) += slev;
|
|
_MATRIX(RR,FR) += slev;
|
|
} else if ((src_mask & FRONT) == FRONT) {
|
|
spa_log_debug(mix->log, "produce REAR from FC");
|
|
_MATRIX(RL,FC) += clev;
|
|
_MATRIX(RR,FC) += clev;
|
|
}
|
|
}
|
|
|
|
done:
|
|
for (jc = 0, ic = 0, i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) {
|
|
float sum = 0.0f;
|
|
if ((dst_mask & (1UL << i)) == 0)
|
|
continue;
|
|
for (jc = 0, j = 0; j < SPA_AUDIO_MAX_CHANNELS; j++) {
|
|
if ((src_mask & (1UL << j)) == 0)
|
|
continue;
|
|
mix->matrix_orig[ic][jc++] = matrix[i][j];
|
|
sum += fabs(matrix[i][j]);
|
|
}
|
|
maxsum = SPA_MAX(maxsum, sum);
|
|
if (i == _CH(LFE) && mix->lfe_cutoff > 0.0f && filter_lfe) {
|
|
spa_log_info(mix->log, "channel %d is LFE cutoff:%f", ic, mix->lfe_cutoff);
|
|
lr4_set(&mix->lr4[ic], BQ_LOWPASS, mix->lfe_cutoff / mix->freq);
|
|
} else if (i == _CH(FC) && mix->fc_cutoff > 0.0f && filter_fc) {
|
|
spa_log_info(mix->log, "channel %d is FC cutoff:%f", ic, mix->fc_cutoff);
|
|
lr4_set(&mix->lr4[ic], BQ_LOWPASS, mix->fc_cutoff / mix->freq);
|
|
} else {
|
|
mix->lr4[ic].active = false;
|
|
}
|
|
ic++;
|
|
}
|
|
if (SPA_FLAG_IS_SET(mix->options, CHANNELMIX_OPTION_NORMALIZE) &&
|
|
maxsum > 1.0f) {
|
|
spa_log_debug(mix->log, "normalize %f", maxsum);
|
|
for (i = 0; i < ic; i++)
|
|
for (j = 0; j < jc; j++)
|
|
mix->matrix_orig[i][j] /= maxsum;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void impl_channelmix_set_volume(struct channelmix *mix, float volume, bool mute,
|
|
uint32_t n_channel_volumes, float *channel_volumes)
|
|
{
|
|
float volumes[SPA_AUDIO_MAX_CHANNELS];
|
|
float vol = mute ? 0.0f : volume, t;
|
|
uint32_t i, j;
|
|
uint32_t src_chan = mix->src_chan;
|
|
uint32_t dst_chan = mix->dst_chan;
|
|
|
|
spa_log_debug(mix->log, "volume:%f mute:%d n_volumes:%d", volume, mute, n_channel_volumes);
|
|
|
|
/** apply global volume to channels */
|
|
for (i = 0; i < n_channel_volumes; i++) {
|
|
volumes[i] = channel_volumes[i] * vol;
|
|
spa_log_debug(mix->log, "%d: %f * %f = %f", i, channel_volumes[i], vol, volumes[i]);
|
|
}
|
|
|
|
/** apply volumes per channel */
|
|
if (n_channel_volumes == src_chan) {
|
|
for (i = 0; i < dst_chan; i++) {
|
|
for (j = 0; j < src_chan; j++) {
|
|
mix->matrix[i][j] = mix->matrix_orig[i][j] * volumes[j];
|
|
}
|
|
}
|
|
} else if (n_channel_volumes == dst_chan) {
|
|
for (i = 0; i < dst_chan; i++) {
|
|
for (j = 0; j < src_chan; j++) {
|
|
mix->matrix[i][j] = mix->matrix_orig[i][j] * volumes[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
SPA_FLAG_SET(mix->flags, CHANNELMIX_FLAG_ZERO);
|
|
SPA_FLAG_SET(mix->flags, CHANNELMIX_FLAG_EQUAL);
|
|
SPA_FLAG_SET(mix->flags, CHANNELMIX_FLAG_COPY);
|
|
|
|
t = 0.0;
|
|
for (i = 0; i < dst_chan; i++) {
|
|
for (j = 0; j < src_chan; j++) {
|
|
float v = mix->matrix[i][j];
|
|
spa_log_debug(mix->log, "%d %d: %f", i, j, v);
|
|
if (i == 0 && j == 0)
|
|
t = v;
|
|
else if (t != v)
|
|
SPA_FLAG_CLEAR(mix->flags, CHANNELMIX_FLAG_EQUAL);
|
|
if (v != 0.0)
|
|
SPA_FLAG_CLEAR(mix->flags, CHANNELMIX_FLAG_ZERO);
|
|
if ((i == j && v != 1.0f) ||
|
|
(i != j && v != 0.0f))
|
|
SPA_FLAG_CLEAR(mix->flags, CHANNELMIX_FLAG_COPY);
|
|
}
|
|
}
|
|
SPA_FLAG_UPDATE(mix->flags, CHANNELMIX_FLAG_IDENTITY,
|
|
dst_chan == src_chan && SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_COPY));
|
|
|
|
spa_log_debug(mix->log, "flags:%08x", mix->flags);
|
|
}
|
|
|
|
static void impl_channelmix_free(struct channelmix *mix)
|
|
{
|
|
mix->process = NULL;
|
|
}
|
|
|
|
int channelmix_init(struct channelmix *mix)
|
|
{
|
|
const struct channelmix_info *info;
|
|
|
|
if (mix->src_chan > SPA_AUDIO_MAX_CHANNELS ||
|
|
mix->dst_chan > SPA_AUDIO_MAX_CHANNELS)
|
|
return -EINVAL;
|
|
|
|
info = find_channelmix_info(mix->src_chan, mix->src_mask, mix->dst_chan, mix->dst_mask,
|
|
mix->cpu_flags);
|
|
if (info == NULL)
|
|
return -ENOTSUP;
|
|
|
|
mix->free = impl_channelmix_free;
|
|
mix->process = info->process;
|
|
mix->set_volume = impl_channelmix_set_volume;
|
|
mix->cpu_flags = info->cpu_flags;
|
|
mix->delay = mix->rear_delay * mix->freq / 1000.0f;
|
|
mix->func_name = info->name;
|
|
|
|
spa_log_debug(mix->log, "selected %s delay:%d options:%08x", info->name, mix->delay,
|
|
mix->options);
|
|
|
|
if (mix->hilbert_taps > 0) {
|
|
mix->n_taps = SPA_CLAMP(mix->hilbert_taps, 15u, 255u) | 1;
|
|
blackman_window(mix->taps, mix->n_taps);
|
|
hilbert_generate(mix->taps, mix->n_taps);
|
|
} else {
|
|
mix->n_taps = 1;
|
|
mix->taps[0] = 1.0f;
|
|
}
|
|
return make_matrix(mix);
|
|
}
|