Android 音視訊開發 - 使用AudioRecord採集音訊
序言
AudioRecord 是 Android 系統提供的用於實現錄音功能的 API,官方文件是這麼解釋的:
AndioRecord類的主要功能是讓各種Java應用能夠管理音訊資源,以便它們通過此類能夠錄製聲音相關的硬體所收集的聲音。此功能的實現就是通過“pulling”(讀取)AudioRecord物件的聲音資料來完成的。在錄音過程中,應用所需要做的就是通過後面三個類方法中的一個去及時地獲取AudioRecord物件的錄音資料。AudioRecord類提供的三個獲取聲音資料的方法分別是read(byte[], int, int),、read(short[], int, int)和read(ByteBuffer, int)。無論選擇使用那一個方法都必須事先設定方便使用者的聲音資料儲存格式。
開始錄音的時候,AudioRecord需要初始化一個相關聯的聲音buffer, 這個buffer主要是用來儲存新的聲音資料。這個buffer的大小,我們可以在物件構造期間去指定。它表明一個AudioRecord物件還沒有被讀取(同步)聲音資料前能錄多長的音(即一次可以錄製的聲音容量)。聲音資料從音訊硬體中被讀出,資料大小不超過整個錄音資料的大小(可以分多次讀出),即每次讀取初始化buffer容量的資料。
實現Android錄音的流程為:
- 構造一個AudioRecord物件,其中最小錄音快取buffer大小可以通過getMinBufferSize方法得到。如果buffer容量過小,將導致物件構造的失敗。
- 初始化一個buffer,該buffer大於等於AudioRecord物件用於寫聲音資料的buffer大小。
- 開始錄音
- 建立一個數據流,一邊從AudioRecord中讀取聲音資料到初始化的buffer,一邊將buffer中資料匯入資料流。
- 關閉資料流
- 停止錄音
其中,構造 AudioRecord 物件,需要這麼幾個引數:
- 音訊源:一般可以使用麥克風作為採集音訊的資料來源。
- 取樣率:一秒內對聲音資料的取樣次數,取樣率越高,音質越好。
- 音訊通道:單聲道,雙聲道等。
- 音訊格式:一般選用 PCM 格式,即原始的音訊樣本。
- 緩衝區大小:音訊資料寫入緩衝區的總數,可以通過 AudioRecord.getMinBufferSize 獲取最小的緩衝區。
注意,最後生成的音訊檔案是 PCM 格式的,也就是最原始的音訊資料,它沒有頭資訊,不能直接播放,必須轉換成可識別的格式才行。這裡我們把它轉成 WAV 格式,在檔案的資料開頭加入 WAVE HEAD 即可。
用程式碼實踐一下錄音的過程
- 建立錄音物件,指定具體的引數。
public void createAudio(String fileName, int audioSource, int sampleRateInHz, int channelConfig, int audioFormat) { // 獲得緩衝區位元組大小 mBufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); mAudioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, mBufferSizeInBytes); int state = mAudioRecord.getState(); Log.i(TAG, "createAudio state:" + state + ", initialized:" + (state == AudioRecord.STATE_INITIALIZED)); mFileName = fileName; mStatus = Status.STATUS_READY; }
- 開始錄音,不斷讀取 Buffer 中聲音資料,並儲存到檔案。
public void startRecord() { if (mStatus == Status.STATUS_NO_READY || mAudioRecord == null) { throw new IllegalStateException("錄音尚未初始化"); } if (mStatus == Status.STATUS_START) { throw new IllegalStateException("正在錄音..."); } Log.d(TAG, "===startRecord==="); mAudioRecord.startRecording(); String currentFileName = FileUtils.getPcmFilePath(mContext, mFileName); final String finalFileName = currentFileName; //將錄音狀態設定成正在錄音狀態 mStatus = Status.STATUS_START; //使用執行緒池管理執行緒 mExecutorService.execute(new Runnable() { @Override public void run() { writeDataToFile(finalFileName); } }); } private void writeDataToFile(String currentFileName) { byte[] audioData = new byte[mBufferSizeInBytes]; BufferedOutputStream bos = null; int readSize; try { File file = new File(currentFileName); if (file.exists()) { file.delete(); } bos = new BufferedOutputStream(new FileOutputStream(file)); while (mStatus == Status.STATUS_START) { readSize = mAudioRecord.read(audioData, 0, mBufferSizeInBytes); if (AudioRecord.ERROR_INVALID_OPERATION != readSize) { try { bos.write(audioData); if (mRecordStreamListener != null) { mRecordStreamListener.onRecording(audioData, 0, audioData.length); } } catch (IOException e) { Log.e(TAG, e.getMessage()); } } } bos.flush(); if (mRecordStreamListener != null) { mRecordStreamListener.finishRecord(); } } catch (IOException e) { Log.e(TAG, e.getMessage()); } finally { try { if (bos != null) { bos.close(); } } catch (IOException e) { Log.e(TAG, e.getMessage()); } } } public interface RecordStreamListener { /** * 錄音過程中 * * @param bytes * @param offset * @param length */ void onRecording(byte[] bytes, int offset, int length); /** * 錄音完成 */ void finishRecord(); }
- 停止錄音,釋放 AudioRecord
public void stopRecord() { Log.d(TAG, "===stopRecord==="); if (mStatus == Status.STATUS_NO_READY || mStatus == Status.STATUS_READY) { throw new IllegalStateException("錄音尚未開始"); } else { mAudioRecord.stop(); mStatus = Status.STATUS_STOP; release(); } } public void release() { Log.d(TAG, "===release==="); if (mAudioRecord != null) { mAudioRecord.release(); mAudioRecord = null; } mStatus = Status.STATUS_NO_READY; }
PCM 轉 WAV 的程式碼就不貼了,有需要的可以到 GitHub 上檢視。
【附錄】

資料圖
需要資料的朋友可以加入Android架構交流QQ群聊:513088520
點選連結加入群聊【Android移動架構總群】: 加入群聊
獲取免費學習視訊,學習大綱另外還有像高階UI、效能優化、架構師課程、NDK、混合式開發(ReactNative+Weex)等Android高階開發資料免費分享。