廢話
權限、權限、權限,必須要先獲取了錄音權限,其他的事情晚點再說。
另外,新版本的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);
}
}