Android 呼叫系統api錄音的兩種方式(MediaRecorder、AudioRecord)
阿新 • • 發佈:2019-04-18
廢話
許可權、許可權、許可權,必須要先獲取了錄音許可權,其他的事情晚點再說。
另外,新版本的Android 10系統會對錄音有調整,引入了一個錄音焦點的概念,也就是說以前的麥克風只能一個APP使用,必須要等它斷開了別人才能用,現在換成可以搶的形式,也就是如果沒有音焦,程式碼有可能不會報錯,但是是錄不進聲音的。
Android系統API提供的錄音方式就兩種:MediaRecorder、AudioRecord
MediaRecorder:簡易模式,呼叫簡單,只有開始、結束,錄音之後的檔案也是指定編碼格式,系統播放器可以直接播放。
AudioRecord:原始模式,可以暫停、繼續,可以實時獲取到錄音錄製的資料,然後進行一些騷操作,然後錄出來的東西是最原始的pcm資料,系統播放器不能直接播放。
MediaRecorder
話不多說,直接上程式碼,具體用法,直接將需要儲存檔案的路徑通過構造方法傳進去,然後呼叫開始和結束方法即可:
import android.media.MediaRecorder; import android.os.Handler; import java.io.File; import java.io.IOException; /** * 錄音功能 */ public class MediaRecordingUtils { //檔案路徑 private String filePath; private MediaRecorder mMediaRecorder; private final String TAG = "fan"; public static final int MAX_LENGTH = 1000 * 60 * 200;// 最大錄音時長,單位毫秒,1000*60*10; private OnAudioStatusUpdateListener audioStatusUpdateListener; /** * 檔案儲存預設sdcard/record */ public MediaRecordingUtils() { } public MediaRecordingUtils(String filePath) { this.filePath=filePath; // File path = new File(filePath); // if (!path.exists()) // path.mkdirs(); // this.FolderPath = filePath; } private long startTime; private long endTime; /** * 開始錄音 使用aac格式 * 錄音檔案 * * @return */ public void startRecord() { // 開始錄音 /* ①Initial:例項化MediaRecorder物件 */ if (mMediaRecorder == null) mMediaRecorder = new MediaRecorder(); try { /* ②setAudioSource/setVedioSource */ mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 設定麥克風 /* ②設定音訊檔案的編碼:AAC/AMR_NB/AMR_MB/Default 聲音的(波形)的取樣 */ mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); /* * ②設定輸出檔案的格式:THREE_GPP/MPEG-4/RAW_AMR/Default THREE_GPP(3gp格式 * ,H263視訊/ARM音訊編碼)、MPEG-4、RAW_AMR(只支援音訊且音訊編碼要求為AMR_NB) */ mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); // filePath = FolderPath + DateUtil.getTimeForLong() + ".aac"; /* ③準備 */ mMediaRecorder.setOutputFile(filePath); mMediaRecorder.setMaxDuration(MAX_LENGTH); mMediaRecorder.prepare(); /* ④開始 */ mMediaRecorder.start(); // AudioRecord audioRecord. /* 獲取開始時間* */ startTime = System.currentTimeMillis(); updateMicStatus(); ALog.e("fan", "startTime" + startTime); } catch (IllegalStateException e) { ALog.e(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage()); } catch (IOException e) { ALog.e(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage()); } } /** * 停止錄音 */ public long stopRecord() { if (mMediaRecorder == null) return 0L; endTime = System.currentTimeMillis(); //有一些網友反應在5.0以上在呼叫stop的時候會報錯,翻閱了一下谷歌文件發現上面確實寫的有可能會報錯的情況,捕獲異常清理一下就行了,感謝大家反饋! try { mMediaRecorder.stop(); mMediaRecorder.reset(); mMediaRecorder.release(); mMediaRecorder = null; audioStatusUpdateListener.onStop(filePath); filePath = ""; } catch (RuntimeException e) { try { mMediaRecorder.reset(); mMediaRecorder.release(); mMediaRecorder = null; File file = new File(filePath); if (file.exists()) file.delete(); filePath = ""; } catch (Exception e1) { } } return endTime - startTime; } /** * 取消錄音 */ public void cancelRecord() { try { mMediaRecorder.stop(); mMediaRecorder.reset(); mMediaRecorder.release(); mMediaRecorder = null; } catch (RuntimeException e) { mMediaRecorder.reset(); mMediaRecorder.release(); mMediaRecorder = null; } File file = new File(filePath); if (file.exists()) file.delete(); filePath = ""; } private final Handler mHandler = new Handler(); private Runnable mUpdateMicStatusTimer = new Runnable() { public void run() { updateMicStatus(); } }; private int BASE = 1; private int SPACE = 100;// 間隔取樣時間 public void setOnAudioStatusUpdateListener(OnAudioStatusUpdateListener audioStatusUpdateListener) { this.audioStatusUpdateListener = audioStatusUpdateListener; } /** * 更新麥克狀態 */ private void updateMicStatus() { if (mMediaRecorder != null) { double ratio = (double) mMediaRecorder.getMaxAmplitude() / BASE; double db = 0;// 分貝 if (ratio > 1) { db = 20 * Math.log10(ratio); if (null != audioStatusUpdateListener) { audioStatusUpdateListener.onUpdate(db, System.currentTimeMillis() - startTime); } } mHandler.postDelayed(mUpdateMicStatusTimer, SPACE); } } public String getFilePath() { return filePath; } public interface OnAudioStatusUpdateListener { /** * 錄音中... * * @param db 當前聲音分貝 * @param time 錄音時長 */ public void onUpdate(double db, long time); /** * 停止錄音 * * @param filePath 儲存路徑 */ public void onStop(String filePath); } }
AudioRecord
/** * 錄音 * 用法:1-init,filePath檔案的字尾為.pcm 2-start 3-stop * stop之後,所有的音訊資料會以pcm的格式寫入到filePath這個檔案內,並且是末尾新增的方式,而非覆蓋(以達到暫停錄音繼續錄音的效果),需要轉換為其他格式才能讓系統播放器直接播放 */ public class AudioRecordingUtils { //指定音訊源 這個和MediaRecorder是相同的 MediaRecorder.AudioSource.MIC指的是麥克風 private static final int mAudioSource = MediaRecorder.AudioSource.MIC; //指定取樣率 (MediaRecoder 的取樣率通常是8000Hz AAC的通常是44100Hz。 設定取樣率為44100,目前為常用的取樣率,官方文件表示這個值可以相容所有的設定) private static final int mSampleRateInHz = 44100; //指定捕獲音訊的聲道數目。在AudioFormat類中指定用於此的常量 private static final int mChannelConfig = AudioFormat.CHANNEL_IN_STEREO; //立體聲 //指定音訊量化位數 ,在AudioFormaat類中指定了以下各種可能的常量。通常我們選擇ENCODING_PCM_16BIT和ENCODING_PCM_8BIT PCM代表的是脈衝編碼調製,它實際上是原始音訊樣本。 //因此可以設定每個樣本的解析度為16位或者8位,16位將佔用更多的空間和處理能力,表示的音訊也更加接近真實。 private static final int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT; //指定緩衝區大小。呼叫AudioRecord類的getMinBufferSize方法可以獲得。 private AudioRecord audioRecord = null; // 宣告 AudioRecord 物件 private int recordBufSize = 0; // 宣告recoordBufffer的大小欄位 private boolean isRecording = false; private String saveFilePath; // private FileOutputStream os = null; private File mRecordingFile; private OnAudioRecordingListener onAudioRecordingListener; public void init(String filePath, OnAudioRecordingListener onAudioRecordingListener) { this.onAudioRecordingListener = onAudioRecordingListener; saveFilePath = filePath; recordBufSize = AudioRecord.getMinBufferSize(mSampleRateInHz, mChannelConfig, mAudioFormat);//計算最小緩衝區 audioRecord = new AudioRecord(mAudioSource, mSampleRateInHz, mChannelConfig, mAudioFormat, recordBufSize);//建立AudioRecorder物件 //建立一個流,存放從AudioRecord讀取的資料 mRecordingFile = new File(saveFilePath); if (mRecordingFile.exists()) {//音訊檔案儲存過了刪除 mRecordingFile.delete(); } try { mRecordingFile.createNewFile();//建立新檔案 } catch (IOException e) { e.printStackTrace(); ALog.e("lu", "建立儲存音訊檔案出錯"); } } public static double bytes2Double(byte[] arr) { long value = 0; for (int i = 0; i < 8; i++) { value |= ((long) (arr[i] & 0xff)) << (8 * i); } return Double.longBitsToDouble(value); } public void startRecording() { //判斷AudioRecord的狀態是否初始化完畢 //在AudioRecord物件構造完畢之後,就處於AudioRecord.STATE_INITIALIZED狀態了。 if (audioRecord == null || audioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) { ALog.e("尚未初始化完成"); return; } XyObservable.addTask(new XyCallBack() {//開一個子執行緒的意思 private double volume = 0; @Override public void run() { //標記為開始採集狀態 isRecording = true; try { //獲取到檔案的資料流 DataOutputStream mDataOutputStream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(mRecordingFile, true))); byte[] buffer = new byte[recordBufSize]; audioRecord.startRecording();//開始錄音 //getRecordingState獲取當前AudioReroding是否正在採集資料的狀態 while (isRecording && audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) { int bufferReadResult = audioRecord.read(buffer, 0, recordBufSize); for (int i = 0; i < bufferReadResult; i++) { mDataOutputStream.write(buffer[i]); } setFinish();//這裡會調到下面的finish()方法,finish()方法處於UI執行緒中 } mDataOutputStream.close(); } catch (Throwable t) { ALog.e("lu", "Recording Failed"); stopRecording(); } } @Override public void finish() { if (onAudioRecordingListener != null) { onAudioRecordingListener.onChange(volume); } } }); } /** * 暫停錄音 */ public void pauseRecording() { isRecording = false; if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) { audioRecord.stop(); } } //停止錄音 public void stopRecording() { isRecording = false; //停止錄音,回收AudioRecord物件,釋放記憶體 if (audioRecord != null) { if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) { audioRecord.stop(); } if (audioRecord.getState() == AudioRecord.STATE_INITIALIZED) { audioRecord.release(); } } } public interface OnAudioRecordingListener { public void onChange(double volume); } }
然後再附帶一個將原始pcm轉換為wav格式的方法:
public class Pcm2WavUtils {
/**
* PCM檔案轉WAV檔案
*
* @param inPcmFilePath 輸入PCM檔案路徑
* @param outWavFilePath 輸出WAV檔案路徑
* @param sampleRate 取樣率,例如44100
* @param channels 聲道數 單聲道:1或雙聲道:2
* @param bitNum 取樣位數,8或16
*/
public void convertPcm2Wav(String inPcmFilePath, String outWavFilePath, int sampleRate,
int channels, int bitNum) {
FileInputStream in = null;
FileOutputStream out = null;
byte[] data = new byte[1024];
try {
//取樣位元組byte率
long byteRate = sampleRate * channels * bitNum / 8;
in = new FileInputStream(inPcmFilePath);
out = new FileOutputStream(outWavFilePath);
//PCM檔案大小
long totalAudioLen = in.getChannel().size();
//總大小,由於不包括RIFF和WAV,所以是44 - 8 = 36,在加上PCM檔案大小
long totalDataLen = totalAudioLen + 36;
writeWaveFileHeader(out, totalAudioLen, totalDataLen, sampleRate, channels, byteRate);
int length = 0;
while ((length = in.read(data)) > 0) {
out.write(data, 0, length);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 輸出WAV檔案
*
* @param out WAV輸出檔案流
* @param totalAudioLen 整個音訊PCM資料大小
* @param totalDataLen 整個資料大小
* @param sampleRate 取樣率
* @param channels 聲道數
* @param byteRate 取樣位元組byte率
* @throws IOException
*/
private void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,
long totalDataLen, int sampleRate, int channels, long byteRate) throws IOException {
byte[] header = new byte[44];
header[0] = 'R'; // RIFF
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
header[4] = (byte) (totalDataLen & 0xff);//資料大小
header[5] = (byte) ((totalDataLen >> 8) & 0xff);
header[6] = (byte) ((totalDataLen >> 16) & 0xff);
header[7] = (byte) ((totalDataLen >> 24) & 0xff);
header[8] = 'W';//WAVE
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
//FMT Chunk
header[12] = 'f'; // 'fmt '
header[13] = 'm';
header[14] = 't';
header[15] = ' ';//過渡位元組
//資料大小
header[16] = 16; // 4 bytes: size of 'fmt ' chunk
header[17] = 0;
header[18] = 0;
header[19] = 0;
//編碼方式 10H為PCM編碼格式
header[20] = 1; // format = 1
header[21] = 0;
//通道數
header[22] = (byte) channels;
header[23] = 0;
//取樣率,每個通道的播放速度
header[24] = (byte) (sampleRate & 0xff);
header[25] = (byte) ((sampleRate >> 8) & 0xff);
header[26] = (byte) ((sampleRate >> 16) & 0xff);
header[27] = (byte) ((sampleRate >> 24) & 0xff);
//音訊資料傳送速率,取樣率*通道數*取樣深度/8
header[28] = (byte) (byteRate & 0xff);
header[29] = (byte) ((byteRate >> 8) & 0xff);
header[30] = (byte) ((byteRate >> 16) & 0xff);
header[31] = (byte) ((byteRate >> 24) & 0xff);
// 確定系統一次要處理多少個這樣位元組的資料,確定緩衝區,通道數*取樣位數
header[32] = (byte) (channels * 16 / 8);
header[33] = 0;
//每個樣本的資料位數
header[34] = 16;
header[35] = 0;
//Data chunk
header[36] = 'd';//data
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (byte) (totalAudioLen & 0xff);
header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
out.write(header, 0, 44);
}
}