Glitchy音频输出,没有欠载

时间:2017-05-01 14:29:35

标签: c linux audio qemu alsa

在非阻塞模式下使用snd_pcm_writei()时,一切都很有效,但最终音频变得不稳定。听起来环形缓冲区指针不同步(即,有时我可以判断音频播放的顺序不正常)。问题需要多长时间才能启动它依赖硬件的问题。在真实硬件上的Gentoo盒子上它很少发生,但在QEMU上运行的buildroot系统上,它发生在大约5分钟后。在这两种情况下,排出pcm流可以解决问题。我已经确认我正确地写了样本,并将它们写入文件并用aplay播放。

目前我将avail_min设置为周期大小(1024帧)并在写入周期大小的块之前调用snd_pcm_wait()。但我尝试了许多不同的变体(不同的块大小,检查自己并使用pthread_cond_timedwait()而不是snd_pcm_wait()等)。但唯一可行的方法是使用阻止模式,但我不能这样做。

你可以在这里看到当前的源代码:https://bitbucket.org/frodzdev/mediabox/src/5a6471316c7ae481b329e7e0d4af1bb68a32e71d/src/audio.c?at=staging&fileviewer=file-view-default(因为我尝试各种各样的东西需要一点清理)。执行实际IO的代码从第375行开始。

编辑: 我想我有一个解决方案,但我不明白为什么它似乎有效。如果我使用非阻塞模式似乎并不重要,问题是当我等待确保缓冲区上有空间时(通过snd_pcm_wait(),pthread_cond_timedwait()或usleep) ())。

似乎有效的版本在这里:https://bitbucket.org/frodzdev/mediabox/src/c3eb290087d9bbe0d5f37653a33a1ba88ef0628b/src/audio.c?fileviewer=file-view-default。我在调用snd_pcm_writei()之前仍然等待时切换到阻止模式,但它并没有产生影响。然后我在调用avbox_audiostream_gettime()上的snd_pcm_status()之前添加了对snd_pcm_avail()的调用。另一个线程不断调用此函数来获取流时钟,它只使用snd_pcm_status()来获取时间戳。现在它似乎有效(至少它很可能发生)但我不明白为什么。我知道snd_pcm_avail()会将指针与内核同步,但我不太了解何时需要调用它以及snd_pcm_state()等和snd_pcm_status()之间的区别。 snd_pcm_status()也会同步吗?这似乎不是因为有时snd_pcm_status_get_state()会在snd_pcm_avail()返回-EPIPE时返回RUNNING。 ALSA文档非常模糊。也许了解这些事情会帮助我理解我的问题吗?

现在,当我说它似乎正在工作时,我的意思是我无法在真实硬件上重现它。尽管如此,它仍然会在QEMU上发生。但是考虑到在下一次提交时我没有等待就切换到阻塞模式(过去我曾经使用过,而且从未在真实硬件上遇到过问题)而且它仍然发生在QEMU中,而且这也是一个常见的事实。 QEMU的问题我开始认为我可能已经解决了这个问题,现在它只是一个QEMU问题。有没有办法确定问题是否是我的结尾的错误,更容易在模拟器上触发,或者它只是一个模拟器问题?

编辑:我意识到我应该在等待之前填充缓冲区,但此时我的担心不是防止欠载,而是确保我的代码可以在它们发生时处理它们。此外,缓冲区在几次迭代后填满。我通过在写入每个数据包之前输出avail,buffer_size等来确认这一点,并且我得到的数字没有完全合理,它们在每第8个周期显示1或2个周期的错误。此外(这是主要问题)我没有检测到任何欠载,音频变得不稳定,但所有写入都成功。事实上,如果问题开始发生并且我通过超载CPU触发欠载,它将在pcm重置时自行纠正。

1 个答案:

答案 0 :(得分:1)

line 505中:你使用时间作为malloc的参数。

line 568:你不是在播放音频吗?在这种情况下,您应该只在编写帧后等待。让我们想想......

音频设备在终止处理句点时会产生中断。

|  period A  |  period B  |
             ^            ^
            irq          irq

在启动pcm之前,音频设备不会产生任何中断。请注意here您正在等待,但尚未启动pcm。您只能在致电snd_pcm_writei()时启动它。

等待音频数据时,只有在当前时间段已经完全处理时才会醒来 - 在第一次等待时,第一个时间段甚至没有被写入 - 所以在舒适的情况下你应该写完整个缓冲区,等待第一个中断,然后写入刚刚处理过的时间段,然后打开。

Initially, buffer is empty:
  |            |            |
write():
  |############|############|
wait():
  ..............
When we wake up:
  |            |############|
write():
  |############|############|

我发现问题是你在播放之前就开始写音频了,有时它可能会在缓冲区中延迟播放。