版本
menu_open
Wwise SDK 2023.1.3
Low-Level I/O
Default Streaming Manager Information

Low-Level I/O 是高级 Stream Manager API 默认实现的一个子模块,用于为 I/O 传输提供比实现高级 Stream Manager API 更简单的接口,因此只有在 Stream Manager 默认实现的环境中才适用。

简介

Low-Level I/O 系统是特别针对 Audiokinetic 厂商实现的 Stream Manager,其接口于 <Wwise Installation>/SDK/include/AK/SoundEngine/AkStreamMgrModule.h 中定义。

Low-Level I/O 系统有两个目的:

  • 解析文件位置。
  • 将实际 I/O 传输抽象化。

实现 AK::StreamMgr::IAkFileLocationResolver 的唯一对象被称为 File Location Resolver ,必须将其注册至 Stream Manager (使用 AK::StreamMgr::SetFileLocationResolver())。为了打开标准流或自动流,Stream Manager 会调用 AK::StreamMgr::IAkFileLocationResolver::GetNextPreferredDevice() 来查找哪个流播放设备可能拥有相应文件。流播放设备通过 AK::StreamMgr::CreateDevice() 在 Stream Manager 中创建和注册。

必须为每个流播放设备提供一个 Low-Level I/O 挂钩。此 I/O 挂钩可实现 AK::StreamMgr::IAkLowLevelIOHook 并负责与平台的 I/O API 进行通信,同时处理该物理设备的特有属性和行为。 每次流播放设备需要执行 I/O 传输时,都会调用 Low-Level I/O 挂钩的 AK::StreamMgr::IAkLowLevelIOHook::BatchRead()AK::StreamMgr::IAkLowLevelIOHook::BatchWrite() 方法。High-Level Stream Manager 不会直接调用平台的 I/O API。File Location Resolver 和 I/O 挂钩共同构成 Low-Level I/O 系统。

下图从默认 Stream Manager 角度描述了 Low-Level I/O 系统接口。

游戏需要实现 Low-Level I/O 接口来解析文件位置,执行实际的 I/O 传输。在游戏中集成 Wwise 声音引擎 I/O 管理的最简单高效的方式,是使用默认 Stream Manager 并实现 Low-Level I/O 系统。 在此,您可以执行自带文件的读取操作,或者使用自己的 I/O 管理技术来处理 I/O 请求。

Wwise SDK 包含默认的 Low-Level I/O。可以直接使用,也可以此为基础来实现您自己的 I/O 管理。请参阅 默认实现示例纵览 一节了解 Low-Level I/O 示例的概述。

文件位置解析

File Location Resolver( File Location Resolver )

File Location Resolver 需要使用 AK::StreamMgr::SetFileLocationResolver() 注册至 Stream Manager。 在 Stream Manager 创建流对象时,其会调用此解析器的 GetNextPreferredDevice() 方法,并返回 AkDeviceID 以识别用来打开相应文件的 I/O Device。此函数只会通过名称、标记、语言等查找文件位置,而不会执行磁盘操作或其他物理检查。真正的 Open 调用会被转发给指定的设备来执行磁盘操作。因此,该函数要快速返回结果。 若 Device 无法查找或打开相应文件,将再次调用 GetNextPreferredDevice。若没有其他已知的 Device 或不太可能在其他位置找到相应文件,则应返回 AK_FileNotFound 以告知搜索已经结束。

Low-Level I/O 中的文件数量与同一时间 Stream Manager 中流对象数量相同。

游戏必须使用 AK::StreamMgr::CreateDevice() 在 Stream Manager 中至少创建一个流播放设备。 返回的 AkDeviceID 应由调用程序保留并按照以上所述在 AK::StreamMgr::GetNextPreferredDevice 中返回。 还可以根据需要创建所需数量的设备。每个流播放设备在独立的线程中运行,并将 I/O 请求发送到各自的 I/O 挂钩。通常应为每个物理设备创建一个流播放设备。 在 AkFileDesc 结构中,有 deviceID 字段。File Location Resolver 需要将其设置为已有流播放设备的 deviceID 之一。这会将文件处理操作指定到相应设备。除此之外,还要返回文件的大小和偏置 (uSector),并创建系统文件句柄(不过并非一定要这样做)。

文件说明

Stream Manager 的客户端通过字符串 (const AkOSChar *)、整数 ID (AkFileID) 或文件描述符 (AkFileDesc) 识别文件。正因如此,Stream Manager 的流创建方法(AK::IAkStreamMgr::CreateStd()AK::IAkStreamMgr::CreateAuto())使用结构调用 AkFileOpenData 来对两种可能的识别方法进行分组。 在打开相应文件后, AK::StreamMgr::IAkLowLevelIOHook::BatchOpen() 方法必须创建有效的文件描述符。

每次调用底层 I/O 挂钩的方法都会传回此文件描述符。高级 Stream Manager 使用该结构的三个成员:

  • deviceID:须为调用 AK::CreateDevice() 所获得的有效设备 ID。Stream Manager 用其将文件关联到正确的高级设备。一旦建立关联,将通过创建设备时传递的 I/O 挂钩执行 I/O 传输。
  • uSector:所述文件开头的偏置。是相对于 AkFileHandle AkFileDesc::hFile 所标识文件开头的偏置。它采用块(扇区)表示,块大小即 AK::StreamMgr::IAkLowLevelIOHook::GetBlockSize() 为此文件描述符所返回的值。 当流播放设备调用 I/O 传输方法时,会将文件开头的偏置(单位:字节)作为 AkTransferInfo 结构的一部分发送给其 Low-Level I/O 挂钩。偏置的计算方式是:Current_Position + ( AkFileDesc::uSector * Block_Size)。注意块大小也通过此挂钩查询,为每个文件描述符各查询一次。
  • iFileSize:Stream Manager 用其来检测文件结尾。然后通过 AK::IAkStdStream::GetPosition(), AK::IAkAutoStream::GetPosition() 向用户报告,并停止自动流的 I/O 传输。

其余的成员归 Low-Level I/O 系统所独自占有。例如,Win32 中类型定义为 HANDLE 的 AkFileHandle 可用于容纳传递到 Win32 ReadFile() 的实际有效文件句柄。它还可以用作 ID 或者指针。高级设备不可修改或读取文件描述符字段。因此,如果冗余文件存在于游戏磁盘中,Low-Level I/O 可以随意地关闭和重新打开文件句柄。

在关联流对象的整个生命周期,由 AK::StreamMgr::IAkLowLevelIOHook::BatchOpen() 返回的文件描述符结构都要保持不变,直到调用 AK::StreamMgr::IAkLowLevelIOHook::Close() 为止。

延迟打开

用户既可同步也可延迟完成由 AK::StreamMgr::IAkLowLevelIOHook::BatchOpen() 执行的所有文件打开操作。 若设备的原生 API 支持延迟打开文件,出于性能上的考虑最好采用这种方式。这样的话,在某些系统上便可并行执行文件打开和读写操作,或者根据需要为其设置不同的优先级。

Open() 的参数为从 AkFileOpenData 获取的 AkAsyncFileOpenData 结构。其中包含所有从 AK::IAkStreamMgr::CreateStd()AK::IAkStreamMgr::CreateAuto() 函数传递的信息。除此之外,其还包含必要的回调函数用以告知高层 Streaming Manager 已完成 Open 操作并返回 AkFileDesc 以供填写。

All calls to AK::StreamMgr::IAkLowLevelIOHook::BatchOpen() are considered asynchronous, from the Stream Manager perspective. Once the open operation is resolved, signal the result of the operation through the notification callback (io_pOpenData->pCallback). It is imperative to notify of the result once it is known, otherwise the stream object waiting for this file will stay alive indefinitely and cause a memory leak. If the result reported by io_pOpenData->pCallback is AK_Success, it is expected that io_pOpenData->pFileDesc is filled properly and usable. Note that it is supported to call io_pOpenData->pCallback right away (synchronously) if the result of the operation is known at the moment of the BatchOpen() call.

技巧: The AkAsyncFileOpenData (io_pOpenData) parameter that is passed to AK::StreamMgr::IAkLowLevelIOHook::BatchOpen() will stay valid until the notification io_pOpenData->pCallback is received.

If the file is opened successfully, set in_eResult to AK_Success and create the file descriptor structure (io_pOpenData->pFileDesc) properly. The pFileDesc member must point to memory that will be valid for the lifetime of the stream object, until Close() is called. Use AK_FileNotFound if the file was not found and set io_pOpenData->pFileDesc to null. Other error codes are possible, such as AK_FilePermissionError, AK_FilePathTooLong or AK_UnknownFileError.

文件系统标志

Stream Manager 的 AK::IAkStreamMgr::CreateStd()AK::IAkStreamMgr::CreateAuto() 方法会接收指向 AkFileSystemFlags 结构的指针,并将其传给 AK::StreamMgr::IAkFileLocationResolver::GetNextPreferredDevice()AK::StreamMgr::IAkLowLevelIOHook::BatchOpen()。此结构是用户直接传递信息到 Low-Level I/O 的一种方式。例如,此信息可以用于完成文件位置逻辑。Stream Manager 的一般用户可以传递 NULL,但声音引擎总是传递含有相关信息的结构,以告知 File Location Resolver 该请求来自声音引擎。

文件系统标志结构包含以下字段:

  • uCompanyID:声音引擎总是设置此字段为 AKCOMPANYID_AUDIOKINETIC,因此 Low-Level I/O 的实现者知道此文件须由声音引擎读取。 在流播放 External Source(外部源)时,声音引擎将传递 AKCOMPANYID_AUDIOKINETIC_EXTERNAL。
  • uCodecID:此字段可用于辨别文件类型。它将告知声音引擎使用的文件类型。声音引擎使用的编解码器 ID 在 AkTypes.h 中定义。主机程序可以使用与其中相同的值来定义它自己的 ID,只要不将 companyID 设为 AKCOMPANYID_AUDIOKINETIC 或 AKCOMPANYID_AUDIOKINETIC_EXTERNAL 就可以。 注意,可基于传统编解码器使用预定义的 CodecID AKCODECID_BANK、AKCODECID_BANK_EVENT、AKCODECID_BANK_BUS 来组织文件。
  • bIsLanguageSpecific:此字段指示查找的文件是否针对当前语言。通常,系统会将包含语言特定内容的文件存放在不同的位置(一般会放在语言文件夹中)。Low-Level I/O 需要根据当前选择的语言解析路径。请参阅 多语言专用的(“语音”和“混合”) SoundBank 了解更多详情。
  • 自定义参数和大小:用于文件位置机制的、游戏特有的扩展名。例如,在文件描述符中可以存储扩展名。声音引擎总是传递 0。

解析声音引擎文件

声音引擎读取 SoundBank 文件和流播放音频文件。此小节解释 Low-Level I/O 如何能够将其标识符解析为实际文件。

在 Low-Level I/O 中将 AK::StreamMgr::IAkLowLevelIOHook::BatchOpen() 中收到的 ID 映射至有效文件描述符时,存在多种不同的策略:

  • 实现并使用 ID 到文件名称的映射
  • 使用 ID 创建文件名字符串
  • 获取所有流播放音频文件(如使用 File Packager 应用程序生成的文件包——请参阅 Wwise Help 了解更多详情)拼接所得大文件的系统句柄,实现并使用从 ID 到文件描述符结构的映射。文件描述符结构定义该流播放音频文件在此大文件中的大小和位置偏置。

Low-Level I/O 的 SDK 示例使用两种不同的策略来解析文件位置。策略一(CAkFileLocationBase)将全局设置的路径拼接起来,创建可用于平台 fopen() 方法的完整路径字符串。策略二(CAkFilePackageLowLevelIO)则使用 File Packager 实用程序创建的文件包。文件包是将许多文件拼接在一起的大文件,其文件头指示每个原始文件的相对偏置。

File Location Resolver 的两种实现也被用作 SDK 的示例。它们使用不同的策略管理文件位置。本节末尾的代码示例详解中描述了这些策略。

请参阅 基本文件定位 了解有关 Low-Level I/O 的默认实现所用策略的描述。

请参阅 文件位置 了解有关使用文件包的 Low-Level I/O 实现(CAkFilePackageLowLevelIO)所用策略的描述。

SoundBank

在显式或隐式请求从主要 API(AK::SoundEngine::LoadBank()AK::SoundEngine::PrepareEvent())加载库后, SoundBank 的 Bank Manager 可以调用 AK::IAkStreamMgr::CreateStd() 的 ansi 字符串或 ID 重载。对于选择哪个重载的条件的详细说明,请参阅 从文件系统加载 SoundBank

在两种情况下,AKCOMPANYID_AUDIOKINETIC 都将用作文件系统标志中的公司 ID,而 AKCODECID_BANK 用作编解码器 ID。

声音引擎 API 的 LoadBank() 方法不会提供标志来指示 SoundBank 是否针对特定语言。解决方案要由您的 Low-Level I/O 来实现。 由于声音引擎不知道 SoundBank 的语言特性,因此它将调用 Stream Manager,其中 bIsLanguageSpecific 标志设为 True。如果 Stream Manager( Low-Level I/O)无法打开它,声音引擎将再次尝试,此次 bIsLanguageSpecific 标志设为 False

有关如何在 Wwise SDK 中使用语言特定 Bank 的详细信息,请参阅 多语言专用的(“语音”和“混合”) SoundBank 章节。

技巧: 若想避免 Bank Manager 针对非本地化的 Bank 调用 Stream Manager 两次,并且文件组织架构允许查找不同语言的内容,则可忽略 bIsLanguageSpecific 标记并直接打开正确位置的 SoundBank。只有您自己知道 SoundBank 的内容和位置。另外,传给异步版本 AK::SoundEngine::LoadBank() 的 Cookie 还会作为 in_pFlags->pCustomData 的值传给 AK::StreamMgr::IAkLowLevelIOHook::BatchOpen()。可以使用它来帮您确定是否应从特定语言的目录中打开 SoundBank。

流播放音频文件

流播放文件的参考将以整型 ID 形式存储在 SoundBank 中。对于准备流播放的转码音频文件,其真实文件路径可以在与 SoundBank 一起生成的 SoundBanksInfo.xml 文件中找到(请参阅 SoundBanksInfo.xml 了解详情)。

技巧: 在 Wwise 的 SoundBank 设置中选择 “Copy streamed files” 将自动复制 Generated SoundBanks 特定平台文件夹中的文件,并按照 [ID].[ext] 方案重新命名。 请参阅 流播放音频文件 了解更多信息。 默认文件位置实现 (CAkFileLocationBase) 要结合 "Copy Loose/Streamed Media" 选项一起使用。

当声音引擎要流播放音频文件时,它会调用 AK::IAkStreamMgr::CreateAuto() 的 ID 重载。由上及下,将使用 AK::StreamMgr::IAkLowLevelIOHook::BatchOpen() 的 ID 重载。将与 ID 一起传递 AkFileSystemFlags 结构及以下信息:

  • uCompanyID 是 AKCOMPANYID_AUDIOKINETIC
  • uCodecID 是 AkTypes.h (AKCODECID_XXX) 中定义的音频格式之一。
  • bIsLanguageSpecific 标志。当需要在当前游戏语言位置中搜索文件时,此标志为 True,否则为 False。 By default, loading soundbanks query the language-specific folder first.

I/O 传输接口

Once file location has been resolved through AK::IAkFileLocationResolver::GetNextPreferredDevice(), the Stream Manager then calls AK::StreamMgr::IAkLowLevelIOHook::BatchOpen() on the selected streaming device, which interacts with the Low-Level I/O system through its own I/O hook. 它将首先调用 AK::StreamMgr::IAkLowLevelIOHook::GetBlockSize(),查询底层块大小限制。 Then, every input data transfer is executed through the hook's BatchRead() method, and output through the hook's BatchWrite() method. When the stream is destroyed, AK::StreamMgr::IAkLowLevelIOHook::Close() is called.

上述各方法将收到由 File Location Resolver 定义的相同文件描述符。

高级设备特性

Stream Manager 的当前实现仅定义了一种 I/O 挂钩,并采用异步握手方式与 Low-Level I/O 交换信号。

AK::StreamMgr::CreateDevice() 会返回由 File Location Resolver 在文件描述符结构中设置的设备 ID。

以下章节介绍了延迟型 I/O 挂钩。

延迟型 I/O 挂钩

Stream Manager 会创建流播放设备。该设备通过 AK::StreamMgr::IAkLowLevelIOHook 接口与 Low-Level I/O 系统进行交互。

Low-Level I/O implementations must handle multiple transfer requests at the same time. The interface defines a few important methods, AK::StreamMgr::IAkLowLevelIOHook::BatchOpen(),AK::StreamMgr::IAkLowLevelIOHook::BatchRead(), AK::StreamMgr::IAkLowLevelIOHook::BatchWrite() and AK::StreamMgr::IAkLowLevelIOHook::BatchCancel(). BatchRead() 和 BatchWrite() 应会立即返回结果,并通过所提供的回调函数通知流播放设备已完成若干传输。See details about AK::StreamMgr::IAkLowLevelIOHook::BatchOpen() in the section 延迟打开.

您可以在初始化设置中指定允许流播放设备发送到 Low-Level I/O 的并行 I/O 传输的最大数量 (AkDeviceSettings::uMaxConcurrentIO)。

For each transfer request sent to AK::StreamMgr::IAkLowLevelIOHook::BatchRead() or AK::StreamMgr::IAkLowLevelIOHook::BatchWrite(), the provided callback function in AkAsyncIOTransferInfo must be called once, with the result of the transfer. It must be called even in failure cases, otherwise the engine will wait for this transfer forever. The callback can be deferred to later, as it is common for asynchronous I/O subsystems to execute the transfer in the background.

The in_eResult parameter of the callback should be AK_Success if the transfer succeeds or any other error code if it fails. If any transfer is marked as AK_Fail, the corresponding stream will be destroyed, and an "I/O error" notification will appear in the transfer log.

备注: AkDeviceSettings::uMaxConcurrentIO 代表设备可能向 Low-Level I/O 发布的传输请求的最大数量。只有在 Stream Manager 的客户端调用 AK::IAkStdStream::Read()/Write() 或正在运行的自动流的缓冲低于缓冲目标 (AkDeviceSettings::fTargetAutoStmBufferLength) 时,设备的调度程序才会决定发送传输请求。有关目标缓冲长度的更多详细信息,请参阅 Audiokinetic Stream Manager 初始化设置 章节。

流播放设备会将一组 BatchIoTransferItem 传给 AK::StreamMgr::IAkLowLevelIOHook 的每个函数。 此结构包含有关每项传输的信息,包括 AkFileDesc、AkIoHeuristics 和 AkAsyncIOTransferInfo。 AkAsyncIOTransferInfo 结构是对前述 AkIOTransferInfo 结构的扩展。AkAsyncIOTransferInfo 包含所要读取或写入的缓冲区地址,并提供有 pUserData 字段以便实现人员将元数据附加到待处理传输。AkAsyncIOTransferInfo 结构将一直存活至调用回调函数为止。在调用回调函数后,不得再引用它。

若要将读取或写入内容输出到自己的 I/O 流播放设备以便对 I/O 请求进行重新排序,所提供的 AkIoHeuristics 中包含的信息可能会很有用。

技巧: 默认 Stream Manager 调度程序的实现基于“客户端启发式算法”,而非“磁盘带宽启发式算法”。Stream Manager 不知道文件在磁盘上的布局。如果您自己的流技术允许,它可以使用这一信息来对 I/O 请求重新排序,来尽量减少磁盘寻址。

有时候流播放设备需要刷新数据。在 Stream Manager 的客户端调用 AK::IAkAutoStream::SetPosition() 或更改循环启发式算法时,可能会发生这种情况。有时候数据甚至需要在相应传输完成前进行刷新。当 AkDeviceSettings::uMaxConcurrentIOAkDeviceSettings::fTargetAutoStmBufferLength 较大时,刷新的可能性就更大。在发生这种情况时,延迟型 I/O 挂钩 API 会提供获取通知的途径:BatchCancel()。在流播放设备需要清除与 Low-Level I/O 中仍待处理的 I/O 传输相关的数据时,它会在内部将这些传输标记为“已取消”,然后调用 AK::StreamMgr::IAkLowLevelIOHook::BatchCancel() 并等待调用回调函数。BatchCancel() 仅用于通知 Low-Level I/O,它可能会执行操作,也可能不会。流播放设备知道哪些传输需要取消,所以,如果让其正常完成而不取消,它们一完成就会被刷新。无论哪种情况都必须调用回调函数,以通知流播放设备可弃用 I/O 传输信息和缓冲区。

注意: 请确保一定不要为给定的传输调用回调函数两次。
技巧:
  • 若在 Low-Level I/O 中建立了队列,则可使用 BatchCancel() 来解散请求队列。若能够解散队列,就可直接在 BatchCancel() 内调用回调函数。
  • 请不要在 BatchCancel() 内阻塞物理设备控制器。这可能阻塞 Stream Manager 的客户端。
注意: 对于已被取消的传输,在调用其回调函数时,必须传递 AK_Success。其他任何操作都将被视为 I/O 错误,相关流将被终止。
注意: 您可以从任何线程调用 AK::StreamMgr::IAkLowLevelIOHook::BatchCancel() 。相应地,倘若实现 AK::StreamMgr::IAkLowLevelIOHook::BatchCancel() ,那么在 Low-Level I/O 中锁定时必须非常小心。具体而言,从 BatchCancel() 和从正常 I/O 完成代码路径回调 pCallback 之间可能发生竞争冲突,您需要避免这种情况发生。 有关更多详情,可从函数说明中找到。
技巧: 请不要仅出于这一原因就实现 AK::StreamMgr::IAkLowLevelIOHook::BatchCancel() 。由于锁定问题,有时候试图取消请求会比让它们正常完成可能需要付出更大的代价。

其他考虑因素

块大小(GetBlockSize())

如前所述,Stream Manager 的用户必须考虑 Low-Level I/O 对于允许传输大小的限制。最常见的限制是这些大小必须是某个值的倍数。此基础值是 AK::StreamMgr::IAkLowLevelIOHook::GetBlockSize() 为给定文件描述符所返回的值。例如,在 Windows® 中打开的带 FILE_FLAG_NO_BUFFERING 标志的文件,必须使用扇区大小数倍的缓冲区大小进行读取。 AK::StreamMgr::IAkLowLevelIOHook::GetBlockSize() 方法返回扇区大小。另一方面,如果 Win32 文件打开后不带该标志,AK::StreamMgr::IAkLowLevelIOHook::GetBlockSize() 应返回 1,从而不对 Stream Manager 的客户端加以限制。

注意: AK::StreamMgr::IAkLowLevelIOHook::GetBlockSize() 决不能返回 0。
技巧: 处理底层块大小限制的重任将交给 Stream Manager 的客户端。 块大小值较大时将导致声音引擎浪费更多的流数据。除非平台的 I/O 系统具有特定的对齐限制或者可以极大地改善 I/O 带宽性能,否则您应使用 1 作为底层块大小。

性能分析

AK::StreamMgr::IAkLowLevelIOHook::GetDeviceDesc() 在 Wwise 中用于执行性能分析。Low-Level I/O 的默认实现提供的信息就是在 Wwise 中进行性能分析时看到的实际内容。

AK::StreamMgr::IAkLowLevelIOHook::GetDeviceData() 与此相似,但是会在每个性能分析帧调用它。 其返回值显示在 Streaming Device 选项卡的 Custom Parameter 列。

默认实现示例纵览

Wwise SDK 附带有 Low-Level I/O 的默认实现。它们位于 samples/SoundEngine/ 目录中。

类概述

下图是 Low-Level I/O 示例及其与 Low-Level I/O API 关系的类图。

CAkDefaultIOHookDeferred implements the File Location Resolver API (AK::StreamMgr::IAkFileLocationResolver) and the deferred I/O hook (AK::StreamMgr::IAkLowLevelIOHook). This implementation can be used in a single-device I/O system. CAkDefaultIOHookDeferred::Init() 可在 Stream Manager 中创建流播放设备,进而向其传递设备设置并存储返回的设备 ID。

另外,此设备还会在 Stream Manager 中将自己注册为唯一的 File Location Resolver。 但是,这只有在 Stream Manager 中尚未注册 File Location Resolver 的情况下才可能。

下图是表示单设备 I/O 系统的方框图。“Low-Level IO” 即是实现 File Location Resolver API 以及 I/O 挂钩 API 之一的类。可以是以下任意示例类:

  • CAkDefaultIOHookDeferred
  • CAkFilePackageIOHookDeferred

下面展示了如何单独使用 CAkDefaultIOHookDeferred 来初始化 I/O 系统(无需处理错误)。

// 创建 Stream Manager。
AkStreamMgrSettings stmSettings;
AK::StreamMgr::Create( stmSettings );
// 创建流播放设备。
AkDeviceSettings deviceSettings;
CAkDefaultIOHookDeferred lowLevelIO;
// 如果 lowLevelIO 尚未定义,则初始化时会将其注册为 File Location Resolver ,并创建流播放设备。
lowLevelIO.Init( deviceSettings );

设备内的 File Location Resolver 实现使用 CAkFileLocationBase 的服务。设备也会沿用这些服务。有关 CAkFileLocationBase 中实现的文件位置策略,更多信息请参阅下面的 基本文件定位

有关默认延迟型 I/O 挂钩实现的更多详细信息,请参阅下文的 延迟型 I/O 挂钩纵览 章节。

除此之外,还有一个编写为模板的类。藉此,可为实现 AK::StreamMgr::IAkFileLocationResolverAK::StreamMgr::IAkLowLevelIOHook 的类(如 CAkDefaultIOHookDeferred)增添文件包管理功能。这个类就是 CAkFilePackageLowLevelIO<>。文件包是使用 AK File Packager 实用程序创建的文件。请参阅下面的 示例 File Package Low-Level I/O 实现纵览 一节了解有关 Low-Level I/O 中文件包处理的更多信息。 CAkFilePackageIOHookDeferred 类是对 CAkDefaultIOHookDeferred 的具体定义,其增添了文件包管理功能。

如果您要使用多个设备实现 I/O 系统,就需要向 Stream Manager 注册单独的 File Location Resolver ,其任务是为适当的设备分派文件管理任务。SDK 提供实现此功能的画布:CAkDefaultLowLevelIODispatcher。请参阅 多设备 I/O 系统 了解有关多设备 I/O 系统的更多详情。

基本文件定位

声音引擎使用的文件会通过 ID(用于流播放音频文件和 SoundBank)或字符串(一般保留用于 SoundBank)打开。CAkDefaultIOHookDeferredCAkFileLocationBase 继承而来,其暴露了用来设置全局路径的方法:SetBasePath()AddBasePath()SetBankPath()SetAudioSrcPath()。Both overloads of CAkDefaultIOHookDeferred::BatchOpen() call CAkFileLocationBase::GetFullFilePath(), to create a full file name that can be used with native file open functions. 最前面是基本路径, 如果文件是 SoundBank,其后将添加 SoundBank 路径。如果是流播放音频文件,则添加音频源路径。对于这两种文件,如果其位置取决于当前语言,则会添加语言目录名称。

如果使用的是字符串重载,则会在此路径后附加文件名字符串。

在 ID 重载中,只解析 Audiokinetic 的文件 ID。使用 ID 重载的游戏需要根据它的 ID 映射机制来更改实现。CAkFileLocationBase 的映射方案如下:它根据文件 ID 创建字符串,然后附加取决于文件类型(使用编解码器 ID 指定)的扩展名。这与 SoundBank 的 “Copy Streamed Files” 生成后续步骤设置使用的流播放文件命名规则兼容。 请参阅 Wwise 帮助了解有关 SoundBank 设置的更多信息。

备注: 未选择 SoundBank 设置的 “Use SoundBank names” 选项时,Wwise 以 [ID].bnk 命名格式生成 SoundBank 文件。因此,默认的 Low-Level I/O 中将可以对使用 ID 的显式 SoundBank 加载(通过 AK::SoundEngine::LoadBank() 的 ID 重载)和从 AK::SoundEngine::PrepareEvent() 触发的隐式 SoundBank 加载进行正确的映射。选择了 “Use SoundBank names” 时,Wwise 将使用它们的原始名称生成 SoundBank 文件(bank_name.bnk)。隐式 SoundBank 加载和使用字符串的显式 SoundBank 加载将得到正确映射。但使用 ID 的显式 SoundBank 加载将不起作用,因为默认 Low-Level I/O 将尝试打开名为 [ID].bnk 的文件,而此文件根本不存在。

请参阅 使用 SoundBank 名称 了解从 SDK 角度开展的有关 “Use SoundBank names” 选项的讨论,或者参阅 Wwise 帮助文档来了解通用 SoundBank 设置。

同样,默认 Low-Level I/O 打开名称格式为 [ID].[ext] 的流播放音频文件。[ext] 是取决于音频格式的扩展名。可以告诉 Wwise 在 SoundBank 生成结束时,自动复制 GeneratedSoundBank 路径中文件名格式为 [ID].[ext] 的所有流播放音频文件(请参阅 流播放音频文件 和 Wwise 帮助)。

在获取完整的文件路径后,CAkDefaultIOHookDeferred::Open() 会直接使用系统 API(封装在平台特定示例文件 AkFileHelpers.h 中实现的辅助函数中)来打开文件。

在游戏代码中,您可以使用上述 CAkFileLocationBase 的方法设置基本路径、 SoundBank 路径、音频源路径和特定语言路径。请参阅 默认底层 I/O 实现 的代码示例了解更多信息。

技巧: 声音引擎不知道何时需要从特定语言的目录中加载 SoundBank。 因此它总是调用 AK::IAkStreamMgr::CreateStd(),第一次调用时,它将 AkFileSystemFlags 的 bIsLanguageSpecific 标志设置为 True,如果第一次调用失败,则将该标志设置为 false。Low-Level I/O 的默认实现示例这样摸索尝试从当前特定语言目录打开文件,由于调用 fopen() 将失败,因此将影响效率,应避免发生这种情况。

应该总是重新实现 Low-Level I/O 来满足需您的求。如果您知道特定语言的 SoundBank 的名称,或者已定义专门名称来标识它们,则应在加载过程的早期从正确的文件夹中加载它们。

延迟型 I/O 挂钩纵览

通常,平台上的异步文件读取 API 需要将平台特定结构传给 fread()(在 Windows 上为 OVERLAPPED)并在整个 I/O 操作期间加以保留,直到调用回调函数来告知您 I/O 已经完成。

此实现在所有平台上都有相似之处。CAkDefaultIOHookDeferred 在其内存池中分配了一系列特定于平台的结构。当调用 CAkDefaultIOHookDeferred::Read() 时,它将查找第一个空闲的结构,将它标记为“已占用”,使用 AkAsyncIOTransferInfo 中提供的信息进行填充,然后将其传递给 fread()。它还传递一个本地静态回调函数,其签名与平台异步 fread() 函数兼容。此函数中会确定操作是否成功,释放特定于平台的 I/O 结构,并回调流播放设备。

从阵列中获取和释放特定于平台的 I/O 结构必须逐个进行,因为需要避免 Read()/Write() 与系统回调之间出现竞争情况。

有些平台会提供这样的服务,可以取消已经发送到内核的 I/O 请求。如果是这样,就会在 CAkDefaultIOHookDeferred::Cancel() 中调用它。在 Windows 下, CancelIO() 会取消给定文件句柄的所有请求。因此在调用此函数前,必须先查看参数 io_bCancelAllTransfersForThisFile 来确保流播放设备的确想取消给定文件的所有请求。如果不是这样,则 Cancel() 不会执行任何操作:只需等待请求完成。流播放设备知道需要放弃哪些请求。

注意: 请勿取消流播放设备已经显式取消的请求。如果您取消,最终可能会输入无效或受损数据,这可能导致声音引擎崩溃。

多设备 I/O 系统

下图表示多设备 I/O 系统。

使用多个流播放设备时,您需要做的是将与设备 Low-Level I/O 挂钩不同的 File Location Resolver 实例化并加以注册。 此对象的目的是将文件分派到适当的设备, 确定由哪个设备处理哪些文件的策略由您定义, 可以使用 CAkDefaultLowLevelIODispatcher 作为画布。默认实现使用比较强硬的方法:要求每个设备都尝试打开文件,直至其中一个设备成功。因此这些设备还必须实现 AK::StreamMgr::IAkFileLocationResolver 接口。 可以是 SDK 中提供的示例:

  • CAkDefaultIOHookDeferred
  • CAkFilePackageIOHookDeferred

下面展示了如何在多设备系统中将延迟型设备和文件包设备实例化(此示例仅供参考,并没有实践意义)。

// 创建 Stream Manager。
AkStreamMgrSettings stmSettings;
AK::StreamMgr::Create( stmSettings );
// 创建并注册 File Location Resolver 。
CAkDefaultLowLevelIODispatcher lowLevelIODispatcher;
AK::StreamMgr::SetFileLocationResolver( &lowLevelIODispatcher );
// 创建第一个设备。
CAkDefaultIOHookDeferred hookIODeferred;
AkDeviceSettings deviceSettings1;
hookIODeferred.Init( deviceSettings1 );
// 将它添加到全局 File Location Resolver 。
lowLevelIODispatcher.AddDevice( hookIODeferred );
// 创建第二个设备(具有文件包管理功能)。
CAkFilePackageIOHookDeferred hookIOFilePackage;
AkDeviceSettings deviceSettings2;
hookIOFilePackage.Init( deviceSettings2 );
// 将它添加到全局 File Location Resolver 。
lowLevelIODispatcher.AddDevice( hookIOFilePackage );
技巧: 仅当存在多个物理设备时才应使用多个设备。

示例 File Package Low-Level I/O 实现纵览

一般说明

CAkFilePackageLowLevelIO<> 类是默认 Low-Level I/O 挂钩的上层,作为其延伸,可以加载示例 File Packager 生成的文件(请参阅 File Packager 实用程序 )。 它使用更先进的策略将 ID 解析为文件描述符。文件包包括大量拼接文件(流播放音频文件和 SoundBank 文件),这些文件的文件头中包含与其相关的信息。

请参阅 文件包底层 I/O 实现 了解代码示例。您可以与对应的 File Packager 实用程序一起按原样使用 File Package Low-Level I/O (CAkFilePackageLowLevelIODeferred)。或者仅将它们视为实现高级文件位置解析方法的概念验证。

File Package Low-Level I/O 提供方法 CAkFilePackageLowLevelIO::LoadFilePackage(),其参数是使用 File Packager 示例生成的文件包名称。 它使用默认实现的服务打开,然后解析文件头并创建查询表。您可以加载任意数量的文件包。LoadFilePackage() 返回 ID,您可以结合 UnloadFilePackage() 使用此 ID 来卸载它。

CAkFilePackage 类包含已加载的文件包,以及用于处理文件查询表的所有数据结构和代码,文件查询表在 CAkFilePackageLUT 类中定义。 CAkFilePackageLowLevelIO<> 类覆盖了默认 I/O 挂钩的一些方法,以调用 CAkFilePackageLUT 的查询服务。未找到文件描述符或者请求的文件描述不属于文件包时,则调用默认实现。

文件位置

在文件包内查找文件的原理:通过平台的文件打开函数一次性获取文件句柄,然后在每次调用 AK::StreamMgr::IAkLowLevelIOHook::BatchOpen() 时,直接返回使用此文件句柄的文件描述符,但会应用与文件包内原始文件的偏置对应的偏置(使用文件描述符 AkFileDesc 的 uSector 字段)。 这也有助于加强对磁盘文件分布的控制。

File Packager 实用程序将仔细构建文件头,以便 Low-Level I/O 仅需将少量指针转型即可获取其中的查询表,即一个指针用于流播放音频文件,一个用于 SoundBank 文件。查询表是以下结构组成的数组:

struct AkFileEntry
{
AkFileID fileID; // 文件标识符。
AkUInt32 uBlockSize; // 一个块的大小,要求对齐(单位:字节)。
AkInt64 iFileSize; // 文件大小,单位:字节。
AkUInt32 uStartBlock;// 启动块,以 uBlockSize 表示。
AkUInt32 uLanguageID;// 语言 ID。如果不是专门针对特定语言,则为 AK_INVALID_LANGUAGE_ID。
};

表格键值是文件的 fileID。然而,不同语言的相应文件具有相同的 fileID,但 uLanguageID 不同。File Packager 总是先根据 fileID 对文件条目排序,然后再根据 uLanguageID 排序。在 CAkFilePackageLowLevelIO::Open() 中,ID 将传递给 CAkFilePackageLUT::LookupFile()(在 Open() 的字符串版本中,字符串首先使用声音引擎 API 的服务 AK::SoundEngine::GetIDFromString() 进行散列)。 CAkFilePackageLUT::LookupFile() 根据 uCodecID 标志选择要搜索的相应表格,并根据 fileID 和 uLanguageID 键执行二进制搜索。如果找到匹配项,将向 CAkFilePackageLowLevelIO::Open() 返回文件条目地址,以收集用于填充文件描述符(AkFileDesc)的必要信息。

技巧: 将搜索每个文件包,直至找到匹配项为止。如果您的一个文件包仅包含 SoundBank,另一个文件包仅包含流播放文件,则可以更改实现,使用 AkFileSystemFlags 仅在正确的文件包中一次性查询文件。

文件描述符的句柄 hFile 也是文件包的句柄。文件大小 iFileSize 直接存储在文件条目中,起始块 uSector 也是。

备注: 如前文所述,对于句柄 hFile 所示文件开头的相对偏置,Stream Manager 不使用字节,而使用块(“扇区”)。块大小代表文件位置的粒度。File Packager 的当前版本对所有文件使用相同的块大小,块大小在生成时指定(使用 -blocksize switch,请参阅 Wwise 帮助文档了解有关 File Packager 命令行参数的更多详情)。将执行补零操作,以便拼接的文件总是从块边界开始。

文件包 Low-Level I/O 使用文件描述符的 uCustomParamSize 字段来存储块大小。这样做有 2 个目的:

  • 易于访问其块大小;
  • 区别文件包中的文件描述符(uCustomParamSize == 0 意味着在文件包中未找到文件)。 例如,当文件属于文件包时,文件句柄(包中所有文件之间共享)在 CAkFilePackageLowLevelIO::Close() 中将不会关闭。

管理语言

自 Wwise 版本 2011.2 以来,当前语言均在默认 Stream Manager 模块中设置,可以在 AkStreamMgrModule.h 中使用 AK::StreamMgr::SetCurrentLanguage() 定义。传递语言的名称时,应 不带结尾的斜杠或反斜杠

CAkFileLocationBase 中继承的默认 Low-Level I/O 实现从 Stream Manager 获取语言名称,并将其附加到基本路径之后。因此语言名称应为该语言本地化素材的存储目录名称。

File Packager 实用程序生成的文件包可以包含同一素材的若干语言版本。 它们的文件头中包含语言名称的字符串映射。文件包 Low-Level I/O 监听 Stream Manager 中的语言变化,并使用当前语言名称查询素材包中的正确本地化版本。

AKSOUNDENGINE_API void GetDefaultSettings(AkStreamMgrSettings &out_settings)
AkUInt32 AkFileID
Integer-type file identifier
Definition: AkTypes.h:159
int64_t AkInt64
Signed 64-bit integer
AKSOUNDENGINE_API IAkStreamMgr * Create(const AkStreamMgrSettings &in_settings)
AKSOUNDENGINE_API void GetDefaultDeviceSettings(AkDeviceSettings &out_settings)
AKSOUNDENGINE_API void SetFileLocationResolver(IAkFileLocationResolver *in_pFileLocationResolver)
uint32_t AkUInt32
Unsigned 32-bit integer

此页面对您是否有帮助?

需要技术支持?

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

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

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

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

开始 Wwise 之旅