设置snd_pcm_sw_params_set_stop_threshold为boundary仍出现snd_pcm_writei underrun错误
ALSA音频开发stop_threshold设置后仍触发underrun问题解答
问题现象
- 开发时将
snd_pcm_sw_params_set_stop_threshold参数设置为boundary值(也测试过设为0的场景)后,调用snd_pcm_writei接口仍然返回缓冲区underrun错误,与官方文档描述存在认知偏差。
官方文档明确说明:如果stop threshold等于软件参数sw_param中的boundary值,PCM流的自动停止机制会被禁用。
最小复现代码
#include <alsa/asoundlib.h> #include <iostream> #define AUDIO_DEV "default" #define AC_FRAME_SIZE 960 #define AC_SAMPLE_RATE 48000 #define AC_CHANNELS 2 //BUILD g++ -o main main.cpp -lasound using namespace std; int main() { int err; unsigned int i; snd_pcm_t *handle; snd_pcm_sframes_t frames; snd_pcm_uframes_t boundary; snd_pcm_sw_params_t *sw; snd_pcm_hw_params_t *params; unsigned int s_rate; unsigned int buffer_time; snd_pcm_uframes_t f_size; unsigned char buffer[AC_FRAME_SIZE * 2]; int rc; for (i = 0; i < sizeof(buffer); i++) buffer[i] = random() & 0xff; if ((err = snd_pcm_open(&handle, AUDIO_DEV, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { cout << "open error " << snd_strerror(err) << endl; return 0; } s_rate = AC_SAMPLE_RATE; f_size = AC_FRAME_SIZE; buffer_time = 2500; cout << s_rate << " " << f_size << endl; snd_pcm_hw_params_alloca(¶ms); snd_pcm_hw_params_any(handle, params); snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE); snd_pcm_hw_params_set_channels(handle, params, AC_CHANNELS); snd_pcm_hw_params_set_rate_near(handle, params, &s_rate, 0); snd_pcm_hw_params_set_period_size_near(handle, params, &f_size, 0); cout << s_rate << " " << f_size << endl; rc = snd_pcm_hw_params(handle, params); if (rc < 0) { cout << "open error " << snd_strerror(err) << endl; return 0; } snd_pcm_sw_params_alloca(&sw); snd_pcm_sw_params_current(handle, sw); snd_pcm_sw_params_get_boundary(sw, &boundary); snd_pcm_sw_params_set_stop_threshold(handle, sw, boundary); rc = snd_pcm_sw_params(handle, sw); if (rc < 0) { cout << "open error " << snd_strerror(err) << endl; return 0; } snd_pcm_sw_params_current(handle, sw); snd_pcm_sw_params_get_stop_threshold(sw, &boundary); cout << "VALUE " << boundary << endl; for (i = 0; i < 1600; i++) { usleep(100 * 1000); frames = snd_pcm_writei(handle, buffer, f_size); if (frames < 0) frames = snd_pcm_recover(handle, frames, 0); if (frames < 0) { cout << "open error " << snd_strerror(frames) << endl; break; } } return 0; }
根因分析
- 对官方文档的功能理解存在偏差:
stop_threshold设为boundary只是禁用XRUN发生后PCM流自动进入停止状态的逻辑,不会消除underrun事件本身的检测和错误返回。设备底层发现缓冲区数据耗尽时,仍然会通过snd_pcm_writei的返回值告知应用层underrun事件,只是不需要手动调用snd_pcm_prepare重启流即可继续写入。 - 代码本身的喂数据速度远低于播放速度:单次写入的960帧音频,在48k采样率下的播放时长仅为20ms,但是每次写入后sleep了100ms,中间有80ms的时间缓冲区没有数据可以播放,必然会触发underrun,和stop_threshold的设置无关。
- 硬件参数配置错误:代码中定义了
buffer_time=2500(即2.5ms)但没有实际调用snd_pcm_hw_params_set_buffer_time_near生效,默认缓冲区大小通常仅为24个周期,也就是4080ms的播放缓存,远不足以支撑单次100ms的等待时长。 snd_pcm_recover的第三个参数设置为0,遇到错误时不会自动恢复状态,会导致后续写入持续报错。
修复方案
- 调用
snd_pcm_hw_params_set_buffer_time_near将缓冲区大小设置为至少大于单次写入间隔的时长,比如设置为200ms以上,避免缓冲区提前耗尽。 - 调整sleep时长到小于单次写入数据的播放时长,比如改为15ms,保证喂数据速度快于播放速度。
- 将
snd_pcm_recover的第三个参数改为1,允许自动恢复underrun错误状态。
内容的提问来源于stack exchange,提问作者ktb92677
相关产品推荐
相关产品推荐

