【问题标题】:Glitchy audio output, no underruns有故障的音频输出,没有欠载
【发布时间】:2017-09-28 23:12:56
【问题描述】:

当在非阻塞模式下使用 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_avail() 返回 -EPIPE 时,有时 snd_pcm_status_get_state() 会返回 RUNNING。 ALSA 文档真的很模糊。或许理解这些事情会帮助我理解我的问题?

现在,当我说它似乎可以工作时,我的意思是我无法在真实硬件上重现它。它仍然发生在 QEMU 上,但发生的频率要低得多。但是考虑到在下一次提交时,我无需等待就切换到阻塞模式(我过去曾使用过,并且在真实硬件上从未遇到过问题)并且它仍然发生在 QEMU 中,而且这是一个常见问题QEMU 我开始认为我可能已经解决了这个问题,现在它只是一个 QEMU 问题。有什么方法可以确定问题是我端的一个更容易在模拟器上触发的错误,还是只是一个模拟器问题?

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

【问题讨论】:

    标签: c linux audio qemu alsa


    【解决方案1】:

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

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

    音频设备在终止处理一个周期时会产生一个中断。

    | A期 | B期| ^ ^ 中断

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

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

    最初,缓冲区是空的: | | | 写(): |############|#############| 等待(): ..................... 当我们醒来时: | |############| 写(): |############|#############|

    我发现问题是你在播放之前写入音频,然后有时它可能会延迟到达缓冲区。

    【讨论】:

    • 很好地捕捉到缓冲区大小,但在我的情况下(48000KHz)只是过度分配,它是临时代码。另外我知道如果avail >=avail_min snd_pcm_wait() 应该立即返回所以我并没有真正等到缓冲区填满,对吗?除了原来的,我只是在我得到 EAGAIN 时才在等待,它仍在发生。现在你可能对你的最终结论是正确的,我能做些什么呢?首先填充缓冲区不是解决方案,因为音频可能来自网络流,所以它仍然可能发生。
    • 我确实注意到有时在调用 snd_pcm_writei() snd_pcm_delay() 之前返回 (buffer_size -avail) + 1024 但下一次写入仍然成功。所以我想我没有低于环形缓冲区,但硬件缓冲区是?如果是这种情况,我该怎么办?我是否应该在 (delay - (buffer_size-avail)) > 0 时休眠一段时间以让 ringbuffer 欠载以便我可以检测到它?
    • 此外,故障听起来不仅仅是延迟,它听起来像是音频播放乱序,好像缓冲区指针以某种方式被破坏了。我什至通过写入文件来确认我正在以正确的顺序写入。
    • @fernan 我必须同意你的观点,ALSA 文档很少。是的,snd_pcm_wait() 将在 (avail >=avail_min) 时立即返回。 snd_pcm_writei() 在 (avail == 0) 时返回 EAGAIN。当音频从网络延迟到达时,您应该将其丢弃并向 pcm 发送零。我不认为 ALSA 指针是混乱的(而且可能不是)。另外,我认为您不应该 sleep(),而只需确保环形缓冲区中始终存在音频。
    • 我将不得不在真实硬件上进行更多测试,因为我认为 QEMU 搞砸了我的测试。这需要一段时间,因为在真实硬件上重现故障需要几个小时。关于 snd_pcm_writei() 的返回值,似乎并非如此,至少在我测试过的所有硬件中,它要么完全成功,要么返回 EAGAIN。每当有部分写入时,我都会记录一条消息,但我从未见过这种情况发生。
    猜你喜欢
    • 1970-01-01
    • 2018-03-08
    • 2013-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多