You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

设置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(&params);
    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;
}

根因分析

  1. 对官方文档的功能理解存在偏差:stop_threshold设为boundary只是禁用XRUN发生后PCM流自动进入停止状态的逻辑,不会消除underrun事件本身的检测和错误返回。设备底层发现缓冲区数据耗尽时,仍然会通过snd_pcm_writei的返回值告知应用层underrun事件,只是不需要手动调用snd_pcm_prepare重启流即可继续写入。
  2. 代码本身的喂数据速度远低于播放速度:单次写入的960帧音频,在48k采样率下的播放时长仅为20ms,但是每次写入后sleep了100ms,中间有80ms的时间缓冲区没有数据可以播放,必然会触发underrun,和stop_threshold的设置无关。
  3. 硬件参数配置错误:代码中定义了buffer_time=2500(即2.5ms)但没有实际调用snd_pcm_hw_params_set_buffer_time_near生效,默认缓冲区大小通常仅为24个周期,也就是4080ms的播放缓存,远不足以支撑单次100ms的等待时长。
  4. snd_pcm_recover的第三个参数设置为0,遇到错误时不会自动恢复状态,会导致后续写入持续报错。

修复方案

  • 调用snd_pcm_hw_params_set_buffer_time_near将缓冲区大小设置为至少大于单次写入间隔的时长,比如设置为200ms以上,避免缓冲区提前耗尽。
  • 调整sleep时长到小于单次写入数据的播放时长,比如改为15ms,保证喂数据速度快于播放速度。
  • 将snd_pcm_recover的第三个参数改为1,允许自动恢复underrun错误状态。

内容的提问来源于stack exchange,提问作者ktb92677

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 10:15:01