最魯棒的Android聲音錄制和播放封裝庫了
來自: http://blog.piasy.com/Robust-Android-Audio-encapsulation/
安卓開發過程中一旦開始和硬件打交道,以及涉及到一定的native代碼之后,各種閃退就開始浮出水面了,聲音錄制和播放當然不例外。本文總結了YOLO安卓客戶端大半年來的安卓音頻實踐,整理出一套系統API的封裝,命名為 RxAndroidAudio 。
概覽
安卓平臺和聲音錄制與播放相關的主要是4個類:MediaRecorder,MediaPlayer,AudioRecord和AudioTrack。
MediaRecorder可以錄制視頻和音頻到文件,MediaPlayer可以播放視頻和音頻文件,AudioRecord可以提供接口讀取音頻流數據(byte數組或者short數組),AudioTrack提供接口用于播放音頻流數據。
小結一下,其中MediaRecorder和AudioRecord用于聲音錄制,MediaPlayer和AudioTrack用于聲音播放。AudioRecord和AudioTrack用于操作音頻流數據,操作對象是byte數組(或者short數組),而MediaRecorder和MediaPlayer提供了經過更高層抽象和封裝接口,直接對文件進行操作,而且他倆功能更豐富,同時支持音頻和視頻。
本文會涉及到部分關于聲音錄制和播放更底層的實現,詳細的分析并不是本文的內容范圍了,此外本文主要目標并不是聲音錄制和播放的使用教程,本文主要關注的是上述4個類的使用過程中需要注意的地方。
基于文件的操作
使用MediaRecorder錄制聲音到文件:
- mRecorder.prepare() 調用需要 捕獲 IOException 和 RuntimeException ,注意需要捕獲 RuntimeException 而不是 IllegalStateException ,盡管Java doc中只聲明了會拋出IllegalStateException,但是查看 jni層代碼 可以看到,prepare的調用也是可能會觸發RuntimeException的;
- mRecorder.start() , mRecorder.stop() , mRecorder.reset() 調用也需要捕獲 RuntimeException ,理由同上;
- 無論是prepare還是start,拋出異常之后 都需要reset和release ;
-
需要保證不會對jni層進行多線程的調用,以免出現下面這樣的“靜默閃退”( 參考資料 ),RxAndroidAudio通過 單例 和 synchronized方法 來保證這一點:
A/libc: Fatal signal 11 (SIGSEGV) at 0x00000010 (code=1), thread 9302 (RxComputationTh)
- 當用戶錄完聲音,需要停止錄音,調用stop的時候, 需要sleep一段時間 ,以免最后幾百毫秒錄不上,這有可能是安卓系統音頻編碼器的bug, 參考資料 ;
- 當prepare返回后,有些低端的設備需要再延遲一段時間開始說話,以免開頭幾百毫秒錄不上,可以 在prepare返回后延遲幾百毫秒(例如300ms)再顯示初始化完畢的UI ,原因還需要繼續尋找;
使用MediaPlayer播放聲音文件:
- 和MediaRecorder一樣,prepare,start,stop,reset,release函數的調用都需要捕獲異常;
- 和MediaRecorder一樣,需要保證不會對jni層進行多線程的調用;
- MediaPlayer提供了兩種音頻文件播放方式:通過文件絕對路徑指定播放文件,或者使用資源文件id指定;絕對路徑的方式 需要調用 mPlayer.prepare() ,而資源文件id方式不需要;
- 在 MediaPlayer.OnCompletionListener 的 onCompletion 回調中,需要 延遲一定時間再釋放MediaPlayer ,否則可能導致下次緊接著的播放無法成功(靜默失敗,不會拋出異常),原因還需要繼續尋找;
基于數據流的操作
使用AudioRecord錄制音頻流數據:
- 和MediaRecorder一樣,startRecording需要捕獲異常;
- 和MediaRecorder一樣,需要保證不會對jni層進行多線程的調用;
- mAudioRecord.read 的返回值需要進行錯誤檢查;
- mAudioRecord.read 傳入的參數類型需要進行區分 , ENCODING_PCM_16BIT 格式的錄音需要傳入short數組, ENCODING_PCM_8BIT 格式的錄音需要傳入byte數組,盡管在 jni層的實現 都是一樣的,但是 Java doc 明確說明了這一注意事項,理應遵循;
- RxAndroidAudio使用 ExecutorService 來執行異步任務(從AudioRecord中循環讀取數據);
使用AudioTrack播放音頻流數據:
- 和MediaRecorder一樣,write需要捕獲異常;
- 和MediaRecorder一樣,需要保證不會對jni層進行多線程的調用;
Bonus Part
Reactive!
RxAndroidAudio之所以叫Rx,就是因為它盡可能的提供了Reactive的API。
RxAudioPlayer播放聲音文件:
</div>