Wwise SDK 2022.1.17
|
Wwise 声音引擎使用 Stream Manager 来加载 SoundBank 和读取流音频文件。如果您还没有 I/O 管理器,还欢应您将它用于所有游戏 I/O。如果您不将它直接当作客户端使用,仅通过实施 Low-Level I/O 挂钩将 Wwise I/O 集成到游戏中,则您可以跳过本章。
Stream Manager 的主要接口通过用来创建流播放对象的 AK::IAkStreamMgr 进行定义。
在使用 Stream Manager 之前,您需要将其实例化。
Default Streaming Manager Information Stream Manager 实例化与特定实现相关。Audiokinetic 的 Stream Manager 的默认实现函数在 AkStreamMgrModule.h: AK::StreamMgr::Create() 中定义。您需要将特定的实现设置传递给它(请参阅 Audiokinetic Stream Manager 初始化设置 了解初始化设置的说明)。 |
Default Streaming Manager Information 在创建和使用流对象之前,您需要创建高级流播放设备,此概念仅针对 Audiokinetic 的 Stream Manager 默认实现。高级流播放设备即为 I/O 调度器,它在自己的线程中运行,将与其相关的流对象集中起来,并向 Low-Level I/O 发送数据传输请求。高级设备通常被简称为设备。游戏使用特定的标志和设置创建若干个设备,最好是在初始化时创建。有关初始化设置的更多详情,请参阅 Audiokinetic Stream Manager 初始化设置 一节。AK::StreamMgr::CreateDevice() 返回设备 ID,出于文件定位和设备分配目的, Low-Level I/O 应保存此设备 ID 。(请参阅 文件位置解析 一节了解更多信息。)此设备 ID 必须传递给 AK::StreamMgr::DestroyDevice() 才能正确终止设备。跟所有针对特定实现的函数一样,AK::StreamMgr::CreateDevice() 和 AK::StreamMgr::DestroyDevice() 在文件头 AkStreamMgrModule.h 中定义。 |
Stream Manager 的主要 API 在很大程度上说是流创建方法的集合。根据文件标识符和设置,它们创建一个流对象并向其返回一个接口,所有流操作都将通过此接口执行。 两个流创建方法各有两个重载,根据其使用的文件标识机制而有所不同。第一个重载使用字符串标识文件,第二个重载使用整型 ID。
Default Streaming Manager Information
|
除文件标识符外,流创建方法还接受指向包含文件定位标记的 AkFileSystemFlags 结构的指针,
Default Streaming Manager Information 并原封不动传递给 Low-Level I/O。请参阅 SoundBank 、流播放音频文件 和 基本文件定位 各节了解有关 AkFileSystemFlags 以及声音引擎如何使用它们的更多详情。 |
流创建方法中还有另一个参数:in_bSyncOpen,当用户(例如声音引擎)需要文件句柄封装在流对象中并在此调用期间打开时,它们将返回 True。如果返回 False,意味着它们允许 Stream Manager 延迟打开文件,文件即有可能被延迟打开。如果延迟打开但打开失败,则 AK::IAkStdStream::Read() 或 AK::IAkAutoStream::GetBuffer() 的下一次调用将返回 AK_Fail,
备注: 这不是致命错误。对于已经流播放的音频文件,声音引擎返回 False。如果 GetBuffer() 返回 False,则会将视此为 I/O 错误并正常地销毁流。 |
Default Streaming Manager Information 参数 in_bSyncOpen 直接传递给 Low-Level I/O。请参阅 延迟打开 了解有关此标志在 Low-Level I/O 中产生结果的详情。 |
AK::IAkStreamMgr::CreateStd() 创建标准流,而 AK::IAkStreamMgr::CreateAuto() 创建自动流。下面是这两种流的描述。
两种流对象均定义有 Destroy() 方法,供您调用来释放流所占用的资源。释放后您将不可再使用此对象。
Default Streaming Manager Information 预定要销毁的流对象向 I/O 线程发出信号,当 Low-Level I/O 中再也没有等待其执行的 I/O 传输时,就会将它们释放。 如果正在进行流性能分析,I/O 线程将等待监控线程允许,然后再处理流对象。监控线程每 200 毫秒执行一次性能分析过程。 |
调用 AK::IAkStreamMgr::CreateStd() 将创建标准流对象,通过返回的 AK::IAkStdStream 接口可以控制该对象。
标准流使用基本的读/写机制来控制 I/O 操作。当调用 AK::IAkStdStream::Read() 或 AK::IAkStdStream::Write() 时,I/O 请求将排队进入调度程序,并且其状态将被流设置为 AK_StmStatusPending. 用户传递请求的传输大小和缓冲区的地址后,其状态将被流设置为 AK_StmStatusCompleted 或 AK_StmStatusError。下一操作将在现有位置将加上实际传输大小所得的新位置处执行。要强制使用另一位置,必须在初始化新传输前使用 AK::IAkStdStream::SetPosition() 方法。
I/O 操作一次只能执行一个。后续操作需要通过调用 AK::IAkStdStream::Read() 或 AK::IAkStdStream::Write() 来显式地初始化。使用完流后,必须调用 AK::IAkStdStream::Destroy(),接口将变为无效。
Default Streaming Manager Information 读\写调用最终在 Low-Level I/O 中执行 I/O 传输。如果客户端请求的大小大于流播放设备的粒度(AkDeviceSettings::uGranularity),则传输将被拆分。流将保持在 AK_StmStatusPending 或 AK_StmStatusIdle 状态,直到整个传输结束或发生错误,或满足文件结束条件。 |
此接口还提供其他方法,包括设置查询、访问当前状态或位置,以及访问供先前操作使用的缓冲区。
标准流操作可以通过两种不同的方式启动:
AK::IAkStdStream::Read()
或 AK::IAkStdStream::Write()
的 in_bWait 参数为 true 时,方法会马上中断流操作,直到完成所有 I/O。AK::IAkStdStream::Read()
或 AK::IAkStdStream::Write()
的 in_bWait 参数为 false 时,方法会在完成 I/O 前返回结果。这时将异步完成剩余操作。可通过调用 AK::IAkStdStream::GetStatus()
来轮询流的状态,直到返回 AK_StmStatusCompleted。或者,通过调用 AK::IAkStdStream::WaitForPendingOperation()
来中断流操作,并等待完成异步任务。对于读取操作,为了安全地访问数据,之后可调用 AK::IAkStdStream::GetData()
,或读取初次调用 AK::IAkStdStream::Read()
的过程中所提供的缓冲区。在创建标准流时,您需要指定打开模式(AkOpenMode)。打开模式指定了流是否可用于读取、写入或者两者均可。显然,对于只为读取而打开的流调用 AK::IAkStdStream::Write() 将失败。
您还可以使用 AK::IAkStdStream::SetStreamName() 为流指定名称。字符串复制至流对象中,并可以通过 AK::IAkStdStream::GetInfo() 查询。
Default Streaming Manager Information 流名称就是 Wwise Advanced Profiler 的 Streaming 选项卡中所显示的名称。 |
标准流的启发式算法根据每个操作而异。
AK::IAkStdStream::Read() 和 AK::IAkStdStream::Write() 需要知道操作的优先级和截止时间(单位:毫秒)。一般而言,Stream Manager 会先处理具有较短截止时间的操作。当应用程序需要的 I/O 带宽超过存储设备能够提供的范围时,将先处理高优先级流。截止时间为零意味着现在就需要数据,因此 I/O 已经迟了。在这种情况下,将先处理它,然后再处理低优先级的其它流。
备注: 声音引擎的 Bank Manager 使用标准流。它在自己的缓冲区中读取和解析 SoundBank 数据。声音引擎 API 中提供一个方法来指定加载 SoundBank 时的平均吞吐量和优先级:AK::SoundEngine::SetBankLoadIOSettings()。(请参阅 Wwise 声音引擎中的 SoundBank 一节了解更多信息。)Bank Manager 调用 AK::IAkStdStream::Read(),在调用结束时解析数据,然后进行读取。该方法将使用作为参数的优先级,并根据用户指定的吞吐量来计算操作的截止时间: 标准流操作的截止时间和自动流的平均吞吐量相当于 Stream Manager 的 I/O 调度程序。通过游戏中的音频流和其他流的这一功能,声音引擎用户即可减轻 I/O 的库加载负担。 |
“块大小”是在 Stream Manager 级别对读取和寻址粒度以及缓冲区对齐的底层限制。这些流接口中的 AK::IAkStdStream::GetBlockSize()
方法可以查询 Low-Level I/O,向其传递与流相关的文件描述符,并返回给调用方。请参阅 Low-Level I/O 一节了解有关文件描述符和 AK::StreamMgr::IAkLowLevelIOHook::GetBlockSize()
方法的更多详情。有些存储设备对数据传输大小有限制。例如,在 Win32 平台上,具有 FILE_FLAG_UNBUFFERED 标志的已打开文件所允许的传输大小,是物理设备扇区大小的数倍。如果读取或写入操作请求的传输大小不是块大小的倍数,则操作将失败。查询读取大小是否是 AK::IAkStdStream::GetBlockSize()
返回值的倍数应由高级接口用户负责。AK::IAkStdStream::SetPosition()
也受相同的限制。但它会自动锁定到块下限,并返回实际的文件位置偏差。
一般而言,游戏是 Stream Manager 的用户。因为游戏也会实现底层 I/O 子模块,所以已经清楚允许使用的传输大小。声音引擎不清楚存储设备限制,因此它总是将标准流读取大小锁定到块大小边界上。
当流处于 AK_StmStatusPending 状态时,如果调用 AK::IAkStdStream::Read() 和 AK::IAkStdStream::Write(),操作将失败。AK::IAkStdStream::GetPosition() 和 AK::IAkStdStream::SetPosition() 将产生不确定的结果,因为这些结果可能发生在 I/O 传输结束之前,也可能在结束之后。但它们不会阻塞 I/O。
当传输处于待定状态时,可以使用 AK::IAkStdStream::Cancel(),但我们不建议使用它,因为性能将受到影响。此方法向调用方确认没有 I/O 处于待定状态。如果在向 Low-Level I/O 发送请求前执行调用,任务将从队列中移除。但如果请求已发送给 Low-Level I/O,调用方在 I/O 结束前将一直被阻塞。
技巧: 在销毁流前(AK::IAkStdStream::Destroy()),用户无需显式调用 AK::IAkStdStream::Cancel()。然而,为了获得最佳性能,只有当流未处于 AK_StmStatusPending 状态时用户才应调用 Destroy()。 |
调用 AK::IAkStreamMgr::CreateAuto() 将创建一个自动流对象,通过返回的 AK::IAkAutoStream 接口可控制该对象。
自动流仅用于输入。它们之所以被称为自动流是因为 I/O 请求将“在后台”发送给 Low-Level I/O,用户无需进行任何显式的函数调用。流内存归 Stream Manager 所有,并通过查询流内存地址来访问流数据。当流内存的区域分配给用户时,该区域将保持锁定状态,直至被显式释放。同时,内部调度程序在未锁定的内存中执行数据传输。在创建时,流处于闲置状态。从用户调用 AK::IAkAutoStream::Start() 时开始将执行 I/O 请求的自动调度。通过调用 AK::IAkAutoStream::Stop() 可以停止或暂停流,而再次调用 AK::IAkAutoStream::Start() 将使它继续工作。
注意: 流停止时请勿调用 GetBuffer()。在尝试从流中获取缓冲区之前,应总是调用 Start()。 |
通过调用 AK::IAkAutoStream::GetBuffer() 可以访问数据。如果已从 Low-Level I/O 中读取数据,此方法将返回 AK_DataReady、包含该数据的缓冲区的地址及其大小。当不再需要缓冲区时,可以通过调用 AK::IAkAutoStream::ReleaseBuffer() 来释放它。于是用户所见的流位置将增加释放缓冲区的大小增量。用户可以通过调用 AK::IAkAutoStream::SetPosition() 来强制使用另外的位置。AK::IAkAutoStream::GetBuffer() 的下一次调用将从该位置开始。
注意: 更改流位置可能导致一些数据被删除。自动流最适于顺序存取。其中有启发式算法来指定循环,帮助 Stream Manager 高效地管理流内存。有关更多信息,请参阅 常见缺陷和其他注意事项 一节。 |
注意: 如果 Low-Level I/O 报告错误,流将进入错误模式。自动流无法从其错误状态中恢复,AK::IAkAutoStream::GetBuffer() 将总是返回 AK_Fail。 |
自动流中的数据可以采用两种不同的方式进行存取:
AK::IAkAutoStream::GetBuffer() 有 4 种可能的返回代码:
如果用户收到填有数据的缓冲区,则此方法将返回 AK_DataReady 或 AK_NoMoreData。如果流缓冲区为空,AK::IAkAutoStream::GetBuffer() 将返回 AK_NoDataReady,大小为零,缓冲区地址为空。如果 Low-Level I/O 报告错误或者使用无效的参数调用方法,则将返回 AK_Fail。
如果收到的是文件的最后一个缓冲区,则返回 AK_NoMoreData 而非 AK_DataReady。Stream Manager 通过比较当前位置(用户所见位置)和 Low-Level I/O 所提供的文件描述符结构中的文件大小成员,来判断它是否是最后一个缓冲区。此后再调用 AK::IAkAutoStream::GetBuffer() 将返回 AK_NoMoreData,大小为零。
每次调用 AK::IAkAutoStream::GetBuffer() 都会提供一个新缓冲区。缓冲区必须通过调用 AK::IAkAutoStream::ReleaseBuffer() 来进行显式释放。释放的顺序就是使用 GetBuffer() 接收它们的顺序。释放后,缓冲区的地址将变为无效。当客户端不再持有缓冲区时,ReleaseBuffer() 返回 AK_Fail,但这不是致命错误。
技巧: 如果可能,请一次只获取一个缓冲区。这可以为 Stream Manager 提供更多的空间来执行 I/O。一次使用多个缓冲区的策略,一般留给需要访问环形/双缓冲区的硬件或其他接口。如果您打算对流一次使用多个缓冲区,应该传递正确的启发式算法( AkAutoStmHeuristics::uMinNumBuffers )来进行通知。 |
注意: 使用自动流时的一个常见错误是忘记调用 ReleaseBuffer()。 |
成功的 GetBuffer() 调用所得的缓冲区大小由 Stream Manager 决定。但它必须是块大小的倍数,除非达到了底层文件的末尾。您还可以使用缓冲限制(请参见 AkAutoStmBufSettings)来强制规定 GetBuffer() 返回的大小,将在创建时传递。
注意: 注意,强制规定缓冲区大小可能造成性能欠佳,因为可能会浪费流内存或带宽。 另外也可能不支持用户指定的限制。IAkStreamMgr::CreateAuto() 将返回 AK_Fail。 |
Default Streaming Manager Information 此大小通常为高层设备创建设置中指定的粒度,但流文件结束时例外。 |
在 in_bWait 标志设置为 True 的情况下,调用 AK::IAkAutoStream::GetBuffer() 将导致用户被阻塞,直至数据准备就绪。因此该方法无法返回 AK_NoDataReady。如果最后一个缓冲区也已经被获取和释放,此方法将立即返回 AK_NoMoreData,大小为零。
调用 AK::IAkAutoStream::GetBuffer() 时将 in_bWait 标志设为 False 后,用户可以通过评估返回的代码来尝试获取数据。如果返回的代码是 AK_NoDataReady,用户应执行其他任务并稍后再试。
自动流实例化的设置比标准流要多,其中包括缓冲区大小限制和启发式算法。这些设置会帮助 Stream Manager 分配最佳的内存量,并对数据传输请求进行优先级排序。
有些设置是用户指定的内存相关限制。缓冲区大小可以直接指定。如果您想让 Stream Manager 在限制范围内选择缓冲区大小,可以指定最小的缓冲区或块大小,也可以两者都指定。缓冲区大小将是块大小的倍数。
技巧: 缓冲区限制是可选的,一般不应使用,这样 Stream Manager 能够最高效地管理流内存。有些平台上,声音引擎使用解码硬件时将用到缓冲区限制。这些解码器一次通常需要访问 2 个缓冲区,有特定的字节对齐方式(块大小)和最小缓冲区大小。 |
Default Streaming Manager Information |
必须提供启发式算来帮助 Stream Manager 执行调度和内存分配。自动流启发式算法是在创建时根据每个流专门指定的,对于确保最佳表现非常重要。它们可随时通过 AK::IAkAutoStream::GetHeuristics() 和 AK::IAkAutoStream::SetHeuristics() 来查询和更改。
在选择缓冲区大小前,Stream Manager 总是先向 Low-Level I/O 查询给定文件的块大小,以确保缓冲区大小是块大小的倍数。因此,自动流的用户不必关心底层块大小,除非它们强制流进入到另一个位置。在这种情况下,它们需要考虑从 AK::IAkAutoStream::GetBlockSize() 获得的块大小或者 AK::IAkAutoStream::SetPosition() 所返回的实际绝对偏置。
流位置是从用户的角度来评估的,可以通过 Get/SetPosition() 方法进行查询和设置。在计算位置时不会考虑从 Low-Level I/O 传输到 Stream Manager 缓冲区的数据。用户释放缓冲区时将对其进行更新。如果调用 AK::IAkAutoStream::SetPosition() 时已有数据从 Low-Level I/O 传出,它们一般会被刷新。
备注: 用户应尽可能避免调用 AK::IAkAutoStream::SetPosition()。如果必须使用,则应尽早调用。例如,在声音引擎中,当循环声源从 Stream Manager 获取缓冲区时,它会查看其中是否包含循环结尾。如果包含,则立即调用 AK::IAkAutoStream::SetPosition()(即早在 ReleaseBuffer() 之前调用),从而最大限度地降低传输无用数据的风险。另外,指定循环启发式算法可以帮助内部调度程序对 Low-Level I/O 请求做出更好的决策。 |
技巧: 在锁定或未锁定缓冲区的情况下均可调用 AK::IAkAutoStream::SetPosition(),但建议尽早更改位置,以便最大限度地降低徒劳无功地传输数据的风险。正确地停止流还有助于将带宽浪费最小化。 AK::IAkAutoStream::SetPosition() 不会释放被客户端占据的缓冲区。客户端通过显式调用 ReleaseBuffer() 来决定何时应该释放缓冲区。AK::IAkAutoStream::SetPosition() 实际上指示的是对于 GetBuffer() 的下一次调用时客户端所期望的位置。 AK::IAkAutoStream::GetPosition() 返回客户端当前占据的第一个缓冲区的位置。如果客户端并未持有缓冲区,它将返回下一次调用 GetBuffer() 时获取的缓冲区位置。 |
AK::IAkAutoStream::GetBuffer() 的 AK_NoMoreData 返回代码是根据流位置来确定的。AK::IAkAutoStream::GetPosition() 返回的out_bEndOfStream 可选标志也将绑定到流位置。当且仅当以下条件下,它才会返回 True
自动流 API 的设计不会使用户阻塞 Low-Level I/O 的待定事务,除非阻止 AK::IAkAutoStream::GetBuffer() 调用。甚至 AK::IAkAutoStream::Destroy() 也不会阻塞。通常而言,如果流在销毁时仍在与 Low-Level I/O 交流,它将继续留存于内部,直至 Low-Level I/O 的当前传输结束。
要覆盖整个 Stream Manager,游戏必须实现 IAkStreamMgr.h 中定义的所有接口。此实现应遵守本节中所述的规则。
您可以将静态库 AkStreamMgr.lib 替换成游戏自己的库。创建函数以及针对实现的其他设置和定义(例如 Low-Level I/O API)位于 AkStreamMgrModule.h 中,因此不是 Stream Manager 的组成部分。
声音引擎通过调用其内联的静态 AK::IAkStreamMgr::Get() 方法来访问 Stream Manager,该方法将返回指向 AK::IAkStreamMgr 接口(它是 AK::IAkStreamMgr 的保护成员)的指针。自定义实现只能声明一个变量(AK::IAkStreamMgr::m_pStreamMgr),链接将自动执行。如果 Stream Manager 和声音引擎没有在同一个可执行文件或 DLL 中链接在一起,则 AK::IAkStreamMgr::m_pStreamMgr 必须拥有 __dllexport 属性。可以使用 AKSTREAMMGR_API 宏,并在自定义 Stream Manager 库的编译程序设置中定义 AKSTREAMMGR_EXPORTS。
还需要实现性能分析接口,以允许与声音引擎的非 AK_OPTIMIZED 版本进行链接。此接口一目了然。实现它后即可在 Wwise 中执行性能分析。如果不需要性能分析,AK::IAkStreamMgr::GetStreamMgrProfile() 应返回 NULL,即可使用空代码实现接口。在这种情况下,Wwise 中将不显示任何性能分析信息。
以下代码用于说明一些注意事项,使用代码比文字更能解释清楚。当您想要更改或不沿用 Stream Manager 时,将尤其有用。此代码有意写得非常复杂,它指出了您应该特别注意的一些问题。