[PATCH v3] ALSA: usb-audio: Fix boot-time audio stuttering for USB Audio device

Zhang Heng <[email protected]>
Newsgroups org.kernel.vger.linux-sound,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
This USB Audio device (0x1e0b:0xd01e) exhibits audio stuttering
during boot when playing audio. Once the system is fully booted,
playback is normal.

The device reports its isochronous endpoints with the Asynchronous
sync type (bmAttributes = 0x03), which causes the driver to
calculate nurbs = min(max_urbs, ...) = 3, providing only ~16ms
of buffering. During boot, the higher system scheduling jitter
(e.g., from init scripts, device enumeration, and driver probing)
can exceed this buffer depth, causing audible stuttering.

This patch adds a device-specific quirk (QUIRK_FLAG_PLAYBACK_URB_FIXUP)
that applies two changes for this device:
1. Forces nurbs to MAX_URBS (12), providing sufficient buffering
2. Sets URB_ISO_ASAP flag for more consistent xHCI scheduling

Both changes are required together for stable boot-time playback:
- The larger buffer absorbs scheduling jitter during boot
- URB_ISO_ASAP ensures consistent URB submission timing, preventing
  the xHCI scheduler from introducing variable delays

Test methodology:
- Without patch: reboot and play audio → stuttering audible in all
  tests (reproduced consistently across multiple attempts)
- With nurbs=8 only: occasional minor stuttering observed after
  multiple tests (insufficient buffer depth)
- With full patch (nurbs=12 + URB_ISO_ASAP): reboot and play audio
  → no stuttering observed (tested in 10+ reboot cycles without
  reproducing the issue)

Signed-off-by: Zhang Heng <[email protected]>
---
 sound/usb/endpoint.c | 7 ++++++-
 sound/usb/quirks.c   | 3 +++
 sound/usb/usbaudio.h | 8 ++++++++
 3 files changed, 17 insertions(+), 1 deletion(-)

diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c
index 24cd7692bd01..54aeac7d087b 100644
--- a/sound/usb/endpoint.c
+++ b/sound/usb/endpoint.c
@@ -1232,7 +1232,10 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep)
 		/* try to use enough URBs to contain an entire ALSA buffer */
 		max_urbs = min((unsigned) MAX_URBS,
 				MAX_QUEUE * packs_per_ms / urb_packs);
-		ep->nurbs = min(max_urbs, urbs_per_period * ep->cur_buffer_periods);
+		if (chip->quirk_flags & QUIRK_FLAG_PLAYBACK_URB_FIXUP)
+			ep->nurbs = MAX_URBS;
+		else
+			ep->nurbs = min(max_urbs, urbs_per_period * ep->cur_buffer_periods);
 	}
 
 	/* allocate and initialize data urbs */
@@ -1256,6 +1259,8 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep)
 			goto out_of_memory;
 		u->urb->pipe = ep->pipe;
 		u->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+		if (chip->quirk_flags & QUIRK_FLAG_PLAYBACK_URB_FIXUP)
+			u->urb->transfer_flags |= URB_ISO_ASAP;
 		u->urb->interval = 1 << ep->datainterval;
 		u->urb->context = u;
 		u->urb->complete = snd_complete_urb;
diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c
index 40aa40fccb46..2b4e665564d2 100644
--- a/sound/usb/quirks.c
+++ b/sound/usb/quirks.c
@@ -2422,6 +2422,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = {
 		   QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16),
 	DEVICE_FLG(0x1bcf, 0x2283, /* NexiGo N930AF FHD Webcam */
 		   QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16),
+	DEVICE_FLG(0x1e0b, 0xd01e, /* Generic USB Audio Device */
+		   QUIRK_FLAG_PLAYBACK_URB_FIXUP),
 	DEVICE_FLG(0x1ff7, 0x0f81, /* SC13A Webcam */
 		   QUIRK_FLAG_GET_SAMPLE_RATE),
 	DEVICE_FLG(0x2040, 0x7200, /* Hauppauge HVR-950Q */
@@ -2631,6 +2633,7 @@ static const char *const snd_usb_audio_quirk_flag_names[] = {
 	QUIRK_STRING_ENTRY(MIXER_CAPTURE_LINEAR_VOL),
 	QUIRK_STRING_ENTRY(IFB_SILENCE_ON_EMPTY),
 	QUIRK_STRING_ENTRY(MIXER_GET_CUR_BROKEN),
+	QUIRK_STRING_ENTRY(PLAYBACK_URB_FIXUP),
 	NULL
 };
 
diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h
index e26f9092417e..dc1d0c8c9c80 100644
--- a/sound/usb/usbaudio.h
+++ b/sound/usb/usbaudio.h
@@ -254,6 +254,12 @@ extern bool snd_usb_skip_validation;
  *  check being non-fatal and only disabling GET_CUR instead of the whole mixer.
  *  The current volume will then be provided by the internal cache that stores
  *  the last set volume
+ * QUIRK_FLAG_PLAYBACK_URB_FIXUP
+ *  Set URB_ISO_ASAP flag for isochronous URBs and force nurbs to MAX_URBS.
+ *  This is needed for devices that exhibit boot-time audio stuttering due
+ *  to insufficient buffer depth combined with xHCI scheduling variability.
+ *  The larger buffer (MAX_URBS = 12, ~64ms) absorbs system scheduling
+ *  jitter during boot, while URB_ISO_ASAP ensures consistent xHCI scheduling.
  */
 
 enum {
@@ -288,6 +294,7 @@ enum {
 	QUIRK_TYPE_MIXER_CAPTURE_LINEAR_VOL	= 28,
 	QUIRK_TYPE_IFB_SILENCE_ON_EMPTY		= 29,
 	QUIRK_TYPE_MIXER_GET_CUR_BROKEN		= 30,
+	QUIRK_TYPE_PLAYBACK_URB_FIXUP		= 31,
 /* Please also edit snd_usb_audio_quirk_flag_names */
 };
 
@@ -324,5 +331,6 @@ enum {
 #define QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL	QUIRK_FLAG(MIXER_CAPTURE_LINEAR_VOL)
 #define QUIRK_FLAG_IFB_SILENCE_ON_EMPTY		QUIRK_FLAG(IFB_SILENCE_ON_EMPTY)
 #define QUIRK_FLAG_MIXER_GET_CUR_BROKEN		QUIRK_FLAG(MIXER_GET_CUR_BROKEN)
+#define QUIRK_FLAG_PLAYBACK_URB_FIXUP		QUIRK_FLAG(PLAYBACK_URB_FIXUP)
 
 #endif /* __USBAUDIO_H */
-- 
2.25.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.