在Unity上面做音游,當在移動端實機運行起來,會發(fā)現(xiàn),音頻的發(fā)出會有一定的延遲,無論是長音效還是短音效伊约,Unity內(nèi)置的Audio內(nèi)部使用的是FMOD降淮,有以下手段改善
通過設(shè)置稍微改善其延遲的問題
- Edit → Project Settings → Audio → 設(shè)置DSP Buffer size為Best latency(設(shè)置 dsp 緩沖區(qū)大小以優(yōu)化延遲或性能系吩,設(shè)置一個不合適的值會導致安卓設(shè)備的電流音)
- 音頻文件的Load Type為Decompress On Load(讓音頻提前讀取到緩存中)
- 讓音頻文件越小越好
代碼來獲取準確的音軌采樣時間
- 糟糕的方式
AudioSource audioSource;
//100ms延遲
float GetTrackTime()
{
return audioSource.time;
}
- 好的方式
//20ms延遲
float GetTrackTime()
{
return 1f * audioSource.timeSamples / audioSource.clip.frequency;
}
- 更好的方式
double trackStartTime;
void StartMusic()
{
trackStartTime = AudioSettings.dspTime + 1;
audioSource.PlayScheduled(trackStartTime);
}
double GetTrackTime()
{
return AudioSettings.dspTime - trackStartTime;
}
- 最好的方式
Stopwatch stopwatch = new Stopwatch();
void StartMusic()
{
audioSource.Play();
stopwatch.Start();
}
double GetTrackTime()
{
return stopwatch.ElapsedMilliseconds/1000f;
}
//timeSample的Get方法受限與音頻異步的問題是有20ms的延遲的帽衙,但是Set方法幾乎沒有延遲
void SetTrackTime(float time)
{
audioSource.timeSample = (int)(time * audioSource.clip.frequency);
}
但是結(jié)果往往還是無法讓人滿意冀泻,經(jīng)過測試肢专,iOS大多數(shù)設(shè)備的延遲降到10ms以內(nèi),誤差范圍外筷登,相當于沒有了剃根,但是安卓設(shè)備的延遲根據(jù)機型的不同有不同的延遲,大約在100ms~500ms:
image.png
硬件和軟件的原因造成了Android設(shè)備的延遲偏高以及不統(tǒng)一
音頻播放的不同階段
-
模擬音頻輸入
可能有幾種不同的模擬組件前方,例如內(nèi)置麥克風的前置放大器狈醉。在這種情況下廉油,這些模擬組件可被視為“零延遲”,因為它們的真實延遲通常低于1 ms苗傅。
延遲:0 -
模數(shù)轉(zhuǎn)換(ADC)
音頻芯片以預定義的間隔測量輸入的音頻流抒线,并將每個測量值轉(zhuǎn)換為數(shù)字。此預定義間隔稱為采樣率渣慕,以Hz為單位嘶炭。我們的移動音頻普查和延遲測試應用程序顯示,48000 Hz是Android和iOS設(shè)備上大多數(shù)音頻芯片的原生采樣率摇庙,這意味著音頻流每秒采樣48000次旱物。
由于ADC實現(xiàn)通常在內(nèi)部包含過采樣濾波器,因此經(jīng)驗法則是將ADC步長歸因于1 ms延遲卫袒。
現(xiàn)在音頻流已經(jīng)數(shù)字化,從這一點開始单匣,音頻流現(xiàn)在是數(shù)字音頻夕凝。數(shù)字音頻幾乎不會一個接一個地傳播,而是以塊狀稱户秤,稱為“緩沖區(qū)”或“周期”码秉。
延遲:1毫秒 -
總線從音頻芯片傳輸?shù)揭纛l驅(qū)動器
音頻芯片有幾個任務(wù)。它處理ADC和DAC鸡号,在多個輸入和輸出之間切換或混合转砖,應用音量等。它還將離散數(shù)字音頻樣本“分組”到緩沖區(qū)中鲸伴,并處理這些緩沖區(qū)到操作系統(tǒng)的傳輸府蔗。
音頻芯片通過總線連接到CPU,例如USB汞窗,PCI姓赤,F(xiàn)irewire等。每個總線都有自己的傳輸延遲仲吏,具體取決于其內(nèi)部緩沖區(qū)大小和緩沖區(qū)計數(shù)不铆。此處的延遲通常為1 ms(內(nèi)部系統(tǒng)總線上的音頻芯片)至6 ms(具有保守USB總線設(shè)置的USB聲卡)。
延遲:1-6毫秒 -
音頻驅(qū)動程序(ALSA裹唆,OSS等)
音頻驅(qū)動器使用音頻芯片的本機采樣率(大多數(shù)情況下為48000 Hz)以“總線緩沖區(qū)大小”步驟將輸入音頻接收到環(huán)形緩沖器中誓斥。
此環(huán)形緩沖區(qū)在平滑總線傳輸抖動(“粗糙度”)中起著重要作用,并將總線傳輸緩沖區(qū)大小“連接”到操作系統(tǒng)音頻堆棧的緩沖區(qū)大小许帐。從環(huán)形緩沖區(qū)消耗數(shù)據(jù)發(fā)生在操作系統(tǒng)音頻堆棧的緩沖區(qū)大小中劳坑,因此它自然會增加一些延遲。
Android運行在Linux的“頂部”舞吭,大多數(shù)Android設(shè)備使用最流行的Linux音頻驅(qū)動程序系統(tǒng)ALSA(高級Linux聲音架構(gòu))泡垃。ALSA像這樣處理環(huán)形緩沖區(qū):
音頻以“周期大小”步驟從環(huán)形緩沖器中消耗析珊。
環(huán)形緩沖區(qū)的大小是“周期大小”的倍數(shù)。
例如:
周期大小= 480個樣本蔑穴。
期間數(shù)= 2忠寻。
環(huán)形緩沖區(qū)的大小為480x2 = 960個樣本。
音頻輸入接收到一個周期(480個樣本)存和,而音頻堆棧讀取/處理另一個周期(480個樣本)奕剃。
延遲= 1個周期,480個樣本捐腿。它等于48000 Hz時的10 ms纵朋。
環(huán)形緩沖液(960個樣品)
期間(480個樣本) 期間(480個樣本)
常見的周期數(shù)為2,但有些系統(tǒng)可能會更高茄袖。
延遲:一個或多個時期 -
Android音頻硬件抽象層(HAL)
HAL充當Android媒體服務(wù)器和Linux音頻驅(qū)動程序之間的中間人操软。在將Android“移植”到設(shè)備上時,移動設(shè)備的制造商提供HAL實現(xiàn)宪祥。
實現(xiàn)是開放的聂薪,供應商可以自由創(chuàng)建任何類型的HAL代碼。使用預定義的結(jié)構(gòu)進行與媒體服務(wù)器的通信蝗羊。媒體服務(wù)器加載HAL并要求創(chuàng)建具有可選首選參數(shù)的輸入或輸出流藏澳,例如采樣率,緩沖區(qū)大小或音頻效果耀找。
注意:HAL可能會也可能不會根據(jù)參數(shù)執(zhí)行翔悠,并且媒體服務(wù)器必須“適應”HAL。
典型的HAL實現(xiàn)是tinyALSA野芒,用于與ALSA音頻驅(qū)動程序通信蓄愁。一些供應商在這里提供了封閉的源代碼來實現(xiàn)他們認為重要的音頻功
在分析Android源存儲庫中的許多開源HAL實現(xiàn)的代碼之后,我們發(fā)現(xiàn)了一些怪癖复罐,由于奇怪的配置和糟糕的編碼而不必要地增加了音頻路徑的大量延遲和CPU負載涝登。
一個好的HAL實現(xiàn)不應該添加任何延遲。
延遲:0個或更多樣本 -
Audio Flinger
Android媒體服務(wù)器包含兩項服務(wù):- AudioPolicy服務(wù)處理音頻會話和權(quán)限處理效诅,例如啟用麥克風訪問或呼叫中斷胀滚。它與iOS的音頻會話處理非常相似。
- Audio Flinger服務(wù)處理數(shù)字音頻流乱投。
Audio Flinger創(chuàng)建了一個RecordThread咽笼,它充當應用程序和音頻驅(qū)動程序之間的中間人。它的基本工作是: - 使用Android HAL從驅(qū)動程序的環(huán)形緩沖區(qū)中獲取下一個輸入音頻緩沖區(qū)戚炫。
- 如果應用程序請求的采樣率與本機采樣率不同剑刑,則重新采樣緩沖區(qū)。
- 如果應用程序請求的緩沖區(qū)大小不同于本機周期大小,則執(zhí)行其他緩沖施掏。
如果按照這種方式配置Android钮惠,音頻Flinger有一個“快速混音器”路徑。如果用戶應用程序使用本機(Android NDK)代碼并設(shè)置具有本機硬件采樣率和周期大小的音頻緩沖區(qū)隊列七芭,則不會在此步驟中進行重新采樣素挽,額外緩沖或混合(“MixerThread”)。
RecordThread使用“推”方法狸驳,沒有與音頻驅(qū)動程序的任何嚴格同步预明。它試圖在醒來和跑步時進行“有根據(jù)的猜測”,但“推”方法對輟學者更敏感耙箍。低延遲系統(tǒng)始終使用“拉”方法撰糠,其中音頻驅(qū)動程序通過整個音頻鏈“指示”音頻i / o。很明顯辩昆,當最初構(gòu)思阅酪,設(shè)計和開發(fā)Android OS時,低延遲音頻不是優(yōu)先考慮的事情汁针。
延遲:1個周期(最佳情況)
-
Binder
Android主進程間通信系統(tǒng)中的共享內(nèi)存用于在Audio Flinger和用戶應用程序之間傳輸音頻緩沖區(qū)遮斥。它是Android的核心,在Android內(nèi)部隨處使用扇丛。
延遲:0 -
AudioRecord
我們現(xiàn)在處于用戶應用程序的過程中。AudioRecord實現(xiàn)音頻輸入的應用程序端尉辑。這是一個可通過OpenSL ES訪問的客戶端庫功能帆精。
AudioRecord運行一個線程,定期從Audio Flinger獲取一個新緩沖區(qū)隧魄,其音頻Flinger描述了“推送”理念卓练。如果開發(fā)人員將其設(shè)置為僅使用一個緩沖區(qū),則不會為音頻路徑添加延遲购啄。
延遲:0+樣本 -
用戶應用程序
最后襟企,音頻輸入到達其目的地,即用戶應用程序狮含。
由于輸入和輸出線程不相同顽悼,因此用戶應用程序必須在線程之間實現(xiàn)環(huán)形緩沖區(qū)。它的大小最小為2個周期(1個用于音頻輸入几迄,1個用于音頻輸出)蔚龙,但寫得不好的應用程序通常使用暴力并使用更多周期來解決CPU瓶頸。
從這一點開始映胁,我們開始帶著一些音頻輸出返回木羹。
延遲:超過1個周期,通常接近2個(最佳情況) -
AudioTrack
AudioTrack實現(xiàn)音頻輸出的用戶應用程序解孙。這是一個可通過OpenSL ES訪問的客戶端庫功能坑填。它運行一個線程抛人,定期將下一個音頻緩沖區(qū)發(fā)送到Audio Flinger。在Android 4.4.4之后脐瑰,AudioTrack不會為音頻路徑添加延遲妖枚,因為它可以設(shè)置為僅使用一個緩沖區(qū)。
延遲:0+樣本 -
Audio Flinger
創(chuàng)建一個PlaybackThread蚪黑,它作為音頻輸入中描述的RecordThread的反轉(zhuǎn)盅惜。
延遲:1期(最佳情況) -
Android音頻HAL
與音頻輸入相同。
延遲:0個或更多樣本 -
音頻驅(qū)動程序(ALSA忌穿,OSS等)
音頻驅(qū)動器中的音頻輸出與音頻輸入的工作方式相同抒寂,也使用環(huán)形緩沖器。
延遲:一個或多個時期 -
總線從音頻驅(qū)動器傳輸?shù)揭纛l芯片
與音頻輸入的總線傳輸類似掠剑,此處的延遲通常為1 ms至6 ms屈芜。
延遲:1-6毫秒 -
數(shù)模轉(zhuǎn)換(DAC)
在這一點上,ADC的反轉(zhuǎn)朴译,數(shù)字音頻被“轉(zhuǎn)換”回模擬井佑。出于與ADC相同的原因,經(jīng)驗法則是假設(shè)DAC有1 ms的延遲眠寿。
延遲:1毫秒 -
模擬音頻輸出
DAC的輸出信號是模擬音頻躬翁,但它需要額外的組件來驅(qū)動連接的設(shè)備,如耳機盯拱。與模擬音頻輸入類似盒发,模擬組件可被視為“零延遲”。
延遲:0
解決方案
在所有階段中狡逢,除非重寫安卓底層音頻系統(tǒng)宁舰,否則我們開發(fā)者能夠操作的部分只有音頻的播放方式,目前安卓原生的播放方式有三種:
- MediaPlayer
- SoundPool
- AudioTrack(OpenSL)
-
AAudio
第一種用于長音頻播放奢浑,實際測試結(jié)果為音頻延遲依然十分大100ms~500ms之間
第二種和第三種用于短音頻播放蛮艰,短音頻的播放延遲得到了很大的改善,基本徘徊在50ms之間
但是由于無法應用于長音頻的播放雀彼,問題依舊還是沒得到解決
image.png
現(xiàn)有的解決方案推薦
-
superpowered
致力于安卓低延遲做底層開發(fā)的C++ API -
NativeAudio
Unity插件壤蚜,泰國音頻大佬 -
Unity2019
聽說2019優(yōu)化了底層音頻的播放機制
關(guān)于長音頻的延遲在各個機型上的不同而無法自動修正的解決方案
- 收集各種機型預設(shè)一個延遲的值
- 設(shè)計一個體驗良好的界面可以幫助用戶設(shè)置這個延遲的值
Unity2017默認音頻、NativeAudio(OpenSL)详羡、Criware(OpenSL)仍律、Unity2019(OpenSL)默認音頻延遲比較
根據(jù)以下視頻的測試方式,通過音軌的采樣圖來
鏈接:https://pan.baidu.com/s/13sxkAWwFqh-9bxRow3DcWA
提取碼:lrcl
最后測出如下延遲
短音效
單位是10ms
短音效延遲上NativeAudio和Criware是最低的实柠,也是差不多的水泉。
長音效
誤差范圍內(nèi)的差距
效率待實驗。