翻譯自OpenSL ES Programming Noteshtml
本節中的註釋補充了OpenSL ES 1.0.1規範。android
OpenSL ES編程模型的兩個方面多是新開發人員不熟悉的,即對象和接口之間的區別以及初始化順序。c++
簡單地說,OpenSL ES對象相似於Java和c++等編程語言中的對象概念,只是OpenSL ES對象僅經過其關聯接口可見。這包括全部對象的初始接口,稱爲SLObjectItf
。沒有對象自己的句柄,只有對象的SLObjectItf
接口的句柄。git
首先建立一個OpenSL ES對象,它返回一個SLObjectItf
,而後實例化它。這相似於常見的編程模式,首先構造一個對象(除非缺乏內存或無效參數,不然不會失敗),而後完成初始化(可能因爲缺少資源而失敗)。實例化這步爲實例提供了在須要時分配額外資源的邏輯內存。github
做爲建立對象的API的一部分,應用程序指定了它計劃稍後獲取的所需接口數組。注意,這個數組不會自動得到接口;它僅僅代表了未來獲取它們的意圖。接口被區分爲隱式或顯式。若是之後要得到顯式接口,則必須在數組中列出它。隱式接口不須要在對象建立數組中列出,可是在那裏列出它並無害處。OpenSL ES還有一種稱爲dynamic的接口,它不須要在對象建立數組中指定,能夠在對象建立後添加。Android實現提供了一個方便的特性來避免這種複雜性,這種複雜性在OpenSL ES的Android擴展這篇文章中的對象建立時的動態接口中進行了描述。算法
在建立和實現對象以後,應用程序應該在SLObjectItf
初始化後使用GetInterface
爲它須要的每一個特性獲取接口。編程
最後,該對象能夠經過其接口使用,不過請注意,有些對象須要進一步設置。特別是,帶有URI數據源的音頻播放器須要作更多的準備,以檢測鏈接錯誤。有關詳細信息,請參閱下面的音頻播放器預讀取部分。api
應用程序處理完對象後,應該顯式地銷燬它;參見下面的銷燬部分。數組
對於具備URI數據源的音頻播放器,Object::Realize
分配資源,但不鏈接數據源(準備階段)或開始預讀取數據。一旦將播放器狀態設置爲sl_playstate_pause
或SL_PLAYSTATE_PLAYING
,就會出現這種狀況。安全
在此序列中,有些信息可能直到相對較晚的時候纔會爲人所知。特別是,初始時,Player::GetDuration
返回SL_TIME_UNKNOWN
還有 MuteSolo::GetChannelCount
返回0,或者返回錯誤結果SL_RESULT_PRECONDITIONS_VIOLATED
。當爲已知時,才返回正確值。
其餘最初未知的屬性包括採樣率和基於檢查內容頭的實際的媒體內容類型(與應用程序指定的MIME類型和容器類型相反)。這些也是在準備/預讀取期間稍後肯定的,可是沒有api來檢索它們。
預讀取狀態接口對於檢測什麼時候全部可用信息很是有用,或者您的應用程序能夠按期輪詢。注意,一些信息,例如MP3流的持續時間,可能永遠不會知道。
預取狀態接口對於檢測錯誤也頗有用。註冊一個回調,並至少啓用SL_PREFETCHEVENT_FILLLEVELCHANGE
和SL_PREFETCHEVENT_STATUSCHANGE
事件。若是這兩個事件同時交付,PrefetchStatus::GetFillLevel
報告0級,PrefetchStatus::GetPrefetchStatus
報告SL_PREFETCHSTATUS_UNDERFLOW
,那麼這代表數據源中有一個不可恢復的錯誤。這包括沒法鏈接數據源,由於本地文件名不存在或網絡URI無效。
OpenSL ES的下一個版本預計將添加對數據源中錯誤處理的更加顯式的支持。然而 ,爲了未來的二進制兼容性,咱們打算繼續支持當前不可恢復錯誤報告的方法。
總之,推薦的代碼序列是:
Engine::CreateAudioPlayer
Object:Realize
Object::GetInterface
for SL_IID_PREFETCHSTATUS
PrefetchStatus::SetCallbackEventsMask
PrefetchStatus::SetFillUpdatePeriod
PrefetchStatus::RegisterCallback
Object::GetInterface
for SL_IID_PLAY
Play::SetPlayState
to SL_PLAYSTATE_PAUSED
, or SL_PLAYSTATE_PLAYING
注意:這裏有準備和預讀取;在這段時間內,你的回調會被按期的狀態更新調用。
在退出應用程序時,請確保銷燬全部對象。對象應該按照建立對象的相反順序被銷燬,由於銷燬具備任何依賴對象的對象是不安全的。例如,按如下順序銷燬:音頻播放器和錄音機,輸出混合,最後是引擎。
OpenSL ES不支持自動垃圾收集或接口的引用計數。在您調用Object::Destroy
以後,全部從關聯對象派生的現有接口都將沒法定義。
Android OpenSL ES實例不會檢測到這些接口的不正確使用狀況。在對象被銷燬後繼續使用這些接口可能致使應用程序崩潰或以不可預知的方式運行。
咱們建議您顯式地將主對象接口和全部關聯接口都設置爲NULL,做爲對象銷燬序列的一部分,這樣能夠防止對陳舊接口句柄的意外濫用。
當Volume::EnableStereoPosition
用於啓用單聲道源的立體平移時,總聲波功率級別下降了3分貝。容許總聲波功率水平保持不變是必要的,由於聲源是從一個通道到另外一個通道。所以,只有在你須要的時候,才能啓用立體聲定位。有關更多信息,請參閱Wikipedia關於音頻平移的文章。
當實例檢測到事件時,一般同步調用回調處理程序。對於應用程序,這一點是異步的,所以應該使用非阻塞同步機制來控制應用程序和回調處理程序之間共享變量的訪問權限。在示例代碼(例如緩衝區隊列)中,爲了簡單起見,咱們要麼省略了這個同步,要麼使用了阻塞同步。然而,適當的非阻塞同步對於任何代碼都是相當重要的。
回調處理程序是從內部非應用程序線程調用的,這些線程不attach到Android runtime ,所以它們不具有使用JNI的資格。由於這些內部線程對OpenSL ES實例的完整性相當重要,因此回調處理程序也不該該阻塞或執行過多的工做。
若是回調處理程序須要使用JNI或執行與回調不相稱的工做,則處理程序應該向另外一個線程發佈一個事件來處理。可接受的回調工做負載的示例包括渲染和排隊下一個輸出緩衝區(用於AudioPlayer)、處理剛剛填充的輸入緩衝區和排隊下一個空緩衝區(用於AudioRecorder)或簡單api(如Get系列的大部分)。關於工做負載,請參閱下面的性能部分。
注意,反過來是安全的:已使用JNI的Android應用程序線程能夠直接調用OpenSL ES api,包括那些阻塞的api。可是,主線程不建議使用阻塞調用,由於它們可能致使應用程序不響應(ANR)。
關於調用回調處理程序的線程的決定很大程度上取決於OpenSL ES實現。這種靈活性的緣由是爲了容許未來進行優化,特別是在多核設備上。
回調處理程序運行的線程不能保證在不一樣調用之間具備相同的標識。所以,不要依賴pthread_self()
返回的pthread_t
或gettid()
返回的pid_t
在調用之間保持一致。出於一樣的緣由,不要從回調中使用線程本地存儲(TLS) api,例如pthread_setspecific()
和pthread_getspecific()
。
該實現保證不會對同一對象發生相同類型的併發回調。然而,在不一樣的線程上,對於相同對象的不一樣類型的併發回調是可能的。
因爲OpenSL ES是一個 native C API,調用OpenSL ES的非運行時應用程序線程沒有與運行時相關的開銷,好比垃圾收集暫停。除了下面描述的一個例外,使用OpenSL ES沒有其餘性能優點。特別是,使用OpenSL ES並不能保證比平臺一般提供的更低的音頻延遲和更高的調度優先級。另外一方面,隨着Android平臺和特定設備實現的不斷髮展,OpenSL ES應用程序有望從將來的系統性能改進中獲益。
其中一個改進是支持減小音頻輸出延遲。減小輸出延遲的基礎首先包含在Android 4.1 (API級別16)中,而後在Android 4.2 (API級別17)中繼續進行。這些改進能夠經過OpenSL ES用於設備實現,這些設備實現聲稱具備android.hardware.audio.low_latency
特性。若是設備沒有聲明這個特性,可是支持Android 2.3 (API級別9)或更高,那麼您仍然可使用OpenSL ES API,可是輸出延遲可能會更高。只有當應用程序請求與設備本機輸出配置兼容的緩衝區大小和採樣率時,才使用較低的輸出延遲路徑。這些參數是特定於設備的,應以下所述得到。
從Android 4.2 (API level 17)開始,應用程序能夠查詢平臺原生或最佳輸出採樣率和設備主輸出流的緩衝區大小。當與剛纔提到的特性測試結合使用時,應用程序如今能夠適當地配置本身,以在聲稱支持的設備上下降輸出延遲。
對於Android 4.2 (API級別17)及更早版本,爲了下降延遲,須要兩個或更多的緩衝區計數。從Android 4.3 (API級別18)開始,一個緩衝區計數就足以下降延遲。
全部用於輸出效果的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);
複製代碼
android.media.AudioManager.getProperty()
的使用。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));//單位緩衝區幀數
複製代碼
注意,sampleRate
和framesPerBuffer
都是字符串。首先檢查null,而後使用Integer.parseInt()
將其轉換爲int。 5. 如今使用OpenSL ES建立一個帶有PCM緩衝隊列數據定位器的AudioPlayer。
注意:您可使用音頻緩衝區大小測試應用程序來肯定音頻設備上OpenSL ES音頻應用程序的本機緩衝區大小和採樣率。您還能夠訪問GitHub,查看音頻緩衝大小的示例。
低延遲音頻播放器的數量是有限的。若是您的應用程序須要多個音頻源,請考慮在應用程序級別混合音頻。當您的活動暫停時,請確保銷燬您的音頻播放器,由於它們是與其餘應用程序共享的全局資源。
爲了不出現可聽見的故障,緩衝區隊列回調處理程序必須在一個小而可預測的時間窗口內執行。這一般意味着對互斥對象、條件或I/O操做沒有不可控制的阻塞。相反,應該考慮使用鎖、鎖和超時等待以及非阻塞算法。
渲染下一個緩衝區(用於AudioPlayer)或使用前一個緩衝區(用於AudioRecord)所需的計算時間應該與每次回調的時間大體相同。避免在不肯定的時間內執行的算法,或者在計算中出現問題。若是在任何給定回調中所花費的CPU時間明顯大於平均值,則回調計算就會很激烈。總之,理想的狀況是處理程序的CPU執行時間接近於零,處理程序在不設限時間內不阻塞。
只對如下輸出作到低延遲音頻是可能的:
在某些設備上,因爲須要對揚聲器進行校訂和保護的數字信號處理,揚聲器等待時間比其餘路徑要長。
在某些設備上,因爲須要對揚聲器進行校訂和維護及數字信號處理,揚聲器等待時間比其餘路徑要長。
從Android 5.0 (API Level 21)開始,被選的設備支持較低的音頻輸入延遲。要利用這個特性,首先要確承認以使用上面描述的較低的延遲輸出。低延遲輸出的能力是低延遲輸入特性的先決條件。而後,建立一個AudioRecorder,其採樣率和緩衝區大小與用於輸出的相同。用於輸入效果的OpenSL ES接口排除了較低的延遲路徑。爲了下降延遲,record預設必須使用 SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION
;此預設將禁用特定於設備的數字信號處理,這可能會增長輸入路徑的延遲。有關record預置的更多信息,請參閱OpenSL ES的Android擴展這篇文章中的Android配置接口部分。
對於同時輸入和輸出,每一方都使用單獨的緩衝區隊列完成處理程序。沒有保證這些回調的相對順序,或音頻時鐘的同步,即便雙方使用相同的採樣率。應用程序應該使用適當的緩衝區同步來緩衝數據。
可能獨立的音頻時鐘的一個後果是須要異步採樣率轉換。異步採樣率轉換的一種簡單(雖然不太理想)技術是在零交叉點附近複製或刪除採樣。更復雜的轉換也是可能的。
從Android 7.1 (API級別25)開始,OpenSL ES引入了一種方法來指定音頻路徑的性能模式。 選項是:
SL_ANDROID_PERFORMANCE_NONE
:沒有特定的性能要求。容許硬件和軟件效果。SL_ANDROID_PERFORMANCE_LATENCY
:優先考慮延遲。沒有硬件或軟件的效果。這是默認模式。SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS
:優先考慮延遲,同時仍然容許硬件和軟件效果。SL_ANDROID_PERFORMANCE_POWER_SAVING
:優先考慮節約能源。容許硬件和軟件效果。注意:若是您不須要低延遲路徑,而且但願利用設備內置的音頻效果(例如提升視頻播放的音質),那麼您必須顯式地將性能模式設置爲
SL_ANDROID_PERFORMANCE_NONE
。
要設置性能模式,必須使用Android配置接口調用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));
複製代碼
至於誰能作什麼,Android的安全是在進程級別完成的。Java編程語言代碼沒有比原生代碼作更多,原生代碼也沒有能比Java編程語言代碼作更多事。它們之間惟一的區別是可用的api。
使用OpenSL ES的應用程序必須請求對相似的非原生api所需的權限。例如,若是您的應用程序錄制音頻,那麼它須要android.permission。RECORD_AUDIO
權限。使用音頻效果的應用程序須要android.permission.MODIFY_AUDIO_SETTINGS
。運行網絡URI資源的應用程序須要android.permission.NETWORK
。有關更多信息,請參見使用系統權限。
根據平臺的版本和實現,媒體內容解析器和軟件編解碼器可能在調用OpenSL ES的Android應用程序上下文中運行(硬件編解碼器是抽象的,但與設備相關)。爲了利用解析器和編解碼器漏洞而設計的畸形內容是一個都知道的攻擊方向。咱們建議您只從可靠的來源播放媒體,或者將應用程序分區,以便處理來自不可靠來源的媒體的代碼在一個相對沙箱環境中運行。例如,您能夠在一個單獨的進程中處理來自不可靠來源的媒體。雖然這兩個進程仍然在同一個UID下運行,可是這種分離確實使攻擊更加困難。