Skip to content

Commit 307cc9b

Browse files
committed
ALSA: usb-audio: Reduce latency at playback start, take#2
This is another attempt for the reduction of the latency at the start of a USB audio playback stream. The first attempt in the commit 9ce650a caused an unexpected regression (a deadlock with pipewire usage) and was later reverted by the commit 4b820e1. The devils are always living in details, of course; the cause of the deadlock was the call of snd_pcm_period_elapsed() inside prepare_playback_urb() callback. In the original code, this callback is never called from the stream lock context as it's driven solely from the URB complete callback. Along with the movement of the URB submission into the trigger START, this prepare call may be also executed in the stream lock context, hence it deadlocked with the another lock in snd_pcm_period_elapsed(). (Note that this happens only conditionally with a small period size that matches with the URB buffer length, which was a reason I overlooked during my tests. Also, the problem wasn't seen in the capture stream because the capture stream handles the period-elapsed only at retire callback that isn't executed at the trigger.) If it were only about avoiding the deadlock, it'd be possible to use snd_pcm_period_elapsed_under_stream_lock() as a solution. However, in general, the period elapsed notification must be sent after the actual stream start, and replacing the call wouldn't satisfy the pattern. A better option is to delay the notification after the stream start procedure finished, instead. In the case of USB framework, one of the fitting place would be the complete callback of the first URB. So, as a workaround of the deadlock and the order fixes above, in addition to the re-applying the changes in the commit 9ce650a, this patch introduces a new flag indicating the delayed period-elapsed handling and sets it under the possible deadlock condition (i.e. prepare callback being called before subs->running is set). Once when the flag is set, the period-elapsed call is handled at a later URB complete call instead. As a reference for the original motivation for the low-latency change, I cite here again: | USB-audio driver behaves a bit strangely for the playback stream -- | namely, it starts sending silent packets at PCM prepare state while | the actual data is submitted at first when the trigger START is | kicked off. This is a workaround for the behavior where URBs are | processed too quickly at the beginning. That is, if we start | submitting URBs at trigger START, the first few URBs will be | immediately completed, and this would result in the immediate | period-elapsed calls right after the start, which may confuse | applications. | | OTOH, submitting the data after silent URBs would, of course, result | in a certain delay of the actual data processing, and this is rather | more serious problem on modern systems, in practice. | | This patch tries to revert the workaround and lets the URB | submission starting at PCM trigger for the playback again. As far | as I've tested with various backends (native ALSA, PA, JACK, PW), I | haven't seen any problems (famous last words :) | | Note that the capture stream handling needs no such workaround, | since the capture is driven per received URB. Link: https://lore.kernel.org/r/4e71531f-4535-fd46-040e-506a3c256bbd@marcan.st Link: https://lore.kernel.org/r/s5hbl7li0fe.wl-tiwai@suse.de Signed-off-by: Takashi Iwai <tiwai@suse.de> Link: https://lore.kernel.org/r/20210707112447.27485-1-tiwai@suse.de Signed-off-by: Takashi Iwai <tiwai@suse.de>
1 parent 31028cb commit 307cc9b

File tree

2 files changed

+22
-5
lines changed

2 files changed

+22
-5
lines changed

sound/usb/card.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ struct snd_usb_substream {
158158
unsigned int stream_offset_adj; /* Bytes to drop from beginning of stream (for non-compliant devices) */
159159

160160
unsigned int running: 1; /* running status */
161+
unsigned int period_elapsed_pending; /* delay period handling */
161162

162163
unsigned int buffer_bytes; /* buffer size in bytes */
163164
unsigned int inflight_bytes; /* in-flight data bytes on buffer (for playback) */

sound/usb/pcm.c

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -611,13 +611,9 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
611611
subs->hwptr_done = 0;
612612
subs->transfer_done = 0;
613613
subs->last_frame_number = 0;
614+
subs->period_elapsed_pending = 0;
614615
runtime->delay = 0;
615616

616-
/* for playback, submit the URBs now; otherwise, the first hwptr_done
617-
* updates for all URBs would happen at the same time when starting */
618-
if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK)
619-
ret = start_endpoints(subs);
620-
621617
unlock:
622618
snd_usb_unlock_shutdown(chip);
623619
return ret;
@@ -1398,6 +1394,10 @@ static void prepare_playback_urb(struct snd_usb_substream *subs,
13981394
subs->trigger_tstamp_pending_update = false;
13991395
}
14001396

1397+
if (period_elapsed && !subs->running) {
1398+
subs->period_elapsed_pending = 1;
1399+
period_elapsed = 0;
1400+
}
14011401
spin_unlock_irqrestore(&subs->lock, flags);
14021402
urb->transfer_buffer_length = bytes;
14031403
if (period_elapsed)
@@ -1413,6 +1413,7 @@ static void retire_playback_urb(struct snd_usb_substream *subs,
14131413
{
14141414
unsigned long flags;
14151415
struct snd_urb_ctx *ctx = urb->context;
1416+
bool period_elapsed = false;
14161417

14171418
spin_lock_irqsave(&subs->lock, flags);
14181419
if (ctx->queued) {
@@ -1423,13 +1424,20 @@ static void retire_playback_urb(struct snd_usb_substream *subs,
14231424
}
14241425

14251426
subs->last_frame_number = usb_get_current_frame_number(subs->dev);
1427+
if (subs->running) {
1428+
period_elapsed = subs->period_elapsed_pending;
1429+
subs->period_elapsed_pending = 0;
1430+
}
14261431
spin_unlock_irqrestore(&subs->lock, flags);
1432+
if (period_elapsed)
1433+
snd_pcm_period_elapsed(subs->pcm_substream);
14271434
}
14281435

14291436
static int snd_usb_substream_playback_trigger(struct snd_pcm_substream *substream,
14301437
int cmd)
14311438
{
14321439
struct snd_usb_substream *subs = substream->runtime->private_data;
1440+
int err;
14331441

14341442
switch (cmd) {
14351443
case SNDRV_PCM_TRIGGER_START:
@@ -1440,6 +1448,14 @@ static int snd_usb_substream_playback_trigger(struct snd_pcm_substream *substrea
14401448
prepare_playback_urb,
14411449
retire_playback_urb,
14421450
subs);
1451+
if (cmd == SNDRV_PCM_TRIGGER_START) {
1452+
err = start_endpoints(subs);
1453+
if (err < 0) {
1454+
snd_usb_endpoint_set_callback(subs->data_endpoint,
1455+
NULL, NULL, NULL);
1456+
return err;
1457+
}
1458+
}
14431459
subs->running = 1;
14441460
dev_dbg(&subs->dev->dev, "%d:%d Start Playback PCM\n",
14451461
subs->cur_audiofmt->iface,

0 commit comments

Comments
 (0)