c++ - ALSA:无法从欠载中恢复,准备失败:管道损坏

标签 c++ multithreading alsa broken-pipe

我正在编写一个程序,从两个单声道 ALSA 设备读取并将它们写入一个立体声 ALSA 设备。

我使用三个线程和乒乓缓冲区来管理它们。两个读取线程和一个写入线程。它们的配置如下:

// Capture ALSA device 
alsaBufferSize = 16384;
alsaCaptureChunkSize = 4096;
bitsPerSample = 16;
samplingFrequency = 24000;
numOfChannels = 1;
block = true;
accessType = SND_PCM_ACCESS_RW_INTERLEAVED;

// Playback device (only list params that are different from above)
alsaBufferSize = 16384 * 2;
numOfChannels = 2;
accessType = SND_PCM_ACCESS_RW_NON_INTERLEAVED;

两个读取线程将写入 ping 缓冲区,然后写入 pong 缓冲区。写入线程将等待两个缓冲区中的任何一个准备就绪,锁定它,从中读取,然后解锁。

但是当我运行这个程序时,出现了xrun,无法恢复。

ALSA lib pcm.c:7316:(snd_pcm_recover) underrun occurred
ALSA lib pcm.c:7319:(snd_pcm_recover) cannot recovery from underrun, prepare failed: Broken pipe

下面是我写入ALSA播放设备的代码:

bool CALSAWriter::writen(uint8_t**  a_pOutputBuffer, uint32_t a_rFrames)
{

    bool ret = false;

    // 1. write audio chunk from ALSA
    const snd_pcm_sframes_t alsaCaptureChunkSize = static_cast<snd_pcm_sframes_t>(a_rFrames); //(m_pALSACfg->alsaCaptureChunkSize);

    const snd_pcm_sframes_t writenFrames = snd_pcm_writen(m_pALSAHandle, (void**)a_pOutputBuffer, alsaCaptureChunkSize); 

    if (0 < writenFrames)
    {// write succeeded

        ret = true;

    }
    else
    {// write failed
        logPrint("CALSAWriter WRITE FAILED for  writen farmes = %d ", writenFrames);
        ret = false;
        const int alsaReadError = static_cast<int>(writenFrames);// alsa error is of int type

        if (ALSA_OK == snd_pcm_recover(m_pALSAHandle, alsaReadError, 0))
        {// recovery succeeded
            a_rFrames = 0;// only recovery was done, no write at all was done
        }
        else
        {    
            logPrint("CALSAWriter: failed to recover from ALSA write error: %s (%i)", snd_strerror(alsaReadError), alsaReadError);
            ret = false;
        }
    }

    // 2. check current buffer load
    snd_pcm_sframes_t framesInBuffer = 0;
    snd_pcm_sframes_t delayedFrames = 0;

    snd_pcm_avail_delay(m_pALSAHandle, &framesInBuffer, &delayedFrames);

    // round to nearest int, cast is safe, buffer size is no bigger than uint32_t
    const int32_t ONE_HUNDRED_PERCENTS = 100;
    const uint32_t bufferLoadInPercents = ONE_HUNDRED_PERCENTS *
            static_cast<int32_t>(framesInBuffer) / static_cast<int32_t>(m_pALSACfg->alsaBufferSize);

    logPrint("write: ALSA buffer percentage: %u, delayed frames: %d",  bufferLoadInPercents, delayedFrames);

    return ret;
}

其他诊断信息:

02:53:00.465047  log info V 1 [write: ALSA buffer percentage: 75, delayed frames: 4096]
02:53:00.635758  log info V 1 [write: ALSA buffer percentage: 74, delayed frames: 4160]
02:53:00.805714  log info V 1 [write: ALSA buffer percentage: 74, delayed frames: 4152]
02:53:00.976781  log info V 1 [write: ALSA buffer percentage: 74, delayed frames: 4144]
02:53:01.147948  log info V 1 [write: ALSA buffer percentage: 0, delayed frames: 0]
02:53:01.317113  log error V 1 [CALSAWriter WRITE FAILED for  writen farmes = -32 ]
02:53:01.317795  log error V 1 [CALSAWriter: failed to recover from ALSA write error: Broken pipe (-32)]

最佳答案

我花了大约 3 天时间才找到解决方案。感谢@CL。 “writen is called too late”的提示。

问题:

  • 线程切换时间不是常数。

解决方案:

  • 在第一次调用“writen”之前插入一个空缓冲区。该缓冲区的时间长度可以是任意值,以避免多线程切换。我将其设置为 150 毫秒。
  • 或者您可以将线程优先级设置为高,而我不能这样做。引用ALSA: Ways to prevent underrun for speaker .

问题诊断:

事实是:

  • “readi”每 171 毫秒返回一次 (4096/24000 = 0.171)。读取线程设置缓冲区就绪。
  • 缓冲区准备就绪后,将在写入线程中调用“writen”。缓冲区被复制到 ALSA 播放设备。播放设备需要171ms来播放这部分缓冲区。
  • 如果播放设备已经播放完所有缓冲区,并且没有写入新的缓冲区。发生“欠载”。

这里的真实场景:

  • 0ms,“readi”开始。在 171 毫秒,“readi”完成。
  • 172ms(线程切换为 1ms),“writen”开始。在 343ms,如果没有写入新缓冲区,将发生“欠载”。
  • 171 毫秒,“readi”再次开始。在 342ms“readi”完成。
  • 此时线程切换需要2ms。在“writen”开始于344ms之前,“underrun”发生在343ms

当 CPU 负载很高时,并不能保证“线程切换”需要多长时间。这就是为什么您可以在第一次写入时插入一个空缓冲区。并将场景转换为:

  • 0ms,“readi”开始。在 171 毫秒,“readi”完成。
  • 172ms(线程切换为 1ms),“writen”以 150ms 长的缓冲区开始。在 493ms,如果没有写入新缓冲区,将发生“欠载”。
  • 171 毫秒,“readi”再次开始。在 342ms“readi”完成。
  • 此时线程切换需要50ms。 “writen”从392ms开始,根本不会出现“underrun”。

关于c++ - ALSA:无法从欠载中恢复,准备失败:管道损坏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26545139/

相关文章:

c# - 如何在 c# .net 中阻止一个方法直到另一个方法执行

c - ALSA 'snd_pcm_writei' 阻塞模式下的行为

linux - 如何在 Linux 中使用 amixer 命令行实用程序设置控件属性?

python - Debian 音频问题,包括 Alsa、PulseAudio 和 QJackCtl

c++ - 由于常量不一致导致模板参数推导失败

c++ - VirtualAlloc 的顺序似乎很重要 (c++)

multithreading - 我如何将非静态数据发送到 Rust 中的线程,此示例是否需要它?

java - 我应该使用 Handler 还是 Thread?

c++ - ContiguousIterator 有实际用途吗?

c++ - C++ 根据鼠标位置查找菜单项