Unity中Wwise音頻可視化及游戲內(nèi)錄屏方案的思路探索

一、前言:
希望本篇分享能拋磚引玉,激發(fā)大家更多的開發(fā)靈感。
在游戲的實際開發(fā)過程中,你是否經(jīng)歷過以下兩個場景:
場景一:策劃小姐姐跑過來說,我們需要美術(shù)特效跟隨音頻的變化去變化…
場景二:策劃小姐姐又跑過來說,我們想做個游戲內(nèi)錄屏的功能,玩家點擊錄屏按鈕會錄一個帶聲音的視頻…
仔細(xì)分析,我們不難發(fā)現(xiàn),場景一中策劃小姐姐的需求本質(zhì),其實就是我們經(jīng)常討論的音頻的可視化實現(xiàn)。那么,場景一中如何實現(xiàn)Wwise音頻可視化和場景二中的如何實現(xiàn)錄制Wwise里輸出的音頻,這兩個問題的本質(zhì)又是什么?
沒錯,是如何獲取Wwise底層的音頻采樣數(shù)據(jù)?
那么我們今天討論的主題就變成了:如何獲取Wwise底層的音頻采樣數(shù)據(jù)并發(fā)送到Unity中進(jìn)行接收?
二、如何獲取Wwise底層的音頻采樣數(shù)據(jù)并發(fā)送到Unity中接收?
我們可以把這個問題拆分為2個問題:
如何獲取Wwise的底層音頻采樣數(shù)據(jù)?
獲取到數(shù)據(jù)之后又如何發(fā)送到Unity中?
在本文探索的方案思路中,上述拆分之后的2個問題的回答如下:
1. 如何獲取Wwise的底層音頻采樣數(shù)據(jù)?
答:編寫并構(gòu)建你的Wwise Effect Plugins
2. 獲取到數(shù)據(jù)之后又如何發(fā)送到Unity中?
答:編寫并構(gòu)建數(shù)據(jù)中轉(zhuǎn)插件,且稱他為中轉(zhuǎn)器。Unity中通過DllImport的形式,將中轉(zhuǎn)器動態(tài)庫庫導(dǎo)入……
那么,根據(jù)上述2個問題的回答,下文將從Wwise Effect Plugins,數(shù)據(jù)中轉(zhuǎn)器,Wwise、中轉(zhuǎn)器和Unity之間的互聯(lián)三個方面逐步探索。
1、Wwise Effect Plugins
我們可以把Wwise理解為是一個密封的黑盒子,我們所需要的采樣數(shù)據(jù)就存在這個黑盒子里的某個地方。Wwise官方并沒有開放相關(guān)的Api,因此我們從外面是無法把“手”伸進(jìn)黑盒子里直接拿到數(shù)據(jù)的。所以,我們只能另辟蹊徑,而編寫插件就是那條蹊徑。
由于我們編寫的插件僅僅用來捕獲Wwise底層數(shù)據(jù)用,因此插件的GUI層面不需要加入什么東西,所以當(dāng)我們構(gòu)建好開發(fā)Wwise效果器插件的vs工程之后,直接進(jìn)入聲音引擎邏輯編寫部分。當(dāng)然,如果你覺得必須要放點什么在插件面板上的話,那么,你可以……
比如:

扯遠(yuǎn)了,回到正題。
我們所編寫的Effect Plugins就像一個“間諜”潛伏在Wwise這個黑盒生態(tài)系統(tǒng)中,時刻為我們拿到需要的情報(數(shù)據(jù))。拿到之后勢必要有一輛“運(yùn)輸車”去裝載數(shù)據(jù),那么,我們先構(gòu)建這輛“運(yùn)輸車”。
直接進(jìn)入聲音引擎編寫部分,在頭文件聲明:

在插件的構(gòu)造函數(shù)中將其初始化為nullptr
進(jìn)入插件的Init()函數(shù)中,為其申請一個大小為2048的浮點型數(shù)組的內(nèi)存
為此wwise提供了AK_PLUGIN_ALLOC宏:

為什么大小是2048呢?
這個取值不是死的,取決于你的設(shè)置,因為我的音頻輸出設(shè)置為Stereo,采樣點數(shù)為1024,所以我這里固定了大小為2048.
在插件的Term()函數(shù)中釋放這部分內(nèi)存:

Wwise提供了AK_PLUGIN_FREE宏:

這樣便完成了“運(yùn)輸車”的構(gòu)建。
那么,我們現(xiàn)在開始往車上裝填數(shù)據(jù)。
進(jìn)入插件的Execute(AkAudioBuffer*?io_pBuffer)函數(shù)中:

在上述代碼中,audioDataForSend數(shù)組中存入的是來自wwise所有通道的音頻數(shù)據(jù),并且整個存儲的過程是不同通道交錯訪問存儲的。也就是說,其中相鄰的數(shù)據(jù)分別來自于不同的通道,舉個例子,比如第10個數(shù)據(jù)來自于左通道,那么第9個和第11個數(shù)據(jù)來自于右通道。這樣在兩個for循環(huán)的不斷刷新執(zhí)行下,當(dāng)outPosition的值等于2047的時候,便完成了當(dāng)前音頻幀的所有音頻數(shù)據(jù)的“裝車“。
這里,我以我拙劣的畫技畫了一幅數(shù)據(jù)“裝車”的示意圖:

數(shù)據(jù)“裝車”之后,面臨的問題是如何將數(shù)據(jù)運(yùn)輸?shù)経nity中?實際上,Unity與Wwise這兩個密閉的系統(tǒng)之間是沒有“路”可以供“運(yùn)輸車”行駛的。那么,我們勢必要動手開辟一條連接Unity與Wwise的通路,就像連接候機(jī)大廳與飛機(jī)之間的登機(jī)通道,或者連接兩座懸崖絕壁的索道一樣。
數(shù)據(jù)中轉(zhuǎn)器便是連接Unity與Wwise的通路上的一個重要樞紐。
2、數(shù)據(jù)中轉(zhuǎn)器
中轉(zhuǎn)器是連接Unity與Wwise的一個重要樞紐,它以動態(tài)庫的形式存在。關(guān)于動態(tài)庫構(gòu)建方面的知識本篇分享就不延展來說了,因為這里又涉及很多方面的內(nèi)容。
中轉(zhuǎn)器的職能簡單來說就是:接收Wwise傳來的數(shù)據(jù),并扔到Unity過來接收的車上。
實際上,在本文提供的思路中,構(gòu)建中轉(zhuǎn)器動態(tài)庫的C++代碼中只需要兩個相同參數(shù)類型的導(dǎo)出函數(shù)。
比如:

上述代碼中,我們可以看到有2個相同參數(shù)類型的函數(shù)。雖然參數(shù)相同,但是他們的實際職責(zé)卻是各不相同的。一個用于Wwise與中轉(zhuǎn)器的連接,另一個則用于Unity與中轉(zhuǎn)器的連接。詳情看下文
3、Wwise、中轉(zhuǎn)器、Unity三者之間的互聯(lián)
Wwise&中轉(zhuǎn)器
Wwise與中轉(zhuǎn)器的連接過程涉及動態(tài)庫的讀取加載、函數(shù)指針的獲取等等。
在Wwise插件編寫聲音引擎部分,聲明一個函數(shù)類型指針,其類型與中轉(zhuǎn)器中的函數(shù)一樣:

在Execute(AkAudioBuffer*?io_pBuffer)?函數(shù)中,在數(shù)據(jù)裝車完成之后,加入以下代碼:

上述代碼中, 我們通過LoadLibrary()?Windows?系統(tǒng)API(Linux系統(tǒng)下是dlopen())獲取動態(tài)庫句柄,通過GetProcAddress()?獲取函數(shù)指針。這里我們獲取中轉(zhuǎn)器中GetSamplesFromWwise函數(shù)指針。通過函數(shù)指針完成函數(shù)的執(zhí)行。
至此完成Wwise與中轉(zhuǎn)器的連接,實現(xiàn)數(shù)據(jù)由Wwise到中轉(zhuǎn)器的轉(zhuǎn)移。
Unity&中轉(zhuǎn)器
在完成Wwise與中轉(zhuǎn)器的連接之后,我們考慮連接中轉(zhuǎn)器與Unity。
Unity與中轉(zhuǎn)器的連接涉及Unity動態(tài)庫導(dǎo)入、外部庫函數(shù)的定義以及非托管層與托管層之間數(shù)據(jù)的轉(zhuǎn)換傳輸?shù)鹊取?/p>
在C#中導(dǎo)入庫并聲明外部庫函數(shù):

非托管層與托管層之間的數(shù)據(jù)轉(zhuǎn)換運(yùn)算,C#提供了Marshal類?供我們使用。我們通過Marshal類?中的接口實現(xiàn)內(nèi)存的申請,數(shù)據(jù)的copy等操作。調(diào)用Dllimport導(dǎo)入的外部庫函數(shù),實現(xiàn)c#層面對于音頻數(shù)據(jù)的獲取。
比如:
分配一個2048大小的float類型數(shù)組內(nèi)存:
將一個數(shù)組拷貝到另一個數(shù)組:

釋放:

實際上,我們?yōu)榱朔奖阍赨nity項目中C#層面更快捷地接收Wwise的數(shù)據(jù),可以將我們導(dǎo)入的庫函數(shù)和Marshal類?中的接口一起封裝成一個函數(shù),方便我們使用,比如:

至此、就完成了Unity與中轉(zhuǎn)器的連接。與此同時,一條Wwise通往Unity的數(shù)據(jù)“運(yùn)輸之路”修建而成,wwise的音頻數(shù)據(jù)便可傳入Unity中接收、使用。
拿到了Wwise的音頻數(shù)據(jù)之后,至于波譜、頻譜、錄屏等等功能需求的滿足便易如反掌了。
舉個例子,我們可以修改項目使用的錄屏插件里的音頻模塊的邏輯,達(dá)到將來自Wwise的音頻數(shù)據(jù)寫入錄制的視頻中的目的。
你要捕獲什么音頻數(shù)據(jù)就把效果器插件掛到對應(yīng)的總線上。比如,想獲取僅僅來自音樂的音頻數(shù)據(jù),那就把插件掛到音樂系統(tǒng)輸出的音樂總線上。那么,你錄到的視頻里面就只有音樂沒有音效了。
三、總結(jié):
以上所說的整套Unity中獲取Wwise音頻數(shù)據(jù)的流程,這里我又展示了我拙劣的畫技,如下:

效果視頻
以下視頻,我將拿到的Wwise音頻數(shù)據(jù),在Unity中繪制了一下:

最后
考慮到性能、兼容性,跨平臺開發(fā)等因素的影響,實際的開發(fā)過程還是比較復(fù)雜,比較麻煩的。
如本篇開頭所說,希望本篇分享能起到拋磚引玉的作用,讓大家在實際的游戲開發(fā)過程中能夠產(chǎn)生更多好玩的想法或靈感。
如果你有更好的方案或想法,歡迎交流。
本文作者

張成功
技術(shù)音頻
盛趣游戲
技術(shù)音頻、獨(dú)游開發(fā)愛好者,目前任職于盛趣游戲。一個喜歡喝粥的男人,在研究點技術(shù)的同時,也會搞些音樂上的事情討好自己。希望游戲聽起來更好。
