mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-11-22 06:59:59 -05:00
resample: make window configurable
Add kaiser window
This commit is contained in:
parent
18ff08243b
commit
bfd26c98e3
5 changed files with 422 additions and 66 deletions
|
|
@ -15,70 +15,189 @@ SPA_LOG_TOPIC_DEFINE(resample_log_topic, "spa.resample");
|
|||
|
||||
struct quality {
|
||||
uint32_t n_taps;
|
||||
double cutoff;
|
||||
double cutoff; /* when upsampling */
|
||||
double cutoff_factor; /* factor for downsampling */
|
||||
double params[32];
|
||||
};
|
||||
|
||||
static const struct quality window_qualities[] = {
|
||||
{ 8, 0.53, },
|
||||
{ 16, 0.67, },
|
||||
{ 24, 0.75, },
|
||||
{ 32, 0.80, },
|
||||
{ 48, 0.85, }, /* default */
|
||||
{ 64, 0.88, },
|
||||
{ 80, 0.895, },
|
||||
{ 96, 0.910, },
|
||||
{ 128, 0.936, },
|
||||
{ 144, 0.945, },
|
||||
{ 160, 0.950, },
|
||||
{ 192, 0.960, },
|
||||
{ 256, 0.970, },
|
||||
{ 896, 0.990, },
|
||||
{ 1024, 0.995, },
|
||||
struct window_info {
|
||||
uint32_t window;
|
||||
void (*func) (struct resample *r, double *w, double t, uint32_t n_taps);
|
||||
uint32_t n_qualities;
|
||||
const struct quality *qualities;
|
||||
void (*config) (struct resample *r);
|
||||
};
|
||||
struct window_info window_info[];
|
||||
|
||||
static const struct quality blackman_qualities[] = {
|
||||
{ 8, 0.53, 1.0, { 0.16, }},
|
||||
{ 16, 0.67, 1.0, { 0.20, }},
|
||||
{ 24, 0.75, 1.0, { 0.16, }},
|
||||
{ 32, 0.80, 1.0, { 0.16, }},
|
||||
{ 48, 0.85, 1.0, { 0.16, }}, /* default */
|
||||
{ 64, 0.88, 1.0, { 0.16, }},
|
||||
{ 80, 0.895, 1.0, { 0.16, }},
|
||||
{ 96, 0.910, 1.0, { 0.16, }},
|
||||
{ 128, 0.936, 1.0, { 0.16, }},
|
||||
{ 144, 0.945, 1.0, { 0.16, }},
|
||||
{ 160, 0.950, 1.0, { 0.16, }},
|
||||
{ 192, 0.960, 1.0, { 0.16, }},
|
||||
{ 256, 0.970, 1.0, { 0.16, }},
|
||||
{ 896, 0.990, 1.0, { 0.16, }},
|
||||
{ 1024, 0.995, 1.0, { 0.16, }},
|
||||
};
|
||||
|
||||
static inline double sinc(double x)
|
||||
static inline void blackman_window(struct resample *r, double *w, double t, uint32_t n_taps)
|
||||
{
|
||||
if (x < 1e-6) return 1.0;
|
||||
double x, alpha = r->config.blackman_params.alpha;
|
||||
uint32_t i, n_taps12 = n_taps/2;
|
||||
for (i = 0; i < n_taps12; i++, t += 1.0) {
|
||||
x = 2.0 * M_PI * t / n_taps;
|
||||
w[i] = (1.0 - alpha) / 2.0 + (1.0 / 2.0) * cos(x) +
|
||||
(alpha / 2.0) * cos(2.0 * x);
|
||||
}
|
||||
}
|
||||
static inline void blackman_config(struct resample *r)
|
||||
{
|
||||
const struct quality *q = &window_info[r->config.window].qualities[r->quality];
|
||||
if (r->config.blackman_params.alpha == 0.0)
|
||||
r->config.blackman_params.alpha = q->params[0];
|
||||
}
|
||||
|
||||
static const struct quality exp_qualities[] = {
|
||||
{ 8, 0.53, 1.0, { 16.97789, }},
|
||||
{ 16, 0.67, 1.0, { 16.97789, }},
|
||||
{ 24, 0.75, 1.0, { 16.97789, }},
|
||||
{ 32, 0.80, 1.0, { 16.97789, }},
|
||||
{ 48, 0.85, 1.0, { 16.97789, }}, /* default */
|
||||
{ 64, 0.88, 1.0, { 16.97789, }},
|
||||
{ 80, 0.895, 1.0, { 16.97789, }},
|
||||
{ 96, 0.910, 1.0, { 16.97789, }},
|
||||
{ 128, 0.936, 1.0, { 16.97789, }},
|
||||
{ 144, 0.945, 1.0, { 16.97789, }},
|
||||
{ 160, 0.950, 1.0, { 16.97789, }},
|
||||
{ 192, 0.960, 1.0, { 16.97789, }},
|
||||
{ 256, 0.970, 1.0, { 16.97789, }},
|
||||
{ 896, 0.990, 1.0, { 16.97789, }},
|
||||
{ 1024, 0.995, 1.0, { 16.97789, }},
|
||||
};
|
||||
|
||||
static inline void exp_window(struct resample *r, double *w, double t, uint32_t n_taps)
|
||||
{
|
||||
double x, A = r->config.exp_params.A;
|
||||
uint32_t i, n_taps12 = n_taps/2;
|
||||
|
||||
for (i = 0; i < n_taps12; i++, t += 1.0) {
|
||||
x = 2.0 * t / n_taps;
|
||||
/* doi:10.1109/RME.2008.4595727 with tweak */
|
||||
w[i] = (exp(A * sqrt(fmax(0.0, 1.0 - x*x))) - 1) / (exp(A) - 1);
|
||||
}
|
||||
}
|
||||
static inline void exp_config(struct resample *r)
|
||||
{
|
||||
const struct quality *q = &window_info[r->config.window].qualities[r->quality];
|
||||
if (r->config.exp_params.A == 0.0)
|
||||
r->config.exp_params.A = q->params[0];
|
||||
}
|
||||
|
||||
#include "dbesi0.c"
|
||||
|
||||
static const struct quality kaiser_qualities[] = {
|
||||
{ 8, 0.53, 1.0, { 5.0, }},
|
||||
{ 16, 0.67, 1.0, { 5.5, }},
|
||||
{ 24, 0.75, 1.0, { 6.5, }},
|
||||
{ 32, 0.80, 1.0, { 7.5, }},
|
||||
{ 48, 0.85, 1.0, { 8.5, }}, /* default */
|
||||
{ 64, 0.88, 1.0, { 9.5, }},
|
||||
{ 80, 0.895, 1.0, { 10.0, }},
|
||||
{ 96, 0.910, 1.0, { 10.5, }},
|
||||
{ 128, 0.936, 1.0, { 10.7, }},
|
||||
{ 144, 0.945, 1.0, { 11.5, }},
|
||||
{ 160, 0.950, 1.0, { 12.5, }},
|
||||
{ 192, 0.960, 1.0, { 13.5, }},
|
||||
{ 256, 0.970, 1.0, { 14.5, }},
|
||||
{ 896, 0.990, 1.0, { 16.67, }},
|
||||
{ 1024, 0.995, 1.0, { 17.67, }},
|
||||
};
|
||||
|
||||
static inline void kaiser_window(struct resample *r, double *w, double t, uint32_t n_taps)
|
||||
{
|
||||
double x, alpha = r->config.kaiser_params.alpha, beta = M_PI*alpha, den = dbesi0(beta);
|
||||
uint32_t i, n_taps12 = n_taps/2;
|
||||
for (i = 0; i < n_taps12; i++, t += 1.0) {
|
||||
x = 2.0 * t / n_taps;
|
||||
w[i] = dbesi0(beta * sqrt(fmax(0.0, 1.0 - x*x))) / den;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
static inline void kaiser_config(struct resample *r)
|
||||
{
|
||||
double A, B, dw, tr_bw;
|
||||
uint32_t n;
|
||||
|
||||
A = r->config.kaiser_params.stopband_attenuation;
|
||||
if (A <= 0.0)
|
||||
A = 160;
|
||||
tr_bw = r->config.kaiser_params.transition_bandwidth;
|
||||
if (tr_bw <= 0.0)
|
||||
tr_bw = 0.0305;
|
||||
|
||||
/* calculate Beta */
|
||||
if (A > 50)
|
||||
B = 0.1102 * (A - 8.7);
|
||||
else if (A >= 21)
|
||||
B = 0.5842 * pow (A - 21, 0.4) + 0.07886 * (A - 21);
|
||||
else
|
||||
B = 0.0;
|
||||
|
||||
/* calculate transition width in radians */
|
||||
dw = 2 * M_PI * (tr_bw);
|
||||
/* order of the filter */
|
||||
n = (uint32_t)((A - 8.0) / (2.285 * dw));
|
||||
|
||||
r->config.kaiser_params.alpha = B;
|
||||
r->config.n_taps = n + 1;
|
||||
}
|
||||
#else
|
||||
static inline void kaiser_config(struct resample *r)
|
||||
{
|
||||
const struct quality *q = &window_info[r->config.window].qualities[r->quality];
|
||||
if (r->config.kaiser_params.alpha == 0.0)
|
||||
r->config.kaiser_params.alpha = q->params[0];
|
||||
}
|
||||
#endif
|
||||
|
||||
struct window_info window_info[] = {
|
||||
[RESAMPLE_WINDOW_EXP] = { RESAMPLE_WINDOW_EXP, exp_window,
|
||||
SPA_N_ELEMENTS(exp_qualities), exp_qualities, exp_config },
|
||||
[RESAMPLE_WINDOW_BLACKMAN] = { RESAMPLE_WINDOW_BLACKMAN, blackman_window,
|
||||
SPA_N_ELEMENTS(blackman_qualities), blackman_qualities, blackman_config },
|
||||
[RESAMPLE_WINDOW_KAISER] = { RESAMPLE_WINDOW_KAISER, kaiser_window,
|
||||
SPA_N_ELEMENTS(kaiser_qualities), kaiser_qualities, kaiser_config },
|
||||
};
|
||||
|
||||
static inline double sinc(double x, double cutoff)
|
||||
{
|
||||
if (x < 1e-6) return cutoff;
|
||||
x *= M_PI;
|
||||
return sin(x) / x;
|
||||
return sin(x * cutoff) / x;
|
||||
}
|
||||
|
||||
static inline double window_blackman(double x, double n_taps)
|
||||
{
|
||||
double alpha = 0.232, r;
|
||||
x = 2.0 * M_PI * x / n_taps;
|
||||
r = (1.0 - alpha) / 2.0 + (1.0 / 2.0) * cos(x) +
|
||||
(alpha / 2.0) * cos(2.0 * x);
|
||||
return r;
|
||||
}
|
||||
|
||||
static inline double window_cosh(double x, double n_taps)
|
||||
{
|
||||
double r;
|
||||
double A = 16.97789;
|
||||
double x2;
|
||||
x = 2.0 * x / n_taps;
|
||||
x2 = x * x;
|
||||
if (x2 >= 1.0)
|
||||
return 0.0;
|
||||
/* doi:10.1109/RME.2008.4595727 with tweak */
|
||||
r = (exp(A * sqrt(1 - x2)) - 1) / (exp(A) - 1);
|
||||
return r;
|
||||
}
|
||||
|
||||
#define window (1 ? window_cosh : window_blackman)
|
||||
|
||||
static int build_filter(float *taps, uint32_t stride, uint32_t n_taps, uint32_t n_phases, double cutoff)
|
||||
static int build_filter(struct resample *r, float *taps, uint32_t stride, uint32_t n_taps,
|
||||
uint32_t n_phases, double cutoff)
|
||||
{
|
||||
uint32_t i, j, n_taps12 = n_taps/2;
|
||||
double window[n_taps12+1];
|
||||
|
||||
for (i = 0; i <= n_phases; i++) {
|
||||
double t = (double) i / (double) n_phases;
|
||||
window_info[r->config.window].func(r, window, t, n_taps);
|
||||
for (j = 0; j < n_taps12; j++, t += 1.0) {
|
||||
/* exploit symmetry in filter taps */
|
||||
taps[(n_phases - i) * stride + n_taps12 + j] =
|
||||
taps[i * stride + (n_taps12 - j - 1)] = (float)
|
||||
(cutoff * sinc(t * cutoff) * window(t, n_taps));
|
||||
(sinc(t, cutoff) * window[j]);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
|
@ -352,11 +471,15 @@ int resample_native_init(struct resample *r)
|
|||
{
|
||||
struct native_data *d;
|
||||
const struct quality *q;
|
||||
double scale;
|
||||
uint32_t c, n_taps, n_phases, filter_size, in_rate, out_rate, gcd, filter_stride;
|
||||
double scale, cutoff_factor;
|
||||
uint32_t i, n_taps, n_phases, filter_size, in_rate, out_rate, gcd, filter_stride;
|
||||
uint32_t history_stride, history_size, oversample;
|
||||
struct resample_config *c = &r->config;
|
||||
|
||||
r->quality = SPA_CLAMP(r->quality, 0, (int) SPA_N_ELEMENTS(window_qualities) - 1);
|
||||
if (c->window == RESAMPLE_WINDOW_DEFAULT)
|
||||
c->window = RESAMPLE_WINDOW_EXP;
|
||||
|
||||
r->quality = SPA_CLAMP(r->quality, 0, (int)(window_info[c->window].n_qualities - 1));
|
||||
r->free = impl_native_free;
|
||||
r->update_rate = impl_native_update_rate;
|
||||
r->in_len = impl_native_in_len;
|
||||
|
|
@ -366,17 +489,22 @@ int resample_native_init(struct resample *r)
|
|||
r->delay = impl_native_delay;
|
||||
r->phase = impl_native_phase;
|
||||
|
||||
q = &window_qualities[r->quality];
|
||||
window_info[c->window].config(r);
|
||||
|
||||
q = &window_info[c->window].qualities[r->quality];
|
||||
cutoff_factor = r->o_rate < r->i_rate ? q->cutoff_factor : 1.0;
|
||||
c->cutoff = c->cutoff <= 0.0 ? q->cutoff * cutoff_factor: c->cutoff;
|
||||
n_taps = c->n_taps == 0 ? q->n_taps : c->n_taps;
|
||||
|
||||
gcd = calc_gcd(r->i_rate, r->o_rate);
|
||||
|
||||
in_rate = r->i_rate / gcd;
|
||||
out_rate = r->o_rate / gcd;
|
||||
|
||||
scale = SPA_MIN(q->cutoff * out_rate / in_rate, q->cutoff);
|
||||
scale = SPA_MIN(c->cutoff * out_rate / in_rate, c->cutoff);
|
||||
|
||||
/* multiple of 8 taps to ease simd optimizations */
|
||||
n_taps = SPA_ROUND_UP_N((uint32_t)ceil(q->n_taps / scale), 8);
|
||||
n_taps = SPA_ROUND_UP_N((uint32_t)ceil(n_taps / scale), 8);
|
||||
n_taps = SPA_MIN(n_taps, 1u << 18);
|
||||
|
||||
/* try to get at least 256 phases so that interpolation is
|
||||
|
|
@ -400,7 +528,7 @@ int resample_native_init(struct resample *r)
|
|||
return -errno;
|
||||
|
||||
r->data = d;
|
||||
d->n_taps = n_taps;
|
||||
c->n_taps = d->n_taps = n_taps;
|
||||
d->n_phases = n_phases;
|
||||
d->in_rate = UINT32_TO_FIXP(in_rate);
|
||||
d->out_rate = out_rate;
|
||||
|
|
@ -411,25 +539,25 @@ int resample_native_init(struct resample *r)
|
|||
d->history = SPA_PTROFF(d->hist_mem, history_size, float*);
|
||||
d->filter_stride = filter_stride / sizeof(float);
|
||||
d->filter_stride_os = d->filter_stride * oversample;
|
||||
for (c = 0; c < r->channels; c++)
|
||||
d->history[c] = SPA_PTROFF(d->hist_mem, c * history_stride, float);
|
||||
for (i = 0; i < r->channels; i++)
|
||||
d->history[i] = SPA_PTROFF(d->hist_mem, i * history_stride, float);
|
||||
|
||||
#ifndef RESAMPLE_DISABLE_PRECOMP
|
||||
/* See if we have precomputed coefficients */
|
||||
for (c = 0; precomp_coeffs[c].filter; c++) {
|
||||
if (precomp_coeffs[c].in_rate == r->i_rate &&
|
||||
precomp_coeffs[c].out_rate == r->o_rate &&
|
||||
precomp_coeffs[c].quality == r->quality)
|
||||
for (i = 0; precomp_coeffs[i].filter; i++) {
|
||||
if (precomp_coeffs[i].in_rate == r->i_rate &&
|
||||
precomp_coeffs[i].out_rate == r->o_rate &&
|
||||
precomp_coeffs[i].quality == r->quality)
|
||||
break;
|
||||
}
|
||||
|
||||
if (precomp_coeffs[c].filter) {
|
||||
if (precomp_coeffs[i].filter) {
|
||||
spa_log_debug(r->log, "using precomputed filter for %u->%u(%u)",
|
||||
r->i_rate, r->o_rate, r->quality);
|
||||
spa_memcpy(d->filter, precomp_coeffs[c].filter, filter_size);
|
||||
spa_memcpy(d->filter, precomp_coeffs[i].filter, filter_size);
|
||||
} else {
|
||||
#endif
|
||||
build_filter(d->filter, d->filter_stride, n_taps, n_phases, scale);
|
||||
build_filter(r, d->filter, d->filter_stride, n_taps, n_phases, scale);
|
||||
#ifndef RESAMPLE_DISABLE_PRECOMP
|
||||
}
|
||||
#endif
|
||||
|
|
@ -440,8 +568,8 @@ int resample_native_init(struct resample *r)
|
|||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
spa_log_debug(r->log, "native %p: q:%d in:%d out:%d gcd:%d n_taps:%d n_phases:%d features:%08x:%08x",
|
||||
r, r->quality, r->i_rate, r->o_rate, gcd, n_taps, n_phases,
|
||||
spa_log_info(r->log, "native %p: q:%d w:%d in:%d out:%d gcd:%d n_taps:%d n_phases:%d features:%08x:%08x",
|
||||
r, r->quality, c->window, r->i_rate, r->o_rate, gcd, n_taps, n_phases,
|
||||
r->cpu_flags, d->info->cpu_flags);
|
||||
|
||||
r->cpu_flags = d->info->cpu_flags;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue