[alsa-devel] [PATCH] Wrong latency in pulseaudio plugin breaks Adobe Flash Player
Philip Spencer
pspencer at fields.utoronto.ca
Fri Feb 17 20:36:20 CET 2012
It seems you are right ... things are much more complicated than I
realized.
> Now; if an application start playback with an empty buffer (either by calling
> "trigger" or by setting a low start threshold), should we assume that 1) the
> application is going to stay almost empty all the time, like it seems Flash
> does in this scenario, or 2) fill it up completely and stay there (IIRC,
> mpg123 did this)?
>
> Lowering tlength might be reasonable in scenario 1, but scenario 2 will cause
> the client to block (trying to fill an already filled buffer) in ways that
> differs from the expected behaviour.
Obviously the plugin needs to work properly for both scenarios -- and
indeed Flash itself could be in either scenario, depending on whether it's
receiving packets from the network smoothly or in bunches.
I agree that it is unacceptable for the client to block if it's writing
fewer bytes than the buffer length it asked for. I did not realize
pulseaudio would do that. I thought that, if there were more than tlength
bytes in the buffer, pulseaudio simply would not issue the "I want more
data!" callback to the plugin until the length dropped below tlength/2,
but that client was free to write, without blocking, as many bytes as it
wished up to maxlength. Apparently I was wrong.
Next complication -- when I went to get a pulseaudio log of how the
latency rose when only prebuf was lowered, I found I had accidentally been
compiling the plugin when configure had been run against an older
pulseaudio version. Compiling it against pulseaudio 1.1 caused everything
to break again -- samples coming with no latency (good) but with part of
each sample cut off (very bad). I realized this is because the plugin is
now setting handle_underrun to 1 by default. With handle_underrun turned
off, I get the behaviour I saw before: works perfectly with tlength
lowered, but latency jumps up to 500ms when tlength=500ms, even with
prebuf lowered.
Putting some trace statements into the plugin code, I see the following
sequence of events is happening:
1. Alsa initialized with buffer = 500ms, io period = 20ms,
start threshold = 20ms, 16 kHz audio, 2byte samples, 1 channel.
2. Flash writes 640 bytes (320 samples, 20ms) and these are sent by the
plugin to pulseaudio.
3. An *explicit* stream start is then called (so in this case the prebuf
setting is actually totally irrelevant!)
4. 11 milliseconds later, the plugin gets an underrun callback from
PulseAudio. (Note that at this point only half the sample will actually
have been played, so this is definitely connected to the "false alarm"
underrun discussions you pointed me to).
5. If the plugin's underrun handling is enabled, this causes the whole
stream to be closed and re-opened. Unplayed samples are lost, but the
next packet plays at exactly the right time. Result: in-sync, but
unacceptably distorted, audio.
6. If the plugin's underrun handling is disabled, nothing further happens.
Flash writes the next 640 bytes about 16ms after its first packet was
written (so well before the first 20ms have finished actually playing).
7. From then on in, there are *NO* underruns reported back to the plugin
from pulseaudio.
8. However, I hear several brief hiccups in playback, and the pulseaudio
latency very quickly increases to 500ms.
I assume, then, that the tlength value also influences the buffering
between pulseaudio and the hardware sink, and it is this that is
underrunning and causing pulseaudio to raise the latency when it is
much larger than the fill level at which the client keeps the buffer.
This means that something probably needs to be fixed in pulseaudio. For
the plugin, there are probably some things that could be changed but they
won't help me directly:
- Definitely prebuf should be lowered if the client requests an explicit
start threshold, but this fix won't have any impact on my situation
since the stream is already being started explicitly.
- Something should be done about that early false-alarm underrun.
Maybe the pulse_start routine should not do anything if fewer than n
IO periods' worth of data has been written yet (where n is to be
determined), just set a flag and have the start retried after enough
data is written? Or maybe an underrun detected when fewer than n IO
period's worth of data have been sent should be silently ignored?
(If no such fix is made, though, I can easily work around things
by arranging my data stream so that Flash player always gets
sound packets in pairs and that will avoid this underrun. Indeed,
I note that the ffmpeg encoder by default always stuffs two
Speex packets together into a single packet and notes that Flash
Player doesn't play it properly otherwise; I wonder if this is
why?)
Now I guess it's time to dig in to pulseaudio, and see whether (a) it can
be changed so that the client is free to write more than tlength bytes
without blocking, or (b) the buffering between pulseaudio and its hardware
sink can be adjusted to use prebuf or minreq or something when the stream
is started early.
> So this is the usual problem with closed source applications: no application
> is bug free, but when the closed source application is broken, it's much
> harder to fix it. The sad reality is that by depending on a closed source
> application, you're putting yourself in this situation.
I agree wholeheartedly about the drawback with closed source applications.
Unfortunately we don't really have any choice: we need something that can
work for anybody who has a browser and webcam, without the need for them
to install any special software from us, and until HTML5's device access
(camera/microphone) and WebSocket APIs have matured and become available
on a majority of browsers, Flash Player is pretty much the only choice.
I do look forward, though, to being able to ditch it in favour of an
all-in-browser, HTML5 solution in a few years once that technology is
ready.
Besides, I really don't think there are any bugs in Flash here. It has to
try to deliver audio packets to the sound system as soon as they are
received and decoded from the network. It can't set a small IO buffer in
case it gets a large bunch of packets at once. It has to set an early
playback start threshold to minimize latency. So I really don't see what
else it can do (except maybe waiting until it sent the second packet to
start the stream, instead of starting it after only the first) -- do you
have any thoughts about what it could/should have been doing differently?
Anyway, I will do some more digging into PulseAudio (and try to get a
detailed log of what's happening).
Thanks,
Philip
--------------------------------------------+-------------------------------
Philip Spencer pspencer at fields.utoronto.ca | Director of Computing Services
Room 336 (416)-348-9710 ext3036 | The Fields Institute for
222 College St, Toronto ON M5T 3J1 Canada | Research in Mathematical Sciences
More information about the Alsa-devel
mailing list