summaryrefslogtreecommitdiffstats
path: root/sound/usb/pcm.c
diff options
context:
space:
mode:
authorPierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>2011-09-06 19:15:34 -0500
committerTakashi Iwai <tiwai@suse.de>2011-09-12 10:30:20 +0200
commit294c4fb8ab01728358836f478bcc1174ba7fb9d8 (patch)
tree2afde5810233ceb00dafa573061f224026238ada /sound/usb/pcm.c
parent1ef0e0a05345b7411bdabbfca27f58bd33dcc7c8 (diff)
downloadlinux-294c4fb8ab01728358836f478bcc1174ba7fb9d8.tar.bz2
ALSA: usb: refine delay information with USB frame counter
Existing code only updates the audio delay when URBs were submitted/retired. This can introduce an uncertainty of 8ms on the number of samples played out with the default settings, and a lot more when URBs convey more packets to reduce the interrupt rate and power consumption. This patch relies on the USB frame counter to reduce the uncertainty to less than 2ms worst-case. The delay information essentially becomes independent of the URB size and number of packets. This should help applications like PulseAudio which require accurate audio timing. Clemens Ladisch reported a decrease of mplayer's A-V difference from nrpacks down to at most 1ms. Thanks to Clemens for also pointing out that the implementation of frame counters varies between different HCDs. Only the 8 lowest-bits are used to estimate the delay. Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> [clemens: changed debug code] Signed-off-by: Clemens Ladisch <clemens@ladisch.de> Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/usb/pcm.c')
-rw-r--r--sound/usb/pcm.c28
1 files changed, 28 insertions, 0 deletions
diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c
index b8dcbf407bbb..0b699ca1957e 100644
--- a/sound/usb/pcm.c
+++ b/sound/usb/pcm.c
@@ -34,6 +34,30 @@
#include "clock.h"
#include "power.h"
+/* return the estimated delay based on USB frame counters */
+snd_pcm_uframes_t snd_usb_pcm_delay(struct snd_usb_substream *subs,
+ unsigned int rate)
+{
+ int current_frame_number;
+ int frame_diff;
+ int est_delay;
+
+ current_frame_number = usb_get_current_frame_number(subs->dev);
+ /*
+ * HCD implementations use different widths, use lower 8 bits.
+ * The delay will be managed up to 256ms, which is more than
+ * enough
+ */
+ frame_diff = (current_frame_number - subs->last_frame_number) & 0xff;
+
+ /* Approximation based on number of samples per USB frame (ms),
+ some truncation for 44.1 but the estimate is good enough */
+ est_delay = subs->last_delay - (frame_diff * rate / 1000);
+ if (est_delay < 0)
+ est_delay = 0;
+ return est_delay;
+}
+
/*
* return the current pcm pointer. just based on the hwptr_done value.
*/
@@ -45,6 +69,8 @@ static snd_pcm_uframes_t snd_usb_pcm_pointer(struct snd_pcm_substream *substream
subs = (struct snd_usb_substream *)substream->runtime->private_data;
spin_lock(&subs->lock);
hwptr_done = subs->hwptr_done;
+ substream->runtime->delay = snd_usb_pcm_delay(subs,
+ substream->runtime->rate);
spin_unlock(&subs->lock);
return hwptr_done / (substream->runtime->frame_bits >> 3);
}
@@ -417,6 +443,8 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
subs->hwptr_done = 0;
subs->transfer_done = 0;
subs->phase = 0;
+ subs->last_delay = 0;
+ subs->last_frame_number = 0;
runtime->delay = 0;
return snd_usb_substream_prepare(subs, runtime);