版本

menu_open
Wwise SDK 2024.1.1
创建声音引擎效果器插件

实现效果器插件接口

Effect 效果器插件接收已有的声音作为输入音频数据,将 DSP 算法作用于这些输入数据。编写效果器插件的工作主要包括实现 AK::IAkInPlaceEffectPlugin 或者 AK::IAkOutOfPlaceEffectPlugin 接口中的一个。在此只介绍与这些接口相关的函数。请参阅 创建声音引擎插件 了解与其他插件类型(AK::IAkPlugin 接口)共享的接口组件的信息。另请参阅随附的 AkDelay 插件了解详情( 示例 )。

AK::IAkEffectPlugin::Init()

此方法为效果器插件用于处理数据做准备,分配内存并设置初始条件。

The plug-in is passed in a pointer to a memory allocator interface (AK::IAkPluginMemAlloc). We recommend that you allocate all dynamic memory through this interface using the provided memory allocation macros (refer to 在音频插件中分配/取消分配内存) so that Wwise can track the memory, and so that any underlying memory hooks in the game engine can use it. For the most common memory allocation needs, specifically allocation at initialization and release at termination, the plug-in does not need to retain a pointer to the allocator. It is also provided to the plug-in on termination.

The AK::IAkEffectPluginContext interface can access the global context through AK::IAkPluginContextBase::GlobalContext().

The plug-in also receives a pointer to its associated parameter node interface (AK::IAkPluginParam). Most plug-ins keep a reference to the associated parameter node to be able to retrieve parameters at runtime. 请参阅 参数节点与插件之间的通信。 了解更多详情。

All of these interfaces remain valid throughout the plug-in's lifespan so it is safe to keep an internal reference to them when necessary.

Effect plug-ins also receive the input/output audio format (which stays the same during the lifespan of the plug-in) to be able to allocate memory and set up processing for a given channel configuration.

备注: AK::IAkEffectPlugin::Init() is called every time the effect is instantiated, which happens when a voice starts playing or a mixing bus is instantiated. Because other sounds are typically playing already, this must occur within a reasonable amount of time. If you need to initialize large common/global data structures, do so when registering the plug-in library. 请参阅 在插件中使用全局声音引擎回调 了解更多详情。

AK::IAkPluginEffect::Execute()

效果器插件可选择实现以下 2 个接口之一:AK::IAkInPlaceEffectPlugin或AK::IAkOutOfPlaceEffectPlugin。一般大多数效果器应该采用原地效果(它们对输入和输出数据使用相同的音频缓冲区)。然而,当数据流有变时(例如 time-stretching 时间伸缩效果器),则必须实现非原地接口。

注意: 具有不同输入/输出声道配置的非原地效果器可插入 Master-Mixer hierarchy(主混音器层级结构)中。然而,无法将变速率效果器放置在混音总线上。具有不同输入/输出缓冲长度的效果器只可插入 Actor-Mixer hierarchy(用作源效果器)。

IAkInPlaceEffectPlugin::Execute

此方法原地执行给定音频缓冲区上的插件信号处理算法(请参阅 访问使用 AkAudioBuffer 结构的数据 了解详情)。此结构向插件提供有关输入采样点中有多少点有效(AkAudioBuffer::uValidFrames)和缓冲区可容纳的最大音频采样帧数(AkAudioBuffer::MaxFrames() method)的信息。AkAudioBuffer::eState结构成员向插件指示"这是不是最后一次执行":AK_NoMoreData 表示"是",AK_DataReady 表示"不是"。

当虚声部从已用时间播放(Play from Elapsed Time)时,AK::IAkInPlaceEffectPlugin::TimeSkip() 将替代 Execute(),以便插件能够在需要的情况下保持更新它们的内部状态。

IAkOutOfPlaceEffectPlugin::Execute

此方法对非原地算法执行插件的信号处理。使用两个 AkAudioBuffer 结构,一个用于输入缓冲区,另一个用于输出缓冲区。管线使用输出音频缓冲区的 eState 来确定效果器的当前状态。只有当效果器在以下两种情况之一下才需要返回:一是处理完输入缓冲区时(会返回 AK_DataNeeded,以便稍后处理更多的数据),二是在填满整个输出缓冲区时(返回 AK_DataReady)。在非原地效果中还可实现效果器尾音,方法是将收到的 AK_NoMoreData 更改为 AK_DataReady,直至效果器排空其内部状态(此时应返回 AK_NoMoreData)。

直接处理完整个输入缓冲区,管线才会释放输入缓冲区。因此,一定要使用 in_uInOffset 编置参数开始读取上次 Execute() 调用停止时未读完的数据。下面示例介绍如何实现这一点。

void CAkSimpleUpsampler::Execute(
AkAudioBuffer * io_pInBuffer,
AkUInt32 in_uInOffset,
AkAudioBuffer * io_pOutBuffer )
{
assert( io_pInBuffer->NumChannels() == io_pOutBuffer->NumChannels() );
const AkUInt32 uNumChannels = io_pInBuffer->NumChannels();
AkUInt32 uFramesConsumed; // 跟踪处理了输入数据中的多少数据
AkUInt32 uFramesProduced; // 跟踪在输出缓冲区中产生了多少数据
for ( AkUInt32 i = 0; i < uNumChannels; i++ )
{
AkReal32 * AK_RESTRICT pInBuf = (AkReal32 * AK_RESTRICT) io_pInBuffer->GetChannel( i ) + in_uInOffset;
AkReal32 * AK_RESTRICT pfOutBuf = (AkReal32 * AK_RESTRICT) io_pOutBuffer->GetChannel( i ) + io_pOutBuffer->uValidFrames;
uFramesConsumed = 0; // 针对各个声道进行复位
uFramesProduced = 0;
while ( (uFramesConsumed < io_pInBuffer->uValidFrames) && (uFramesProduced < io_pOutBuffer->MaxFrames()) )
{
// 执行一些处理,以不同的速率处理输入并产生输出(例如时间伸缩效果器或重采样)
*pfOutBuf++ = *pInBuf;
*pfOutBuf++ = *pInBuf++;
uFramesConsumed++;
uFramesProduced += 2;
}
}
// 更新 AkAudioBuffer 结构以继续进行处理
io_pInBuffer->uValidFrames -= uFramesConsumed;
io_pOutBuffer->uValidFrames += uFramesProduced;
if ( io_pInBuffer->eState == AK_NoMoreData && io_pInBuffer->uValidFrames == 0 )
io_pOutBuffer->eState = AK_NoMoreData; // 输入全部处理完毕,没有更多可输出,效果器已执行完毕
else if ( io_pOutBuffer->uValidFrames == io_pOutBuffer->MaxFrames() )
io_pOutBuffer->eState = AK_DataReady; // 整个音频缓冲区准备就绪
else
io_pOutBuffer->eState = AK_DataNeeded; // 我们需要更多数据来继续处理
}

当虚声部采用 Play from Elapsed Time 时,AK::IAkOutOfPlaceEffectPlugin::TimeSkip() 将替代 Execute(),以便插件能够在需要的情况下能一直更新其内部状态。然后此函数负责告诉管线,产生给定数量的输出帧通常需要处理多少输入采样点。

Implementing 效果器插件尾音

某些效果器具有内部状态,在输入完成播放后,必须输出该内部状态,以便正确进行衰减,最典型的是带延时线(delay line)的效果器。效果器 API 使得即使在没有任何有效输入数据的情况下也可继续执行。当 AkAudioBuffer 结构的 eState 标志变成 AK_NoMoreData 时,在完成当前执行后,管线不会再将有效的输入采样帧送到插件。然后插件可以自由在缓冲区中写入新帧(最大帧数为 MaxFrames() 返回的值),以便在完成输入信号后清空延时线。应始终告诉音频管线已经输出了多少帧,方法是正确更新 uValidFrames 字段。如果需要再次调用插件 Execute() 函数来完成效果器尾音刷新,则应将 eState 成员应为 AK_DataReady。只有当效果器为 eState 字段设置了 AK_NoMoreData 时,管线才会停止调用插件 Execute()。

处理尾音的最简单方法是使用 SDK 中提供的 AkFXTailHandler 服务类。由于一份 AkFXTailHandler 用作插件的类成员,因此我们在原位效果器中唯一需要做的是调用 AkFXTailHandler::HandleTail(),并将它和输出完成时要输出的音频样本总数(根据参数的不同,各个执行之间存在差异)传输给 AkAudioBuffer。请参阅 AkDelay 插件源代码了解详情(示例 )。

备注: 有关执行效果器插件的重要说明

  • 在 Wwise 中使用插件时,无论参数是否与 RTPC 关联,参数变化都会发送至参数节点。如果使用 Wwise 时需要,这可以让插件支持在运行时更改非 RTPC 参数的值。如果不希望插件支持此功能,应在初始化时复制这些参数值,确保它们在插件的存续期间保持不变。
  • 插件应处理多个声道配置(如果可以插入到总线上,至少有单声道、立体声和 5.1),或者在初始化时返回 AK_UnsupportedChannelConfig。

旁通

在 Wwise 或游戏中可通过各种机制(例如 UI、事件和 RTPC)旁通插件。在这些情况下,不会调用插件 Execute() 函数。当解除旁通的时候,插件继续运行并再次调用 Execute() 函数时,插件将重新启动它的处理工作。旁通时将调用插件的 Reset() 函数,以清除延时线和其他状态信息,从而在最终解除旁通时能够有一个全新的开始。请参阅 AK::IAkPlugin::Reset() 了解详情。

注意: 运行时旁通和解除旁通插件可能导致信号中断,具体取决于插件和被处理的声音素材。

有关更多信息,请参阅以下各节:

声音引擎端的监控

“监控”是指在播放期间察看音频插件状态的流程。

在通过通信模块连接设计工具时,允许声音引擎插件实例将状态作为任意大小的数据缓冲区来传给插件的设计工具端。 此数据既可以是执行过程中计算得出的性能指标,也可以是在不同处理阶段测得的信号电平。

本节主要介绍声音引擎端的监控。 有关“接收”和“反序列化”流程的详细信息,请参阅 设计工具端的监控 章节。

发送监控数据

“发送”由插件的声音引擎部分完成。以下为此流程的步骤:

  • 序列化:因为监控数据是作为任意大小的缓冲区来提供的,所以插件的声音引擎部分必须将所要发送的数据序列化,而对应的设计工具端则须在接收时进行反序列化处理。序列化由插件创建者自己实现,相关数据对 Wwise 来说是不透明的。 确保所用数据类型的大小对各个平台来说是相互统一的。就拿 long 来说,其大小对 64 位平台来说就各不相同。有些使用 4 字节 (LLP64),有些则使用 8 字节 (LP64)。另外还要注意,若各个平台具有不同的对齐要求,则可能会因封装策略而导致 struct 布局互不相同。您可以使用 pack pragma 来指定自定义封装对齐值。
  • 发送:一旦将监控数据序列化为缓冲区,声音引擎插件便会使用其插件上下文实例提供的 AK::IAkPluginContextBase::PostMonitorData() 函数,来通过性能分析器通信通道发送数据。此函数应在 Execute 函数之内调用。
备注: 因为监控数据采用异步形式发送,所以 AK::IAkPluginContextBase::PostMonitorData() 会复制声音引擎插件提供的缓冲区。也就是说,缓冲区可安全地分配到堆栈上。在调用 PostMonitorData 后,还可将其释放。

有时可能无法发送监控数据:仅在 Wwise 设计工具执行性能分析时可使用监控数据。若设计工具的性能分析处于非活跃状态或确实无法对游戏实施性能分析,则没有必要准备和发送该数据。我们可以通过以下两种方式来避免这种不必要的资源浪费:

  • 运行时检查:插件上下文提供 AK::IAkPluginContextBase::CanPostMonitorData() 函数。若性能分析处于非活跃状态或不支持发送监控数据,则该函数将会返回 false。在调用 AK::IAkPluginContextBase::PostMonitorData() 之前,一定要使用上述函数来检查是否支持发送监控数据。
  • 编译时检查:在 Release 版本中,一般不会初始化通信模块,所以也就无法实施性能分析。您可以通过对 AK_OPTIMIZED 进行预处理器检查,来从编译中移除用来准备和发送监控数据的代码段。对于 Release 版本,会在使用 Wwise Plug-in 开发工具 wp.py 时默认定义 AK_OPTIMIZED

以下代码示例展示了如何序列化并发送监控数据:

void MyPlugin::Execute( AkAudioBuffer * io_pBuffer )
{
const AkUInt32 uNumChannels = io_pBuffer->NumChannels();
// Algorithm tracks signal peaks for all uNumChannels channels inside the following array
float fChannelPeaks[MAX_NUM_CHANNELS];
...
#ifndef AK_OPTIMIZED // Compile-time check
if ( m_pContext->CanPostMonitorData() ) // Runtime check, m_pContext is cached from MyPlugin::Init()
{
// == Serialization
// Compute the buffer size and allocate a buffer
unsigned int uMonitorDataSize = sizeof(AkUInt32) * uNumChannels*sizeof(float);
char * pMonitorData = (char *) AkAlloca( uMonitorDataSize );
// Fill the monitoring data buffer
*((AkUInt32 *) pMonitorData ) = uNumChannels;
memcpy( pMonitorData + sizeof(AkUInt32), fChannelPeaks, uNumChannels*sizeof(float) );
// == Sending
m_pContext->PostMonitorData( pMonitorData, uMonitorDataSize );
// If the buffer was allocated using \c AK::IAkPluginMemAlloc, free it here
// When using AkAlloca, nothing to do: the memory is freed automatically when the function returns
}
#endif
...
}

对象处理器由音频插件衍生而来,其可在 Execute 函数中以显式方式处理一组不同的音频信号(各自对应有单独的对象)。Execute 会接收拥有一系列 AkAudioBuffer 实例的 Ak3DAudioObjects 封装器对象(每个对象对应一个实例),而非单个 AkAudioBuffer 实例。

因为一个对象处理器插件就要处理一组音频缓冲区,所以此插件发送的监控数据必须囊括与其处理的各个对象相关的所有监控数据。最简单的方式是发送对象的数量,并将与各个信号相关的监控数据序列化为数组。

有关对象处理器的详细信息,请参阅 创建声音引擎对象处理器插件 章节。

AkSampleType * GetChannel(AkUInt32 in_uIndex)
Definition: AkCommonDefs.h:432
@ AK_DataReady
The provider has available data.
Definition: AkTypes.h:162
AkForceInline AkUInt32 NumChannels() const
Get the number of channels.
Definition: AkCommonDefs.h:348
@ AK_NoMoreData
No more data is available from the source.
Definition: AkTypes.h:147
float AkReal32
32-bit floating point
AKRESULT eState
Execution status
Definition: AkCommonDefs.h:507
AkUInt16 uValidFrames
Number of valid sample frames in the audio buffer
Definition: AkCommonDefs.h:513
#define AkAlloca(_size_)
Stack allocations.
@ AK_DataNeeded
The consumer needs more.
Definition: AkTypes.h:160
uint32_t AkUInt32
Unsigned 32-bit integer
#define AK_RESTRICT
Refers to the __restrict compilation flag available on some platforms
Definition: AkTypes.h:45
AkForceInline AkUInt16 MaxFrames() const
Definition: AkCommonDefs.h:500

此页面对您是否有帮助?

需要技术支持?

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

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

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

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

开始 Wwise 之旅