I'm trying to make my ALSA interface work with as low latency as possible. The ALSA documentation about all the frames / periods / buffers is very confusing, so I'm asking here.
The below code forces the ALSA to actually read 1024 times the buffer size (I set the buffer size to 1024 bytes). The writeAudio
function takes 1024 * 1024 bytes before it slows down. I would like to keep this frames ? count as low as possible, so I could track the playtime in my application itself. If I try to set the period size to 2 with the snd_pcm_hw_params_set_periods
(I guess it would then slow down the reading after 2 * 1024 bytes has been written to the buffer.) However this change in code doesn't change the behaviour; the player still buffers 1024 * 1024 bytes before the buffering slows down to the rate the audio is being played from the speakers.
TLDR; 1024 * 1024 bytes buffering size is way too much for me, how to lower it? Below is my code. And yes, I'm playing unsigned 8 bit with mono output.
int32_t ALSAPlayer::initPlayer(ALSAConfig cfg)
{
std::cout << "=== INITIALIZING ALSA ===" << std::endl;
if(!cfg.channels || !cfg.rate)
{
std::cout << "ERROR: player config was bad" << std::endl;
return -1;
}
m_channels = cfg.channels;
m_rate = cfg.rate;
m_frames = 1024;
uint32_t tmp;
uint32_t buff_size;
int dir = 0;
/* Open the PCM device in playback mode */
if ((pcm = snd_pcm_open(&pcm_handle, PCM_DEVICE, SND_PCM_STREAM_PLAYBACK, 0)) < 0)
{
printf("ERROR: Can't open \"%s\" PCM device. %s\n", PCM_DEVICE, snd_strerror(pcm));
}
snd_pcm_hw_params_alloca(¶ms);
snd_pcm_hw_params_any(pcm_handle, params);
if ((pcm = snd_pcm_hw_params_set_access(pcm_handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
{
printf("ERROR: Can't set interleaved mode. %s\n", snd_strerror(pcm));
}
if ((pcm = snd_pcm_hw_params_set_format(pcm_handle, params, SND_PCM_FORMAT_S8)) < 0)
{
printf("ERROR: Can't set format. %s\n", snd_strerror(pcm));
}
if ((pcm = snd_pcm_hw_params_set_channels(pcm_handle, params, m_channels)) < 0)
{
printf("ERROR: Can't set channels number. %s\n", snd_strerror(pcm));
}
if ((pcm = snd_pcm_hw_params_set_rate_near(pcm_handle, params, &m_rate, &dir)) < 0)
{
printf("ERROR: Can't set rate. %s\n", snd_strerror(pcm));
}
// force the ALSA interface to use exactly *m_frames* number of frames
snd_pcm_hw_params_set_period_size(pcm_handle, params, m_frames, dir);
/* Write parameters */
if ((pcm = snd_pcm_hw_params(pcm_handle, params)) < 0)
{
printf("ERROR: Can't set harware parameters. %s\n", snd_strerror(pcm));
}
std::cout << "ALSA output device name: " << snd_pcm_name(pcm_handle) << std::endl;
std::cout << "ALSA output device state: " << snd_pcm_state_name(snd_pcm_state(pcm_handle)) << std::endl;
snd_pcm_hw_params_get_channels(params, &tmp);
std::cout << "ALSA output device channels: " << tmp << std::endl;
snd_pcm_hw_params_get_rate(params, &tmp, 0);
std::cout << "ALSA output device rate: " << tmp << std::endl;
snd_pcm_hw_params_get_period_size(params, &m_frames, &dir);
buff_size = m_frames * m_channels;
std::cout << "ALSA output device frames size: " << m_frames << std::endl;
std::cout << "ALSA output device buffer size: " << buff_size << "(should be 1024)" << std::endl;
return 0;
}
int ALSAPlayer::writeAudio(byte* buffer, uint32_t buffSize)
{
int pcmRetVal;
if(buffSize == 0)
{
snd_pcm_drain(pcm_handle);
snd_pcm_close(pcm_handle);
return -1;
}
if((pcmRetVal = snd_pcm_writei(pcm_handle, buffer, m_frames)) == -EPIPE)
{
snd_pcm_prepare(pcm_handle);
}
else if(pcm < 0)
{
std::cout << "ERROR: could not write to audio interface" << std::endl;
}
return 0;
}
(One frame is not necessarily one byte; please don't confuse them.)
This code does not set the buffer size.
snd_pcm_hw_params_set_periods()
sets the number of periods.
snd_pcm_hw_params_set_period_size()
would set the period size.
To have a 2048-frame buffer with two periods with 1024 frames each, set the number of periods to 2, and the period size to 1024.
You must check all function calls for errors, including snd_pcm_hw_params_set_period_size()
.