Android音頻開發之音頻采集
在 Android 系統中,一般使用 AudioRecord
或者 MediaRecord
來采集音頻。
AudioRecord 是一個比較偏底層的API,它可以獲取到一幀幀 PCM 數據,之后可以對這些數據進行處理。
而 MediaRecorder 是基于 AudioRecorder 的 API(最終還是會創建AudioRecord用來與AudioFlinger進行交互) ,它可以直接將采集到的音頻數據轉化為執行的編碼格式,并保存。
直播技術采用的就是 AudioRecorder 采集音頻數據。
本文主要介紹例如 AudioRecord
進行音頻的采集。
基本API
- 獲取最小的緩沖區大小,用于存放 AudioRecord 采集到的音頻數據。
static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)
- AudioRecord構造方法
根據具體的參數配置,請求硬件資源創建一個可以用于采集音頻的 AudioRecord 對象。
參數描述:參考Android音頻開發之音頻基本概念
-
audioResource
音頻采集的來源
-
audioSampleRate
音頻采樣率
-
channelConfig
聲道
-
audioFormat
音頻采樣精度,指定采樣的數據的格式和每次采樣的大小。
-
bufferSizeInBytes
AudioRecord 采集到的音頻數據所存放的緩沖區大小。
//設置采集來源為麥克風
private static final int AUDIO_RESOURCE = MediaRecorder.AudioSource.MIC;
//設置采樣率為44100,目前為常用的采樣率,官方文檔表示這個值可以兼容所有的設置
private final static int AUDIO_SAMPLE_RATE = 44100;
//設置聲道聲道數量為雙聲道
private final static int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO;
//設置采樣精度,將采樣的數據以PCM進行編碼,每次采集的數據位寬為16bit。
private final static int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes)
- 開始采集
開始采集之后,狀態變為RECORDSTATE_RECORDING 。
public void startRecording ()
- 讀取錄制內容,將采集到的數據讀取到緩沖區
方法調用的返回值的狀態碼:
情況異常:
1.ERROR_INVALID_OPERATION if the object wasn't properly initialized
2.ERROR_BAD_VALUE if the parameters don't resolve to valid data and indexes.
情況正常:the number of bytes that were read
public int read (ByteBuffer audioBuffer, int sizeInBytes)
public int read (byte[] audioData, int offsetInBytes, int sizeInBytes)
public int read (short[] audioData, int offsetInShorts, int sizeInShorts)
- 停止采集
停止采集之后,狀態變為 RECORDSTATE_STOPPED 。
public void stop ()
- 獲取AudioRecord的狀態
用于檢測AudioRecord是否確保了獲得適當的硬件資源。在AudioRecord對象實例化之后調用。
STATE_INITIALIZED 初始完畢
STATE_UNINITIALIZED 未初始化
public int getState ()
- 返回當前AudioRecord的采集狀態
public static final int RECORDSTATE_STOPPED = 1; 停止狀態
調用 void stop() 之后的狀態
public static final int RECORDSTATE_RECORDING = 3;正在采集
調用 startRecording () 之后的狀態
public int getRecordingState()
AudioRecord 采集音頻的基本流程
- 權限
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
構造一個
AudioRecord
對象。開始采集。
讀取采集的數據。
停止采集。
構造一個 AudioRecord
對象
AudioRecord audioRecord = new AudioRecord(audioResource, audioSampleRate, channelConfig, audioFormat, bufferSizeInBytes);
獲取 bufferSizeInBytes 值
bufferSizeInBytes 是 AudioRecord 采集到的音頻數據所存放的緩沖區大小。
注意:這個大小不能隨便設置,AudioRecord 提供對應的 API 來獲取這個值。
this.bufferSizeInBytes = AudioRecord.getMinBufferSize(audioSampleRate, channelConfig, audioFormat);
通過 bufferSizeInBytes
返回就可以知道傳入給AudioRecord.getMinBufferSize
的參數是否支持當前的硬件設備。
if (AudioRecord.ERROR_BAD_VALUE == bufferSizeInBytes || AudioRecord.ERROR == bufferSizeInBytes) {
throw new RuntimeException("Unable to getMinBufferSize");
}
//bufferSizeInBytes is available...
開始采集
- 在開始錄音之前,首先要判斷一下
AudioRecord
的狀態是否已經初始化完畢了。
//判斷AudioRecord的狀態是否初始化完畢
//在AudioRecord對象構造完畢之后,就處于AudioRecord.STATE_INITIALIZED狀態了。
int state = audioRecord.getState();
if (state == AudioRecord.STATE_UNINITIALIZED) {
throw new RuntimeException("AudioRecord STATE_UNINITIALIZED");
}
- 開始采集
audioRecord.startRecording();
//開啟線程讀取數據
new Thread(recordTask).start();
讀取采集的數據
上面提到,
AudioRecord
在采集數據時會將數據存放到緩沖區中,因此我們只需要創建一個數據流去從緩沖區中將采集的數據讀取出來即可。
創建一個數據流
,一邊從 AudioRecord
中讀取音頻數據到緩沖區
,一邊將緩沖區
中數據寫入到數據流
。
因為需要使用IO操作,因此讀取數據的過程應該在子線程中執行
//創建一個流,存放從AudioRecord讀取的數據
File saveFile = new File(Environment.getExternalStorageDirectory(), "audio-record.pcm");
DataOutputStream dataOutputStream = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream(saveFile)));
private Runnable recordTask = new Runnable() {
@Override
public void run() {
//設置線程的優先級
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIOR
Log.i(TAG, "設置采集音頻線程優先級");
final byte[] data = new byte[bufferSizeInBytes];
//標記為開始采集狀態
isRecording = true;
Log.i(TAG, "設置當前當前狀態為采集狀態");
//getRecordingState獲取當前AudioReroding是否正在采集數據的狀態
while (isRecording && audioRecord.getRecordingState() == AudioRecord
//讀取采集數據到緩沖區中,read就是讀取到的數據量
final int read = audioRecord.read(data, 0, bufferSizeInBytes);
if (AudioRecord.ERROR_INVALID_OPERATION != read && AudioRecord.E
//將數據寫入到文件中
dataOutputStream.write(buffer,0,read);
}
}
}
};
停止采集
/**
* 停止錄音
*/
public void stopRecord() throws IOException {
Log.i(TAG, "停止錄音,回收AudioRecord對象,釋放內存");
isRecording = false;
if (audioRecord != null) {
if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
audioRecord.stop();
Log.i(TAG, "audioRecord.stop()");
}
if (audioRecord.getState() == AudioRecord.STATE_INITIALIZED) {
audioRecord.release();
Log.i(TAG, "audioRecord.release()");
}
}
}
幾個小問題
采集數據之后,保存的文件為
audio-record.pcm
,這個文件并不能使用普通的播放器播放。它是一個原始的文件,沒有任何播放格式,因此就無法被播放器識別并播放。-
上面的問題可以有兩種解決方法
- 使用 AudioTrack 播放 pcm 格式的音頻數據。
- 將 pcm 數據轉化為 wav 格式的數據,這樣就可以被播放器識別。