在 Wwise 声音引擎中,使用 Event(事件)和 Game Syncs(游戏同步器)控制播放。这些元素引用 SoundBank 中存储的内部声音结构,并最终引用松散的媒体文件。所有这些内容在使用之前必须由声音引擎完成加载。
您可以显式或隐式加载 SoundBank 。这两种方法(将在下文中详细介绍)提供的选择都暗含优缺点,需要根据特定场景对这些优缺点做出评估。
您可以使用声音引擎 API 的 AK::SoundEngine::LoadBank() 方法之一显式加载 SoundBank 。在加载 SoundBank 后,它里面的所有对象就准备就绪了。
在下例中,将显式加载 SoundBank BANK_01。此 SoundBank 通过 ID 进行标识,ID 在 Wwise_IDs.h 头文件中定义。有关使用字符串(Unicode 或 ANSI)或 ID 标识 SoundBank 的讨论,请参阅 标识 SoundBank 。其中包含事件 PLAY_SOUND_01 及其相关的声音结构和媒体(请参阅 Wwise 帮助,了解有关使用 Wwise 生成 SoundBank 的详情)。加载后,事件会发送给声音引擎。
|
Note: 声音数据存储在为此目的自动创建的内存池中。请参阅 内存占用 一节,了解有关内存占用的详情。 |
#include "Wwise_IDs.h" // ... AkGameObjectID gameObj = 3; AK::SoundEngine::RegisterGameObj( gameObj ); // 使用 ID 同步加载 SoundBank 。 AkBankID returnedBankID; AKRESULT eResult = LoadBank( AK::BANKS::BANK_01, // 要加载的 SoundBank 的标识符。 AK_DEFAULT_POOL_ID, // 内存池 ID(当传入 AK_DEFAULT_POOL_ID 时,数据写入默认声音引擎内存池中)。 returnedBankID // 返回的 SoundBank ID。 ); if( eResult != AK_Success ) { // SoundBank 加载失败。 // 处理错误…… } // 加载成功。 // 根据要求使用 SoundBank …… // 举例而言,如果 SoundBank 中包含事件 PLAY_SOUND_01 及其 // 相关声音结构和媒体。 AK::SoundEngine::PostEvent( AK::EVENTS::PLAY_SOUND_01, // 事件的唯一 ID gameObj // 相关游戏对象 ID );
PrepareEvent/PrepareGameSync API 可以隐式加载 SoundBank 中未包含的媒体。为此,您必须首先通过 LoadBank() 显式加载包含 Event 或 Game Sync 定义的 SoundBank 。这些 SoundBank 通常只含 Event 和结构,不含媒体,因此很轻量。请参阅 Wwise 帮助,了解如何定义没有媒体数据的 SoundBank 。当 SoundBank 中不包含被引用的媒体时,文件系统中必须以松散媒体文件的形式提供该媒体。在加载 Event 和 Game Sync 的定义后,必须对它们做“Prepare”操作以便使用(AKSoundEngine::PrepareEvent() 和 AK::SoundEngine::PrepareGameSyncs())。在对事件或游戏同步器做 Prepare 操作时,声音引擎将从文件系统中隐式提取被引用的媒体文件。可把声音结构包含在不包含事件定义的 SoundBank 中。然后 Event 所在的 SoundBank 中将包含对含有结构数据的 SoundBank 的引用,在 对事件做 Prepare 操作时,被引用 SoundBank 中的结构将与媒体一起隐式加载。包含的所有结构将从链接的 SoundBank 中加载,而非仅从 Event 所引用的 SoundBank 中加载,为此,把 Event 和结构绑定在同一个 SoundBank 中通常更为实用。
在下例中,SoundBank BANK_02 显式加载,它所引用的媒体隐式加载。它包含事件 PLAY_SOUND_02 的定义和相关声音结构。由于它不包含事件相关媒体,因此若不提前 对事件做 Prepare 操作,则该事件的发送将会失败。因此,在成功加载 SoundBank 后,先 对事件做 Prepare 操作,然后再发送。在 对事件做 Prepare 操作时,声音引擎将自动加载成功发送事件所需要的所有媒体。
#include "Wwise_IDs.h" // ... AkGameObjectID gameObj = 3; AK::SoundEngine::RegisterGameObj( gameObj ); // 使用 ID 同步加载 SoundBank 。 // 此 SoundBank 可能只包含您要使用的事件的定义。 AkBankID returnedBankID; AKRESULT eResult = LoadBank( AK::BANKS::BANK_02, // 要加载的 SoundBank 的标识符。 AK_DEFAULT_POOL_ID, // 内存池 ID(在传入 AK_DEFAULT_POOL_ID 时,数据会写入默认的声音引擎内存池)。 returnedBankID // 返回的 SoundBank ID。 ); if( eResult != AK_Success ) { // SoundBank 加载失败。 // 处理错误…… } // 加载成功。事件 PLAY_SOUND_02 的定义及其相关结构已加载。 // 为了发送事件,需要 对事件做 Prepare 操作(在此例中采用同步模式)。 AkUniqueID eventToPrepare = AK::EVENTS::PLAY_SOUND_02; eResult = PrepareEvent( Preparation_Load, // 准备类型:加载 &eventToPrepare, // 事件 ID 的数组。 1 // 数组中的事件 ID 数量。 ); if ( eResult != AK_Success ) { // 事件 Prepare 操作失败。 // 处理错误…… } // 事件的 Prepare 操作成功:声音引擎收集了成功发送事件 // 所需要的全部媒体(以及必要的声音结构), // 方法是在后台加载所有必要的媒体。 // 发送事件。 AK::SoundEngine::PostEvent( AK::EVENTS::PLAY_SOUND_02, // 事件的唯一 ID gameObj // 相关游戏对象 ID );
获得有关如何使用 AK::SoundEngine::PrepareEvent 和 AK::SoundEngine::PrepareGameSyncs 的完整示例,请参阅以下章节:
SoundBank 加载是在单独的声音引擎线程中执行的。主要 API 的所有 LoadBank()、PrepareEvent() 和 PrepareGameSyncs() 功能均可通过同步和异步加载方案获取。
同步 AK::SoundEngine::LoadBank() 功能是阻塞功能。这些 API 函数将在加载 SoundBank 完成或发生错误时将返回。
异步 AK::SoundEngine::LoadBank() 功能立即返回,并在完成请求的操作后,以 cookie 作为参数来调用回调函数。异步调用时,必须在回调函数中执行错误处理。
cookie 参数是可选参数,为方便操作而提供。如果您不使用它,只需赋予 NULL 值。声音引擎将不使用此指针;它仅通过回调函数返回。
搭配 LoadBank()、UnloadBank()、PrepareEvent() 和 PrepareGameSyncs() 的异步版本使用的回调函数必须遵循此原型:
typedef void( *AkBankCallbackFunc )( AkBankID in_bankID, void * in_pInMemoryBankPtr, AKRESULT in_eLoadResult, AkMemPoolId in_memPoolId, void * in_pCookie );
您负责实现回调函数,因此您必须保证它的有效性。
参数 in_bankID 和 in_memPoolId 与接收 PrepareEvent 和 PrepareGameSync 的回调无关,应予以忽略。
|
Note: 内存池 ID 是 AK_DEFAULT_POOL_ID 或者您创建的任何池的 ID。有关 SoundBank 内存分配模式的详情,请参阅 内存占用 。 |
请参阅 AkBankCallbackFunc 了解传递给回调函数的参数的定义。
如前面 标识 SoundBank 中述,您可以自己从文件中加载 SoundBank ,然后为相应的 LoadBank() 重载提供指针和大小,也可以指定 SoundBank 标识符(ID 或字符串,见上文讨论),让声音引擎通过 Stream Manager 加载 SoundBank 文件。
使用这些 LoadBank() 原型中的一个从内存中加载 SoundBank :
// 同步。 AKRESULT LoadBank( void * in_pInMemoryBankPtr, ///< 指向要加载的内存中 SoundBank 的指针 AkUInt32 in_uInMemoryBankSize, ///< 要加载的内存中 SoundBank 的大小 AkBankID & out_bankID ///< 返回的 SoundBank ID ); // 异步。 AKRESULT LoadBank( void * in_pInMemoryBankPtr, ///< 指向要加载的内存中 SoundBank 的指针 AkUInt32 in_uInMemoryBankSize, ///< 要加载的内存中 SoundBank 的大小 AkBankCallbackFunc in_pfnBankCallback, ///< 回调函数 void * in_pCookie, ///< 回调 cookie AkBankID & out_bankID ///< 返回的 SoundBank ID );
声音引擎内不复制内存,因此必须确保在卸载 SoundBank 之前,内存一直保持有效。根据具体平台要求,SoundBank 加载涉及的内存指针上可能要用到一些对齐限制。在所有平台上,内存必须与 AK_BANK_PLATFORM_DATA_ALIGNMENT 字节对齐。某些平台可能有不同的要求,因此您应该检查对应平台的 SDK 文档。
LoadBank() 解析随附指针前几个字节中存储的 SoundBank ID,并返回此 ID。保存好 SoundBank ID,以备稍后卸载 SoundBank 。
|
Note: 如果您选择自己执行文件 I/O,并向声音引擎馈送指针,则必须使用显式 SoundBank 加载。隐式 SoundBank 加载可能无法预测。按照 PrepareXXXX 命令,您无法确定要加载的 SoundBank 数量以及这些 SoundBank 的内存需求。这就是没有 PrepareEvent 和 PrepareGameSyncs 内存版的原因。 |
每当需要读取文件时,声音引擎总是使用 Stream Manager。请参阅 标识 SoundBank 了解有关 SoundBank 标识与 I/O 的讨论。
您可以使用 AK::SoundEngine::SetBankLoadIOSettings() 功能,在 Stream Manager 方面微调声音引擎 SoundBank 加载程序的行为。有关 I/O 的详情,请参阅 流播放/流管理器 一节。
声音引擎解析 SoundBank 的元数据,并在声音引擎的默认池中创建它的对象。
在涉及 I/O 的显式 LoadBank() 函数中,媒体从磁盘读取,并复制到内存池(这些函数的 in_poolID 参数)。如果您传输 AK_DEFAULT_POOL_ID,内部则会为此而创建一个内存池。对于通过 PrepareEvent() 和 PrepareGameSyncs() 隐式加载的媒体,您必须在声音引擎的初始化设置中指定自定义池(AkInitSettingsuPrepareEventMemoryPoolID)。
|
Tip: 分配内存池的过程可以在游戏启动时执行,也可以在每次玩家进入新关卡时执行。默认的池分配模型在开发周期的初期就可以使用,一直到 SoundBank 大小被固定。这样,您可以最高效地管理为 SoundBank 分配的内存。 |
有关使用 SDK 创建和管理内存池的详情,请参阅 内存池 。
有许多 UnloadBank() 重载可用于显式卸载 SoundBank :
同样,搭配 Preparation_Unload 标志使用 PrepareEvent() 函数来减少与这些事件相关的结构和媒体的引用计数。当隐式加载的 SoundBank 中所有对象的引用计数减少到 0 时,它会自动卸载。
另外,要搭配 Preparation_Unload 标志使用 PrepareGameSyncs() 函数来卸载采用了 Prepare 操作的事件所加载的媒体。只有当选择了指定游戏同步器时,这些事件才会播放。请注意,游戏同步器没有引用计数,使用 Preparation_Unload 标志调用它一次将立即卸载未被引用的媒体。
AK::SoundEngine::UnloadBank() 函数返回加载 SoundBank 所在内存池的 ID。如果在 LoadBank 调用中未指定内存池或者内存池不存在,则将采用的内存池 ID 是 AK_DEFAULT_POOL_ID。
|
Note: 如果在卸载 SoundBank 时其所引用的声音正在播放,并且 SoundBank 中包含声音的声音结构,则声音将停止播放。如果 SoundBank 中只包含媒体,但其它已加载的 SoundBank 中有媒体和声音结构,则声音有可能会停止。这取决于媒体是使用该 SoundBank 中的数据播放还是使用其它 SoundBank 中的数据播放。请参阅 复制库内容 。 |
|
Note: 如果有事件更改过此声音的参数(例如 SetVolume 事件),则此更改信息将被移除。如果参数已被 RTPC、State 或 Swtich 更改过,则参数将保留在内存中,并将在重新加载 SoundBank 时自动应用。 |
以下代码使用 Unicode 字符串标识符和默认 SoundBank 内存分配方法同步加载和卸载 SoundBank 。它通过 PrepareEvent() 隐式加载和卸载 SoundBank 。
AkBankID bankID; AKRESULT eResult = AK::SoundEngine::LoadBank( L"Bank1.bnk", AK_DEFAULT_POOL_ID, bankID ); if( eResult != AK_Success ) { // 加载 SoundBank 失败。 // 处理错误…… } // SoundBank 加载成功。 // 对事件做 Prepare 操作。 const char * pszEvent = "Event1"; eResult = PrepareEvent( Preparation_Load, &pszEvent, 1 ); if( eResult != AK_Success ) { // 准备事件失败。 // 处理错误…… } // 使用事件和 SoundBank 数据…… // 然后解除对事件做的 Prepare 操作。 eResult = PrepareEvent( Preparation_Unload, &pszEvent, 1 ); if( eResult != AK_Success ) { // 解除事件的 Prepare 操作失败。 // 处理错误…… } // 卸载 SoundBank 。 // 注:SoundBank 使用 LoadBank() 返回的 SoundBank ID 卸载。此调用 // AK::SoundEngine::UnloadBank( L"Bank1.bnk", NULL ); // 也可行:声音引擎在内部把“Bank1.bnk”字符串转换成 SoundBank ID。 eResult = AK::SoundEngine::UnloadBank( bankID, NULL ); if( eResult != AK_Success ) { // 卸载 SoundBank 失败。 // 处理错误…… }
除 LoadBank() 和 PrepareEvent() 函数外,还可对 SoundBank 做 Prepare 操作。您可以使用以下任一方法对 SoundBank 做 Prepare:
这两种方法使用的是相同的 PrepareBank 机制,只是在实现上有少许不同,但最常用的是 AkBankContent_All。
记住,当使用 PrepareEvent 或 PrepareBank 机制时,您必须提供要加载媒体的有效内存池。这通过把结构 AkInitSettings 的参数 uPrepareEventMemoryPoolID 传递到声音引擎来实现。有关示例,请参阅:方法 4:准备事件 。
使用 AkBankContent_All
来对 SoundBank 做 Prepare 可以克服 LoadBank() 机制的一些弱点,同时还可以发挥 PrepareEvent() 机制的优势。在使用此方法时, SoundBank 中仍可包含所有内容类型(事件、结构数据和媒体文件),但是此方法不直接加载媒体文件,而是运用类似于 PrepareEvent 的机制把所有媒体加载到内存中。在使用 PrepareBank() 加载媒体时,Wwise 首先查看媒体文件在内存中是否已经存在,然后再加载。这可以避免内存中出现媒体文件重复,从而把内存占用保持在最低水平。
AkBankContent_All 是 PrepareBank() 的默认加载机制,将检查 SoundBank 中需要载入 Prepare 内存池的媒体。如果特定事件的媒体在 SoundBank 中不存在,则稍后可调用 PrepareEvent() 从松散文件中加载它。
在搭配 AkBankContent_StructureOnly
使用 PrepareBank() 时,会从 SoundBank 中加载事件和结构元数据,但会忽略 SoundBank 中包含的媒体。由于 PrepareEvent() 必须把媒体当作磁盘中的松散文件进行访问,并且无法读取包含在 SoundBank 中的文件,因此只有当您打算稍后使用其它加载机制加载 SoundBank 时,AkBankContent_StructureOnly
才有用。在使用 PrepareEvent() 分别加载媒体的大多数场景中,媒体不应包含在 SoundBank 中,AkBankContent_StructureOnly
标志将产生与 AkBankContent_All
相同的结果。
AkBankContent_StructureOnly
标志可能有用的一个场合是实现多个加载的配置。游戏可有使用 PrepareEvent() 按需加载松散文件的“工具模式”,以及使用 LoadBank() 整体加载同一 SoundBank 的“游戏模式”。
如果需要,可通过 API 同步或异步调用 PrepareBank()。然而,建议不要对同一 SoundBank 同时使用 AkBankContent_All
和AkBankContent_StructureOnly
,因为媒体一旦使用 AkBankContent_All
进行加载,卸载时将释放所有内容,包括事件、结构和媒体。
当您想要重置声音引擎的内容时,调用 AK::SoundEngine::ClearBanks()
函数非常有用。请注意,在调用 SoundEngine::Term()
之前,您不必调用 ClearBanks()
。ClearBanks
在内部调用 AK::SoundEngine::ClearPreparedEvents
。
在调用此函数后:
无论某个事件已经做了多少次 Prepare,调用 AK::SoundEngine::ClearPreparedEvents() 函数将取消到目前为止已做了 Prepare 操作的所有事件。在调用 AK::SoundEngine::ClearBanks()
时,内部将调用 AK::SoundEngine::ClearPreparedEvents()
。
各个 SoundBank 只可加载一次。如果您试图第二次显式加载某个库,则将导致 SoundBank 加载错误。
Wwise 声音引擎可让相同的事件、声音结构或媒体存在于两个或两个以上的 SoundBank 中,并同时全部加载。
|
Note: 对于不支持媒体重定位的 AAC 和 Opus,若某一声音同时用于多个正在播放的音频包,而该音频包已被卸载,则将停止播放该声音。 播放不会过渡到其它有这份声音并且已加载的 SoundBank 中。 |
在使用 PrepareEvent
时,如果多个不同事件需要加载同一媒体内容,则内存中不会出现重复加载该媒体复制品的情况。 多个事件可引用同一媒体对象,只有当引用同一媒体对象的所有事件都被取消了 Prepare 操作时才会卸载此媒体对象。
由于在使用 LoadBank
时 SoundBank 作为实体加载,因此结合 PrepareEvent
使用 LoadBank
来加载同一媒体内容可能导致媒体重复。