预加载音频缓冲区 - 啥是合理和可靠的?
Posted
技术标签:
【中文标题】预加载音频缓冲区 - 啥是合理和可靠的?【英文标题】:Pre-loading audio buffers - what is reasonable and reliable?预加载音频缓冲区 - 什么是合理和可靠的? 【发布时间】:2015-03-19 05:04:13 【问题描述】:我正在将音频信号处理应用程序从 Win XP 转换为 Win 7(至少)。你可以想象它是一个声纳应用程序——一个信号被生成并发送出去,一个相关/修改的信号被读回。该应用程序想要独占使用音频硬件,并且不能承受故障——我们不想读取诸如“Windows 哔声导致导弹发射”之类的头条新闻。
查看 Windows SDK 音频示例,与我的案例最相关的是 RenderExclusiveEventDriven 示例。在音频引擎之外,它准备播放 10 秒的音频,它通过 IAudioRenderClient
对象的 GetBuffer()
和 ReleaseBuffer()
以 10 毫秒的块将其提供给渲染引擎。它首先使用这些函数预加载单个 10ms 的音频块,然后依靠常规的 10ms 事件来加载后续块。
希望这意味着将始终缓冲 10-20 毫秒的音频数据。我们应该期望这在相当现代的硬件(不到 18 个月的历史)上有多可靠(即无故障)?
以前,人们可以很容易地通过waveXXX()
API 预先加载至少半秒的音频,这样如果 Windows 在其他地方忙,音频连续性不太可能受到影响。 500 毫秒似乎比 10-20 毫秒更安全……但如果你想要事件驱动和独占模式,IAudioRenderClient
文档并没有明确说明是否可以预加载超过一个 IAudioRenderClient
缓冲区的价值。
谁能确认是否还可以进行更广泛的预加载?是推荐、不鼓励还是两者都不推荐?
【问题讨论】:
IAudioRenderClient
整个 WASAPI 是 Win Vista+;总体而言,使用 WASAPI 的独占流是一种好方法(操作系统版本通知除外)。使用独占流,您可能会很好地预加载 10-20 毫秒的数据(一些报告认为 5 毫秒是可以的),如果没有独占性,您将必须预加载至少 80-100 毫秒,以便数据可以准时穿过混合层。
【参考方案1】:
如果您担心发射导弹,我认为您不应该使用 Windows 或任何其他非实时操作系统。
也就是说,我们正在开发另一个应用程序,该应用程序消耗更高的数据带宽(连续数小时或更长时间为 400 MB/s)。我们已经看到操作系统停止响应长达 5 秒的故障,因此我们在数据采集硬件上有很大的缓冲区。
【讨论】:
我猜这些故障可能是因为存储 IO 始终是链中最薄弱的环节。 我实际上并不担心发射导弹——这是对这个应用程序对业务的关键任务的一种隐喻。我也很高兴在 Windows 的非实时限制范围内工作,因为它带来的其他优势......这似乎也是你的情况:-) 我们使用的是 Linux CentOS...它在 RTOS 方面并不比 Windows 好。 @ddriver - 我们不做任何文件 IO。故障是由于 TLB 未命中造成的。【参考方案2】:与计算领域的其他事物一样,您的应用范围越广:
提高吞吐量 增加延迟我会说 512 个样本缓冲区是通常用于不要求延迟的明智应用程序的最小值。我见过多达 4k 的缓冲区。明智的内存使用对于现代设备来说仍然几乎没有 - 每个通道只有 8 KB 的内存用于 16 位音频。您有更好的播放稳定性和更少的 CPU 周期浪费。对于音频应用程序,这意味着您可以在音频开始跳过之前使用更多 DSP 处理更多轨道。
另一方面 - 我只见过几个专业的音频接口,它们可以处理 32 个样本缓冲区。大多数能够达到 128 个样本,自然您仍然受限于较低的通道和效果数,即使使用专业硬件,您也会随着项目变大而增加缓冲,当您需要“实时”捕捉时将其降低并禁用轨道或效果一场表演。就可能的最低延迟而言,实际上同一个机器能够在 Linux 和自定义实时内核上实现更低的延迟,而不是在不允许你做这些事情的 Windows 上。请记住,64 个样本缓冲区在理论上可能听起来像 8 毫秒的延迟,但实际上它更像是两倍 - 因为输入和输出延迟加上处理延迟。
对于延迟不是问题的音乐播放器,使用较大的缓冲区完全没问题,对于游戏之类的东西,您需要保持较低的缓冲区,以便在视觉和声音之间保持一定程度的同步- 你根本不能让你的声音落后于动作半秒,为了捕捉音乐表演和已经录制的素材,你需要低延迟。你永远不应该超过你的用例要求,因为一个小的缓冲区会不必要地增加 CPU 的使用和音频丢失的几率。 如果您可以忍受从点击播放到听到歌曲开始的那一刻之间的半秒延迟,那么音频播放器的 4k 缓冲就可以了。
我在我的 DAW 项目中完成了一种“混合”解决方案 - 因为我想使用 GPGPU 来获得相对于 CPU 的巨大性能,所以我在内部使用两条处理路径分割了工作 - 64 个样本缓冲区用于在 CPU 上处理的实时音频,以及由 GPU 处理的数据的另一个相当宽的缓冲区大小。当然,它们都是通过“CPU 缓冲区”出来的,以实现完美同步,但 GPU 路径是“提前处理的”,因此可以为已记录的数据提供更高的吞吐量,并保持较低的 CPU 使用率,因此实时音频是更可靠。老实说,我很惊讶专业的 DAW 软件还没有走这条路,但也不是太多,因为我知道业界的大鱼们在比现代中端 GPU 强大得多的硬件上赚了多少钱。自从 Cuda 和 OpenCL 出现以来,他们一直声称“GPU 的延迟太多了”,但是对于已经记录的数据来说,预缓冲和预处理确实不是问题,并且增加了DAW 可以处理的项目。
【讨论】:
谢谢 - 我现在也在研究一种混合 - 旧应用程序使用 512 个样本缓冲区用于输出和输入,以便更容易地将两者联系起来。输入需要尽可能接近实时地处理(约 10 毫秒周期)。旧应用程序将多个缓冲区预加载到 waveOut() 端以帮助保持信号连续性,但我想只要我可以关联两个流,就不需要保持输入和输出缓冲区大小相同。所以我会独立增加输出缓冲区的大小,直到得到可靠的结果。 如果您的目标是低延迟,那么在 Windows 上最好的选择是 ASIO——我建议构建和使用 RTAudio——它在其他一些 API 之上支持 ASIO,而且如果您决定将您的应用程序迁移到其他平台 - 相同的代码将适用于不同平台上的不同音频 API。 另外,强烈建议在处理部分使用OpenCL——它的优点是可以在旅途中快速编译内核,允许在单个内核中生成很长的处理链,省去了很多开销,加上相同的内核可以在 CPU 或 GPU 上运行,具体取决于您的延迟要求。【参考方案3】:简短的回答是肯定的,你可以预加载更多的数据。
此example 使用对GetDevicePeriod
的调用来返回设备的最小服务间隔(以纳秒为单位),然后将该值传递给Initialize
。如果你愿意,你可以传递一个更大的值。
增加周期的不利方面是您增加了延迟。如果您只是回放波形并且不打算即时进行更改,那么这不是问题。但是,例如,如果您有一个正弦发生器,那么增加的延迟意味着您需要更长的时间才能听到频率或幅度的变化。
你是否辍学取决于很多事情。您是否适当地设置了线程优先级?缓冲区有多小?您在准备样品时使用了多少 CPU?但总的来说,现代 CPU 可以处理相当低的延迟。作为比较,ASIO 音频设备在 96kHz 和 2048 个采样缓冲区(20 毫秒)和多通道下运行得非常好——没问题。 ASIO 使用类似的双缓冲方案。
【讨论】:
谢谢-有用的答案...这并不能完全回答我真正想要的问题,因为我似乎在提交问题之前已经将其编辑了(D'oh!)。在 waveXXX() 下,您可以有一个多重缓冲方案 - 预加载大量缓冲区。在 WASAPI 下,并且在缓冲区持续时间和周期性相等的独占模式/事件驱动下,是否(仅)强制执行双缓冲......或者您可以在启动渲染客户端之前预加载多个缓冲区吗?我即将对其进行测试-您可以在我发现之前编辑您的答案:-) 肯定是双缓冲仅用于独占模式 - 我不确定共享模式。不过,如果您一开始就选择了足够的缓冲区大小,则不会有任何影响。【参考方案4】:这太长了,不能作为评论,所以它还不如作为一个答案(有条件)。
虽然它是从我提交的问题的最终形式中编辑出来的,但我所说的“更广泛的预加载”并不是关于使用的缓冲区的大小,而是使用的缓冲区的数量。结果得到的(有些出乎意料的)答案都有助于扩大我的理解。
但我很好奇。在旧的waveXXX()
世界中,可以通过waveOutPrepareHeader()
和waveOutWrite()
调用“预加载”多个缓冲区,其中第一个waveOutWrite()
将开始播放。我的旧应用程序在一次突发中“预加载”了 64 个缓冲区中的 60 个缓冲区,每个缓冲区以 48kHz 播放 512 个样本,在一个周期为 10.66 毫秒的系统中创建了超过 600 毫秒的缓冲。
在 WASAPI 世界中,在 IAudioCient::Start()
之前使用多个 IAudioRenderClient::GetBuffer()
和 IAudioRenderClient::ReleaseBuffer()
调用,似乎仍然有可能......至少在我的(非常普通的)硬件上,并且没有进行广泛的测试(还)。尽管文档强烈建议独占的、事件驱动的音频严格来说是一个双缓冲系统。
我不知道任何人都应该通过设计来利用它,但我想我会指出它可能会受到支持。
【讨论】:
以上是关于预加载音频缓冲区 - 啥是合理和可靠的?的主要内容,如果未能解决你的问题,请参考以下文章