目录

Wwise SDK 2018.1.11
加载 SoundBank

在 Wwise 声音引擎中,使用 Event(事件)和 Game Syncs(游戏同步器)控制播放。这些元素引用 SoundBank 中存储的内部声音结构,并最终引用松散的媒体文件。所有这些内容在使用之前必须由声音引擎完成加载。

显式与隐式媒体加载

您可以显式或隐式加载 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;
// 使用 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::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”操作以便使用(AK::SoundEngine::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;
// 使用 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::EVENTS::PLAY_SOUND_02, // 事件的唯一 ID
gameObj // 相关游戏对象 ID
);

获得有关如何使用 AK::SoundEngine::PrepareEventAK::SoundEngine::PrepareGameSyncs 的完整示例,请参阅以下章节:

同步与异步加载

SoundBank 加载是在单独的声音引擎线程中执行的。主要 API 的所有 LoadBank()、PrepareEvent() 和 PrepareGameSyncs() 功能均可通过同步和异步加载方案获取。

同步 SoundBank 加载

同步 AK::SoundEngine::LoadBank() 功能是阻塞功能。这些 API 函数将在加载 SoundBank 完成或发生错误时将返回。

异步 SoundBank 加载

异步 AK::SoundEngine::LoadBank() 功能立即返回,并在完成请求的操作后,以 cookie 作为参数来调用回调函数。异步调用时,必须在回调函数中执行错误处理。

cookie 参数是可选参数,为方便操作而提供。如果您不使用它,只需赋予 NULL 值。声音引擎将不使用此指针;它仅通过回调函数返回。

SoundBank 回调原型

搭配 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 了解传递给回调函数的参数的定义。

从内存或者通过文件 I/O 加载 SoundBank

如前面 标识 SoundBank 中述,您可以自己从文件中加载 SoundBank ,然后为相应的 LoadBank() 重载提供指针和大小,也可以指定 SoundBank 标识符(ID 或字符串,见上文讨论),让声音引擎通过 Stream Manager 加载 SoundBank 文件。

从内存中加载

使用这些 LoadBank() 原型中的一个从内存中加载 SoundBank :

// 同步。
void * in_pInMemoryBankPtr, ///< 指向要加载的内存中 SoundBank 的指针
AkUInt32 in_uInMemoryBankSize, ///< 要加载的内存中 SoundBank 的大小
AkBankID & out_bankID ///< 返回的 SoundBank ID
);
// 异步。
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 内存版的原因。

通过文件 I/O 加载

每当需要读取文件时,声音引擎总是使用 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() 隐式加载的媒体,您必须在声音引擎的初始化设置中指定自定义池(AkInitSettings::uPrepareEventMemoryPoolID)。

Tip: 分配内存池的过程可以在游戏启动时执行,也可以在每次玩家进入新关卡时执行。默认的池分配模型在开发周期的初期就可以使用,一直到 SoundBank 大小被固定。这样,您可以最高效地管理为 SoundBank 分配的内存。

有关使用 SDK 创建和管理内存池的详情,请参阅 内存池

卸载 SoundBank

有许多 UnloadBank() 重载可用于显式卸载 SoundBank :

  • 使用 ID 标识
  • 使用字符串标识(Unicode 或 ANSI)
  • 同步
  • 异步

同样,搭配 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 失败。
// 处理错误……
}

对 SoundBank 做 Prepare 操作

LoadBank()PrepareEvent() 函数外,还可对 SoundBank 做 Prepare 操作。您可以使用以下任一方法对 SoundBank 做 Prepare:

  • AkBankContent_All
  • AkBankContent_StructureOnly

这两种方法使用的是相同的 PrepareBank 机制,只是在实现上有少许不同,但最常用的是 AkBankContent_All。

记住,当使用 PrepareEvent 或 PrepareBank 机制时,您必须提供要加载媒体的有效内存池。这通过把结构 AkInitSettings 的参数 uPrepareEventMemoryPoolID 传递到声音引擎来实现。有关示例,请参阅:方法 4:准备事件

AkBankContent_All

使用 AkBankContent_All 来对 SoundBank 做 Prepare 可以克服 LoadBank() 机制的一些弱点,同时还可以发挥 PrepareEvent() 机制的优势。在使用此方法时, SoundBank 中仍可包含所有内容类型(事件、结构数据和媒体文件),但是此方法不直接加载媒体文件,而是运用类似于 PrepareEvent 的机制把所有媒体加载到内存中。在使用 PrepareBank() 加载媒体时,Wwise 首先查看媒体文件在内存中是否已经存在,然后再加载。这可以避免内存中出现媒体文件重复,从而把内存占用保持在最低水平。

AkBankContent_All 是 PrepareBank() 的默认加载机制,将检查 SoundBank 中需要载入 Prepare 内存池的媒体。如果特定事件的媒体在 SoundBank 中不存在,则稍后可调用 PrepareEvent() 从松散文件中加载它。

AkBankContent_StructureOnly

在搭配 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_AllAkBankContent_StructureOnly ,因为媒体一旦使用 AkBankContent_All 进行加载,卸载时将释放所有内容,包括事件、结构和媒体。

清空库

当您想要重置声音引擎的内容时,调用 AK::SoundEngine::ClearBanks() 函数非常有用。请注意,在调用 SoundEngine::Term() 之前,您不必调用 ClearBanks()ClearBanks 在内部调用 AK::SoundEngine::ClearPreparedEvents

在调用此函数后:

  • 所有声音停止播放。
  • 所有 SoundBank 均会卸载,包括初始化包。
  • 所有状态管理信息均会清空,因为这些信息是初始化包的一部分。

清空已做了 Prepare 操作的事件

无论某个事件已经做了多少次 Prepare,调用 AK::SoundEngine::ClearPreparedEvents() 函数将取消到目前为止已做了 Prepare 操作的所有事件。在调用 AK::SoundEngine::ClearBanks() 时,内部将调用 AK::SoundEngine::ClearPreparedEvents()

复制库内容

各个 SoundBank 只可加载一次。如果您试图第二次显式加载某个库,则将导致 SoundBank 加载错误。

Wwise 声音引擎可让相同的事件、声音结构或媒体存在于两个或两个以上的 SoundBank 中,并同时全部加载。

Note: 对于不支持媒体重定位的 AAC、XMA 和 OpusNX,若某一声音同时用于多个正在播放的音频包,而该音频包已被卸载,则将停止播放该声音。 播放不会过渡到其它有这份声音并且已加载的 SoundBank 中。

在使用 PrepareEvent 时,如果多个不同事件需要加载同一媒体内容,则内存中不会出现重复加载该媒体复制品的情况。 多个事件可引用同一媒体对象,只有当引用同一媒体对象的所有事件都被取消了 Prepare 操作时才会卸载此媒体对象。

由于在使用 LoadBank 时 SoundBank 作为实体加载,因此结合 PrepareEvent 使用 LoadBank 来加载同一媒体内容可能导致媒体重复。

参见