Hi ALSA developers!
I am working on my first project that uses sound under Linux, and I've chosen to implement directly to the ALSA API. I am amazed that I was able to get sound working in so few lines of code with ALSA. I like the API and its performance alot but I have a problem!
My program, early in its development, wanted to play just one sound stream at a time; and it is a sound stream which normally delivers audio at a sufficent rate to avoid XRUN but in some circumstances the thread generating audio cannot keep up and it falls behind, resulting in XRUN. Well I found that in cases where the audio thread is 'almost' keeping up, the standard behavior of allowing an XRUN to occur, detecting it on the next call to snd_pcm_writei, and 'fixing' it by calling snd_pcm_prepare() and then proceeding to continue delivering audio frames, had the following unfortunate problems:
1. The audio sounds crackly and awful (which is to be expected to some degree with XRUN) 2. The time taken to re-prepare the ALSA device is counterproductive; it takes even more time away from the thread that is delivering audio and thus makes the case where the audio is very marginally behind 'real time' much worse.
I thought, if only I could tell ALSA to just play silence if it has run out of frames of audio instead of XRUNning, and then I can add real frames of audio back in as soon as they are available and pick up the audio stream where it left off.
Some digging into the ALSA API and I found that someone on the ALSA team had already beaten me to the thought! The API docs kind of hint at how you would go about doing this, and I found this technique to be very successful:
1. Use snd_pcm_sw_params_set_stop_threshold to set the stop threshold to 0. Now, no XRUN - ALSA will keep looping in the circular buffer and play whatever is there; and if you haven't updated quickly enough, you just get looping snippets of audio.
2. Use snd_pcm_sw_params_set_silence_threshold to 0 and snd_pcm_sw_params_set_silence_size to set the silence 'size' to the boundary value obtained by calling snd_pcm_sw_params_get_boundary(). Now ALSA is instructed to fill the already-played portions of the circular buffer with silence, which means that even when looping due to XRUN, no sound is played over and over, instead silence is played.
Hey presto - works perfectly!
Then later on I needed to play more than one sound simultaneously. It turns out that I had been opening the "plughw:0,0" device originally and this had been working great with my sound trick. The problem is, you cannot use plughw:0,0 and open it more than once to play more than one sound simultaneously; the second open will fail.
So after some digging I found that the device I really should have been using all along is called "default" (hey, you'd think it would be obvious, but somehow I never found ALSA documentation that told me to do this).
Now the real problem: when I use the "default" device with my silence technique, I have no problem playing multiple audio streams simultaneously by opening the "default" device multiple times and simultaneously playing streams into the different devices. BUT, whenever I cannot deliver audio fast enough to a stream, and the silence trick takes over and the stream plays some silence - boom, the device goes silent forever. I don't get any errors from any ALSA function, I just get silence no matter what I write into the device after that point. This is in sharp contrast to the plughw:0,0 device, which does play silence if I don't deliver audio fast enough but immediately plays whatever audio I do end up delivering to it when I finally have audio to deliver.
Am I using an invalid technique with this silence trick? Is this a known and expected behavior of the "default" device? Or could something else be going on?
Thanks for any advice you can give!
Best wishes, Bryan