]> git.itanic.dy.fi Git - linux-stable/commitdiff
ALSA: usb-audio: Work around for XRUN with low latency playback
authorTakashi Iwai <tiwai@suse.de>
Fri, 27 Aug 2021 20:33:11 +0000 (22:33 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 8 Sep 2021 06:52:41 +0000 (08:52 +0200)
commit 4267c5a8f3133db0572cd9abee059b42cafbbdad upstream.

The recent change for low latency playback works in most of test cases
but it turned out still to hit errors on some use cases, most notably
with JACK with small buffer sizes.  This is because USB-audio driver
fills up and submits full URBs at the beginning, while the URBs would
return immediately and try to fill more -- that can easily trigger
XRUN.  It was more or less expected, but in the small buffer size, the
problem became pretty obvious.

Fixing this behavior properly would require the change of the
fundamental driver design, so it's no trivial task, unfortunately.
Instead, here we work around the problem just by switching back to the
old method when the given configuration is too fragile with the low
latency stream handling.  As a threshold, we calculate the total
buffer bytes in all plus one URBs, and check whether it's beyond the
PCM buffer bytes.  The one extra URB is needed because XRUN happens at
the next submission after the first round.

Fixes: 307cc9baac5c ("ALSA: usb-audio: Reduce latency at playback start, take#2")
Cc: <stable@vger.kernel.org>
Link: https://lore.kernel.org/r/20210827203311.5987-1-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
sound/usb/card.h
sound/usb/endpoint.c
sound/usb/pcm.c

index 6c0a052a28f9953b9c6cc42890b1aed6007d51dd..5b19901f305a3b1d50b00959cd2443c8d34a26a3 100644 (file)
@@ -94,6 +94,7 @@ struct snd_usb_endpoint {
        struct list_head ready_playback_urbs; /* playback URB FIFO for implicit fb */
 
        unsigned int nurbs;             /* # urbs */
+       unsigned int nominal_queue_size; /* total buffer sizes in URBs */
        unsigned long active_mask;      /* bitmask of active urbs */
        unsigned long unlink_mask;      /* bitmask of unlinked urbs */
        char *syncbuf;                  /* sync buffer for all sync URBs */
@@ -187,6 +188,7 @@ struct snd_usb_substream {
        } dsd_dop;
 
        bool trigger_tstamp_pending_update; /* trigger timestamp being updated from initial estimate */
+       bool early_playback_start;      /* early start needed for playback? */
        struct media_ctl *media_ctl;
 };
 
index 8b1bec51c8065fb9de8e1d002255f62199ad24bf..bf26c04cf47162337e2cb29d28462829883e851b 100644 (file)
@@ -1126,6 +1126,10 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep)
                INIT_LIST_HEAD(&u->ready_list);
        }
 
+       /* total buffer bytes of all URBs plus the next queue;
+        * referred in pcm.c
+        */
+       ep->nominal_queue_size = maxsize * urb_packs * (ep->nurbs + 1);
        return 0;
 
 out_of_memory:
index 4e5031a680647bcc5222a2b31433f38c6731a87b..f5cbf61ac366ea6861c25b4eaac8fe73cb264e29 100644 (file)
@@ -614,6 +614,14 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
        subs->period_elapsed_pending = 0;
        runtime->delay = 0;
 
+       /* check whether early start is needed for playback stream */
+       subs->early_playback_start =
+               subs->direction == SNDRV_PCM_STREAM_PLAYBACK &&
+               subs->data_endpoint->nominal_queue_size >= subs->buffer_bytes;
+
+       if (subs->early_playback_start)
+               ret = start_endpoints(subs);
+
  unlock:
        snd_usb_unlock_shutdown(chip);
        return ret;
@@ -1394,7 +1402,7 @@ static void prepare_playback_urb(struct snd_usb_substream *subs,
                subs->trigger_tstamp_pending_update = false;
        }
 
-       if (period_elapsed && !subs->running) {
+       if (period_elapsed && !subs->running && !subs->early_playback_start) {
                subs->period_elapsed_pending = 1;
                period_elapsed = 0;
        }
@@ -1448,7 +1456,8 @@ static int snd_usb_substream_playback_trigger(struct snd_pcm_substream *substrea
                                              prepare_playback_urb,
                                              retire_playback_urb,
                                              subs);
-               if (cmd == SNDRV_PCM_TRIGGER_START) {
+               if (!subs->early_playback_start &&
+                   cmd == SNDRV_PCM_TRIGGER_START) {
                        err = start_endpoints(subs);
                        if (err < 0) {
                                snd_usb_endpoint_set_callback(subs->data_endpoint,