#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