[pulseaudio-tickets] [PulseAudio] #866: pa_simple_drain() takes much longer (about 2000ms) than expected to complete
PulseAudio
trac-noreply at tango.0pointer.de
Sat Oct 16 04:32:58 PDT 2010
#866: pa_simple_drain() takes much longer (about 2000ms) than expected to
complete
---------------------+------------------------------------------------------
Reporter: th | Owner: lennart
Type: defect | Status: new
Milestone: | Component: daemon
Resolution: | Keywords:
---------------------+------------------------------------------------------
Changes (by tanuk):
* component: libpulse => daemon
Comment:
There are two problems in the current implementation. One of them is such
that if it's solved, it fixes this bug completely. The other problem is
probably easier to solve, but it will only reduce the average delay.
The first problem (harder to fix, but required for complete solution):
Let's assume that the sink's hw buffer size is 2 seconds. You want to play
a 0.5s long audio clip. There's nothing playing before the new stream.
When you create the stream, you immediately give all 0.5 seconds of audio
in one go. The sink rewinds the whole hw buffer and puts your audio in the
beginning of the buffer and fills the rest of the buffer with silence.
Then the sink goes to sleep for 1.9 seconds (leaving 0.1 seconds of
processing time when the sleep ends).
Then you send a drain request. This doesn't cause any additional wake-ups
for the sink. This means that you can't notice the drain completion until
the sink asks for more data, which happens 1.9 - 0.5 = 1.4 seconds after
the last sample of your clip has been consumed.
This could be fixed by scheduling a wake-up to happen when 0.5 seconds of
the hw buffer has been consumed. That's probably not terribly hard, but
what makes this significantly harder is the fact that the stream may be
moved to another sink before the scheduled wake-up happens. When moving
the stream, the drain notification wake-up has to be somehow transferred
to the new sink too.
When implementing the wake-up scheduling, it's not actually obvious when
the wake-up should happen. Should it happen when the last sample hits the
speakers, or should it happen at some other point?
One can imagine setups where the audio has to travel through many hops
(through a network or otherwise), so the latency from the original client
to the speakers may be very high. Also, the routing may change at any time
at some later point than just at the immediate stream-sink interface.
Tracking the latency and updating the wake-up moment in such situation
gets really hard, because the downstream system (components after the
first sink) has to carry the wake-up request along with the audio data,
and send the "drain complete" signal back upstream when the last sink
finally has passed the last sample to the speakers.
That's probably far too complex to implement without any real benefit. But
that would be required if the promise of pa_simple_drain() is to wait
until the audio has passed the whole system. Therefore, we can't promise
that pa_simple_drain() waits until the audio has passed the whole system.
(Well, it's impossible anyway, because the required latency information
isn't always available).
What should be the function's promise, then? Should it be that after
pa_simple_drain() returns, calling pa_simple_free() won't truncate any
written audio? If this is the case, then this problem could be solved in
different way also: when the drain request comes in, move all data written
so far into a buffer that is guaranteed not to be wiped when the client
disconnects. This way no additional wake-ups would be needed.
That solution would cause pa_simple_drain() to return rather early in
pretty much all cases. I don't think that's good either. I believe that
the truncation promise is still the only promise that we can make for
pa_simple_drain(), but in order to make things work as expected in most
cases, I would still use the wake-up scheduling solution: When a drain
request comes in, schedule a wake-up to happen at the time when the rewind
boundary passes the last sample written. When the wake-up happens, get the
current fixed latency of the system and wait for that time before sending
the drain acknowledgement to the client. Since the fixed latency can
change (due to rerouting) while the audio passes the other system, or the
latency information is wrong to begin with, we can't promise that the
audio is really out of the system before pa_simple_drain() returns, but we
can try it anyway so that it works that way in common cases.
Phew, this is getting long.
Still the other problem (probably not very hard to fix, but fixing this
provides only a partial solution):
Consider the earlier example: you've requested draining after writing 0.5
seconds of audio data, and the sink is waiting 1.9 seconds until it asks
for more data, which is the first point where we have an opportunity to
detect that the drain is complete. The clip has finished already 1.4
seconds ago. There's 0.1 seconds of silence already written to the
hardware buffer, which means that it's possible to rewind 0.1 seconds.
Your stream, however, does not know that there's only 0.1 seconds in the
buffer. It should be informed about that, but the current implementation
doesn't. Since it doesn't know that, it has to play safe, and assume that
the whole hardware buffer can be rewound, i.e. 2 seconds.
So, when the sink asks for more data, the stream notices that draining has
been requested, but it checks the rewind buffer and sees that it's not
empty - the rewind buffer contains all data from the last 2 seconds, and
since the clip started 1.9 seconds ago and finished 1.4 seconds ago, it
means that the stream must still wait 1.4 seconds until it can be sure
that it's safe to send the drain acknowledgement.
And so the sink fills the buffer again with silence and sleeps 1.9
seconds. After that wait is over, the stream sees that the most recent
data is 1.4 + 1.9 = 3.3 seconds old, which is more than the maximum rewind
amount, so now it's safe to send the drain acknowledgement - 3.3 seconds
late.
That's how it works in principle - the calculations used a bunch of
assumptions that aren't entirely correct, so don't take the numbers as
exact, but as your experiments show, the extra delay is always measured in
seconds, if the hw buffer is two seconds.
So, who wants to implement the needed fixes?
--
Ticket URL: <http://pulseaudio.org/ticket/866#comment:7>
PulseAudio <http://pulseaudio.org/>
The PulseAudio Sound Server
More information about the pulseaudio-bugs
mailing list