#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
_______________________________________________
pulseaudio-tickets mailing list
pulseaudio-tickets@mail.0pointer.de
https://tango.0pointer.de/mailman/listinfo/pulseaudio-tickets

Reply via email to