alsa-pcm: add support for api.alsa.dll-bandwidth-max

In USB Audio Class 2 (UAC2) setups, pitch control is handled by
feedback endpoints. The host adjusts its data rate accordingly.

When pitch control is active (pitch_elem), applying the default
delay-locked loop (DLL) bandwidth can lead to instability and
oscillations around the target rate.

This patch adds a new parameter, api.alsa.dll-bandwidth-max, to
configure the maximum DLL bandwidth. It introduces a new field
in the ALSA state to store this value.

By default, it uses SPA_DLL_BW_MAX, but when pitch control is in
use, setting it to a lower value (e.g. 0.02) helps ensure better
stability, based on empirical testing.
This commit is contained in:
Julien Massot 2025-07-04 14:51:15 +02:00 committed by Wim Taymans
parent deb7dddbef
commit 8aa836d588
3 changed files with 13 additions and 2 deletions

View file

@ -778,6 +778,12 @@ Setting this to 0 makes htimestamp never get disabled.
Disable timer-based scheduling, and use IRQ for scheduling instead.
The "Pro Audio" profile will usually enable this setting, if it is expected it works on the hardware.
@PAR@ node-prop api.alsa.dll-bandwidth-max # double
Sets the maximum bandwidth of the DLL (delay-locked loop) filter used to smooth out rate adjustments.
The default value may be too responsive in some scenarios.
For example, with UAC2 pitch control, the host reacts more slowly compared to local resampling,
so using a lower bandwidth helps avoid oscillations or instability.
@PAR@ node-prop api.alsa.auto-link = false # boolean
Link follower PCM devices to the driver PCM device when using IRQ-based scheduling.
The "Pro Audio" profile will usually enable this setting, if it is expected it works on the hardware.

View file

@ -20,6 +20,7 @@
static struct spa_list cards = SPA_LIST_INIT(&cards);
static struct spa_list states = SPA_LIST_INIT(&states);
#define SPA_ALSA_DLL_BW_MIN 0.001
static struct card *find_card(uint32_t index)
{
@ -198,6 +199,9 @@ static int alsa_set_param(struct state *state, const char *k, const char *s)
state->disable_batch = spa_atob(s);
} else if (spa_streq(k, "api.alsa.disable-tsched")) {
state->disable_tsched = spa_atob(s);
} else if (spa_streq(k, "api.alsa.dll-bandwidth-max")) {
state->dll_bw_max = SPA_CLAMPD(spa_strtod(s, NULL),
SPA_ALSA_DLL_BW_MIN, SPA_DLL_BW_MAX);
} else if (spa_streq(k, "api.alsa.use-chmap")) {
state->props.use_chmap = spa_atob(s);
} else if (spa_streq(k, "api.alsa.multi-rate")) {
@ -2799,7 +2803,7 @@ static int update_time(struct state *state, uint64_t current_time, snd_pcm_sfram
int32_t diff;
if (SPA_UNLIKELY(state->dll.bw == 0.0)) {
spa_dll_set_bw(&state->dll, SPA_DLL_BW_MAX, state->threshold, state->rate);
spa_dll_set_bw(&state->dll, state->dll_bw_max, state->threshold, state->rate);
state->next_time = current_time;
state->base_time = current_time;
}
@ -2862,7 +2866,7 @@ static int update_time(struct state *state, uint64_t current_time, snd_pcm_sfram
err, state->max_error, state->max_resync, state->err_avg, state->err_var, bw);
spa_dll_set_bw(&state->dll,
SPA_CLAMPD(bw, 0.001, SPA_DLL_BW_MAX),
SPA_CLAMPD(bw, SPA_ALSA_DLL_BW_MIN, state->dll_bw_max),
state->threshold, state->rate);
}

View file

@ -244,6 +244,7 @@ struct state {
uint64_t underrun;
struct spa_dll dll;
double dll_bw_max;
double max_error;
double max_resync;
double err_avg, err_var, err_wdw;