alsasink: Brief gap after unpause if audio clock is not the pipeline clock
Submitted by Carlos Rafael Giani
Created attachment 304947
gst-play patch , do not commit! Just for testing purposes
If playback is paused, later unpaused, and the audio clock is not the pipeline clock, then after unpause, a short audio sequence can be heard, then a brief gap, then audio continues. This is 100% reproducible, even with gst-play, if the attached gst-play-alsasink-test.patch is used. Patch gstreamer core with this, rebuild, and then play an audio file and pause&unpause a few times.
The problem can be fixed/cirumvented by using snd_pcm_drain() instead of snd_pcm_drop() in the gst_alsasink_reset() definition. (snd_pcm_nonblock() has to be used before and after calling snd_pcm_drain(), since draining in nonblock mode won't work properly.)
There are two open questions. First, it is unclear if draining instead of dropping is a viable option. Draining will introduce a small delay (since it blocks until draining is complete).
Second, it is not fully known why this error occurs, and why draining helps. I can only speculate:
Draining essentially instructs ALSA to block until the playback pointer catches up with the write pointer. Drop just stops playback immediately. Therefore, if drop is called, all pending writes are discarded, but the current one is finished (in case a write is happening concurrently in the background). The write pointer inside ALSA is moved forward, but the playback pointer isn't, since drop stopped playback. Then, after unpause, the sink resynchronizes itself, which leads to nullsamples being inserted prior to the actual audio data. These nullsamples are what cause the gap. The problem then boils down to the fact that ALSA's internal write pointer is moved forward, but the playback pointer isn't: the playback pointer is still in front of the old samples that were written before the pause. In other words, after unpause, ALSA plays a few "old" samples that were written earlier.
I see no way to tell ALSA to drop and not move forward the write pointer. So, draining really seems to be the only simple choice. Another, nontrivial option would be mmap, since it might be possible then to do a manual memset() after dropping, which would clear out any of these "old" samples.
Patch 304947, "gst-play patch , do not commit! Just for testing purposes":