Reputation: 303
I've tried multiple example programs that appear to have code to handle xruns under playback:
https://albertlockett.wordpress.com/2013/11/06/creating-digital-audio-with-alsa/
https://www.linuxjournal.com/article/6735 (listing 3)
When using snd_pcm_writei(), it appears that when the return value is -EPIPE (which is xrun/underrun), they do:
if (rc == -EPIPE) {
/* EPIPE means underrun */
fprintf(stderr, "underrun occurred\n");
snd_pcm_prepare(handle);
}
I.e. call snd_pcm_prepare() on the handle.
However, I still get stuttering when I attempt to run programs like these. Typically, I will get at least a few, to maybe half a dozen xrun, and then it will play smoothly and continuously without further xruns. However, if I have something else using the sound card, such as Firefox, I will get many more xruns, and sometimes only xruns. But again, even if I kill any other program that uses the sound card, I still experience the issue with some initial xruns and actual stuttering on the speakers.
This is not acceptable for me, how can I modify this type of xrun handling to prevent stuttering?
My own attempt at figuring this out:
From the ALSA API, I see that snd_pcm_prepare() does:
Prepare PCM for use.
This is not very helpful to an ALSA beginner like myself. It is not explained how this can be used to recover xrun issues.
I also note, from: https://www.alsa-project.org/alsa-doc/alsa-lib/pcm.html
SND_PCM_STATE_XRUN The PCM device reached overrun (capture) or underrun (playback). You can use the -EPIPE return code from I/O functions (snd_pcm_writei(), snd_pcm_writen(), snd_pcm_readi(), snd_pcm_readn()) to determine this state without checking the actual state via snd_pcm_state() call. It is recommended to use the helper function snd_pcm_recover() to recover from this state, but you can also use snd_pcm_prepare(), snd_pcm_drop() or snd_pcm_drain() calls.
Again, it is not clear to me. I can use snd_pcm_prepare() OR I can use these other calls? What is the difference? What should I use?
Upvotes: 3
Views: 4618
Reputation: 180020
The best way to handle underruns is to avoid handling them by preventing them. This can be done by writing samples early enough before the buffers is empty. To do this,
snd_pcm_write*()
, and/orWhen an underrun happens, you have to decide what should happen with the samples that should have been played but were not written to the buffer at the correct time.
To continue with the following samples at the same time as if the missing samples had actually be played, configure the device so that an underrun does not stop it. This can be done by setting the stop threshold¹ to the boundary value². (Other errors, like unplugging a USB device, will still stop the device.)
When an underrun does happen, the device will play those samples that happen to be in the ring buffer. By default, these are the old samples from some time ago, which will not sound correct. To play silence instead (which will not sound correct either, but in a different way), tell the device to clear each part of the buffer immediately after it has been played by setting the silence threshold³ to zero and the silence size⁴ to the boundary value.
To (try to) reinitialize a device after an error (an xrun or some other error), you could call either snd_pcm_prepare()
or snd_pcm_recover()
. The latter calls the former, and also handles a suspended device (by waiting for it to be resumed).
¹stop threshold: snd_pcm_sw_params_set_stop_threshold()
²boundary value: snd_pcm_sw_params_get_boundary()
³silence threshold: snd_pcm_sw_params_set_silence_threshold()
⁴silence size: snd_pcm_sw_params_set_silence_size()
Upvotes: 4