目录

Wwise SDK 2018.1.11
如何创建 Wwise 声音引擎效果器插件

效果器插件接口实现

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

AK::IAkEffectPlugin::Init()

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

此插件通过指针传递给内存分配器接口(AK::IAkPluginMemAlloc)。您应该通过此接口来执行所有动态内存分配,并使用随附的内存分配宏(请参阅 在音频插件中分配/取消分配内存 )。对于最常见的内存分配需求,即在初始化时分配内存和在终止时释放内存的情况下,插件将不需要保留指向分配器的指针。在终止时也会将此指针提供给插件。

AK::IAkEffectPluginContext 接口可用于获取信息,例如旁通掉状态或与效果器插件工作环境相关的其他信息。该接口还可以通过 AK::IAkPluginContextBase::GlobalContext() 访问全局上下文。

插件还接收指向其相关参数节点接口(AK::IAkPluginParam)的指针。大多数插件希望保留对相关参数节点的引用,以便能够在运行时获取参数。请参阅 参数节点与插件之间的通信。 了解更多详情。

所有这些接口将在插件的生命周期内有效,因此必要时在内部引用它们是安全的。

效果器插件还接收输入/输出音频格式(此格式在插件寿命期间保持不变),以便能够为给定的声道配置分配内存和设置处理。

Note: 每次效果器实例化时都会调用 AK::IAkEffectPlugin::Init(),当声部开始播放或者混音总线实例化时,就会发生效果器实例化。典型情况下,其他声音已经在播放,因此实例化需要在合理的时间段内进行。如果您需要初始化通用/全局数据结构,那么在注册插件库时就应该这样做。请参阅 在插件中使用全局声音引擎回调 了解更多详情。

AK::IAkPluginEffect::Execute()

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

Caution: 具有不同输入/输出声道配置的非原地效果器可插入 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() 了解详情。

Caution: 运行时旁通和解除旁通插件可能导致信号中断,具体取决于插件和被处理的声音素材。
AKRESULT CAkGain::Init( AK::IAkPluginMemAlloc * in_pAllocator, // 内存分配器接口。
AK::IAkEffectPluginContext * in_pFXCtx, // FX 环境。
AK::IAkPluginParam * in_pParams, // 效果器参数。
AkAudioFormat & in_rFormat // 必需的音频输入格式。
)
{
if ( in_pFXCtx->IsSendModeEffect() )
// 环境(已发送)环境中使用的效果器……
else
// DSP 链中插入的效果器……
}

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

发布 Wwise 插件的监控数据

在用于监控的时候,插件可能需要将信息发回到 Wwise。常见例子有发送有关音频信号的信息(如 VU 电平表)、运行时信息比如内存占用等等。

在通过性能分析系统异步发送数据前,效果器应该首先确定是否可通过调用 AK::IAkEffectPluginContext::CanPostMonitorData() 将数据发送到此效果器插件的对应 UI。 要做到这点,您需要将指针缓存到插件执行环境(execution context)中,该执行环境会在在效果器初始化时交给插件。 注意,只有插件位于总线上时才可发送数据,因为在这种情况下,它与其效果器设置视图呈一对一的关系。

如果数据可以发送并且构建目标版本可与 Wwise 通信(即非 Release 版本),则您可以发布任何大小的数据块。您可以通过 AK::IAkEffectPluginContext::PostMonitorData() 来按照您的喜好组织数据块。监控数据一旦发布,您就可以安心地放弃插件中的数据块了。

void MyPlugin::Execute( AkAudioBuffer * io_pBuffer )
{
// 算法跟踪以下浮点数组 fChannelPeaks[MAX_NUM_CHANNELS] 中所有 m_uNumChannels 个声道的信号峰值;
...
#ifndef AK_OPTIMIZED
if ( m_pCtx->CanPostMonitorData() )
{
unsigned int uMonitorDataSize = sizeof( unsigned int ) * m_uNumChannels*sizeof(float);
char * pMonitorData = (char *) AkAlloca( uMonitorDataSize );
*((unsigned int *) pMonitorData ) = m_uNumChannels;
memcpy( pMonitorData+sizeof(unsigned int), fChannelPeaks, m_uNumChannels*sizeof(float) );
m_pCtx->PostMonitorData( pMonitorData, uMonitorDataSize );
// 现在可释放 pMonitorData
}
#endif
...
}