版權聲明:本文為衛(wèi)偉學習總結(jié)文章,轉(zhuǎn)載請注明出處!
在Android系統(tǒng)中,一般使用AudioRecord或者MediaRecord來采集音頻。
AudioRecord:是一個比較偏底層的API,它可以獲取到一幀幀PCM數(shù)據(jù),之后可以對這些數(shù)據(jù)進行處理。
MediaRecorder:是基于AudioRecorder的API(最終還是會創(chuàng)建AudioRecord用來與AudioFlinger進行交互),它可以直接將采集到的音頻數(shù)據(jù)轉(zhuǎn)化為執(zhí)行的編碼格式,并保存。
直播技術采用的就是AudioRecorder采集音頻數(shù)據(jù)。
一、基本API
獲取最小的緩沖區(qū)大小,用于AudioRecord采集到的音頻數(shù)據(jù)。
static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)
audioRecord構(gòu)造方法: 根據(jù)具體的參數(shù)配置,請求硬件資源創(chuàng)建一個可以用于采集音頻的AudioRecord對象。
audioSource:音頻采集的來源
audioSampleRate:音頻采樣率
channelConfig: 聲道
audioFormat: 音頻采樣精度,指定采樣的數(shù)據(jù)的格式和每次采樣的大小
bufferSizeBytes:AudioRecord采集的音頻數(shù)據(jù)所存放的緩沖區(qū)大小。
public Params initAudioDevice() {
int[] sampleRates = {48000, 44100, 22050, 16000, 11025,8000,4000};
for (int sampleRate : sampleRates) {
//編碼制式
int audioFormat = mConfig.audioFormat;
// stereo 立體聲,
int channelConfig = mConfig.channelConfig;
int buffsize = 2 * AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate, channelConfig,
audioFormat, buffsize);
if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
continue;
}
this.buffer = new byte[Math.min(4096, buffsize)];
return new Params(sampleRate,
channelConfig == AudioFormat.CHANNEL_CONFIGURATION_STEREO ? 2 : 1);
}
return null;
}
開始采集
開始采集之后,狀態(tài)為RECORDSTATE_RECORDING 。
public void startRecording ()
讀取錄制內(nèi)存,將采集到的數(shù)據(jù)讀取到緩沖區(qū)
方法調(diào)用的返回值的狀態(tài)碼:
情況異常:
- 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)
獲取AudioRecord的狀態(tài)
用于檢測AudioRecord是否確保了獲取適當?shù)挠布Y源。在AudioRecord對象實例化之后調(diào)用。
STATE_INITIALIZED 初始完畢
STATE_UNINITIALIZED 未初始化
public int getState ()
返回當前AudioRecord的采集狀態(tài)
public static final int RECORDSTATE_STOPPED = 1; 停止狀態(tài)
調(diào)用void stop()之后的狀態(tài)
public static final int RECORDSTATE_RECORDING = 3;正在采集
調(diào)用startRecording()之后的狀態(tài)
public int getRecordingState()
二、AudioRecord采集音頻的基本流程
-
權限
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> 構(gòu)造一個AudioRecord對象。
開始采集
讀取采集的數(shù)據(jù)
停止采集
三、Android音頻開發(fā)之音頻采集
構(gòu)造一個AudioRecord對象
AudioRecord audioRecord = new AudioRecord(audioResource, audioSampleRate, channelConfig, audioFormat, bufferSizeInBytes);
獲取bufferSizeLnBytes值
bufferSizeLnBytes是AudioRecord采集的音頻數(shù)據(jù)所存放的緩沖區(qū)大小。
注意:這個大小不能隨便設置,AudioRecord提供對應的API來獲取這個值。
this.bufferSizeInBytes = AudioRecord.getMinBufferSize(audioSampleRate, channelConfig, audioFormat);
通過bufferSizeInBytes返回就可以知道傳入給AudioRecord.getMinBufferSize的參數(shù)是否支持當前的硬件設備。
if (AudioRecord.ERROR_BAD_VALUE == bufferSizeInBytes || AudioRecord.ERROR == bufferSizeInBytes) {
throw new RuntimeException("Unable to getMinBufferSize");
}
//bufferSizeInBytes is available...
開始采集
-
在開始錄音之前,首先要判斷一下AudioRecord的狀態(tài)是否已經(jīng)初始化完畢了。
//判斷AudioRecord的狀態(tài)是否初始化完畢 //在AudioRecord對象構(gòu)造完畢之后,就處于AudioRecord.STATE_INITIALIZED狀態(tài)了。 int state = audioRecord.getState(); if (state == AudioRecord.STATE_UNINITIALIZED) { throw new RuntimeException("AudioRecord STATE_UNINITIALIZED"); } -
開始采集
audioRecord.startRecording(); //開啟線程讀取數(shù)據(jù) new Thread(recordTask).start(); 讀取采集的數(shù)據(jù)
上面提到,AudioRecord在采集數(shù)據(jù)時會將數(shù)據(jù)存放到緩沖區(qū)中,因此我們只需要創(chuàng)建一個數(shù)據(jù)流去從緩沖區(qū)中將采集的數(shù)據(jù)讀取出來即可。
創(chuàng)建一個數(shù)據(jù)流,一邊從AudioRecord中讀取音頻數(shù)據(jù)到緩沖區(qū),一邊將緩沖區(qū)中數(shù)據(jù)寫入到數(shù)據(jù)流。
因為需要使用IO操作,因此讀取數(shù)據(jù)的過程應該在子線程中執(zhí)行
//創(chuàng)建一個流,存放從AudioRecord讀取的數(shù)據(jù)
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() {
//設置線程的優(yōu)先級
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIOR
Log.i(TAG, "設置采集音頻線程優(yōu)先級");
final byte[] data = new byte[bufferSizeInBytes];
//標記為開始采集狀態(tài)
isRecording = true;
Log.i(TAG, "設置當前當前狀態(tài)為采集狀態(tài)");
//getRecordingState獲取當前AudioReroding是否正在采集數(shù)據(jù)的狀態(tài)
while (isRecording && audioRecord.getRecordingState() == AudioRecord
//讀取采集數(shù)據(jù)到緩沖區(qū)中,read就是讀取到的數(shù)據(jù)量
final int read = audioRecord.read(data, 0, bufferSizeInBytes);
if (AudioRecord.ERROR_INVALID_OPERATION != read && AudioRecord.E
//將數(shù)據(jù)寫入到文件中
dataOutputStream.write(buffer,0,read);
}
}
}
};
-
停止采集
/** * 停止錄音 */ public void stopRecord() throws IOException { Log.i(TAG, "停止錄音,回收AudioRecord對象,釋放內(nèi)存"); 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()"); } }
AudioRecord采集PCM音頻原始數(shù)據(jù)完整功能實現(xiàn)代碼:
package com.weiwei.mediarecordertest;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
/**
* AudioRecord 錄音采集
* AudioTrack 實現(xiàn)錄音播放
* @author 60116
*
*/
public class StramActivity extends Activity{
private Button bt_stream_recorder;
private TextView tv_stream_msg;
private ExecutorService mExecutorService;
private long startRecorderTime, stopRecorderTime;
private volatile boolean mIsRecording = false;
private AudioRecord mAudioRecord;
private FileOutputStream mFileOutputStream;
private File mAudioRecordFile;
private byte[] mBuffer;
//buffer 值不能太大 避免OOM
private static int BUFFER_SIZE = 2048;
private boolean mIsPlaying = false;
private Handler mHandler = new Handler(Looper.getMainLooper());
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setTitle("字節(jié)流錄音");
initView();
mExecutorService = Executors.newSingleThreadExecutor();
mBuffer = new byte[BUFFER_SIZE];
}
private void initView() {
bt_stream_recorder = (Button) findViewById(R.id.bt_stream_recorder);
tv_stream_msg = (TextView) findViewById(R.id.tv_stream_msg);
}
public void recorderaudio(View view) {
if(mIsRecording) {
bt_stream_recorder.setText("開始錄音");
//在開始錄音中如果這個值沒有變false,則一直進行,當再次點擊變false時,錄音才停止
mIsRecording = false;
} else {
bt_stream_recorder.setText("停止錄音");
//提交后臺任務 執(zhí)行錄音邏輯
mIsRecording = true;
//提交后臺任務,執(zhí)行錄音邏輯
mExecutorService.submit(new Runnable() {
@Override
public void run() {
startRecorder();
}
});
}
}
/**
* 開始錄音
*/
private void startRecorder() {
//realeseRecorder();
if(!dostart()) recorderFail();
}
/**
* 停止錄音
*/
private void stopRecorder() {
mIsRecording=false;
if (!doStop()) recorderFail();
}
private boolean dostart() {
try {
// 記錄開始錄音時間
startRecorderTime = System.currentTimeMillis();
//創(chuàng)建錄音文件
mAudioRecordFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() +
"/recorderdemo/" + System.currentTimeMillis() + ".pcm");
if (!mAudioRecordFile.getParentFile().exists())
mAudioRecordFile.getParentFile().mkdirs();
// 創(chuàng)建文件輸出流
mFileOutputStream = new FileOutputStream(mAudioRecordFile);
// 配置AudioRecord
int audioSource = MediaRecorder.AudioSource.MIC;
//所有支持android系統(tǒng)都支持
int sampleRate = 44100;
// 單聲道輸入
int channelConfig = AudioFormat.CHANNEL_IN_MONO;
// pcm_16時所有android系統(tǒng)都支持的
int autioFormat = AudioFormat.ENCODING_PCM_16BIT;
//計算AudioRecord內(nèi)部buffer最小
int minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, autioFormat);
//buffer不能小于最低要求,也不能小于我們每次我們讀取的大小。
mAudioRecord = new AudioRecord(audioSource, sampleRate, channelConfig, autioFormat, Math.max(minBufferSize, BUFFER_SIZE));
//開始錄音
mAudioRecord.startRecording();
//循環(huán)讀取數(shù)據(jù),寫入輸出流中
while (mIsRecording) {
//只要還在錄音就一直讀取
int read = mAudioRecord.read(mBuffer, 0, BUFFER_SIZE);
if(read <= 0) {
return false;
} else {
Log.i("Tag8","mBuffer =" +mBuffer.toString());
mFileOutputStream.write(mBuffer, 0, read);
}
}
//退出循環(huán),停止錄音,釋放資源
stopRecorder();
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
if (mAudioRecord != null) {
mAudioRecord.release();
}
}
return true;
}
private boolean recorderFail() {
mHandler.post(new Runnable() {
@Override
public void run() {
bt_stream_recorder.setText("開始錄音");
tv_stream_msg.setText("錄取失敗,請重新錄入");
mIsRecording = false;
Log.i("Tag8", "go here111111111");
}
});
return false;
}
private void realeseRecorder() {
mAudioRecord.release();
}
private boolean doStop() {
//停止錄音,關閉文件輸出流
mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
Log.i("Tag8", "go here");
//記錄結(jié)束時間,統(tǒng)計錄音時長
stopRecorderTime = System.currentTimeMillis();
//大于3秒算成功,在主線程更新UI
final int send = (int) (stopRecorderTime - startRecorderTime) / 1000;
if (send > 3) {
mHandler.post(new Runnable() {
@Override
public void run() {
tv_stream_msg.setText("錄音成功:" + send + "秒");
bt_stream_recorder.setText("開始錄音");
Log.i("Tag8", "go there");
}
});
} else {
recorderFail();
return false;
}
return true;
}
/**
* 播放聲音
* @param view
*/
public void player(View view) {
mExecutorService.submit(new Runnable() {
@Override
public void run() {
if (!mIsPlaying) {
Log.i("Tag8", "go here");
mIsPlaying = true;
Log.i("Tag8","mAudioRecordFile =" +mAudioRecordFile.toString());
doPlay(mAudioRecordFile);
}
}
});
}
private void doPlay(File audioFile) {
if(audioFile != null) {
Log.i("Tag8","go there");
//配置播放器
//音樂類型,揚聲器播放
int streamType = AudioManager.STREAM_MUSIC;
//錄音時采用的采樣頻率,所有播放時同樣的采樣頻率
int sampleRate = 44100;
//單聲道,和錄音時設置的一樣
int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
// 錄音使用16bit,所有播放時同樣采用該方式
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
//流模式
int mode = AudioTrack.MODE_STREAM;
//計算最小buffer大小
int minBufferSize=AudioTrack.getMinBufferSize(sampleRate,channelConfig,audioFormat);
//構(gòu)造AudioTrack 不能小于AudioTrack的最低要求,也不能小于我們每次讀的大小
AudioTrack audioTrack=new AudioTrack(streamType,sampleRate,channelConfig,audioFormat,
Math.max(minBufferSize,BUFFER_SIZE),mode);
audioTrack.setVolume(16f);
//從文件流讀數(shù)據(jù)
FileInputStream inputStream = null;
try {
//循環(huán)讀數(shù)據(jù),寫到播放器去播放
inputStream = new FileInputStream(audioFile);
//循環(huán)讀數(shù)據(jù),寫到播放器去播放
int read;
//只要沒讀完,循環(huán)播放
while((read=inputStream.read(mBuffer)) > 0) {
Log.i("Tag8","read:"+read);
int ret = audioTrack.write(mBuffer, 0, read);
Log.i("Tag8","ret ==="+ret);
//檢查write的返回值,處理錯誤
switch(ret) {
case AudioTrack.ERROR_INVALID_OPERATION:
case AudioTrack.ERROR_BAD_VALUE:
case AudioManager.ERROR_DEAD_OBJECT:
playFail();
return;
default:
break;
}
}
audioTrack.play();
Log.i("Tag8","播放成功。。。。");
} catch (Exception e) {
e.printStackTrace();
Log.i("Tag8","e =" +e.toString());
//讀取失敗
playFail();
} finally {
mIsPlaying = false;
Log.i("Tag8","播放完畢");
//關閉文件輸入流
if(inputStream !=null){
closeStream(inputStream);
}
//播放器釋放
resetQuietly(audioTrack);
}
}
}
private void closeStream(FileInputStream inputStream) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private void resetQuietly(AudioTrack audioTrack) {
try {
audioTrack.stop();
audioTrack.release();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 播放失敗
*/
private void playFail() {
mHandler.post(new Runnable() {
@Override
public void run() {
tv_stream_msg.setText("播放失敗");
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mExecutorService != null) {
mExecutorService.shutdownNow();
}
if (mAudioRecord != null) {
mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
}
}
獲取到的PCM格式的音頻文件


- 采集數(shù)據(jù)之后,保存的文件為audio-record.pcm,這個文件并不能使用普通的播放器播放。它是一個原始的文件,沒有任何播放格式,因此就無法被播放器識別并播放。
- 上面的問題可以有兩種解決方法
- 使用AudioTrack播放pcm格式的音頻數(shù)據(jù)。
- 將pcm數(shù)據(jù)轉(zhuǎn)化為wav格式數(shù)據(jù),這樣就可以被播放器識別啦。