版本

menu_open
Wwise SDK 2022.1.17
I/O 技巧、故障排除和优化
Default Streaming Manager Information

本章所述的大量技巧和其他面向 I/O 的注意事项仅在使用高级 Stream Manager API 的默认实现并在 Low-Level I/O 接口下挂接 I/O 代码的情况下适用。本章大部分内容假定已经您了解默认 Stream Manager 的设置(Audiokinetic Stream Manager 初始化设置 )和 Low-Level I/O 接口(Low-Level I/O )。

调节流播放设备设置

默认设备设置(从 AK::StreamMgr::GetDefaultDeviceSettings 获取)对 DVD 设备适用。默认粒度为 16 KB,而更大的粒度很可能会让单个流具有更大的 DVD 吞吐量。32 KB 对于大多数 DVD 可能是不错的选择。只有仅同时流播放一两个音频文件时才应使用更大的粒度,否则优化一条流的吞吐量可能会以牺牲其他性能为代价。另外请注意更大的粒度需要更大的流 I/O 内存。

若要使用声音引擎来从文件加载 SoundBank,建议最好修改声音初始化设置 AkInitSettings::uBankReadBufferSize。该参数为从文件读取 SoundBank 数据时请求每次传送的数据量。值越大,读取操作/次数越少,但占用的内存会多些。这在从内存加载 SoundBank 时没有什么影响。

有些情况下,您可能希望优化内存利用率,可以在 降低 I/O 池内存占用率 中找到与此相关的有用信息。

使用延迟型 I/O 挂钩而非阻塞型 I/O 挂钩

使用延迟型 I/O 挂钩一般比使用阻塞型 I/O 挂钩效率低。这是因为延迟型 I/O 设备预测错误的数量更多(导致传输被取消和带宽浪费),且在 Stream Manager 池中需要更多的内存。只有发生以下情形之一时才可以使用此类挂钩。

游戏引擎 I/O 管理器提供异步 API

如果您的 I/O 管理器仅提供异步 API,并且希望将 Wwise I/O 调用指向它,则使用延迟型 I/O 挂钩会更容易实现适配层。为了减少延迟型 I/O 设备常见的错误预测,请将 AkDeviceSettings::uMaxConcurrentIO 设为 1 或 2。

游戏引擎 I/O 管理器可以根据文件位置分布打乱 I/O 请求顺序

Wwise Stream Manager 不能根据文件在磁盘上的物理位置来进行决策。如果您的 I/O 管理器可以根据文件分布来重新排列处理请求的顺序,例如优先处理相邻的传输,则一次从 Wwise Stream Manager 接收更多的传输并将它们推入您自己的调度程序可能会更好。

传输完成与已发布的传输数量无关

某些设备会定期解析传输请求,其机制大多与已发布的传输数量无关。基于 DMA 控制器的设备通常即是如此,DMA 完成时传输将结束,但您不想一直等到它结束后再准备另一个 DMA;这种情况下,可以对延迟型设备使用较大的 AkDeviceSettings::uMaxConcurrentIO,它应该大到永远不会阻止将 I/O 请求发送到 Low-Level I/O。因此带宽量将完全由目标缓冲长度控制。

使用多个设备

通常您应对每种物理设备(DVD、硬盘、RAM/VRAM 等)使用不同的流播放设备。原因之一是每个流播放设备将在单独的线程中运行,如果您不希望传输请求在物理设备之间序列化,就必须这样做。在发布硬盘读取指令前等待 DVD 读取操作非是常低效的。原因之二是每个设备的最佳设置通常不同。

在使用多个设备时,您需要一个调度程序。请参阅 多设备 I/O 系统 了解有关使用多个设备来实现系统的更多详情。

调度过程中需要您知道哪些文件应从哪个设备打开。首选的解决方案是使用文件包。在生成 SoundBank 后,可以使用 File Packager 将 SoundBank 和流播放音频文件一起打包到各文件包中。每个文件包只能在特定设备中加载。在运行时,简单的调度程序可能查询每个设备,直至其中一个设备接受并打开文件。这就是默认调度程序所执行的操作(请参阅 SDK 示例中的 AkDefaultLowLevelIODispatcher.h/cpp)。您甚至可以用此技术实现回调机制。例如,尝试从 RAM/VRAM 设备加载文件,如果无效,则从 DVD 加载。

注意,文件调度/设备分配必须同步:由于不允许延迟,因此要确保速度够快。在 SDK 示例中实现的文件包查询速度很快,因为它使用了二进制搜索算法。

故障排除 I/O

将 Wwise 编创工具连接到游戏,并使用性能分析器进行故障排除和优化 I/O 系统及设置。捕获日志将显示源干涸和其他 I/O 错误。Advanced Profiler 视图有两个选项卡专门针对 I/O:Streams 选项和 Streaming Devices 选项卡。

另外,您可以使用 AkFileDesc 的自定义参数,以及 AK::StreamMgr::IAkLowLevelIOHook::GetDeviceDesc()AK::StreamMgr::IAkLowLevelIOHook::GetDeviceData() 来让 Wwise Profiler 显示一些 Low-Level I/O 系统数据。

源干涸故障排除

当流数据未在规定截止时间内发送到 Stream Manager 时,就会发生源(I/O)干涸。此时性能分析器的捕获日志中将显示错误通知,以及干涸音频源的名称/ID。

源干涸的原因

源干涸可能不是错误的 I/O 设置导致的,而另有原因:

1)互动音乐:Interactive Music 层级结构的流对象在事先预定,需要流数据及时准备就绪。此预读时间为每个音乐轨的属性。如果互动音乐发生干涸,可能是因为您指定的 Look-ahead Time(预读时间)不够大造成的。

2)零延迟流:如果您在声音和音乐轨属性中选择“Zero Latency”选项,流文件的开头将存储在 SoundBank 中。发布播放事件时,会使用存储在 SoundBank 中的数据(预取数据)立即开始播放。但是,如果在文件的后续部分从磁盘流播放之前就已经处理完预读数据,即会发生源干涸。在这种情况下,请增加预读长度。

Actor-mixer(角色混音器)层级结构中,未标记为“零延迟”的声音绝不会在达到其目标缓冲长度之前开始播放。达到目标缓冲所需的时间即为声音的初始延迟。因此如果这些声音发生干涸,可能是由于 I/O 条件有误或失衡造成的。

I/O 内存不足

除了吞吐量需求大于设备的提供能力,导致干涸的主要原因是 I/O 内存被耗尽。当 I/O 内存池已满时,流播放设备将停止向 Low-Level I/O发送传输请求。请在性能分析器的 Streaming Devices 选项卡中检查 I/O 内存占用率。

负荷过重

如果源干涸仅在特定情况下发生,而并不针对特定的流文件 ,这可能是因为您请求的音频数据量已超出 I/O 设备的提供能力所造成的。请记住,您需要与游戏的其他素材共享它的带宽。限制 I/O 音频负担的首选方法是使用 Wwise 的数据驱动功能(例如实例限制和虚声部)来限制流数量。

如果您的目标缓冲太小,也可能导致干涸。在 Streams 选项卡中,注意缓冲状态和被引用内存量(Ref. Memory)。若 Buffering Status 条大部分时间显示为灰色(即达到目标)但引用内存量经常降到 0,则表示很可能目标缓冲太小:I/O 线程大多处于闲置状态,但在 I/O 计划程序决定发送 I/O 请求时缓冲不足以弥补从磁盘读取缓冲区所花的时间。

另一方面,对于应处于稳定状态(即已开始播放)的播放流,若 Buffering Status 条显示为橙色或蓝色(取决于主题),则表示底层 I/O 设备运行太慢而无法为其服务。注意,这也可能是因为 I/O 池中没有可用的内存(请检查 Streaming Devices 选项卡),或是因为调度程序正在等待并行 I/O 请求数量降到 AkDeviceSettings::uMaxConcurrentIO 以下。

特定流播放时的规律性干涸

有时候,在特定流存在的情况下,可能规律性地发生源干涸。

流播放设备正确地服务所有音频流时,所需要的总体吞吐量会根据音频流的压缩格式、采样速率、声道数量等,通过启发式算法在音频流之间进行均衡分配。然而,某些可变比特率编解码器(例如 XMA 和 Vorbis)的获取数据量有时会超出它们初始声明的 I/O 数据量,比如在寻址期间,或在循环边界上。如果在寻址时发生这种情况,则应尝试在转码设置中使用较小的 Seek Table(寻址表)块大小。注意,在互动音乐中或者使用“From Elapsed Time”虚声部行为时也会发生寻址。

另外,读取磁盘上位置较远的文件也可能导致这种情况。可以使用平台工具(如果您使用文件包,则使用 File Packager 实用程序)通过将运行时的寻址次数最小化来优化磁盘上的文件分布。

您还可以增加目标缓冲长度,来补偿吞吐量需求的峰值(但要小心 I/O 内存占用率升高)。

更多技巧

若要对较短的循环声音进行流播放,建议最好启用流缓存 (AkDeviceSettings::bUseStreamCache)。这将有助于节省带宽和 I/O 内存,

也能避免浪费 DVD/HDD 设备的带宽。如果您使用延迟型设备,请留意已取消的传输(在性能分析器的 Streaming Devices 选项卡中)。减少并行传输的最大数量(AkDeviceSettings::uMaxConcurrentIO)应该有所帮助。

降低 I/O 线程的 CPU 占用率

I/O 设备的线程所用的 CPU 量通常微不足道,因为它们大多数时间都在等待 I/O。高速设备(例如 RAM 设备)占用 CPU 可能更显著。在这种情况下,您可以

  • 降低目标缓冲;
  • 降低线程优先级。

如果只有在打开文件时才出现 CPU 峰值,可能是因为打开文件的操作需要大量时间造成的。有些平台/磁盘设备的 fopen() 返回结果很慢。在这种情况下,应使文件打开操作延迟(请参阅 延迟打开 了解更多详情)。

降低 I/O 池内存占用率

除了减少并行播放流数量以外,您还可以通过以下方式降低所需 I/O 内存的大小

  • 降低粒度(但要小心对设备吞吐量产生负面影响);
  • 降低目标缓冲(但要小心源干涸);
  • 使用流数据缓存(另请注意,池越小,缓冲效率越低);

注意 I/O 内存池中不会产生碎片。此池中的内存偶尔耗尽也是可以接受的。已经流播放的数据通常足以支持内存占用率峰值。可能发生的最坏情况就是源干涸。

降低 Stream Manager 池内存占用率

Stream Manager 内存池用于分配较小对象:设备和流对象、传输结构、用于异步文件打开的存储器、流内存查询表等。通常此池所需的大小非常小,但是最好确保永远不会耗尽此池中的内存,因为可能致不可挽救的 I/O 故障。注意,大多数池是在创建设备时分配的。

以下设置将降低 Stream Manager 池的内存需求:

  • 使用阻塞型设备而非延迟型设备;
  • 如果使用了延迟型设备,则降低并行请求的最大数量(AkDeviceSettings::uMaxConcurrentIO);
  • 禁用流缓存 (AkDeviceSettings::bUseStreamCache);
  • 执行同步文件打开操作(请参阅 延迟打开 了解更多详情);
  • 限制同时打开的流数量。

此页面对您是否有帮助?

需要技术支持?

仍有疑问?或者问题?需要更多信息?欢迎联系我们,我们可以提供帮助!

查看我们的“技术支持”页面

介绍一下自己的项目。我们会竭力为您提供帮助。

来注册自己的项目,我们帮您快速入门,不带任何附加条件!

开始 Wwise 之旅