翻譯自OpenSL ES Programming Notes
本節(jié)中的注釋補(bǔ)充了OpenSL ES 1.0.1規(guī)范。
對象和接口初始化
OpenSL ES編程模型的兩個(gè)方面可能是新開發(fā)人員不熟悉的杖玲,即對象和接口之間的區(qū)別以及初始化順序顿仇。
簡單地說,OpenSL ES對象類似于Java和c++等編程語言中的對象概念摆马,只是OpenSL ES對象僅通過其關(guān)聯(lián)接口可見臼闻。這包括所有對象的初始接口,稱為SLObjectItf
囤采。沒有對象本身的句柄述呐,只有對象的SLObjectItf
接口的句柄。
首先創(chuàng)建一個(gè)OpenSL ES對象蕉毯,它返回一個(gè)SLObjectItf
乓搬,然后實(shí)例化它。這類似于常見的編程模式代虾,首先構(gòu)造一個(gè)對象(除非缺少內(nèi)存或無效參數(shù)进肯,否則不會失敗),然后完成初始化(可能由于缺乏資源而失敗)棉磨。實(shí)例化這步為實(shí)例提供了在需要時(shí)分配額外資源的邏輯內(nèi)存坷澡。
作為創(chuàng)建對象的API的一部分,應(yīng)用程序指定了它計(jì)劃稍后獲取的所需接口數(shù)組含蓉。注意频敛,這個(gè)數(shù)組不會自動獲得接口;它僅僅表明了將來獲取它們的意圖。接口被區(qū)分為隱式或顯式馅扣。如果以后要獲得顯式接口斟赚,則必須在數(shù)組中列出它。隱式接口不需要在對象創(chuàng)建數(shù)組中列出差油,但是在那里列出它并沒有害處拗军。OpenSL ES還有一種稱為dynamic的接口任洞,它不需要在對象創(chuàng)建數(shù)組中指定,可以在對象創(chuàng)建后添加发侵。Android實(shí)現(xiàn)提供了一個(gè)方便的特性來避免這種復(fù)雜性交掏,這種復(fù)雜性在OpenSL ES的Android擴(kuò)展這篇文章中的對象創(chuàng)建時(shí)的動態(tài)接口中進(jìn)行了描述。
在創(chuàng)建和實(shí)現(xiàn)對象之后刃鳄,應(yīng)用程序應(yīng)該在SLObjectItf
初始化后使用GetInterface
為它需要的每個(gè)特性獲取接口盅弛。
最后,該對象可以通過其接口使用叔锐,不過請注意挪鹏,有些對象需要進(jìn)一步設(shè)置。特別是愉烙,帶有URI數(shù)據(jù)源的音頻播放器需要做更多的準(zhǔn)備讨盒,以檢測連接錯誤。有關(guān)詳細(xì)信息步责,請參閱下面的音頻播放器預(yù)讀取部分返顺。
應(yīng)用程序處理完對象后,應(yīng)該顯式地銷毀它;參見下面的銷毀部分蔓肯。
音頻播放器預(yù)讀取
對于具有URI數(shù)據(jù)源的音頻播放器创南,Object::Realize
分配資源,但不連接數(shù)據(jù)源(準(zhǔn)備階段)或開始預(yù)讀取數(shù)據(jù)省核。一旦將播放器狀態(tài)設(shè)置為sl_playstate_pause
或SL_PLAYSTATE_PLAYING
稿辙,就會出現(xiàn)這種情況。
在此序列中气忠,有些信息可能直到相對較晚的時(shí)候才會為人所知邻储。特別是,初始時(shí)旧噪,Player::GetDuration
返回SL_TIME_UNKNOWN
還有 MuteSolo::GetChannelCount
返回0吨娜,或者返回錯誤結(jié)果SL_RESULT_PRECONDITIONS_VIOLATED
。當(dāng)為已知時(shí)淘钟,才返回正確值宦赠。
其他最初未知的屬性包括采樣率和基于檢查內(nèi)容頭的實(shí)際的媒體內(nèi)容類型(與應(yīng)用程序指定的MIME類型和容器類型相反)。這些也是在準(zhǔn)備/預(yù)讀取期間稍后確定的米母,但是沒有api來檢索它們勾扭。
預(yù)讀取狀態(tài)接口對于檢測何時(shí)所有可用信息非常有用,或者您的應(yīng)用程序可以定期輪詢铁瞒。注意妙色,一些信息,例如MP3流的持續(xù)時(shí)間慧耍,可能永遠(yuǎn)不會知道身辨。
預(yù)取狀態(tài)接口對于檢測錯誤也很有用丐谋。注冊一個(gè)回調(diào),并至少啟用SL_PREFETCHEVENT_FILLLEVELCHANGE
和SL_PREFETCHEVENT_STATUSCHANGE
事件煌珊。如果這兩個(gè)事件同時(shí)交付号俐,PrefetchStatus::GetFillLevel
報(bào)告0級,PrefetchStatus::GetPrefetchStatus
報(bào)告SL_PREFETCHSTATUS_UNDERFLOW
定庵,那么這表明數(shù)據(jù)源中有一個(gè)不可恢復(fù)的錯誤吏饿。這包括無法連接數(shù)據(jù)源,因?yàn)楸镜匚募淮嬖诨蚓W(wǎng)絡(luò)URI無效洗贰。
OpenSL ES的下一個(gè)版本預(yù)計(jì)將添加對數(shù)據(jù)源中錯誤處理的更加顯式的支持找岖。然而 陨倡,為了將來的二進(jìn)制兼容性敛滋,我們打算繼續(xù)支持當(dāng)前不可恢復(fù)錯誤報(bào)告的方法。
總之兴革,推薦的代碼序列是:
Engine::CreateAudioPlayer
Object:Realize
-
Object::GetInterface
forSL_IID_PREFETCHSTATUS
PrefetchStatus::SetCallbackEventsMask
PrefetchStatus::SetFillUpdatePeriod
PrefetchStatus::RegisterCallback
-
Object::GetInterface
forSL_IID_PLAY
-
Play::SetPlayState
toSL_PLAYSTATE_PAUSED
, orSL_PLAYSTATE_PLAYING
注意:這里有準(zhǔn)備和預(yù)讀取;在這段時(shí)間內(nèi)绎晃,你的回調(diào)會被定期的狀態(tài)更新調(diào)用。
銷毀
在退出應(yīng)用程序時(shí)杂曲,請確保銷毀所有對象庶艾。對象應(yīng)該按照創(chuàng)建對象的相反順序被銷毀,因?yàn)殇N毀具有任何依賴對象的對象是不安全的擎勘。例如咱揍,按以下順序銷毀:音頻播放器和錄音機(jī),輸出混合棚饵,最后是引擎煤裙。
OpenSL ES不支持自動垃圾收集或接口的引用計(jì)數(shù)。在您調(diào)用Object::Destroy
之后噪漾,所有從關(guān)聯(lián)對象派生的現(xiàn)有接口都將無法定義硼砰。
Android OpenSL ES實(shí)例不會檢測到這些接口的不正確使用情況。在對象被銷毀后繼續(xù)使用這些接口可能導(dǎo)致應(yīng)用程序崩潰或以不可預(yù)知的方式運(yùn)行欣硼。
我們建議您顯式地將主對象接口和所有關(guān)聯(lián)接口都設(shè)置為NULL题翰,作為對象銷毀序列的一部分,這樣可以防止對陳舊接口句柄的意外濫用诈胜。
立體聲平移
當(dāng)Volume::EnableStereoPosition
用于啟用單聲道源的立體平移時(shí)豹障,總聲波功率級別降低了3分貝。允許總聲波功率水平保持不變是必要的焦匈,因?yàn)槁曉词菑囊粋€(gè)通道到另一個(gè)通道沼填。因此,只有在你需要的時(shí)候括授,才能啟用立體聲定位坞笙。有關(guān)更多信息岩饼,請參閱Wikipedia關(guān)于音頻平移的文章。
回調(diào)和線程
當(dāng)實(shí)例檢測到事件時(shí)薛夜,通常同步調(diào)用回調(diào)處理程序籍茧。對于應(yīng)用程序,這一點(diǎn)是異步的梯澜,因此應(yīng)該使用非阻塞同步機(jī)制來控制應(yīng)用程序和回調(diào)處理程序之間共享變量的訪問權(quán)限寞冯。在示例代碼(例如緩沖區(qū)隊(duì)列)中,為了簡單起見晚伙,我們要么省略了這個(gè)同步吮龄,要么使用了阻塞同步。然而咆疗,適當(dāng)?shù)姆亲枞綄τ谌魏未a都是至關(guān)重要的漓帚。
回調(diào)處理程序是從內(nèi)部非應(yīng)用程序線程調(diào)用的,這些線程不attach到Android runtime 午磁,因此它們不具備使用JNI的資格尝抖。因?yàn)檫@些內(nèi)部線程對OpenSL ES實(shí)例的完整性至關(guān)重要,所以回調(diào)處理程序也不應(yīng)該阻塞或執(zhí)行過多的工作迅皇。
如果回調(diào)處理程序需要使用JNI或執(zhí)行與回調(diào)不相稱的工作昧辽,則處理程序應(yīng)該向另一個(gè)線程發(fā)布一個(gè)事件來處理〉峭牵可接受的回調(diào)工作負(fù)載的示例包括渲染和排隊(duì)下一個(gè)輸出緩沖區(qū)(用于AudioPlayer)搅荞、處理剛剛填充的輸入緩沖區(qū)和排隊(duì)下一個(gè)空緩沖區(qū)(用于AudioRecorder)或簡單api(如Get系列的大部分)。關(guān)于工作負(fù)載框咙,請參閱下面的性能部分咕痛。
注意,反過來是安全的:已使用JNI的Android應(yīng)用程序線程可以直接調(diào)用OpenSL ES api扁耐,包括那些阻塞的api暇检。但是,主線程不建議使用阻塞調(diào)用婉称,因?yàn)樗鼈兛赡軐?dǎo)致應(yīng)用程序不響應(yīng)(ANR)块仆。
關(guān)于調(diào)用回調(diào)處理程序的線程的決定很大程度上取決于OpenSL ES實(shí)現(xiàn)。這種靈活性的原因是為了允許將來進(jìn)行優(yōu)化王暗,特別是在多核設(shè)備上悔据。
回調(diào)處理程序運(yùn)行的線程不能保證在不同調(diào)用之間具有相同的標(biāo)識。因此俗壹,不要依賴pthread_self()
返回的pthread_t
或gettid()
返回的pid_t
在調(diào)用之間保持一致科汗。出于同樣的原因,不要從回調(diào)中使用線程本地存儲(TLS) api绷雏,例如pthread_setspecific()
和pthread_getspecific()
头滔。
該實(shí)現(xiàn)保證不會對同一對象發(fā)生相同類型的并發(fā)回調(diào)怖亭。然而,在不同的線程上坤检,對于相同對象的不同類型的并發(fā)回調(diào)是可能的兴猩。
性能
由于OpenSL ES是一個(gè) native C API,調(diào)用OpenSL ES的非運(yùn)行時(shí)應(yīng)用程序線程沒有與運(yùn)行時(shí)相關(guān)的開銷早歇,比如垃圾收集暫停倾芝。除了下面描述的一個(gè)例外,使用OpenSL ES沒有其他性能優(yōu)勢箭跳。特別是晨另,使用OpenSL ES并不能保證比平臺通常提供的更低的音頻延遲和更高的調(diào)度優(yōu)先級。另一方面谱姓,隨著Android平臺和特定設(shè)備實(shí)現(xiàn)的不斷發(fā)展借尿,OpenSL ES應(yīng)用程序有望從未來的系統(tǒng)性能改進(jìn)中獲益。
其中一個(gè)改進(jìn)是支持減少音頻輸出延遲逝段。減少輸出延遲的基礎(chǔ)首先包含在Android 4.1 (API級別16)中垛玻,然后在Android 4.2 (API級別17)中繼續(xù)進(jìn)行割捅。這些改進(jìn)可以通過OpenSL ES用于設(shè)備實(shí)現(xiàn)奶躯,這些設(shè)備實(shí)現(xiàn)聲稱具有android.hardware.audio.low_latency
特性。如果設(shè)備沒有聲明這個(gè)特性亿驾,但是支持Android 2.3 (API級別9)或更高嘹黔,那么您仍然可以使用OpenSL ES API,但是輸出延遲可能會更高莫瞬。只有當(dāng)應(yīng)用程序請求與設(shè)備本機(jī)輸出配置兼容的緩沖區(qū)大小和采樣率時(shí)儡蔓,才使用較低的輸出延遲路徑。這些參數(shù)是特定于設(shè)備的疼邀,應(yīng)如下所述獲得喂江。
從Android 4.2 (API level 17)開始,應(yīng)用程序可以查詢平臺原生或最佳輸出采樣率和設(shè)備主輸出流的緩沖區(qū)大小旁振。當(dāng)與剛才提到的特性測試結(jié)合使用時(shí)获询,應(yīng)用程序現(xiàn)在可以適當(dāng)?shù)嘏渲米约海栽诼暦Q支持的設(shè)備上降低輸出延遲拐袜。
對于Android 4.2 (API級別17)及更早版本吉嚣,為了降低延遲,需要兩個(gè)或更多的緩沖區(qū)計(jì)數(shù)蹬铺。從Android 4.3 (API級別18)開始尝哆,一個(gè)緩沖區(qū)計(jì)數(shù)就足以降低延遲。
所有用于輸出效果的OpenSL ES接口都排除了較低的延遲路徑甜攀。
推薦順序如下:
- 檢查API級別9或更高秋泄,以確認(rèn)OpenSL ES的使用琐馆。
- 檢查
android.hardware.audio.low_latency
特性使用如下代碼:
import android.content.pm.PackageManager;
...
PackageManager pm = getContext().getPackageManager();
boolean claimsFeature = pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_LOW_LATENCY);
- 檢查API級別17或更高,以確認(rèn)
android.media.AudioManager.getProperty()
的使用恒序。 - 使用以下代碼獲得原生或最優(yōu)輸出采樣率和此設(shè)備的主輸出流的緩沖區(qū)大小:
import android.media.AudioManager;
...
AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
String sampleRate = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE));//采樣率
String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER));//單位緩沖區(qū)幀數(shù)
注意啡捶,sampleRate
和framesPerBuffer
都是字符串。首先檢查null奸焙,然后使用Integer.parseInt()
將其轉(zhuǎn)換為int瞎暑。
- 現(xiàn)在使用OpenSL ES創(chuàng)建一個(gè)帶有PCM緩沖隊(duì)列數(shù)據(jù)定位器的AudioPlayer。
注意:您可以使用音頻緩沖區(qū)大小測試應(yīng)用程序來確定音頻設(shè)備上OpenSL ES音頻應(yīng)用程序的本機(jī)緩沖區(qū)大小和采樣率与帆。您還可以訪問GitHub了赌,查看音頻緩沖大小的示例。
低延遲音頻播放器的數(shù)量是有限的玄糟。如果您的應(yīng)用程序需要多個(gè)音頻源勿她,請考慮在應(yīng)用程序級別混合音頻。當(dāng)您的活動暫停時(shí)阵翎,請確保銷毀您的音頻播放器逢并,因?yàn)樗鼈兪桥c其他應(yīng)用程序共享的全局資源。
為了避免出現(xiàn)可聽見的故障郭卫,緩沖區(qū)隊(duì)列回調(diào)處理程序必須在一個(gè)小而可預(yù)測的時(shí)間窗口內(nèi)執(zhí)行砍聊。這通常意味著對互斥對象、條件或I/O操作沒有不可控制的阻塞贰军。相反殿漠,應(yīng)該考慮使用鎖制肮、鎖和超時(shí)等待以及非阻塞算法漓骚。
渲染下一個(gè)緩沖區(qū)(用于AudioPlayer)或使用前一個(gè)緩沖區(qū)(用于AudioRecord)所需的計(jì)算時(shí)間應(yīng)該與每次回調(diào)的時(shí)間大致相同废境。避免在不確定的時(shí)間內(nèi)執(zhí)行的算法,或者在計(jì)算中出現(xiàn)問題贰盗。如果在任何給定回調(diào)中所花費(fèi)的CPU時(shí)間明顯大于平均值许饿,則回調(diào)計(jì)算就會很激烈《嬗總之陋率,理想的情況是處理程序的CPU執(zhí)行時(shí)間接近于零,處理程序在不設(shè)限時(shí)間內(nèi)不阻塞书释。
只對以下輸出做到低延遲音頻是可能的:
- 設(shè)備內(nèi)置揚(yáng)聲器翘贮。
- 有線耳麥。
- 有線耳機(jī)爆惧。
- 線路輸出(音響)狸页。
- USB數(shù)字音頻。
在某些設(shè)備上,由于需要對揚(yáng)聲器進(jìn)行校正和保護(hù)的數(shù)字信號處理芍耘,揚(yáng)聲器等待時(shí)間比其他路徑要長址遇。
在某些設(shè)備上,由于需要對揚(yáng)聲器進(jìn)行校正和維護(hù)及數(shù)字信號處理斋竞,揚(yáng)聲器等待時(shí)間比其他路徑要長倔约。
從Android 5.0 (API Level 21)開始,被選的設(shè)備支持較低的音頻輸入延遲坝初。要利用這個(gè)特性浸剩,首先要確認(rèn)可以使用上面描述的較低的延遲輸出。低延遲輸出的能力是低延遲輸入特性的先決條件鳄袍。然后绢要,創(chuàng)建一個(gè)AudioRecorder,其采樣率和緩沖區(qū)大小與用于輸出的相同拗小。用于輸入效果的OpenSL ES接口排除了較低的延遲路徑重罪。為了降低延遲,record預(yù)設(shè)必須使用 SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION
;此預(yù)設(shè)將禁用特定于設(shè)備的數(shù)字信號處理哀九,這可能會增加輸入路徑的延遲剿配。有關(guān)record預(yù)置的更多信息,請參閱OpenSL ES的Android擴(kuò)展這篇文章中的Android配置接口部分阅束。
對于同時(shí)輸入和輸出呼胚,每一方都使用單獨(dú)的緩沖區(qū)隊(duì)列完成處理程序。沒有保證這些回調(diào)的相對順序围俘,或音頻時(shí)鐘的同步砸讳,即使雙方使用相同的采樣率琢融。應(yīng)用程序應(yīng)該使用適當(dāng)?shù)木彌_區(qū)同步來緩沖數(shù)據(jù)界牡。
可能獨(dú)立的音頻時(shí)鐘的一個(gè)后果是需要異步采樣率轉(zhuǎn)換。異步采樣率轉(zhuǎn)換的一種簡單(雖然不太理想)技術(shù)是在零交叉點(diǎn)附近復(fù)制或刪除采樣漾抬。更復(fù)雜的轉(zhuǎn)換也是可能的宿亡。
性能模式
從Android 7.1 (API級別25)開始,OpenSL ES引入了一種方法來指定音頻路徑的性能模式纳令。
選項(xiàng)是:
-
SL_ANDROID_PERFORMANCE_NONE
:沒有特定的性能要求挽荠。允許硬件和軟件效果。 -
SL_ANDROID_PERFORMANCE_LATENCY
:優(yōu)先考慮延遲平绩。沒有硬件或軟件的效果圈匆。這是默認(rèn)模式。 -
SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS
:優(yōu)先考慮延遲捏雌,同時(shí)仍然允許硬件和軟件效果跃赚。 -
SL_ANDROID_PERFORMANCE_POWER_SAVING
:優(yōu)先考慮節(jié)約能源。允許硬件和軟件效果。
注意:如果您不需要低延遲路徑纬傲,并且希望利用設(shè)備內(nèi)置的音頻效果(例如提高視頻播放的音質(zhì))满败,那么您必須顯式地將性能模式設(shè)置為
SL_ANDROID_PERFORMANCE_NONE
。
要設(shè)置性能模式叹括,必須使用Android配置接口調(diào)用SetConfiguration
算墨,如下所示:
// Obtain the Android configuration interface using a previously configured SLObjectItf.
SLAndroidConfigurationItf configItf = nullptr;
(*objItf)->GetInterface(objItf, SL_IID_ANDROIDCONFIGURATION, &configItf);
// Set the performance mode.
SLuint32 performanceMode = SL_ANDROID_PERFORMANCE_NONE;
result = (*configItf)->SetConfiguration(configItf, SL_ANDROID_KEY_PERFORMANCE_MODE,
&performanceMode, sizeof(performanceMode));
安全與權(quán)限
至于誰能做什么,Android的安全是在進(jìn)程級別完成的汁雷。Java編程語言代碼沒有比原生代碼做更多净嘀,原生代碼也沒有能比Java編程語言代碼做更多事。它們之間唯一的區(qū)別是可用的api侠讯。
使用OpenSL ES的應(yīng)用程序必須請求對類似的非原生api所需的權(quán)限面粮。例如,如果您的應(yīng)用程序錄制音頻继低,那么它需要android.permission熬苍。RECORD_AUDIO
權(quán)限。使用音頻效果的應(yīng)用程序需要android.permission.MODIFY_AUDIO_SETTINGS
袁翁。運(yùn)行網(wǎng)絡(luò)URI資源的應(yīng)用程序需要android.permission.NETWORK
柴底。有關(guān)更多信息,請參見使用系統(tǒng)權(quán)限粱胜。
根據(jù)平臺的版本和實(shí)現(xiàn)柄驻,媒體內(nèi)容解析器和軟件編解碼器可能在調(diào)用OpenSL ES的Android應(yīng)用程序上下文中運(yùn)行(硬件編解碼器是抽象的,但與設(shè)備相關(guān))焙压。為了利用解析器和編解碼器漏洞而設(shè)計(jì)的畸形內(nèi)容是一個(gè)都知道的攻擊方向鸿脓。我們建議您只從可靠的來源播放媒體,或者將應(yīng)用程序分區(qū)涯曲,以便處理來自不可靠來源的媒體的代碼在一個(gè)相對沙箱環(huán)境中運(yùn)行野哭。例如,您可以在一個(gè)單獨(dú)的進(jìn)程中處理來自不可靠來源的媒體幻件。雖然這兩個(gè)進(jìn)程仍然在同一個(gè)UID下運(yùn)行拨黔,但是這種分離確實(shí)使攻擊更加困難。