查看原文
其他

Unity中Wwise音频可视化及游戏内录屏方案的思路探索

张成功 Audiokinetic官方 2022-06-07

一、前言:


希望本篇分享能抛砖引玉,激发大家更多的开发灵感。


在游戏的实际开发过程中,你是否经历过以下两个场景:

  1. 场景一:策划小姐姐跑过来说,我们需要美术特效跟随音频的变化去变化…

  2. 场景二:策划小姐姐又跑过来说,我们想做个游戏内录屏的功能,玩家点击录屏按钮会录一个带声音的视频…

仔细分析,我们不难发现,场景一中策划小姐姐的需求本质,其实就是我们经常讨论的音频的可视化实现。那么,场景一中如何实现Wwise音频可视化和场景二中的如何实现录制Wwise里输出的音频,这两个问题的本质又是什么?

没错,是如何获取Wwise底层的音频采样数据?

那么我们今天讨论的主题就变成了:如何获取Wwise底层的音频采样数据并发送到Unity中进行接收?


二、如何获取Wwise底层的音频采样数据并发送到Unity中接收?


我们可以把这个问题拆分为2个问题:
  1. 如何获取Wwise的底层音频采样数据?

  2. 获取到数据之后又如何发送到Unity中?

在本文探索的方案思路中,上述拆分之后的2个问题的回答如下:

1. 如何获取Wwise的底层音频采样数据?

答:编写并构建你的Wwise Effect Plugins

2. 获取到数据之后又如何发送到Unity中?

答:编写并构建数据中转插件,且称他为中转器。Unity中通过DllImport的形式,将中转器动态库库导入……

那么,根据上述2个问题的回答,下文将从Wwise Effect Plugins数据中转器Wwise、中转器和Unity之间的互联三个方面逐步探索。



1

Wwise Effect Plugins



我们可以把Wwise理解为是一个密封的黑盒子,我们所需要的采样数据就存在这个黑盒子里的某个地方。Wwise官方并没有开放相关的Api,因此我们从外面是无法把“手”伸进黑盒子里直接拿到数据的。所以,我们只能另辟蹊径,而编写插件就是那条蹊径。

由于我们编写的插件仅仅用来捕获Wwise底层数据用,因此插件的GUI层面不需要加入什么东西,所以当我们构建好开发Wwise效果器插件的vs工程之后,直接进入声音引擎逻辑编写部分。当然,如果你觉得必须要放点什么在插件面板上的话,那么,你可以……

比如:

扯远了,回到正题。


我们所编写的Effect Plugins就像一个“间谍”潜伏在Wwise这个黑盒生态系统中,时刻为我们拿到需要的情报(数据)。拿到之后势必要有一辆“运输车”去装载数据,那么,我们先构建这辆“运输车”。

直接进入声音引擎编写部分,在头文件声明:

在插件的构造函数中将其初始化为nullptr

进入插件的Init()函数中,为其申请一个大小为2048的浮点型数组的内存

为此wwise提供了AK_PLUGIN_ALLOC宏:

为什么大小是2048呢?

这个取值不是死的,取决于你的设置,因为我的音频输出设置为Stereo,采样点数为1024,所以我这里固定了大小为2048.

在插件的Term()函数中释放这部分内存:

Wwise提供了AK_PLUGIN_FREE宏:

这样便完成了“运输车”的构建。


那么,我们现在开始往车上装填数据。

进入插件的Execute(AkAudioBuffer* io_pBuffer)函数中:

在上述代码中,audioDataForSend数组中存入的是来自wwise所有通道的音频数据,并且整个存储的过程是不同通道交错访问存储的。也就是说,其中相邻的数据分别来自于不同的通道,举个例子,比如第10个数据来自于左通道,那么第9个和第11个数据来自于右通道。这样在两个for循环的不断刷新执行下,当outPosition的值等于2047的时候,便完成了当前音频帧的所有音频数据的“装车“


这里,我以我拙劣的画技画了一幅数据“装车”的示意图:

数据“装车”之后,面临的问题是如何将数据运输到Unity中?实际上,Unity与Wwise这两个密闭的系统之间是没有“路”可以供“运输车”行驶的。那么,我们势必要动手开辟一条连接Unity与Wwise的通路,就像连接候机大厅与飞机之间的登机通道,或者连接两座悬崖绝壁的索道一样。


数据中转器便是连接Unity与Wwise的通路上的一个重要枢纽。



2

数据中转器




中转器是连接Unity与Wwise的一个重要枢纽,它以动态库的形式存在。关于动态库构建方面的知识本篇分享就不延展来说了,因为这里又涉及很多方面的内容。


中转器的职能简单来说就是:接收Wwise传来的数据,并扔到Unity过来接收的车上

实际上,在本文提供的思路中,构建中转器动态库的C++代码中只需要两个相同参数类型的导出函数。

比如:

上述代码中,我们可以看到有2个相同参数类型的函数。虽然参数相同,但是他们的实际职责却是各不相同的。一个用于Wwise与中转器的连接,另一个则用于Unity与中转器的连接。详情看下文




3

Wwise、中转器、Unity三者之间的互联




Wwise&中转器

Wwise与中转器的连接过程涉及动态库的读取加载、函数指针的获取等等。

在Wwise插件编写声音引擎部分,声明一个函数类型指针,其类型与中转器中的函数一样:

在Execute(AkAudioBuffer* io_pBuffer) 函数中,在数据装车完成之后,加入以下代码:

上述代码中, 我们通过LoadLibrary() Windows 系统API(Linux系统下是dlopen())获取动态库句柄,通过GetProcAddress() 获取函数指针。这里我们获取中转器中GetSamplesFromWwise函数指针。通过函数指针完成函数的执行。

至此完成Wwise与中转器的连接,实现数据由Wwise到中转器的转移。


Unity&中转器

在完成Wwise与中转器的连接之后,我们考虑连接中转器与Unity。

Unity与中转器的连接涉及Unity动态库导入、外部库函数的定义以及非托管层与托管层之间数据的转换传输等等。

在C#中导入库并声明外部库函数:

非托管层与托管层之间的数据转换运算,C#提供了Marshal 供我们使用。我们通过Marshal 中的接口实现内存的申请,数据的copy等操作。调用Dllimport导入的外部库函数,实现c#层面对于音频数据的获取。


比如:


分配一个2048大小的float类型数组内存:

将一个数组拷贝到另一个数组:

释放:

实际上,我们为了方便在Unity项目中C#层面更快捷地接收Wwise的数据,可以将我们导入的库函数和Marshal 中的接口一起封装成一个函数,方便我们使用,比如:

至此、就完成了Unity与中转器的连接。与此同时,一条Wwise通往Unity的数据“运输之路”修建而成,Wwise的音频数据便可传入Unity中接收、使用。

拿到了Wwise的音频数据之后,至于波谱、频谱、录屏等等功能需求的满足便易如反掌了。

举个例子,我们可以修改项目使用的录屏插件里的音频模块的逻辑,达到将来自Wwise的音频数据写入录制的视频中的目的。

你要捕获什么音频数据就把效果器插件挂到对应的总线上。比如,想获取仅仅来自音乐的音频数据,那就把插件挂到音乐系统输出的音乐总线上。那么,你录到的视频里面就只有音乐没有音效了。


三、总结:


以上所说的整套Unity中获取Wwise音频数据的流程,这里我又展示了我拙劣的画技,如下:

效果视频

以下视频,我将拿到的Wwise音频数据,在Unity中绘制了一下:




最后

考虑到性能、兼容性,跨平台开发等因素的影响,实际的开发过程还是比较复杂,比较麻烦的。

如本篇开头所说,希望本篇分享能起到抛砖引玉的作用,让大家在实际的游戏开发过程中能够产生更多好玩的想法或灵感。

如果你有更好的方案或想法,欢迎交流。



本文作者



张成功

盛趣游戏,技术音频。技术音频、独游开发爱好者,目前任职于盛趣游戏。一个喜欢喝粥的男人,在研究点技术的同时,也会搞些音乐上的事情讨好自己。希望游戏听起来更好。



往期推荐



Audiokinetic中国2021互动音乐沙龙回顾


Impacter 插件简介


FPS游戏中枪械模块的音频设计思路


更多内容,欢迎关注我们官方B站和新浪微博!




您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存