Unity-原生音頻插件 SDK
本文檔介紹 Unity 5.0 的內置原生音頻插件接口。為此,我們將展示一些具體示例插件,并按順序逐步推進復雜程度。因此,我們從很基本的概念開始,在文檔將要結束時介紹復雜的用例。
下載
首先需要從這里下載最新的音頻插件 SDK。
概述
原生音頻插件系統(tǒng)由兩部分組成:
1.原生 DSP(數(shù)字信號處理)插件,此插件必須以 .dll(對于 Windows)或 .dylib(對于 OSX)形式使用 C 或 C++ 來實現(xiàn)。此插件不同于腳本,而且由于對性能的高要求,必須針對您要支持的任何平臺來編譯此插件,可能還需要進行特定于平臺的優(yōu)化。
1.用 C# 開發(fā)的 GUI。請注意,GUI 是可選的,所以您隨時可以通過創(chuàng)建基本原生 DSP 插件來開始插件開發(fā),然后讓 Unity 根據(jù)原生插件暴露的參數(shù)描述來顯示基于滑動條的默認 UI。我們建議采用此方法啟動項目開發(fā)。
請注意,開始時可通過 .cs 文件形式構建 C# GUI 的原型,只需將 .cs 文件放到 Assets/Editor 文件夾中(就像任何其他編輯器腳本一樣)。此后,隨著代碼開始增長并需要更好的模塊化和更好的 IDE(集成開發(fā)環(huán)境)支持,您便可以將此文件移動到合適的 Visual Studio 項目中。如此一來,可將該文件編譯為 .dll,以便用戶更輕松地將文件拖入項目中,并且還能保護您的代碼。
另外還要注意,原生 DSP 和 GUI DLL 都能包含多個插件,而且綁定時僅采用插件中效果的名稱,與調用的 DLL 文件無關。
都有哪些文件?
插件 SDK 的本地端實際上僅包含一個文件 (AudioPluginInterface.h),但為了在同一個 DLL 中提供多個插件效果,我們添加了支持代碼,從而以一種簡單、統(tǒng)一的方式來處理效果定義和參數(shù)注冊(AudioPluginUtil.h 和 AudioPluginUtil.cpp)。清注意,NativePluginDemo 項目包含多個示例插件以供您入門,并且顯示了游戲上下文中有用的各種不同插件類型。我們將此代碼放在公共域中,請盡管使用此代碼作為創(chuàng)建自有項目的起點。
開發(fā)插件時,第一步是定義插件應具備的參數(shù)。您不需要在開始之前就制定有關插件將包含的所有參數(shù)的詳細總計劃,但大致構思一下您所期望的用戶體驗以及需要的組件會很有幫助。
我們提供的示例插件中具有眾多實用函數(shù)可以讓您輕松實現(xiàn)目標。 我們來看看“Ring Modulator”示例插件。這個簡單插件將傳入信號乘以某個正弦波,因此將產生類似于無線電噪聲/信號接收中斷的奇妙效果,特別是多個具有不同頻率的環(huán)形調制效果鏈接在一起時。
示例插件中處理參數(shù)的基本方案是,將參數(shù)定義為枚舉值,我們在浮點數(shù)組中將這些枚舉值用作索引,這樣既簡潔又方便。
RegisterParameter 調用中的數(shù)字表示最小值、最大值和默認值,后跟僅用于顯示用途的比例因子,例如,如果是百分比值,實際值是從 0 到 1,顯示時乘以 100。此情況中沒有自定義 GUI 代碼,但如上所述,Unity 將根據(jù)這些基本參數(shù)定義來生成一個默認 GUI。請注意,不會對未定義參數(shù)執(zhí)行任何檢查,因此 AudioPluginUtil 系統(tǒng)期望所有聲明的枚舉值(P_NUM
?除外)都與對應的參數(shù)定義匹配。
RegisterParameter 函數(shù)在后臺填充與該插件相關聯(lián)的 UnityAudioEffectDefinition 結構的 UnityAudioParameterDefinition 數(shù)組中的條目(請參閱“AudioEffectPluginInterface.h”)。UnityAudioEffectDefinition 中需要設置的其他內容是對各種函數(shù)的回調,這些函數(shù)負責實例化插件 (CreateCallback)、設置/獲取參數(shù) (SetFloatParameterCallback/UnityAudioEffect_GetFloatParameterCallback
)、執(zhí)行實際處理 (UnityAudioEffect_ProcessCallback
) 以及在完成后最終銷毀插件實例 (UnityAudioEffect_ReleaseCallback
)。
為了便于在同一個 DLL 中包含多個插件,每個插件都位于自己的命名空間中,并且使用回調函數(shù)的特定命名約定,以便?DEFINE_EFFECT
?和?DECLARE_EFFECT
?宏可以填充 UnityAudioEffectDefinition 結構。在底層中,所有效果定義都存儲在一個數(shù)組中,僅 UnityGetAudioEffectDefinitions 庫的入口點可以返回該數(shù)組的指針。
如果您希望開發(fā)橋接插件(在其他插件格式(比如 VST 或 AudioUnit)與 Unity 音頻插件接口之間進行映射),那么了解上面的內容很有幫助,在這種情況下,您需要開發(fā)一種更加動態(tài)的方法在加載時設置參數(shù)描述。
實例化插件
接下來是插件實例的數(shù)據(jù)。在示例插件中,我們將其全部都放到了 EffectData 結構中。必須在對應的 CreateCallback 中進行這種分配(對混音器中插件的每個實例調用 CreateCallback)。在這個簡單示例中,只有一個擴展到多個聲道的正弦波,其他更高級的插件需要為每個輸入聲道分配額外數(shù)據(jù)。
UnityAudioEffectState 包含來自主機的各種數(shù)據(jù)(比如采樣率、已處理的樣本總數(shù)(用于計時)或者是否繞過了插件),并傳遞到所有回調函數(shù)。
顯然,也有一個對應的函數(shù)用于釋放插件實例:
音頻的主要處理工作在 ProcessCallback 中進行:
頂部的 GetEffectData 函數(shù)只是一個 helper 函數(shù),用于將 state 變量的 effectdata 字段轉換為我們之前聲明的結構中的 EffectData::Data。

包括的另外兩個簡單插件是 NoiseBox 插件和 Lofinator 插件,前者將輸入信號與可變頻率下的白噪聲相加或相乘,后者對信號進行簡單的下采樣和量化。所有這些插件都可以結合使用,也可與游戲驅動的動畫參數(shù)一起使用,從而模擬各種聲音,包括手機、對講機信號接收不良、擴音器損壞等等的聲音。
StereoWidener 可將立體聲輸入信號分解為具有可變延遲的單聲道分量和側向分量,然后重新組合這些分量以改善感知的立體聲效果。
哪個插件在哪個平臺上加載?
原生音頻插件使用與其他原生或托管插件相同的方案,因為它們都必須通過插件導入器檢視面板與其各自的平臺相關聯(lián)。您可以在這里進一步了解用于放置插件的子文件夾。必須進行平臺關聯(lián),這樣系統(tǒng)才能知道在獨立構建中每個構建目標上要包含哪些插件,并且隨著 64 位支持的引入,甚至必須在平臺內指定平臺關聯(lián)。OSX 插件在這方面具有特殊性,因為通用二進制格式允許 OSX 插件在同一個 Bundle 中包含 32 位和 64 位變體。
從托管代碼調用的 Unity 內部原生插件通過 [DllImport] 屬性來加載(此屬性引用要從原生 DLL 中導入的函數(shù))。但如果是原生音頻插件,則情況不同。此處出現(xiàn)的特殊問題是,需要先加載音頻插件,然后 Unity 才能開始創(chuàng)建可能需要插件效果的混音器資源。編輯器中不存在這樣的問題,因為我們可以重新加載和重新構建依賴于插件的混音器,但在獨立構建中,必須先加載插件后再創(chuàng)建混音器資源。為了解決這個問題,目前的慣例是在插件“audioplugin”中添加前綴 DLL(不區(qū)分大小寫),以便系統(tǒng)可以檢測到這一點,并將其添加到將在啟動后自動加載的插件列表中。請記住,只有插件中的定義才能定義效果在 Unity 混音器中的顯示名稱,因此 DLL 可以是任何名稱,但它需要以字符串“audioplugin”開頭才能被檢測到。
對于像 IOS 這樣的平臺,插件代碼需要靜態(tài)鏈接到由生成的 XCode 項目產生的 Unity 二進制文件中,而在此位置,就像插件呈現(xiàn)設備一樣,必須將插件注冊顯式添加到應用程序的啟動代碼中。

在 OSX 上,一個 Bundle 可同時包含插件的 32 位和 64 位版本。也可以拆分它們以節(jié)省空間。
包含自定義 GUI 的插件
現(xiàn)在讓我們看一些更高級的內容:均衡和多頻段壓縮的效果。相比前一節(jié)中介紹的簡單插件,這些插件具有更多的參數(shù),并且參數(shù)之間存在某種物理耦合關系,因此需要更好的方式來可視化參數(shù),而不僅僅是一堆簡單的滑動條。例如,假設有一個均衡器:每個頻段有 3 個不同的濾波器,它們共同作用于最終的均衡曲線,每個濾波器都有 3 個參數(shù):頻率、Q 因子和增益,這三個參數(shù)有物理關聯(lián)性,定義了每個濾波器的形狀。因此,如果均衡器插件有一個出色的大型顯示界面來顯示結果曲線、每個濾波器影響,并且操作方式變?yōu)橥ㄟ^在控件上的簡單拖動操作便可以同時設置多個參數(shù)(而不是一次改變一個滑動條),這樣將對用戶大有幫助。

均衡器插件的自定義 GUI。拖動三個波段可更改濾波器曲線的增益和頻率。按住 Shift 進行拖動可以更改每個波段的形狀。
因此,定義、初始化、取消初始化和參數(shù)處理再次遵循簡單插件使用的完全相同的基于枚舉的方法,甚至 ProcessCallback 代碼也相當簡短?,F(xiàn)在,我們不再看本機代碼,而是在 Visual Studio 中打開 AudioPluginDemoGUI.sln 項目。在這里,找到 GUI 代碼的相關 C# 類。工作方式很簡單:一旦 Unity 加載了原生插件 DLL 并注冊了包含的音頻插件,就會開始尋找與所注冊插件的名稱相匹配的對應 GUI。此過程通過 EqualizerCustomGUI 類的 Name 屬性進行(與所有自定義插件 GUI 一樣,該類必須繼承自 IAudioEffectPluginGUI)。此類中只有一個重要的函數(shù),也就是 bool OnGUI(IAudioEffectPlugin plugin) 函數(shù)。通過 IAudioEffectPlugin 插件參數(shù),該函數(shù)可獲取原生插件的句柄,通過此句柄可讀取和寫入原生插件已定義的參數(shù)。為讀取參數(shù),它會調用:
plugin.GetFloatParameter("MasterGain", out masterGain);
如果找到參數(shù),則返回 true;為設置參數(shù),它將調用:
plugin.SetFloatParameter("MasterGain", masterGain);
如果參數(shù)存在,也返回 true。這基本上是 GUI 和本機代碼之間最重要的綁定關系。也可以使用以下函數(shù)
plugin.GetFloatParameterInfo("NAME", out minVal, out maxVal, out defVal);
來查詢參數(shù)“NAME”以獲得其最小值、最大值和默認值,從而避免在本機代碼和 UI 代碼中重復出現(xiàn)這些的定義。請注意,如果 OnGUI 函數(shù)返回 true,則檢視面板將在自定義 GUI 下方顯示默認 UI 滑動條。同樣,這對于啟動 GUI 開發(fā)非常有幫助,因為您在開發(fā)自定義 GUI 時擁有了所有可用參數(shù),并且可以輕松檢查對其執(zhí)行的正確操作是否會產生預期的參數(shù)變化。
我們不會在這里討論均衡器插件和多頻段插件中將要進行的 DSP 處理的細節(jié),對于感興趣的用戶,請知悉這些濾波器取自于 Robert Bristow Johnson 的杰作:Audio EQ Cookbook(音頻均衡器說明書),而為了繪制曲線,Unity 還提供了一些內部 API 函數(shù)來為頻率響應繪制抗鋸齒曲線。
還有一點值得一提的是,均衡器插件和多頻段插件都提供了用于覆蓋輸入和輸出頻譜的代碼,旨在將插件的效果可視化,這就產生了一件有意思的事情:與音頻處理速度比較,GUI 代碼以低得多的更新速度(幀率)運行,并且無法訪問音頻流,那么我們如何讀取這些數(shù)據(jù)呢?對此,本機代碼中有一個特殊函數(shù)可解決此問題:
此函數(shù)的作用就是實現(xiàn)從原生插件讀取浮點數(shù)據(jù)數(shù)組。插件系統(tǒng)不在乎數(shù)據(jù)是什么,但前提是請求不會大幅減慢 UI 或本機代碼的運行速度。對于均衡器和多頻段代碼,有一個名為 FFTAnalyzer 的實用程序類,可輕松從插件中饋入輸入和輸出數(shù)據(jù)并獲得頻譜。此頻譜數(shù)據(jù)隨后由 GetFloatBufferCallback 重新采樣,然后傳遞到 C# UI 代碼。需要重新采樣數(shù)據(jù)的原因是 FFTAnalyzer 以固定的頻率分辨率運行分析,而 GetFloatBufferCallback 只返回請求樣本的數(shù)量,這取決于顯示數(shù)據(jù)的視圖寬度。對于具有最少量 DSP 代碼的非常簡單的插件,您還可以了解 CorrelationMeter 插件,該插件簡單地繪制左聲道幅度與右聲道幅度的關系圖,以便顯示信號的“立體聲程度”。

左:CorrelationMeter 插件的自定義 GUI。

右:覆蓋了頻譜分析的均衡器 GUI(綠色曲線表示源,紅色表示已處理)。
至此,我們還要指出的是,均衡器效果和多頻段效果都是有意保持簡單并且未進行優(yōu)化,但我們認為它們是插件系統(tǒng)支持的更復雜 UI 的參考典范。顯然還需要執(zhí)行大量工作來進行特定于平臺的相關優(yōu)化,并執(zhí)行大量參數(shù)調整以使其真正合理并以最具音樂性的方式響應,等等。我們也可能在某些時候在 Unity 中將上述其中某些效果作為內置插件實現(xiàn),這樣做只是為了方便增加 Unity 的標準插件庫,但我們真誠希望讀者也能接受挑戰(zhàn),制作一些非常棒的插件,說不定它們以后可能變?yōu)閮戎貌寮?-)

卷積混響示例插件。脈沖響應是衰減的隨機噪聲 (由參數(shù)定義)。此插件僅用于演示目的,因為生產插件 應該允許用戶加載任意記錄的脈沖,但底層卷積算法 仍然相同。

在 3 個不同時間標度測量響度的響度監(jiān)測工具的示例。 同樣僅用于演示目的,但這是構建符合 現(xiàn)代響度標準的監(jiān)測工具的良好起點。Unity 中內置了曲線渲染代碼。
與 DSP 時鐘同步
是時候進行一些有趣的練習了。為何僅僅處理聲音而不使用插件系統(tǒng)來生成聲音呢?讓我們嘗試做一些簡單的低音和鼓合成器(喜歡聽迷幻風格的人應該很熟悉),這是用于定義這種音樂流派的部分主要合成器的簡單克隆。請看一下 Plugin_TeeBee.cpp 和 Plugin_TeeDee.cpp。這些簡單的合成器只生成帶有隨機音符的圖譜,并具有一些參數(shù)可用于在合成引擎中調整濾波器、包絡等。同樣,我們不會在這里討論這些細節(jié),僅指出在 ProcessCallback 中讀取 state->dsptick 參數(shù)來確定“song”中的位置。此計數(shù)器是一個全局樣本位置,因此我們只需將其除以樣本中指定的每個音符的長度,并在該除法的余數(shù)為零時向合成引擎觸發(fā)音符事件。這樣,所有插件效果都與同一個基于樣本的時鐘保持同步,如果您想通過這樣的效果播放具有已知節(jié)拍的預錄制音樂,則可以使用該時序信息來對音樂應用節(jié)拍同步的濾波器效果或延遲。


空間化
原生音頻插件 SDK 是空間化 SDK 的基礎,用于開發(fā)根據(jù)每個音頻源進行實例化的自定義空間化效果。 可在此處找到更多相關信息。
前景
這只是將聲音系統(tǒng)部分提升到高性能本機代碼的努力的開始。我們計劃進一步集成到 Unity 的其他部分,以便在混音器之外也能使用這些效果,并擴展 SDK 以便支持除浮點之外的其他參數(shù)類型,能夠支持更出色的默認 GUI 以及二進制數(shù)據(jù)的存儲。
創(chuàng)建自己的插件其樂無窮。希望在 Asset Store 中看到您的大作。;-)
“免責聲明”
雖然設計中有許多相似之處,但 Unity 原生音頻 SDK 并非基于其他插件 SDK(如 Steinberg VST 或 Apple AudioUnit)。應該注意的是,感興趣的讀者很容易使用這個 SDK 實現(xiàn)這些插件的基本包裝器,從而允許在 Unity 中使用這些插件。這不是 Unity 開發(fā)團隊計劃內的工作。正確托管插件的工作很快就會變得非常復雜,而為了處理預期調用順序的所有復雜性以及處理基于本機代碼的自定義 GUI 窗口,相關工作會迅速發(fā)生跨越式增長,這使得它作為示例代碼的實用性降低。
雖然我們確實了解加載 VST或 AU 插件可能非常有用,即使僅使用效果來模擬/測試聲音設計也是如此,但請注意,使用 VST/AU 也會讓您局限于幾個特定平臺?;?Unity SDK 編寫音頻插件的潛在好處在于,它能擴展到支持軟件混音和動態(tài)加載的本機代碼的所有平臺。也就是說,在決定花時間開發(fā)自定義插件之前,可參考一些有效的用例來使用您最喜歡的工具模擬早期聲音設計(或者只是為了能夠在編輯器中使用不會在任何情況下改變聲音的計量插件),所以如果有人想為此開發(fā)一種很好的解決方案,我們非常歡迎。