From 8aa836d588f9b998bd5d825aacac1dde44b805bc Mon Sep 17 00:00:00 2001 From: Julien Massot Date: Fri, 4 Jul 2025 14:51:15 +0200 Subject: [PATCH] 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. --- doc/dox/config/pipewire-props.7.md | 6 ++++++ spa/plugins/alsa/alsa-pcm.c | 8 ++++++-- spa/plugins/alsa/alsa-pcm.h | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/doc/dox/config/pipewire-props.7.md b/doc/dox/config/pipewire-props.7.md index 8d1cbd70c..e7ee8f848 100644 --- a/doc/dox/config/pipewire-props.7.md +++ b/doc/dox/config/pipewire-props.7.md @@ -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. diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 769e7443e..3e5e67b38 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -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); } diff --git a/spa/plugins/alsa/alsa-pcm.h b/spa/plugins/alsa/alsa-pcm.h index 6fb4a3403..0d23fa2f6 100644 --- a/spa/plugins/alsa/alsa-pcm.h +++ b/spa/plugins/alsa/alsa-pcm.h @@ -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;