https://gitlab.freedesktop.org/pipewire/pipewire/-/commit/ba7790123c345002b08712babc7bf252f39e4485

From ba7790123c345002b08712babc7bf252f39e4485 Mon Sep 17 00:00:00 2001
From: Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
Date: Tue, 23 Sep 2025 13:20:12 +0300
Subject: [PATCH] alsa: Use the minimum period size as headroom for SOF cards

Configure the headroom to be equal of the minimum allowed period size for
the configuration.

This is desirable when the ALSA driver's hw_ptr is 'jumpy' due to
underplaying hardware architecture, like SOF.
In case of SOF the DSP firmware will burst read at stream start to fill
it's host facing buffer and later settles to a constant pace. The minimal
period size is constrained by the driver to cover the initial burst and
settling time of the hw_ptr.

Guard this mode of working with a new boolean flag, which is only enabled
for SOF cards, kept it disabled for other cards to avoid any unforeseen
side effects.

Even if the use-period-size-min-as-headroom is set to true, the manual
headroom configuration will take precedence to allow experimentation.

Link: https://github.com/thesofproject/linux/issues/5284
Link: https://github.com/thesofproject/sof/issues/9695#issuecomment-2569033847
Link: https://github.com/thesofproject/sof/issues/10172
Link: https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/4489
Signed-off-by: Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
---
 spa/plugins/alsa/alsa-pcm.c | 26 ++++++++++++++++++++++----
 spa/plugins/alsa/alsa-pcm.h |  2 ++
 2 files changed, 24 insertions(+), 4 deletions(-)

diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c
index 509e187423..1bb31e7a32 100644
--- a/spa/plugins/alsa/alsa-pcm.c
+++ b/spa/plugins/alsa/alsa-pcm.c
@@ -969,6 +969,13 @@ int spa_alsa_init(struct state *state, const struct spa_dict *info)
 	if ((str = spa_dict_lookup(info, "device.profile.pro")) != NULL)
 		state->is_pro = spa_atob(str);
 
+	if (info && spa_strstartswith(spa_dict_lookup(info, SPA_KEY_API_ALSA_CARD_NAME), "sof-") &&
+	    state->stream == SND_PCM_STREAM_PLAYBACK) {
+		state->use_period_size_min_as_headroom = true;
+		spa_log_info(state->log,
+			     "ALSA SOF driver detected: default api.alsa.use-period-size-min-as-headroom=true");
+	}
+
 	state->multi_rate = true;
 	state->htimestamp = false;
 	state->htimestamp_max_errors = MAX_HTIMESTAMP_ERROR;
@@ -2032,7 +2039,12 @@ static void recalc_headroom(struct state *state)
 	if (state->position != NULL)
 		rate = state->position->clock.target_rate.denom;
 
-	state->headroom = state->default_headroom;
+	if (state->use_period_size_min_as_headroom)
+		state->headroom = state->default_headroom ?
+				  state->default_headroom : state->period_size_min;
+	else
+		state->headroom = state->default_headroom;
+
 	if (!state->disable_tsched || state->resample) {
 		/* When using timers, we might miss the pointer update for batch
 		 * devices so add some extra headroom. With IRQ, we know the pointers
@@ -2349,6 +2361,12 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_
 		periods = UINT_MAX;
 	}
 
+	/* Query the minimum period size for this configuration
+	 * This information is used as headroom if use_period_size_min_as_headroom is
+	 * set and default_headroom is 0 (not forced by user)
+	 */
+	CHECK(snd_pcm_hw_params_get_period_size_min(params, &state->period_size_min, &dir), "snd_pcm_hw_params_get_period_size_min");
+
 	if (state->default_period_size == 0) {
 		/* Some devices (FireWire) don't produce audio if period number is too
 		 * small, so force a minimum. This will restrict possible period sizes if
@@ -2408,14 +2426,14 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_
 	recalc_headroom(state);
 
 	spa_log_info(state->log, "%s: format:%s access:%s-%s rate:%d channels:%d "
-			"buffer frames %lu, period frames %lu, periods %u, frame_size %zd "
+			"buffer frames %lu, period frames %lu (min:%lu), periods %u, frame_size %zd "
 			"headroom %u start-delay:%u batch:%u tsched:%u resample:%u",
 			state->name, snd_pcm_format_name(state->format),
 			state->use_mmap ? "mmap" : "rw",
 			planar ? "planar" : "interleaved",
 			state->rate, state->channels, state->buffer_frames, state->period_frames,
-			periods, state->frame_size, state->headroom, state->start_delay,
-			state->is_batch, !state->disable_tsched, state->resample);
+			state->period_size_min, periods, state->frame_size, state->headroom,
+			state->start_delay, state->is_batch, !state->disable_tsched, state->resample);
 
 	/* write the parameters to device */
 	CHECK(snd_pcm_hw_params(hndl, params), "set_hw_params");
diff --git a/spa/plugins/alsa/alsa-pcm.h b/spa/plugins/alsa/alsa-pcm.h
index 576144bfeb..6f2876fbc3 100644
--- a/spa/plugins/alsa/alsa-pcm.h
+++ b/spa/plugins/alsa/alsa-pcm.h
@@ -203,6 +203,7 @@ struct state {
 	int n_fds;
 	uint32_t threshold;
 	uint32_t last_threshold;
+	snd_pcm_uframes_t period_size_min;
 	uint32_t headroom;
 	uint32_t start_delay;
 	uint32_t min_delay;
@@ -233,6 +234,7 @@ struct state {
 	unsigned int linked:1;
 	unsigned int is_batch:1;
 	unsigned int force_rate:1;
+	unsigned int use_period_size_min_as_headroom:1;
 
 	uint64_t iec958_codecs;
 
-- 
GitLab
