Reputation: 31
I am working on an audio player written in C that uses libasound (ALSA) as the audio back-end. I have implemented a callback mechanism that allows the audio player to send audio to ALSA in a timely manner. I configured my pcm_handle to internally hold a buffer with 500ms (= buffer_time) worth of audio data. By using poll()
on ALSA's poll descriptors the program is notified to add more data to the buffer. I also poll()
on my own custom poll descriptor in order to notify the loop when to stop/pause.
I want to be able to pause the audio output (just like pausing a song) and thus I must pause the pcm handle, i.e. tell ALSA to stop sending audio frames from the internal buffer to the soundcard. One could use the snd_pcm_pause()
function, however, as the documentation shows, this function does not work on all hardware. My audio player is targeted towards embedded devices so I want to support all hardware, as a result I prefer not to use that function.
Alternatively I could use the snd_pcm_drain()
function, which will pause the pcm handle after all the pending audio samples in the 500ms buffer have been played. This will however, result in a latency of up to 500ms. It would be possible to minimize this latency by decreasing the buffer size, but this will never eliminate the problem and would increase the chances for an underrun.
Another option is to use the snd_pcm_drop()
function, this function will pause the pcm handle and discard any pending audio samples in the buffer. This solves the latency problem but the result is that when that pcm_handle is restarted, some audio samples are lost, which makes this option also not ideal.
I was personally thinking about using the snd_pcm_drop()
function. To solve the lost samples problem I am looking for a way to retrieve all the pending samples in the ALSA buffer so that I can play them as soon as the pcm handle is started again. I tried using snd_pcm_readi()
just before calling snd_pcm_drop()
in order to retrieve the pending samples but this gave me segmentation faults. I believe ALSA does not allow the use of this function on SND_PCM_STREAM_PLAYBACK
handles.
So is there another option to pause the pcm handle without latency and without losing pending audio frames? If not, as suggested in my solution, is there a way to retrieve those pending frames from ALSA's internal buffer?
My implementation strongly resembles the "write and wait transfer method" shown here. The following pseudo code gives a simplified version of my implementation (without a solution for the current problem):
write_and_poll_loop(...) {
while(1) {
poll(...) ; /** Here we wait for room in the buffer or for a pause command to come in */
if(ALSA_buffer_has_room) {
customAudioCallback(); /** In this callback audio samples are written to the ALSA buffer */
}
else if (pause_command) {
snd_pcm_drop(); /** Discard all samples in the ALSA buffer */
snd_pcm_perpare(); /** Prepare pcm_handle for future playback */
block_until_play_command() /** Block until we want to play audio again */
}
}
}
EDIT: I realized that by using the snd_pcm_hw_params_can_pause()
I can check whether the hardware supports pausing, if it cannot, I fall back to the snd_pcm_drop()
method and just pay the price of losing samples. Nevertheless, I would love to see a solution that is independent of the hardware.
Upvotes: 3
Views: 617
Reputation: 167
I suspect it is not hardware independent (i.e. some hardware might not support it, so you might need the mentioned fallback as well), but there is one more option that I am aware of; Before calling snd_pcm_drain()
you can potentially use snd_pcm_rewind()
to remove the pending frames. Rewinding in general lets you use large period buffers while still having the ability to quickly react and overwrite already scheduled frames, at the cost of increased implementation complexity.
Otherwise, while it may not be sample accurate, but I'd think with snd_pcm_status_get_avail()
(or even more sophisticated together with snd_pcm_status_get_htstamp
or other time stamp options, I haven't looked at all those yet) you should be able to estimate the current playback position fairly accurately at the time point of calling snd_pcm_drop()
, at least good enough to be able to resume without any audible skip.
Upvotes: 0