将 snd_pcm_sw_params_set_stop_threshold 设置为边界,在 snd_pcm_writei 上仍会出现资源不足

Set snd_pcm_sw_params_set_stop_threshold to boundary, still getting underrun on snd_pcm_writei

问题说明了一切。我在这里兜圈子。我将 snd_pcm_sw_params_set_stop_threshold 设置为边界(为了好玩也设置为零),但我仍然在 snd_pcm_writei 上遇到缓冲区欠载错误。我不明白为什么。文档对此非常清楚:

If the stop threshold is equal to boundary (also software parameter - sw_param) then automatic stop will be disabled

这是一个可重现性最低的示例:

#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;
}

好的,我明白了。对于 运行 陷入此问题且将 pipwire 或 pulse(或任何其他第三方非 alsa 音频卡)启用为“默认”卡的任何人,解决方案是不直接使用 pipwire 或 pulse。似乎 snd_pcm_sw_params_set_stop_threshold 在 pipewire/pulseaudio 中没有正确实现。您会注意到,如果您禁用 pipwire 或 pulse,此代码将 运行 完全按照您希望的方式 运行。

这是禁用 pulseaudio 的方法(这是我系统上的问题):

systemctl --user stop pulseaudio.socket
systemctl --user stop pulseaudio.service

尽管更好的解决方案是将 AUDIO_DEV 设置为直接写入 alsa 卡。您可以通过 运行ning aplay -L 找到这些卡的名称。但在 95% 的情况下,将我的示例代码中的 AUDIO_DEV 更新为以下内容:

#define AUDIO_DEV "hw:0,0"

通常会解决问题。