Alsa audio playback program occurs underrun errors frequency

  alsa, c++, ffmpeg, linux

My environment is Raspberry Pi 3B/Raspberry Pi OS, and I’m building remotely from Visual Studio 2019 on Windows 10.

I want to play PCM audio decoded by FFmpeg in ALSA(C language API).
But this code returns Underrun error(EPIPE) at "snd_pcm_writei" and plays a cracky sound.

Probably the way the resources are managed is wrong, but I don’t know how to optimize it.

avres AlsaPlayer::OpenOutputDevice(AlsaDeviceType ao_type, OutputMode omode)
{
    memset(&output_format, 0, sizeof(InterfaceFormat));

    avres res;

    string dname = GetAlsaDeviceName(false, (ao_type == AD_DIGITAL));
    res = snd_pcm_open(&play_handle, dname.c_str(), SND_PCM_STREAM_PLAYBACK, 0);

    if (AV_FAILED(res)) return res;

    unsigned int sample_rate = DEFAULT_OUTPUT_RATE;
    uint64_t ch_layout = (ao_type == AD_ANALOG_6CH) ? AV_CH_LAYOUT_5POINT1 : AV_CH_LAYOUT_STEREO;
    int channels = av_get_channel_layout_nb_channels(ch_layout);

    snd_pcm_hw_params_t *hw_params;
    res = snd_pcm_hw_params_malloc(&hw_params);

    snd_pcm_hw_params_any(play_handle, hw_params);

    res = snd_pcm_hw_params_set_access(play_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
    res = snd_pcm_hw_params_set_format(play_handle, hw_params, SND_PCM_FORMAT_S16_LE);
    res = snd_pcm_hw_params_set_channels(play_handle, hw_params, channels);
    res = snd_pcm_hw_params_set_rate_near(play_handle, hw_params, &sample_rate, 0);
    res = snd_pcm_hw_params(play_handle, hw_params);

    snd_pcm_uframes_t frames; 
    snd_pcm_hw_params_get_period_size(hw_params, &frames, 0);
    snd_pcm_hw_params_get_period_time(hw_params, &out_period_time, 0);

    snd_pcm_hw_params_free(hw_params);

    if (AV_FAILED(res)) return res;

    output_format.channels = channels;
    output_format.ch_layout = ch_layout;
    output_format.format = AV_SAMPLE_FMT_S16;
    output_format.frame_size = channels * 16 / 8;
    output_format.mode.output = omode;
    output_format.sample_rate = sample_rate;

    out_period_size = (int)frames * output_format.frame_size;
    out_buf_size = 10 * out_period_size;
    output_fifo = av_fifo_alloc(out_buf_size);

    return res;
}

void AlsaPlayer::RenderThread()
{
    while (av_fifo_size(output_fifo) == 0) usleep(1000);

    avres res;
    int frame_size = (output_format.mode.output == OutputMode::Analog) ? output_format.frame_size : 4;
    uint8_t *dst = new uint8_t[out_buf_size];
    while (need_exit == false) {
        int sz = FFMIN(out_buf_size, av_fifo_size(output_fifo));
        int frames = sz / frame_size;
        if (sz > frame_size) {
            res = av_fifo_generic_read(output_fifo, dst, sz, nullptr);
            res = (int)snd_pcm_writei(play_handle, dst, frames);

            if (AV_FAILED(res)) {
                if (AV_FAILED(snd_pcm_recover(play_handle, res, 0))) {
                    need_exit = true;
                    SendComErrorMessage(false, res, "dest_device_error");
                    break;
                }
            }
        }
    }
    delete[]dst;
}

Can I pick your brains?

Source: Windows Questions C++

LEAVE A COMMENT