Wwise SDK 2022.1.17
|
警告: 对象处理器是效果器插件的超集,可实现效果器插件的所有功能,还可提供其所不具备的功能。不过,编写起来比较麻烦一些。若不需要同时处理多个 Audio Object,并且无需知道处理的对象而只需了解其音频信号,则应编写效果器插件而非对象处理器(参见 实现效果器插件接口 章节)。 |
对象处理器与效果器插件(参见 实现效果器插件接口 章节)类似,两者都是插入到 Wwise 对象所对应 Effect 选项卡的某个插槽中。不过在结合使用 Audio Object 时,其与效果器插件有明显的区别。从本质上来说,效果器插件无法感知 Audio Object (AkAudioObject),而只能处理音频信号;对象处理器则可感知通过总线发送的所有 Audio Object,进而对其进行统一处理并访问相应的元数据。
效果器插件接收单个 AkAudioBuffer,对象处理器则接收 AkAudioObject 实例。该实例提供:
下节对这些概念进行了阐释。
Audio Object 携带有音频信号,单声道或多声道都可以。在 Wwise 中,可通过将总线配置设为 Audio Objects 来轻松查看 Audio Object。这些总线称为 Audio Object 总线。Audio Object 总线会汇集 Audio Object 并保留其元数据,而非将其输入混音到单个缓冲区中(无论多声道与否)。Audio Object 总线可灵活地支持不同数量的 Audio Object,非 Audio Object 总线在任意时刻都只支持一个 Audio Object。
Audio Object 的元数据主要由定位信息及一系列自定义元数据构成,其本身就是插件并可由对象处理器插件使用。有关更多详细信息,请参阅 Audio Object 元数据 章节。
在添加到 Audio Object 总线上时,有多少个 Audio Object,效果器插件就会被实例化多少次。各个实例的生命周期与其被指派到的 Audio Object 的生命周期对应。事实上,单个实例不可重复用来轮流处理各个 Audio Object。因为效果器可能需要保持在某个状态,来确保下一帧音频的连续性。
另一方面,无论有多少个 Audio Object,对象处理器都只会被实例化一次。它会一并处理所有这些对象。
对象处理器会依据是否实施原地处理来决定实现 AK::IAkInPlaceObjectPlugin 还是 AK::IAkOutOfPlaceObjectPlugin。插件通过 AK::IAkPlugin::GetPluginInfo 将 AkPluginInfo::bCanProcessObjects 设置重置为 true 来声明其为对象处理器,并通过相应地设置 AkPluginInfo::bIsInPlace 来声明是否实施原地处理。
警告: 非原地对象处理器仅可添加到总线上。跟总线上的效果器插件一样,其无法改变数据的使用和生成速率(AkPluginInfo::bCanChangeRate 不可为 true)。不过,可更改输出对象及其声道配置。 |
此处仅介绍与这些接口相关的函数。如需了解与其他插件类型共用的接口组件(AK::IAkPlugin 接口),请参阅 创建声音引擎插件 章节;如需了解与效果器插件共用的功能(如初始化、旁通和监控),请参阅 实现效果器插件接口 章节。
原地对象处理器在 AK::IAkInPlaceObjectPlugin::Execute 中接收一组 Audio Object 的缓冲区和元数据(分别对应 AkAudioBuffer 和 AkAudioObject)。它们能够读取并修改所有 Audio Object 的音频信号和元数据,但无法创建/移除 Audio Object 或更改其声道配置。
注意,在将原地对象处理器作为效果器插入到 Actor-Mixer Hierarchy 或 Interactive Music Hierarchy 中的对象上时,与插入到总线上不同的是,在 Execute 期间会将 Object Metadata 视为无效,且不会保留任何对 Metadata 所作的修改。对象处理器可通过判断 Object Key 是否等于 AK_INVALID_AUDIO_OBJECT_ID 来对此予以确认,并决定是要妥善处理这一情况还是直接标记错误。
Compressor 就属于原地对象处理器。它本身需要是对象处理器,因为其算法依赖于即时感知所有 Audio Object 的音频信号。不过,它不会修改 Audio Object 列表。它只会修改通过对象发送的音频信号。
Compressor 当然能够处理单个对象。换句话说,它可以应用到传统的基于声道的总线上。因此,我们使用其取代了原有的效果器插件实现。
跟效果器插件一样,原地对象处理器可通过将 AkAudioBuffer::eState 字段由 AK_NoMoreData 改为 AK_DataReady 来按照所需帧数处理尾音。有关更多详细信息,请参阅 Implementing 效果器插件尾音 章节。不过要注意,对象处理器需要单独处理各个 Audio Object 的尾音。因此,其需要追踪每个对象。
注意: 不要存储传给 Execute 的 Audio Object 地址,因为每一帧的地址都可能是不一样的。最好使用 AkAudioObject::key 字段来识别 Audio Object。 |
非原地对象处理器在输入端和输出端管理两组不同的 Audio Object。输入端 Audio Object 依托于主机总线,而输出端 Audio Object 由插件以显式方式创建(选用以下章节所述的两种方法)。在每一帧,都会通过 AK::IAkOutOfPlaceObjectPlugin::Execute 将所有这些对象传给插件。
输出端 Audio Object 的声道配置由插件决定。
从本质上来说,非 Audio Object 总线(或称为单一对象总线)算是一种特殊的 Audio Object 总线。只不过,它仅支持一个 Audio Object。对象处理器可以不加区分地接收和生成任意数量的 Audio Object,因而能让单一对象总线根据情况输出不同数量的 Audio Object。反过来,也能让 Audio Object 输出单个 Audio Object,就像传统的混音总线一样。
备注: 在插入到非 Audio Object 总线上时,对象处理器在 AK::IAkEffectPlugin::Init 中接收总线的实际声道配置。因为对象处理器是效果器插件的超集,所以应尽量确保它们能够流畅地工作,无论其是插在 Audio Object 总线上还是非 Audio Object 总线上。也就是说,除非用户错误地将其插入到非 Audio Object 总线上。比如,用户不应将 Software Binauralizer 插件插入到非 Audio Object 总线上,因为下混后的音频不会携带任何有用的定位信息。在这种情况下,最好告知用户其可能执行了错误的操作。 |
声音引擎会在每帧的开头将所有输入端和输出端 Audio Object 的 AkAudioBuffer::eState 重置为 AK_NoMoreData。若处理之后 AkAudioBuffer::eState 仍被设为 AK_NoMoreData,则将相应的 Audio Object 销毁。在销毁所有输入和输出对象之后,才会将对象处理器销毁。因此,要想让非原地对象处理器在已无任何输入端 Audio Object 的情况下继续输出音频,必须通过将状态设为 AK_DataReady 来确保让一个或多个输出端 Audio Object 处于活跃状态。
注意: 在对象处理器内追踪对象时,请务必谨慎。切勿引用已被声音引擎销毁的对象。 |
备注: 若将 AkAudioBuffer::eState 设为 AK_DataReady,则输入端 Audio Object 将保持活跃状态。不过尽量不要这样做,因为会生成不必要的尾音。 |
备注: 若没有活跃的输出端 Audio Object,则非原地对象处理器输出无声内容。 |
下面我们来以以下三个范例为参考探讨一下非原地对象处理。
Software Binauralizer 可作为非原地对象处理器加以实现,来输入多个 Audio Object 并采用立体声声道配置输出单个 Audio Object。这种插件应添加到 Audio Object 总线上,不过可让该总线输出单个立体声信号。
为方便起见,可通过 AK::IAkEffectPlugin::Init 的握手方法来创建唯一的立体声输出对象。
然后,在 Execute 中插入以下代码:
3D Panner 可采用与上述 Software Binauralizer 类似的方式实现。不过,最好还是让它实例化一系列输出端 Audio Object,并使之与空间化虚拟话筒一一对应。如此一来,便可由下游总线或设备来对这些 Audio Object 的信号实施声像摆位,以此充分利用这些虚拟话筒的定位元数据。
在 AK::IAkEffectPlugin::Init 中,请以显式方式使用 AK::IAkEffectPluginContext::CreateOutputObjects 来创建输出对象,而不要返回非对象配置。
在上述示例中,您可能会好奇为什么 (-0.707f, 0.f, 0.707f) 代表左前位置。有关详细信息,请参阅 关于 3D 转换 章节。
对于每个输入端 Audio Object,Particle Generator 可创建 N 个输出端 Audio Object,并将其随机定位在对应对象所在位置的周围。这种对象处理器无法在 Init 中创建对象,而需要在 Execute 中动态地加以创建,同时追踪这些对象及与其对应的输入对象。在输入对象的状态为 AK_NoMoreData 时,对应输出对象的状态也要设为 AK_NoMoreData。这样可以确保由声音引擎对对象进行垃圾回收。
注意: 若非原地对象处理器从 Execute 内调用 AK::IAkEffectPluginContext::CreateOutputObjects,则其无法稳定地访问 out_objects 中传递的输出对象。在这种情况下,须使用 AK::IAkEffectPluginContext::GetOutputObjects。 |
在设计工具的 Audio Object Profiler 中,会将发起端 Wwise 对象的名称赋予 Audio Object。相应地,非原地对象处理器的输出对象全部使用其所在总线的名称。为方便实施性能分析,建议在适用情况下使用 AkAudioObject::SetName 来为输出对象指派名称。
比如,在上述 3D Panner 中创建对象时,可按照以下方式进行命名:
AkAudioObject 结构包含整条对象管线中随 Audio Object 音频缓冲区一并传输的所有 Audio Object 元数据。其可分为三个类别:
Audio Object 在 AkAudioObject::positioning 中携带发起端声音的定位数据。AkAudioObject::positioning.behavioral
保存 Wwise 对象上可设定的所有相关定位设置。比如,若声音使用扬声器声像摆位,则 AkAudioObject::positioning.behavioral.panType
将被设为其中一种声像摆位器类型,panLR
、panBF
和 panDU
将与声像摆位器的位置对应。
若空间化模式为 3D((AK_SpatializationMode_PositionOnly
或 AK_SpatializationMode_PositionAndOrientation
),则 AkAudioObject::positioning.threeD
包含所有与 3D 位置相关的数据:
AkAudioObject::positioning.threeD.xform
一般会沿用关联游戏对象的位置。不过,可依据其 3D 定位设置(如 3D Automation)来针对各个声音加以修改或覆盖。AkAudioObject::positioning.threeD.spread
和 AkAudioObject::positioning.threeD.focus
一般通过 Attenuation 曲线来进行计算。Audio Object 具有上游所应用的累积增益,比如音频源的 Volume 设置或来自总线和总线通路的增益变化。对于没有效果器或对象处理器的简单场景,这意味着在最终下混到扬声器 Bed 或作为 Audio Object 发送到系统输出之前不会将 Audio Object 应用于其音频信号。这样可以避免在一帧中多次调节增益并逐步应用于 Audio Object 的音频信号以确保混音平滑变化,尤其是在因 Game Object 添加或移除位置而创建和销毁 Audio Object 的情况下。
针对此元数据的支持对对象处理器来说是可选的,其可通过在对象处理器对 IAkPlugin::GetPluginInfo
的实现中将 IAkPluginInfo::bUsesGainAttribute
设为 true 来启用。若 bUsesGainAttribute 保留为 false,则所有传给 Execute 的音频缓冲区将具有在执行之前应用于 Audio Object 的累积增益,而传给对象处理器的增益将取中立值。倘若将 bUsesGainAttribute 设为 true,则不会修改音频缓冲区,累积增益可为无单位值。藉此,对象处理器可根据需要确认增益并修改 Audio Object 的累积增益。
若要修改 Audio Object 的累积增益,请注意该值为 AkRamp
且游戏引擎不会自动处理某一帧 fNext 值和下一帧 fPrev 值之间的连贯性。也就是说,若对象处理器要修改某一帧的 fNext,则须将同样的修改应用于下一帧的 fPrev。倘若管理不当,在音频管线的其他部分需要消耗 Audio Object 的增益时,可能会出现毛刺噪声或音频信号断续问题。
若空间化模式 AkAudioObject::positioning.behavioral.spatMode
为3D(AK_SpatializationMode_PositionOnly
或 AK_SpatializationMode_PositionAndOrientation
),则将对 3D 位置实施转换(变换和旋转)以使其与所在总线关联的游戏对象(听者)相对。比如,声音可以定位在 (2, 0, 0);在由与处于 (10, 0, 0) 的听者关联的 Audio Object 总线处理时,将把生成的 Audio Object 定位在 (-8, 0, 0)。该总线上的对象处理器会将所述 Audio Object 视为处在 (-8, 0, 0) 位置。
Wwise 中采用左手坐标系,默认朝向的前向矢量指向 Z,顶向矢量指向 Y(由 AkCommonDefs.h 定义)。
比如,对于 Software Binauralizer ,已在到达插件之前对输入对象进行旋转,以使 Z 轴正半轴指向听者前方,X 轴正半轴指向其右方,以此类推。正因如此,示例中使用的 AK::IAkMixerPluginContext::ComputePositioning 服务才不会采用听者的朝向,而是假定为默认朝向。
因此,在上述 3D Panner 示例中,(-0.707, 0, 0.707) 指向与所在总线关联的游戏对象左前 45 度。
对象处理器能够访问与 Audio Object 绑定的自定义元数据。
自定义元数据是种只包含一组参数的插件。在设计工具中,元数据插件可添加到任何 Wwise 对象上,并且支持 ShareSet。在声音引擎中,其实现 AK::IAkPluginParam 接口。
在适用情况下,Audio Object 会在每一帧收集与发起端声音绑定的所有元数据插件,并将其添加到 AkAudioObject::arCustomMetadata 数组。然后,收集与流经的每条总线绑定的所有元数据插件。对象处理器(无论原地还是非原地)可读取此数组。当然,必须知晓插件才能转译其内容。
对象处理器实现类可写入一个或多个配套元数据插件,便于用户添加到 Wwise 对象。
比如,在前述 Software Binauralizer 中(参见 Software Binauralizer 章节),想要支持直通模式,以避免对某些声音实施 HRTF 滤波。为此,可使用名为 Passthrough 的布尔值属性来创建配套元数据插件 ObjectBinauralizerMetadata。这样的话用户就可以将此插件添加到任何想要禁用 HRTF 的 Wwise 对象。然后,在对象处理器的 Execute() 中插入以下代码:
备注: 因为 Audio Object 从访问过的每个 Wwise 对象收集元数据插件,所以单个 Audio Object 上的同一插件类型可能会存在多个实例。在这种情况下,需要由您决定采用怎样的策略,并相应的信息告知用户。 |