ALSA:无法从欠载中恢复,准备失败:管道损坏
Posted
技术标签:
【中文标题】ALSA:无法从欠载中恢复,准备失败:管道损坏【英文标题】:ALSA: cannot recovery from underrun, prepare failed: Broken pipe 【发布时间】:2014-12-20 03:03:37 【问题描述】:我正在编写一个程序,它从两个单声道 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)]
【问题讨论】:
问题出在程序的其他地方;writen
调用太晚了。
【参考方案1】:
我花了大约 3 天时间才找到解决方案。感谢@CL。 “写的太晚了”的提示。
问题:
线程切换时间不是恒定的。解决方案:
在第一次调用“writen”之前插入一个空缓冲区。这个缓冲区的时间长度可以是任何值,以避免多线程切换。我将其设置为 150 毫秒。 或者您可以将线程优先级设置为高,而我不能这样做。请参阅ALSA: Ways to prevent underrun for speaker。问题诊断:
事实是:
“readi”每 171 毫秒返回一次 (4096/24000 = 0.171)。读取线程将缓冲区设置为就绪。 一旦缓冲区准备就绪,就会在写入线程中调用“writen”。缓冲区被复制到 ALSA 播放设备。播放设备播放这部分缓冲区需要 171 毫秒。 如果播放设备已播放完所有缓冲区,并且没有写入新缓冲区。发生“欠载”。这里的真实场景:
在 0ms,“readi”开始。在 171 毫秒,“readi”完成。 在 172ms(线程切换为 1ms)时,“写入”开始。在343ms,如果没有写入新的缓冲区,将会发生“underrun”。 在 171 毫秒,“readi”再次开始。在 342 毫秒,“readi”完成。 此时线程切换需要2ms。在“writen”开始于 344ms 之前,“underrun”发生在 343ms当 CPU 负载很高时,不能保证“线程切换”需要多长时间。这就是为什么您可以在第一次写入时插入一个空缓冲区的原因。并将场景转化为:
在 0ms,“readi”开始。在 171 毫秒,“readi”完成。 在 172ms(线程切换为 1ms)时,“writen”从 150ms 长的缓冲区开始。在493ms,如果没有写入新的缓冲区,就会发生“underrun”。 在 171 毫秒,“readi”再次开始。在 342 毫秒,“readi”完成。 此时线程切换耗时50ms。 “writen”从 392ms 开始,“underrun”根本不会发生。【讨论】:
以上是关于ALSA:无法从欠载中恢复,准备失败:管道损坏的主要内容,如果未能解决你的问题,请参考以下文章
使用 mysql 和烧瓶登录的 Flask-sqlalchemy 损坏管道错误 32
Gitlab管道失败:错误:准备失败:来自守护进程的错误响应:toomanyrequests