Wwise SDK 2024.1.0
|
Wwise 声音引擎插件的创建可以很有创意,但也可能会很复杂。我们强烈建议您仔细阅读以下章节,并结合查看 示例插件 部分提供的示例。您可以参考示例中提供的标准样式和结构来构建插件。
插件可以让您将自定义 DSP 程序插入由声音引擎执行的整体信号处理链中。插件参数既可在设计工具中控制,也可在游戏中通过 RTPC 控制(请参阅 了解 Real-Time Parameter Control (RTPC) )。
每个插件包括 2 个组件:
技巧:为运行时组件创建通用静态库。通过这种方法,游戏和插件用户界面(Wwise 加载的 DLL)都可以链接它。 |
需要实现参数节点接口,以对来自声音引擎 RTPC 管理器或 Wwise 设计工具的更改做出反应,并在执行期间获取当前插件参数。(请参阅 参数节点接口的实现 。)
可集成到声音引擎中的插件主要有 3 类:
插件及其相关参数接口由声音引擎通过插件机制创建。该插件机制要求暴露静态创建函数,这些函数在必要时返回新参数节点和新插件实例。以下代码演示如何可以做到这一点。创建函数必须打包到插件库用户可见的 AK::PluginRegistration 静态实例中。每个插件类别/类型需要一个 AK::PluginRegistration
类。
备注: 根据您要制作的插件类型,设置 AK::PluginRegistration 函数的 AkPluginType 参数。例如,源插件的 AkPluginTypeSource、效果器插件的 AkPluginTypeEffect 。 |
备注: Registration 对象的命名非常重要。AK_STATIC_LINK_PLUGIN(pluginname) 宏将使用它,在 _pluginname_后加上“Registration”。请参见下文的 插件静态注册 。 |
如果您是插件提供者,希望确保您的插件不会与来自其它供应商的插件有相同的 ID,则请联系 support@audiokinetic.com 以获得预留的公司 ID。
各种音频插件全部由 Plug-in Manager 处理,后者可根据 CompanyID 和 PluginID 识别不同的插件类别。插件必须先在 Plug-in Manager 中注册,然后才可在游戏中使用。注册过程将 PluginID 绑定到以参数形式提供的创建函数回调上。
以下例程演示游戏注册插件的方式。如果游戏要使用 Audiokinetic 提供的插件,则也必须先注册插件。
为了方便使用,所有插件还有一个出厂头文件,其中只包含 AK_STATIC_LINK_PLUGIN 宏。为了使插件管理更加方便,按照库名加上“Factory”的方式命名您的出厂头文件。例如,以下代码是与 MyPlugin.lib 相关联的 MyPluginFactory.h 的内容。
使用 MyPlugin 的游戏仅包含 MyPluginFactory 文件,并与 MyPlugin.lib 链接。
备注: 如果您收到符号结尾定义了多重 _linkonceonly 的链接错误,这意味着您在多个 .cpp 文件中加入了 Factory include。每个链接单元(如 DLL、so、dylib、exe 等)中只需加入一次。 |
在游戏中使用插件的方式分为两种:一是通过静态库,二是通过动态库。静态库是必须要发布的。动态库则不强制发布,但强烈建议发布动态库,因为Unity中集成Wwise使用的是动态库。但是Wwise Unreal集成需要使用静态库。
使用静态库来创建动态库极其容易。Audiokinetic 对所有效果器插件都通过静态库创建了动态库,因此您可以在文件夹 \SDK\samples\DynamicLibraries
中找到大量的示例。您必须:
DEFINE_PLUGIN_REGISTER_HOOK
定义符号。EngineDllName
属性,则以 XML 的名称对它命名。 注意,您可以把多个插件组合到一个 DLL 中。简单来说,只需使用以下代码便可将静态库转换为动态库:
要在 Unity 中部署您的插件,请在 Wwise\Deployment\Plugins\[Platform]\DSP 文件夹中放置一份动态库 。位于该文件夹中的所有 DSP 插件应采用优化过的构建配置,这样才可以供发布版的游戏使用。
备注: 在 iOS 中,构建系统阻止使用动态库 。因此在 Unity 中,您必须部署 .a 文件和相应的 Factory.h 文件。其它平台上使用的动态库与在 iOS 中使用的静态库之间的链接通过名称来完成:确保 DLL 名称与库名称(或子字符串)相同,不带“lib”前缀。 例如:
|
备注: 在 Mac 中,Wwise 将只加载 DYLIB 文件。然而,Unity 不将 DYLIB 视为有效的扩展名。因此在创建游戏时,它不会复制和部署这些文件。解决这一问题的方法是,把扩展名重命名为 BUNDLE,即使文件本身不是 BUNDLE。 例如:
|
备注: 在Android中,您需要给动态库加“lib”前缀,比如libMyPlugin.so。 |
Unlike statically linked plug-ins, dynamically linked plug-ins are loaded on demand. The Init bank contains a list of plug-in IDs that the project uses. When the Init bank is being loaded, the Sound Engine searches this list for unrecognized plug-in IDs. An attempt to load the dynamic library that contains the plug-in is made for each of these unrecognized plug-ins.
For the attempt to succeed, the plug-in library file must be placed in a location that the operating system's dynamic library loader searches. Library search paths vary from system to system. On several platforms, the directory containing the game executable is part of the library search paths.
Alternatively, set AkInitSettings::szPluginDLLPath
to the local system path that contains the plug-in library files. This path takes precedence over the operating system's library search paths. Refer to the IntegrationDemo sample code for the recommended AkInitSettings::szPluginDLLPath
setup method for various platforms.
备注: When using a game engine integration, AkInitSettings::szPluginDLLPath is managed automatically. Plug-in library files are automatically copied to the appropriate directory when Wwise is integrated into the game engine project. If you are using Unity, refer to Integrating Wwise into a Unity Project. If you are using Unreal Engine, refer to Integrating Wwise into an Unreal Engine Project. |
您应通过提供的内存分配器接口来执行音频插件中的所有动态内存分配或取消分配。这样可确保 Memory Manager 能够追踪插件占用和释放的所有内存,并将其标记为特定内存类别,同时显示在 Wwise 性能分析器中。
提供了用于重载 new / delete 操作和使用随附的内存分配器调用 malloc() / free() 的宏。这些宏由 IAkPluginMemAlloc.h 提供。
要分配对象,则需使用 AK_PLUGIN_NEW() 宏,并将指针传递到内存分配器接口和期望的对象类型。宏返回指向新分配对象的指针。需要使用相应的 AK_PLUGIN_DELETE() 宏来释放内存。
要分配数组,则要使用 AK_PLUGIN_ALLOC(),后者从 Memory Manager 获取请求的大小(单位:比特)并将空指针返回给分配的内存地址。使用相应的 AK_PLUGIN_FREE() 宏释放分配的内存。以下例程演示如何使用这些宏。
参数节点在本质上集中了参数的读写访问权限。插件参数接口包括以下方法:
此方法应该会创建一份完全相同的参数,并调整必要的内部状态变量,以便随时供新插件使用。例如,当事件创建新播放实例时,就会发生这种情况。此函数必须使用 AK_PLUGIN_NEW() 宏返回新的参数节点。在许多情况下,调用拷贝构造函数就够了(如下面的例程中所示)。如果在参数节点中分配内存,则应执行深度复制。
此函数使用提供的参数块对参数进行初始化。当提供的参数块大小为零(即当设计工具中使用此插件时),AK::IAkPluginParam::Init() 应使用默认值对参数结构进行初始化。
技巧:当参数块有效时,调用 AK::IAkPluginParam::SetParamsBlock() 对参数块进行初始化。 |
此方法使用在 Wwise 中创建 SoundBank 期间通过 AK::Wwise::Plugin::AudioPlugin::GetBankParameters() 存储的参数块一次性地设置全部插件参数。插件的 Wwise 设计工具版本将按照写入 SoundBank 的同一格式读取参数。注意,数据为压缩格式,因此根据某些目标平台所需的数据类型,可能无法对齐变量。使用 AkBankReadHelpers.h 中提供的 READBANKDATA 助手宏来避免这些与特定平台相关的问题。无需担心插件参数的字节顺序问题,因为应用程序已经正确地交换数据的字节。
此方法一次更新一个参数。每当参数值发生变化时都会调用此方法,有的从插件 UI 调用,有的从 RTPC 调用,等等。要更新的参数由类型为 AkPluginParamID 的参数指定,对应于 Wwise XML 插件描述文件中定义的 AudioEnginePropertyID。(请参阅 Wwise 插件 XML 描述文件 了解更多信息)。
技巧:我们建议将 XML 文件中定义的每个 AudioEngineParameterID 绑定到 AkPluginParamsID 类型的常变量。 |
备注: 无论 XML 文件中指定的属性类型,支持 RTPC 的参数被赋于 AkReal32 类型。 |
备注: 若使用了 复杂属性,则须在 AK::IAkPluginParam::SetParam 和 AK::Wwise::Plugin::CustomData::GetPluginData 中处理 AK::IAkPluginParam::ALL_PLUGIN_DATA_ID 。 该 ID 至少要在首次运行插件时使用一次。 |
当终止参数节点时,声音引擎将调用此方法。必须释放使用的任何内存资源,参数节点负责自行析构。
每个插件都有一个相关联的参数节点,插件可以从中获取参数值,相应的更新其 DSP。相关联的参数节点接口在插件初始化时传入,在插件的生命期间将一直保持有效。然后插件可以根据 DSP 处理的需要频繁地从参数节点中查询信息。由于插件与其相关联的参数节点之间为单向关系,因此要响应参数节点对参数值的查询,则要由实现方自主处理(例如使用访问器方法)。
要开发音频插件,必须实现特定函数,以让插件能够在引擎的音频数据流中正常工作。对于源插件,您应该继承 AK::IAkSourcePlugin 接口,对于可将输入缓冲区替换成输出的效果器(例如无需无序访问或改变数据速率),您应该继承 AK::IAkInPlaceEffectPlugin。对于需要实现非就地方法的其它效果器,您应继承 AK::IAkOutOfPlaceEffectPlugin 接口。
A plug-in life cycle always begins with a call to one of the AK::IAkAudioDeviceEffectPlugin::Init(), AK::IAkEffectPlugin::Init(), AK::IAkSinkPluginBase::Init() or AK::IAkSourcePlugin::Init() functions, immediately followed by a call to AK::IAkPlugin::Reset(). 只要插件需要输出更多数据,就会使用新的缓冲区调用 AK::IAkPlugin::Execute()。当不再需要插件时调用 AK::IAkPlugin::Term()。
在终止插件时调用此方法。AK::IAkPlugin::Term() 必须释放插件所使用的所有内存资源,并自行析构插件。
复位方法必须重新初始化插件的状态,使它准备接纳新的无关音频内容。声音引擎管线将在初始化刚结束后和任何对象状态需要复位的时刻立即调用 AK::IAkPlugin::Reset()。一般所有内存分配都要在初始化时执行,但是在调用 AK::IAkPlugin::Reset() 时应清除例如延迟线状态和采样点计数。
当声音引擎需要有关插件的信息时,要使用此插件信息查询机制。在 AkPluginInfo 结构中填写正确的信息,以描述所实现的插件类型(例如源插件或效果器插件)、它的缓冲区使用方案(例如就地使用)和处理模式(例如同步)。
备注: 在所有其它平台上效果器插件应该是同步的。 // 声音引擎查询效果信息。
AKRESULT CAkMyPlugin::GetPluginInfo( AkPluginInfo & out_rPluginInfo )
{
out_rPluginInfo.bIsAsynchronous = false; // 同步插件。
return AK_Success;
}
|
在执行时音频数据缓冲区通过指向 AkAudioBuffer 结构的指针传递给插件。传递给插件的所有音频缓冲区使用固定格式。对于支持软件效果器的平台,音频缓冲区的声道不是交错存取的,所有样本在 (-1.f,1.f) 范围为归一化 32 位浮点,以 48 kHz 采样率运行。
AkAudioBuffer 结构提供可访问交错存取和非交错存取数据的手段。它包含一个字段用来指定每个声道缓冲区中的有效采样帧数(AkAudioBuffer::uValidFrames)以及这些缓冲区可包含的最大采样帧数(由 AkAudioBuffer::MaxFrames() 返回)。
AkAudioBuffer 结构还包含缓冲区的声道掩码,此掩码定义数据中存在的声道。如果您仅需要声道数,则使用 AkAudioBuffer::NumChannels()。
插件可通过 AkAudioBuffer::GetInterleavedData() 访问交错存取数据的缓冲区。只有源插件可访问和输出交错存取数据。为了做到这一点,它们必须在初始化期间正确准备声音引擎(请参阅 AK::IAkSourcePlugin::Init() )。声音引擎将处理的 DSP 实例化,以正确地把数据转换成内建管线格式。
技巧: 如果源插件输出数据本来符合声音引擎的内建格式,您则可以获得更高的性能。 |
插件通过 AkAudioBuffer::GetChannel() 访问单个非交错存取声道。数据类型总是符合声音引擎的内建格式(AkSampleType)。以下例程显示如何获取所有非交错存取声道进行处理。
注意: 插件决不可想当然地认为每个声道的缓冲区在内存中是连续的。 |
声道排序如下:Front Left(左前)、Front Right(右前)、Center(中置)、Rear Left(左后)、Rear Right(右后),和 LFE。低频声道(LFE)总是布置在末尾,这是因为很多 DSP 运算需要单独来处理 LFE。插件可以使用 AkAudioBuffer::HasLFE()
来查询音频缓冲区中是否存在 LFE 声道。它可以通过调用 AkAudioBuffer::GetLFE()
直接访问 LFE 声道。以下代码演示独立处理 LFE 声道的两种不同方式。
如果您要对非 LFE 的声道做特定处理,则需要使用 AkCommonDefs.h 的声道索引定义。 例如,如果您想只处理 5.x 配置的中置声道,则要执行以下操作:
技巧: 注意,声道索引定义与配置是 N.0 还是 N.1 无关。这是因为, LFE 声道始终是最后一个声道,除了源插件(如果声道配置没有LFE,则不应使用AK_IDX_SETUP_N_LFE)。 |
备注: 在7.1的情况下,源插件交错数据的声道顺序是 L-R-C-LFE-BL-BR-SL-SR。 |
插件可使用 AK::IAkGlobalPluginContext::RegisterGlobalCallback()
注册到各个全局声音引擎回调。例如,对于每个音频帧,可能需要通过插件知道的 Singleton(单例)对象调用一次插件类。您还可使用全局挂钩来初始化在声音引擎整个生命期间一直保留的数据结构。
为此,将默认的 AK_IMPLEMENT_PLUGIN_FACTORY 宏替换为您自己的实现,以利用 AK::PluginRegistration 的“RegisterCallback”。下面的代码片段中为此定义了一个静态回调 MyRegisterCallback,并将它传递给 AK::PluginRegistration 对象 MyPluginRegistration。
备注: AK_IMPLEMENT_PLUGIN_FACTORY 宏声明出厂函数和 AK::PluginRegistration 对象,将字符串附加到插件名称后面,名称是作为参数传递给它的。在下例中,AK_IMPLEMENT_PLUGIN_FACTORY 已按照相同的命名规则重新实现了。对于您自己的插件,应将“MyPlugin”替换成插件的实际名称。另外,AK_IMPLEMENT_PLUGIN_FACTORY 的姊妹宏 AK_STATIC_LINK_PLUGIN 遵循相同的命名规则。如果您没采用 AK_IMPLEMENT_PLUGIN_FACTORY 的命名规则,则还需要相应地重新实现 AK_STATIC_LINK_PLUGIN 。 |
注意: 在接收 AkGlobalCallbackLocation::AkGlobalCallbackLocation_Register 时,仅允许从插件注册回调内调用 AK::IAkGlobalPluginContext::RegisterGlobalCallback() ;在接收 AkGlobalCallbackLocation::AkGlobalCallbackLocation_Term 时,仅允许调用 AK::IAkGlobalPluginContext::UnregisterGlobalCallback() 。 确切地说,就是不可从插件实例(如 Init、Execute 等)内调用此函数,以免在并行处理插件时发生死锁。 |
有关更多信息,请参阅以下各节: