AlphaCentauri
AlphaCentauri

Reputation: 303

How to properly handle xrun in ALSA programming when playing audio with snd_pcm_writei()?

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

Answers (1)

CL.
CL.

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,

  • reorganize your program so that the new samples are already available to be written when you need to call snd_pcm_write*(), and/or
  • increase the priority of your process/thread (if possible; this is probably not helpful if other programs interfere with your disk OI/O), and/or
  • increase the buffer size (this also increases the latency).

When 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 play these samples later (i.e., to move all following samples to a later time), configure the device so that an underrun stops it. (This is the default setting.) Your program has to restart the device when it has new samples.
  • 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

Related Questions