使用 snd_pcm_writei() 播放音频时如何正确处理 ALSA 编程中的 xrun?

Posted

技术标签:

【中文标题】使用 snd_pcm_writei() 播放音频时如何正确处理 ALSA 编程中的 xrun?【英文标题】:How to properly handle xrun in ALSA programming when playing audio with snd_pcm_writei()? 【发布时间】:2020-04-11 06:48:41 【问题描述】:

我已经尝试了多个示例程序,这些示例程序似乎具有处理回放下的 xruns 的代码:

https://albertlockett.wordpress.com/2013/11/06/creating-digital-audio-with-alsa/

https://www.linuxjournal.com/article/6735(清单 3)

当使用 snd_pcm_writei() 时,似乎当返回值为 -EPIPE(即 xrun/underrun)时,它们会这样做:

if (rc == -EPIPE) 
  /* EPIPE means underrun */
  fprintf(stderr, "underrun occurred\n");
  snd_pcm_prepare(handle);

即在句柄上调用 snd_pcm_prepare()。

但是,当我尝试运行此类程序时,我仍然会口吃。通常情况下,我会得到至少几个,可能是半打 xrun,然后它会流畅连续地播放而无需进一步的 xrun。但是,如果我有其他使用声卡的东西,比如 Firefox,我会得到更多的 xruns,有时只有 xruns。但同样,即使我杀死任何其他使用声卡的程序,我仍然会遇到一些初始 xruns 和扬声器实际卡顿的问题。

这对我来说是不可接受的,我该如何修改这种类型的 xrun 处理以防止卡顿?

我自己尝试解决这个问题:

从 ALSA API,我看到 snd_pcm_prepare() 可以:

准备 PCM 以供使用。

这对像我这样的 ALSA 初学者帮助不大。没有说明如何使用它来恢复 xrun 问题。

我还注意到,来自:https://www.alsa-project.org/alsa-doc/alsa-lib/pcm.html

SND_PCM_STATE_XRUN PCM 设备达到溢出(捕获)或欠载(播放)。您可以使用来自 I/O 函数的 -EPIPE 返回码 (snd_pcm_writei(), snd_pcm_writen(), snd_pcm_readi(), snd_pcm_readn()) 在不检查实际状态的情况下通过以下方式确定此状态 snd_pcm_state() 调用。推荐使用辅助功能 snd_pcm_recover() 从这个状态恢复,但你也可以使用 snd_pcm_prepare()、snd_pcm_drop() 或 snd_pcm_drain() 调用。

我也不清楚。我可以使用 snd_pcm_prepare() 还是可以使用这些其他调用?有什么区别?我应该使用什么?

【问题讨论】:

【参考方案1】:

处理欠载的最佳方法是通过防止它们来避免处理它们。这可以通过在缓冲区为空之前足够早地写入样本来完成。为此,

重新组织您的程序,以便在您需要调用 snd_pcm_write*() 和/或时已经可以编写新示例 提高进程/线程的优先级(如果可能;如果其他程序干扰您的磁盘 OI/O,这可能没有帮助),和/或 增加缓冲区大小(这也会增加延迟)。

当发生欠载时,您必须决定应该如何处理应该播放但未在正确时间写入缓冲区的样本。

若要稍后播放这些样本(即,将所有以下样本移至稍后时间),请配置设备以使欠载停止它。 (这是默认设置。)您的程序必须在有新样本时重新启动设备。

要在播放缺失样本的同时继续播放以下样本,请配置设备以使欠载不会停止它。这可以通过将停止阈值¹设置为边界值²来完成。 (其他错误,如拔出 USB 设备,仍会停止设备。)

当确实发生欠载时,设备将播放那些恰好位于环形缓冲区中的样本。默认情况下,这些是前一段时间的旧样本,听起来不正确。要改为播放静音(听起来也不正确,但以不同的方式),通过将静音阈值³设置为零并将静音大小⁴设置为边界值,告诉设备在播放缓冲区后立即清除缓冲区的每个部分.

要(尝试)在发生错误(xrun 或其他错误)后重新初始化设备,您可以调用snd_pcm_prepare()snd_pcm_recover()。后者调用前者,并且还处理挂起的设备(通过等待它恢复)。

¹停止阈值:snd_pcm_sw_params_set_stop_threshold() ²边界值:snd_pcm_sw_params_get_boundary() ³静音阈值:snd_pcm_sw_params_set_silence_threshold() ⁴静音大小:snd_pcm_sw_params_set_silence_size()

【讨论】:

以上是关于使用 snd_pcm_writei() 播放音频时如何正确处理 ALSA 编程中的 xrun?的主要内容,如果未能解决你的问题,请参考以下文章

使用 AVPlayer 播放音频时如何在 UIWebView 中暂停音频

播放音频时更改音频路由

使用火焰/音频播放器时,如何停止来自 AudioCache 的音频?

尽管不使用自动播放标签,但 HTML 音频在页面加载时播放

播放音频 WKWebView 时如何使用 MPNowPlayingInfoCenter 显示播放信息?

使用avplayer播放音频时如何将音频缓存到本地