mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-02-14 04:28:13 -05:00
When setting up the mix matrix, don't just iterate over the first 64 (CHANNEL_BITS) positions because then we will never be able to configure more than 64 channels in the matrix. Instead iterate until we have filled all src and dst entries in the matrix. For the first 64 positions we might need to check the channel mask to get the right positions in our source matrix. Fixes the channel mixer for >64 channels where the positions above 64 where 0 in the matrix and muted. Fixes #5118
913 lines
28 KiB
C
913 lines
28 KiB
C
/* Spa */
|
|
/* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */
|
|
/* SPDX-License-Identifier: MIT */
|
|
|
|
#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>
|
|
#include <spa/debug/types.h>
|
|
|
|
#include "channelmix-ops.h"
|
|
#include "hilbert.h"
|
|
|
|
#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),
|
|
#if defined (HAVE_SSE)
|
|
MAKE(2, MASK_STEREO, 4, MASK_3_1, channelmix_f32_2_3p1_sse, SPA_CPU_FLAG_SSE),
|
|
#endif
|
|
MAKE(2, MASK_STEREO, 4, MASK_3_1, channelmix_f32_2_3p1_c),
|
|
#if defined (HAVE_SSE)
|
|
MAKE(2, MASK_STEREO, 6, MASK_5_1, channelmix_f32_2_5p1_sse, SPA_CPU_FLAG_SSE),
|
|
#endif
|
|
MAKE(2, MASK_STEREO, 6, MASK_5_1, channelmix_f32_2_5p1_c),
|
|
#if defined (HAVE_SSE)
|
|
MAKE(2, MASK_STEREO, 8, MASK_7_1, channelmix_f32_2_7p1_sse, SPA_CPU_FLAG_SSE),
|
|
#endif
|
|
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),
|
|
|
|
#if defined (HAVE_SSE)
|
|
MAKE(ANY, 0, ANY, 0, channelmix_f32_n_m_sse, SPA_CPU_FLAG_SSE),
|
|
#endif
|
|
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)
|
|
{
|
|
SPA_FOR_EACH_ELEMENT_VAR(channelmix_table, info) {
|
|
if (!MATCH_CPU_FLAGS(info->cpu_flags, cpu_flags))
|
|
continue;
|
|
|
|
if (src_chan == dst_chan && src_mask == dst_mask)
|
|
return info;
|
|
|
|
if (MATCH_CHAN(info->src_chan, src_chan) &&
|
|
MATCH_CHAN(info->dst_chan, dst_chan) &&
|
|
MATCH_MASK(info->src_mask, src_mask) &&
|
|
MATCH_MASK(info->dst_mask, dst_mask))
|
|
return info;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
#define SQRT3_2 1.224744871f /* sqrt(3/2) */
|
|
#define SQRT1_2 0.707106781f
|
|
#define SQRT2 1.414213562f
|
|
#define HALF 0.5f
|
|
|
|
#define MATRIX_NORMAL 0
|
|
#define MATRIX_DOLBY 1
|
|
#define MATRIX_DPLII 2
|
|
|
|
#define _SH 2
|
|
#define _CH(ch) ((SPA_AUDIO_CHANNEL_ ## ch)-_SH)
|
|
#define _MASK(ch) (1ULL << _CH(ch))
|
|
#define FRONT (_MASK(FC))
|
|
#define STEREO (_MASK(FL)|_MASK(FR))
|
|
#define TSTEREO (_MASK(TFL)|_MASK(TFR))
|
|
#define TCSTEREO (_MASK(TFLC)|_MASK(TFRC))
|
|
#define REAR (_MASK(RL)|_MASK(RR))
|
|
#define TREAR (_MASK(TRL)|_MASK(TRR))
|
|
#define CREAR (_MASK(RLC)|_MASK(RRC))
|
|
#define SIDE (_MASK(SL)|_MASK(SR))
|
|
#define CHANNEL_BITS (64u)
|
|
|
|
static uint32_t mask_to_ch(struct channelmix *mix, uint64_t mask)
|
|
{
|
|
uint32_t ch = 0;
|
|
while (mask > 1) {
|
|
ch++;
|
|
mask >>= 1;
|
|
}
|
|
return ch;
|
|
}
|
|
|
|
static void distribute_mix(struct channelmix *mix,
|
|
float matrix[MAX_CHANNELS][MAX_CHANNELS],
|
|
uint64_t mask)
|
|
{
|
|
uint32_t i, ch = mask_to_ch(mix, mask);
|
|
for (i = 0; i < MAX_CHANNELS; i++)
|
|
matrix[i][ch]= 1.0f;
|
|
}
|
|
static void average_mix(struct channelmix *mix,
|
|
float matrix[MAX_CHANNELS][MAX_CHANNELS],
|
|
uint64_t mask)
|
|
{
|
|
uint32_t i, ch = mask_to_ch(mix, mask);
|
|
for (i = 0; i < MAX_CHANNELS; i++)
|
|
matrix[ch][i]= 1.0f;
|
|
}
|
|
static void pair_mix(float matrix[MAX_CHANNELS][MAX_CHANNELS])
|
|
{
|
|
uint32_t i;
|
|
for (i = 0; i < MAX_CHANNELS; i++)
|
|
matrix[i][i]= 1.0f;
|
|
}
|
|
static bool match_mix(struct channelmix *mix,
|
|
float matrix[MAX_CHANNELS][MAX_CHANNELS],
|
|
uint64_t src_mask, uint64_t dst_mask)
|
|
{
|
|
bool matched = false;
|
|
uint32_t i;
|
|
for (i = 0; i < CHANNEL_BITS; i++) {
|
|
if ((src_mask & dst_mask & (1ULL << i))) {
|
|
spa_log_info(mix->log, "matched channel %u (%f)", i, 1.0f);
|
|
matrix[i][i] = 1.0f;
|
|
matched = true;
|
|
}
|
|
}
|
|
return matched;
|
|
}
|
|
|
|
static int make_matrix(struct channelmix *mix)
|
|
{
|
|
float matrix[MAX_CHANNELS][MAX_CHANNELS] = {{ 0.0f }};
|
|
uint64_t src_mask = mix->src_mask, src_paired;
|
|
uint64_t dst_mask = mix->dst_mask, dst_paired;
|
|
uint32_t src_chan = mix->src_chan;
|
|
uint32_t dst_chan = mix->dst_chan;
|
|
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, matched = false, normalize;
|
|
#define _MATRIX(s,d) matrix[_CH(s)][_CH(d)]
|
|
|
|
normalize = SPA_FLAG_IS_SET(mix->options, CHANNELMIX_OPTION_NORMALIZE);
|
|
|
|
spa_log_debug(mix->log, "src-mask:%08"PRIx64" dst-mask:%08"PRIx64
|
|
" options:%08x", src_mask, dst_mask, mix->options);
|
|
|
|
/* shift so that bit 0 is MONO */
|
|
src_mask >>= _SH;
|
|
dst_mask >>= _SH;
|
|
|
|
if (src_chan > 1 && (src_mask & _MASK(MONO)))
|
|
src_mask = 0;
|
|
if (dst_chan > 1 && (dst_mask & _MASK(MONO)))
|
|
dst_mask = 0;
|
|
|
|
src_paired = src_mask;
|
|
dst_paired = dst_mask;
|
|
|
|
/* unknown channels */
|
|
if (src_mask == 0 || dst_mask == 0) {
|
|
if (src_chan == 1) {
|
|
/* one src channel goes everywhere */
|
|
spa_log_info(mix->log, "distribute UNK (%f) %"PRIu64, 1.0f, src_mask);
|
|
distribute_mix(mix, matrix, src_mask);
|
|
} else if (dst_chan == 1) {
|
|
/* one dst channel get average of everything */
|
|
spa_log_info(mix->log, "average UNK (%f) %"PRIu64, 1.0f / src_chan, dst_mask);
|
|
average_mix(mix, matrix, dst_mask);
|
|
normalize = true;
|
|
} else {
|
|
/* just pair channels */
|
|
spa_log_info(mix->log, "pairing UNK channels (%f)", 1.0f);
|
|
if (src_mask == 0)
|
|
src_paired = dst_mask;
|
|
else if (dst_mask == 0)
|
|
dst_paired = src_mask;
|
|
pair_mix(matrix);
|
|
}
|
|
goto done;
|
|
} else {
|
|
spa_log_debug(mix->log, "matching channels");
|
|
matched = match_mix(mix, matrix, src_mask, dst_mask);
|
|
}
|
|
|
|
unassigned = src_mask & ~dst_mask;
|
|
keep = dst_mask & ~src_mask;
|
|
|
|
if (!SPA_FLAG_IS_SET(mix->options, CHANNELMIX_OPTION_UPMIX)) {
|
|
/* upmix completely disabled */
|
|
keep = 0;
|
|
} else {
|
|
/* some upmixing (FC and LFE) enabled. */
|
|
if (mix->upmix == CHANNELMIX_UPMIX_NONE)
|
|
keep = 0;
|
|
if (mix->fc_cutoff > 0.0f)
|
|
keep |= FRONT;
|
|
else
|
|
keep &= ~FRONT;
|
|
if (mix->lfe_cutoff > 0.0f)
|
|
keep |= _MASK(LFE);
|
|
else
|
|
keep &= ~_MASK(LFE);
|
|
}
|
|
/* if we have no channel matched, try to upmix or keep the stereo
|
|
* pair or else we might end up with silence. */
|
|
if (dst_mask & STEREO && !matched)
|
|
keep |= STEREO;
|
|
|
|
spa_log_info(mix->log, "unassigned downmix %08" PRIx64 " %08" PRIx64, unassigned, keep);
|
|
|
|
if (unassigned & _MASK(MONO)) {
|
|
if ((dst_mask & STEREO) == STEREO) {
|
|
spa_log_info(mix->log, "assign MONO to STEREO (%f)", 1.0f);
|
|
_MATRIX(FL,MONO) += 1.0f;
|
|
_MATRIX(FR,MONO) += 1.0f;
|
|
keep &= ~STEREO;
|
|
} else if ((dst_mask & FRONT) == FRONT) {
|
|
spa_log_info(mix->log, "assign MONO to FRONT (%f)", 1.0f);
|
|
_MATRIX(FC,MONO) += 1.0f;
|
|
normalize = true;
|
|
} else {
|
|
spa_log_warn(mix->log, "can't assign MONO");
|
|
}
|
|
}
|
|
|
|
if (unassigned & FRONT) {
|
|
if ((dst_mask & STEREO) == STEREO){
|
|
if (src_mask & STEREO) {
|
|
spa_log_info(mix->log, "assign FC to STEREO (%f)", clev);
|
|
_MATRIX(FL,FC) += clev;
|
|
_MATRIX(FR,FC) += clev;
|
|
} else {
|
|
spa_log_info(mix->log, "assign FC to STEREO (%f)", SQRT1_2);
|
|
_MATRIX(FL,FC) += SQRT1_2;
|
|
_MATRIX(FR,FC) += SQRT1_2;
|
|
}
|
|
keep &= ~STEREO;
|
|
} else if (dst_mask & _MASK(MONO)){
|
|
spa_log_info(mix->log, "assign FC to MONO (%f)", 1.0f);
|
|
for (i = 0; i < MAX_CHANNELS; i++)
|
|
matrix[i][_CH(FC)]= 1.0f;
|
|
normalize = true;
|
|
} else {
|
|
spa_log_warn(mix->log, "can't assign FC");
|
|
}
|
|
}
|
|
|
|
if (unassigned & STEREO){
|
|
if (dst_mask & FRONT) {
|
|
spa_log_info(mix->log, "assign STEREO to FC (%f)", SQRT1_2);
|
|
_MATRIX(FC,FL) += SQRT1_2;
|
|
_MATRIX(FC,FR) += SQRT1_2;
|
|
if (src_mask & FRONT) {
|
|
spa_log_info(mix->log, "assign FC to FC (%f)", clev * SQRT2);
|
|
_MATRIX(FC,FC) = clev * SQRT2;
|
|
}
|
|
keep &= ~FRONT;
|
|
} else if ((dst_mask & _MASK(MONO))){
|
|
spa_log_info(mix->log, "assign STEREO to MONO (%f)", 1.0f);
|
|
for (i = 0; i < MAX_CHANNELS; i++) {
|
|
matrix[i][_CH(FL)]= 1.0f;
|
|
matrix[i][_CH(FR)]= 1.0f;
|
|
}
|
|
normalize = true;
|
|
} else {
|
|
spa_log_warn(mix->log, "can't assign STEREO");
|
|
}
|
|
}
|
|
|
|
if (unassigned & _MASK(RC)) {
|
|
if (dst_mask & REAR){
|
|
spa_log_info(mix->log, "assign RC to RL+RR (%f)", SQRT1_2);
|
|
_MATRIX(RL,RC) += SQRT1_2;
|
|
_MATRIX(RR,RC) += SQRT1_2;
|
|
} else if (dst_mask & SIDE) {
|
|
spa_log_info(mix->log, "assign RC to SL+SR (%f)", SQRT1_2);
|
|
_MATRIX(SL,RC) += SQRT1_2;
|
|
_MATRIX(SR,RC) += SQRT1_2;
|
|
} else if(dst_mask & STEREO) {
|
|
spa_log_info(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_info(mix->log, "assign RC to FC (%f)", slev * SQRT1_2);
|
|
_MATRIX(FC,RC) += slev * SQRT1_2;
|
|
} else if (dst_mask & _MASK(MONO)){
|
|
spa_log_info(mix->log, "assign RC to MONO (%f)", 1.0f);
|
|
for (i = 0; i < MAX_CHANNELS; i++)
|
|
matrix[i][_CH(RC)]= 1.0f;
|
|
normalize = true;
|
|
} else {
|
|
spa_log_warn(mix->log, "can't assign RC");
|
|
}
|
|
}
|
|
|
|
if (unassigned & REAR) {
|
|
if (dst_mask & _MASK(RC)) {
|
|
spa_log_info(mix->log, "assign RL+RR to RC");
|
|
_MATRIX(RC,RL) += SQRT1_2;
|
|
_MATRIX(RC,RR) += SQRT1_2;
|
|
} else if (dst_mask & SIDE) {
|
|
spa_log_info(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;
|
|
}
|
|
keep &= ~SIDE;
|
|
} else if (dst_mask & STEREO) {
|
|
spa_log_info(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_info(mix->log, "assign RL+RR to FC (%f)",
|
|
slev * SQRT1_2);
|
|
_MATRIX(FC,RL)+= slev * SQRT1_2;
|
|
_MATRIX(FC,RR)+= slev * SQRT1_2;
|
|
} else if (dst_mask & _MASK(MONO)){
|
|
spa_log_info(mix->log, "assign RL+RR to MONO (%f)", 1.0f);
|
|
for (i = 0; i < MAX_CHANNELS; i++) {
|
|
matrix[i][_CH(RL)]= 1.0f;
|
|
matrix[i][_CH(RR)]= 1.0f;
|
|
}
|
|
normalize = true;
|
|
} else {
|
|
spa_log_warn(mix->log, "can't assign RL+RR");
|
|
}
|
|
}
|
|
|
|
if (unassigned & SIDE) {
|
|
if (dst_mask & REAR) {
|
|
if (src_mask & _MASK(RL)) {
|
|
spa_log_info(mix->log, "assign SL+SR to RL+RR (%f)", SQRT1_2);
|
|
_MATRIX(RL,SL) += SQRT1_2;
|
|
_MATRIX(RR,SR) += SQRT1_2;
|
|
} else {
|
|
spa_log_info(mix->log, "assign SL+SR to RL+RR (%f)", 1.0f);
|
|
_MATRIX(RL,SL) += 1.0f;
|
|
_MATRIX(RR,SR) += 1.0f;
|
|
}
|
|
keep &= ~REAR;
|
|
} else if (dst_mask & _MASK(RC)) {
|
|
spa_log_info(mix->log, "assign SL+SR to RC (%f)", SQRT1_2);
|
|
_MATRIX(RC,SL)+= SQRT1_2;
|
|
_MATRIX(RC,SR)+= SQRT1_2;
|
|
} else if (dst_mask & STEREO) {
|
|
if (matrix_encoding == MATRIX_DOLBY) {
|
|
spa_log_info(mix->log, "assign SL+SR to FL+FR (%f)",
|
|
slev * SQRT1_2);
|
|
_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) {
|
|
spa_log_info(mix->log, "assign SL+SR to FL+FR (%f / %f)",
|
|
slev * SQRT3_2, slev * SQRT1_2);
|
|
_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 {
|
|
spa_log_info(mix->log, "assign SL+SR to FL+FR (%f)", slev);
|
|
_MATRIX(FL,SL) += slev;
|
|
_MATRIX(FR,SR) += slev;
|
|
}
|
|
} else if (dst_mask & FRONT) {
|
|
spa_log_info(mix->log, "assign SL+SR to FC (%f)", slev * SQRT1_2);
|
|
_MATRIX(FC,SL) += slev * SQRT1_2;
|
|
_MATRIX(FC,SR) += slev * SQRT1_2;
|
|
} else if (dst_mask & _MASK(MONO)){
|
|
spa_log_info(mix->log, "assign SL+SR to MONO (%f)", 1.0f);
|
|
for (i = 0; i < MAX_CHANNELS; i++) {
|
|
matrix[i][_CH(SL)]= 1.0f;
|
|
matrix[i][_CH(SR)]= 1.0f;
|
|
}
|
|
normalize = true;
|
|
} else {
|
|
spa_log_warn(mix->log, "can't assign SL");
|
|
}
|
|
}
|
|
|
|
if (unassigned & _MASK(FLC)) {
|
|
if (dst_mask & STEREO) {
|
|
spa_log_info(mix->log, "assign FLC+FRC to FL+FR (%f)", 1.0f);
|
|
_MATRIX(FL,FLC)+= 1.0f;
|
|
_MATRIX(FR,FRC)+= 1.0f;
|
|
} else if(dst_mask & FRONT) {
|
|
spa_log_info(mix->log, "assign FLC+FRC to FC (%f)", SQRT1_2);
|
|
_MATRIX(FC,FLC)+= SQRT1_2;
|
|
_MATRIX(FC,FRC)+= SQRT1_2;
|
|
} else if (dst_mask & _MASK(MONO)){
|
|
spa_log_info(mix->log, "assign FLC+FRC to MONO (%f)", 1.0f);
|
|
for (i = 0; i < MAX_CHANNELS; i++) {
|
|
matrix[i][_CH(FLC)]= 1.0f;
|
|
matrix[i][_CH(FRC)]= 1.0f;
|
|
}
|
|
normalize = true;
|
|
} 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_info(mix->log, "assign LFE to FC (%f)", llev);
|
|
_MATRIX(FC,LFE) += llev;
|
|
} else if (dst_mask & STEREO) {
|
|
spa_log_info(mix->log, "assign LFE to FL+FR (%f)",
|
|
llev * SQRT1_2);
|
|
_MATRIX(FL,LFE) += llev * SQRT1_2;
|
|
_MATRIX(FR,LFE) += llev * SQRT1_2;
|
|
} else if ((dst_mask & _MASK(MONO))){
|
|
spa_log_info(mix->log, "assign LFE to MONO (%f)", 1.0f);
|
|
for (i = 0; i < MAX_CHANNELS; i++)
|
|
matrix[i][_CH(LFE)]= 1.0f;
|
|
normalize = true;
|
|
} else {
|
|
spa_log_warn(mix->log, "can't assign LFE");
|
|
}
|
|
}
|
|
if (unassigned & TSTEREO){
|
|
if (dst_mask & STEREO) {
|
|
spa_log_info(mix->log, "assign TSTEREO to FL+FR (%f)", HALF);
|
|
_MATRIX(FL,TFL) += HALF;
|
|
_MATRIX(FR,TFR) += HALF;
|
|
} else if (dst_mask & _MASK(MONO)){
|
|
spa_log_info(mix->log, "assign TSTEREO to MONO (%f)", 1.0f);
|
|
for (i = 0; i < MAX_CHANNELS; i++) {
|
|
matrix[i][_CH(TFL)]= 1.0f;
|
|
matrix[i][_CH(TFR)]= 1.0f;
|
|
}
|
|
normalize = true;
|
|
} else {
|
|
spa_log_warn(mix->log, "can't assign TSTEREO");
|
|
}
|
|
}
|
|
if (unassigned & TCSTEREO){
|
|
if (dst_mask & STEREO) {
|
|
spa_log_info(mix->log, "assign TCSTEREO to FL+FR (%f)", HALF);
|
|
_MATRIX(FL,TFLC) += HALF;
|
|
_MATRIX(FR,TFRC) += HALF;
|
|
} else if (dst_mask & _MASK(MONO)){
|
|
spa_log_info(mix->log, "assign TCSTEREO to MONO (%f)", 1.0f);
|
|
for (i = 0; i < MAX_CHANNELS; i++) {
|
|
matrix[i][_CH(TFLC)]= 1.0f;
|
|
matrix[i][_CH(TFRC)]= 1.0f;
|
|
}
|
|
normalize = true;
|
|
} else {
|
|
spa_log_warn(mix->log, "can't assign TCSTEREO");
|
|
}
|
|
}
|
|
if (unassigned & TREAR){
|
|
if (dst_mask & REAR) {
|
|
if (src_mask & _MASK(RL)) {
|
|
spa_log_info(mix->log, "assign TREAR to RL+RR (%f)", HALF);
|
|
_MATRIX(RL,TRL) += HALF;
|
|
_MATRIX(RR,TRR) += HALF;
|
|
} else {
|
|
spa_log_info(mix->log, "assign TREAR to RL+RR (%f)", 1.0f);
|
|
_MATRIX(RL,TRL) += 1.0f;
|
|
_MATRIX(RR,TRR) += 1.0f;
|
|
}
|
|
keep &= ~REAR;
|
|
} else if (dst_mask & STEREO) {
|
|
spa_log_info(mix->log, "assign TREAR to FL+FR (%f)", HALF);
|
|
_MATRIX(FL,TRL) += HALF;
|
|
_MATRIX(FR,TRR) += HALF;
|
|
} else if (dst_mask & _MASK(MONO)){
|
|
spa_log_info(mix->log, "assign TREAR to MONO (%f)", 1.0f);
|
|
for (i = 0; i < MAX_CHANNELS; i++) {
|
|
matrix[i][_CH(TRL)]= 1.0f;
|
|
matrix[i][_CH(TRR)]= 1.0f;
|
|
}
|
|
normalize = true;
|
|
} else {
|
|
spa_log_warn(mix->log, "can't assign TREAR");
|
|
}
|
|
}
|
|
if (unassigned & CREAR){
|
|
if (dst_mask & REAR) {
|
|
if (src_mask & _MASK(RL)) {
|
|
spa_log_info(mix->log, "assign CREAR to RL+RR (%f)", SQRT1_2);
|
|
_MATRIX(RL,RLC) += SQRT1_2;
|
|
_MATRIX(RR,RRC) += SQRT1_2;
|
|
} else {
|
|
spa_log_info(mix->log, "assign CREAR to RL+RR (%f)", 1.0f);
|
|
_MATRIX(RL,RLC) += 1.0f;
|
|
_MATRIX(RR,RRC) += 1.0f;
|
|
}
|
|
keep &= ~REAR;
|
|
} else if (dst_mask & STEREO) {
|
|
spa_log_info(mix->log, "assign CREAR to FL+FR (%f)",
|
|
SQRT1_2);
|
|
_MATRIX(FL,RLC) += SQRT1_2;
|
|
_MATRIX(FR,RRC) += SQRT1_2;
|
|
} else if (dst_mask & _MASK(MONO)){
|
|
spa_log_info(mix->log, "assign CREAR to MONO (%f)", 1.0f);
|
|
for (i = 0; i < MAX_CHANNELS; i++) {
|
|
matrix[i][_CH(RLC)]= 1.0f;
|
|
matrix[i][_CH(RRC)]= 1.0f;
|
|
}
|
|
normalize = true;
|
|
} else {
|
|
spa_log_warn(mix->log, "can't assign CREAR");
|
|
}
|
|
}
|
|
|
|
unassigned = dst_mask & ~src_mask & keep;
|
|
|
|
spa_log_info(mix->log, "unassigned upmix %08"PRIx64" lfe:%f",
|
|
unassigned, mix->lfe_cutoff);
|
|
|
|
if (unassigned & STEREO) {
|
|
if ((src_mask & FRONT) == FRONT) {
|
|
spa_log_info(mix->log, "produce STEREO from FC (%f)", clev);
|
|
_MATRIX(FL,FC) += clev;
|
|
_MATRIX(FR,FC) += clev;
|
|
} else if (src_mask & _MASK(MONO)) {
|
|
spa_log_info(mix->log, "produce STEREO from MONO (%f)", 1.0f);
|
|
_MATRIX(FL,MONO) += 1.0f;
|
|
_MATRIX(FR,MONO) += 1.0f;
|
|
} else {
|
|
spa_log_warn(mix->log, "can't produce STEREO");
|
|
}
|
|
}
|
|
if (unassigned & FRONT) {
|
|
if ((src_mask & STEREO) == STEREO) {
|
|
spa_log_info(mix->log, "produce FC from STEREO (%f)", clev);
|
|
_MATRIX(FC,FL) += clev;
|
|
_MATRIX(FC,FR) += clev;
|
|
filter_fc = true;
|
|
} else if (src_mask & _MASK(MONO)) {
|
|
spa_log_info(mix->log, "produce FC from MONO (%f)", 1.0f);
|
|
_MATRIX(FC,MONO) += 1.0f;
|
|
filter_fc = true;
|
|
} else {
|
|
spa_log_warn(mix->log, "can't produce FC");
|
|
}
|
|
}
|
|
if (unassigned & _MASK(LFE)) {
|
|
if ((src_mask & STEREO) == STEREO) {
|
|
spa_log_info(mix->log, "produce LFE from STEREO (%f)", llev);
|
|
_MATRIX(LFE,FL) += llev;
|
|
_MATRIX(LFE,FR) += llev;
|
|
filter_lfe = true;
|
|
} else if ((src_mask & FRONT) == FRONT) {
|
|
spa_log_info(mix->log, "produce LFE from FC (%f)", llev);
|
|
_MATRIX(LFE,FC) += llev;
|
|
filter_lfe = true;
|
|
} else if (src_mask & _MASK(MONO)) {
|
|
spa_log_info(mix->log, "produce LFE from MONO (%f)", 1.0f);
|
|
_MATRIX(LFE,MONO) += 1.0f;
|
|
filter_lfe = true;
|
|
} else {
|
|
spa_log_warn(mix->log, "can't produce LFE");
|
|
}
|
|
}
|
|
if (unassigned & SIDE) {
|
|
if ((src_mask & REAR) == REAR) {
|
|
spa_log_info(mix->log, "produce SIDE from REAR (%f)", 1.0f);
|
|
_MATRIX(SL,RL) += 1.0f;
|
|
_MATRIX(SR,RR) += 1.0f;
|
|
} else if ((src_mask & STEREO) == STEREO) {
|
|
spa_log_info(mix->log, "produce SIDE from STEREO (%f)", slev);
|
|
_MATRIX(SL,FL) += slev;
|
|
_MATRIX(SR,FR) += slev;
|
|
} else if ((src_mask & FRONT) == FRONT &&
|
|
mix->upmix == CHANNELMIX_UPMIX_SIMPLE) {
|
|
spa_log_info(mix->log, "produce SIDE from FC (%f)", clev);
|
|
_MATRIX(SL,FC) += clev;
|
|
_MATRIX(SR,FC) += clev;
|
|
} else if (src_mask & _MASK(MONO) &&
|
|
mix->upmix == CHANNELMIX_UPMIX_SIMPLE) {
|
|
spa_log_info(mix->log, "produce SIDE from MONO (%f)", 1.0f);
|
|
_MATRIX(SL,MONO) += 1.0f;
|
|
_MATRIX(SR,MONO) += 1.0f;
|
|
} else {
|
|
spa_log_info(mix->log, "won't produce SIDE");
|
|
}
|
|
}
|
|
if (unassigned & REAR) {
|
|
if ((src_mask & SIDE) == SIDE) {
|
|
spa_log_info(mix->log, "produce REAR from SIDE (%f)", 1.0f);
|
|
_MATRIX(RL,SL) += 1.0f;
|
|
_MATRIX(RR,SR) += 1.0f;
|
|
} else if ((src_mask & STEREO) == STEREO) {
|
|
spa_log_info(mix->log, "produce REAR from STEREO (%f)", slev);
|
|
_MATRIX(RL,FL) += slev;
|
|
_MATRIX(RR,FR) += slev;
|
|
} else if ((src_mask & FRONT) == FRONT &&
|
|
mix->upmix == CHANNELMIX_UPMIX_SIMPLE) {
|
|
spa_log_info(mix->log, "produce REAR from FC (%f)", clev);
|
|
_MATRIX(RL,FC) += clev;
|
|
_MATRIX(RR,FC) += clev;
|
|
} else if (src_mask & _MASK(MONO) &&
|
|
mix->upmix == CHANNELMIX_UPMIX_SIMPLE) {
|
|
spa_log_info(mix->log, "produce REAR from MONO (%f)", 1.0f);
|
|
_MATRIX(RL,MONO) += 1.0f;
|
|
_MATRIX(RR,MONO) += 1.0f;
|
|
} else {
|
|
spa_log_info(mix->log, "won't produce SIDE");
|
|
}
|
|
}
|
|
if (unassigned & _MASK(RC)) {
|
|
if ((src_mask & REAR) == REAR) {
|
|
spa_log_info(mix->log, "produce RC from REAR (%f)", 0.5f);
|
|
_MATRIX(RC,RL) += 0.5f;
|
|
_MATRIX(RC,RR) += 0.5f;
|
|
} else if ((src_mask & SIDE) == SIDE) {
|
|
spa_log_info(mix->log, "produce RC from SIDE (%f)", 0.5f);
|
|
_MATRIX(RC,SL) += 0.5f;
|
|
_MATRIX(RC,SR) += 0.5f;
|
|
} else if ((src_mask & STEREO) == STEREO) {
|
|
spa_log_info(mix->log, "produce RC from STEREO (%f)", 0.5f);
|
|
_MATRIX(RC,FL) += 0.5f;
|
|
_MATRIX(RC,FR) += 0.5f;
|
|
} else if ((src_mask & FRONT) == FRONT &&
|
|
mix->upmix == CHANNELMIX_UPMIX_SIMPLE) {
|
|
spa_log_info(mix->log, "produce RC from FC (%f)", slev);
|
|
_MATRIX(RC,FC) += slev;
|
|
} else if (src_mask & _MASK(MONO) &&
|
|
mix->upmix == CHANNELMIX_UPMIX_SIMPLE) {
|
|
spa_log_info(mix->log, "produce RC from MONO (%f)", 1.0f);
|
|
_MATRIX(RC,MONO) += 1.0f;
|
|
} else {
|
|
spa_log_info(mix->log, "won't produce RC");
|
|
}
|
|
}
|
|
|
|
done:
|
|
if (dst_paired == 0)
|
|
dst_paired = ~0LU;
|
|
if (src_paired == 0)
|
|
src_paired = ~0LU;
|
|
|
|
for (jc = 0, ic = 0, i = 0; ic < dst_chan; i++) {
|
|
float sum = 0.0f;
|
|
char str1[1024], str2[1024];
|
|
struct spa_strbuf sb1, sb2;
|
|
|
|
spa_strbuf_init(&sb1, str1, sizeof(str1));
|
|
spa_strbuf_init(&sb2, str2, sizeof(str2));
|
|
|
|
if (i < CHANNEL_BITS && (dst_paired & (1UL << i)) == 0)
|
|
continue;
|
|
for (jc = 0, j = 0; jc < src_chan; j++) {
|
|
if (j < CHANNEL_BITS && (src_paired & (1UL << j)) == 0)
|
|
continue;
|
|
|
|
if (ic == 0)
|
|
spa_strbuf_append(&sb2, "%-4.4s ",
|
|
src_mask == 0 ? "UNK" :
|
|
spa_debug_type_find_short_name(spa_type_audio_channel, j + _SH));
|
|
|
|
mix->matrix_orig[ic][jc++] = matrix[i][j];
|
|
sum += fabsf(matrix[i][j]);
|
|
|
|
if (matrix[i][j] == 0.0f)
|
|
spa_strbuf_append(&sb1, " ");
|
|
else
|
|
spa_strbuf_append(&sb1, "%1.3f ", matrix[i][j]);
|
|
}
|
|
if (sb2.pos > 0)
|
|
spa_log_info(mix->log, " %s", str2);
|
|
if (sb1.pos > 0) {
|
|
spa_log_info(mix->log, "%03d %-4.4s %s %f", ic,
|
|
dst_mask == 0 ? "UNK" :
|
|
spa_debug_type_find_short_name(spa_type_audio_channel, i + _SH),
|
|
str1, sum);
|
|
}
|
|
|
|
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 {
|
|
lr4_set(&mix->lr4[ic], BQ_NONE, mix->fc_cutoff / mix->freq);
|
|
}
|
|
ic++;
|
|
}
|
|
if (normalize && maxsum > 1.0f) {
|
|
spa_log_info(mix->log, "normalize %f", maxsum);
|
|
for (i = 0; i < dst_chan; i++)
|
|
for (j = 0; j < src_chan; 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[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];
|
|
}
|
|
}
|
|
} else if (n_channel_volumes == 0) {
|
|
for (i = 0; i < dst_chan; i++) {
|
|
for (j = 0; j < src_chan; j++) {
|
|
mix->matrix[i][j] = mix->matrix_orig[i][j] * vol;
|
|
}
|
|
}
|
|
}
|
|
|
|
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];
|
|
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));
|
|
|
|
if (SPA_UNLIKELY(spa_log_level_topic_enabled(mix->log,
|
|
SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_DEBUG))) {
|
|
char str1[1024], str2[1024];
|
|
struct spa_strbuf sb1, sb2;
|
|
spa_strbuf_init(&sb2, str2, sizeof(str2));
|
|
for (i = 0; i < dst_chan; i++) {
|
|
spa_strbuf_init(&sb1, str1, sizeof(str1));
|
|
for (j = 0; j < src_chan; j++) {
|
|
float v = mix->matrix[i][j];
|
|
if (i == 0)
|
|
spa_strbuf_append(&sb2, " %03d ", j);
|
|
if (v == 0.0f)
|
|
spa_strbuf_append(&sb1, " ");
|
|
else
|
|
spa_strbuf_append(&sb1, "%1.3f ", v);
|
|
}
|
|
if (i == 0 && sb2.pos > 0)
|
|
spa_log_debug(mix->log, " %s", str2);
|
|
if (sb1.pos > 0)
|
|
spa_log_debug(mix->log, "%03d %s %03d", i, str1, i);
|
|
}
|
|
if (sb2.pos > 0)
|
|
spa_log_debug(mix->log, " %s", str2);
|
|
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 > MAX_CHANNELS ||
|
|
mix->dst_chan > 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 = (uint32_t)(mix->rear_delay * mix->freq / 1000.0f);
|
|
mix->func_name = info->name;
|
|
|
|
spa_zero(mix->taps_mem);
|
|
mix->taps = SPA_PTR_ALIGN(mix->taps_mem, CHANNELMIX_OPS_MAX_ALIGN, float);
|
|
mix->buffer[0] = SPA_PTR_ALIGN(&mix->buffer_mem[0], CHANNELMIX_OPS_MAX_ALIGN, float);
|
|
mix->buffer[1] = SPA_PTR_ALIGN(&mix->buffer_mem[2*BUFFER_SIZE], CHANNELMIX_OPS_MAX_ALIGN, float);
|
|
|
|
if (mix->hilbert_taps > 0) {
|
|
mix->n_taps = SPA_CLAMP(mix->hilbert_taps, 15u, MAX_TAPS) | 1;
|
|
blackman_window(mix->taps, mix->n_taps);
|
|
hilbert_generate(mix->taps, mix->n_taps);
|
|
reverse_taps(mix->taps, mix->n_taps);
|
|
} else {
|
|
mix->n_taps = 1;
|
|
mix->taps[0] = 1.0f;
|
|
}
|
|
if (mix->delay + mix->n_taps > BUFFER_SIZE)
|
|
mix->delay = BUFFER_SIZE - mix->n_taps;
|
|
|
|
spa_log_debug(mix->log, "selected %s delay:%d options:%08x", info->name, mix->delay,
|
|
mix->options);
|
|
|
|
return make_matrix(mix);
|
|
}
|