安卓錄音有兩種實現(xiàn)方式顷锰,分別用MediaRecorder和AudioRecord實現(xiàn)角塑,遺憾的是兩種實現(xiàn)方式都沒有暫停的api。
1.MediaRecorder實現(xiàn)暫停的思路是每次點擊暫停都生成一個文件盈咳,用數(shù)組保存該文件的路徑痛阻,在結(jié)束的時候把數(shù)組里所有的路徑對應文件拼接起來生成一個完成的錄音文件油够,但是嘗試發(fā)現(xiàn)MediaRecorder錄制出來的每個文件都帶有頭文件蚁袭,不知道頭文件的格式合并起來的語音無法正常播放。
最終選擇了AudioRecord的方式實現(xiàn)石咬,AudioRecord支持實時文件流處理揩悄,并且錄制出來的原始PCM格式文件是不含任務頭文件的裸文件,合并數(shù)據(jù)之后就簡單了鬼悠。
2.主要的思路是在recoding正在錄音的時候不斷的讀取語音文件流删性,不斷地往文件中追加內(nèi)容,當按了暫停之后停止向文件追加內(nèi)容焕窝,再次點擊開始的時候繼續(xù)往getRecordSdcardPCMFile路徑中追加內(nèi)容蹬挺,直至結(jié)束錄音為止
while (status == recoding) {
audioRecord.read(b, 0, b.length);
? ? //向文件中追加內(nèi)容
? ? mRandomAccessFile.seek(mRandomAccessFile.length());
? ? mRandomAccessFile.write(b, 0, b.length);
? ? //LogUtils.e("buff大小:" + b.length);
}
以下為完成的實現(xiàn)代碼
public class AudioRecoderTool {
?? public static int recodEnd =0;//結(jié)束或者未開始
? ? public static int recoding =1;//正在錄音
? ? public static int recodPasue =2;//暫停中
//音頻輸入-麥克風
? ? private final static int AUDIO_INPUT = MediaRecorder.AudioSource.MIC;
? ? //采用頻率
//44100是目前的標準,但是某些設(shè)備仍然支持22050它掂,16000汗侵,11025
//采樣頻率一般共分為22.05KHz、44.1KHz群发、48KHz三個等級
? ? private final static int AUDIO_SAMPLE_RATE =16000;
? ? //聲道 單聲道 // 設(shè)置音頻的錄制的聲道CHANNEL_IN_STEREO為雙聲道,CHANNEL_CONFIGURATION_MONO為單聲道
? ? private final static int AUDIO_CHANNEL = AudioFormat.CHANNEL_IN_STEREO;
? ? //編碼
? ? private final static int AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
? ? // 緩沖區(qū)字節(jié)大小
? ? private int bufferSizeInBytes =0;
? ? //錄音對象
? ? private AudioRecord audioRecord;
? ? //文件名
? ? private String fileName;
? ? //錄音狀態(tài)
? ? private int status = recodEnd;
? ? //線程池
? ? private ExecutorService mExecutorService;
? ? private OnAudioStatusUpdateListener audioStatusUpdateListener;
? ? private long startTime;
? ? public AudioRecoderTool() {
mExecutorService = Executors.newCachedThreadPool();
? ? }
private void initAudio()
{
// 獲得緩沖區(qū)字節(jié)大小
? ? ? ? bufferSizeInBytes = AudioRecord.getMinBufferSize(AUDIO_SAMPLE_RATE,
? ? ? ? ? ? ? ? AUDIO_CHANNEL, AUDIO_ENCODING);
? ? ? ? audioRecord =new AudioRecord(AUDIO_INPUT, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING, bufferSizeInBytes);
? ? }
private final Handler mHandler =new Handler();
? ? private Runnable mUpdateMicStatusTimer =new Runnable() {
public void run() {
if(status == recoding)
updateMicStatus();
? ? ? ? }
};
? ? private int SPACE =100;// 間隔取樣時間
? ? /**
* 更新錄音時長
*/
? ? private void updateMicStatus() {
if (audioRecord !=null) {
if (null != audioStatusUpdateListener) {
audioStatusUpdateListener.onUpdate(0, System.currentTimeMillis() - startTime);
? ? ? ? ? ? }
mHandler.postDelayed(mUpdateMicStatusTimer, SPACE);
? ? ? ? }
}
public void setOnAudioStatusUpdateListener(OnAudioStatusUpdateListener audioStatusUpdateListener) {
this.audioStatusUpdateListener = audioStatusUpdateListener;
? ? }
/**
* 開始錄音
*
*/
? ? public void startRecord(final String fileName) {
this.fileName = fileName;
? ? ? ? if (audioRecord==null) {
initAudio();
? ? ? ? }
if(status == recoding)
{
LogUtils.e("正在錄音~");
? ? ? ? }
status = recoding;
? ? ? ? Log.d("AudioRecorder", "===startRecord===" + audioRecord.getState());
? ? ? ? /* 獲取開始時間* */
? ? ? ? startTime = System.currentTimeMillis();
? ? ? ? updateMicStatus();
? ? ? ? //使用線程池管理線程
? ? ? ? mExecutorService.execute(new Runnable() {
@Override
public void run() {
writeDataTOFile(fileName);
? ? ? ? ? ? }
});
? ? }
/**
* 暫停錄音
*/
? ? public void pauseRecord() {
status = recodPasue;
? ? ? ? if(audioStatusUpdateListener !=null)
{
audioStatusUpdateListener.onUpdate(0,System.currentTimeMillis() - startTime);
? ? ? ? ? ? audioStatusUpdateListener.onStop(fileName);
? ? ? ? }
}
/**
* 停止錄音
*/
? ? public void stopRecord() {
Log.d("AudioRecorder", "===stopRecord===");
? ? ? ? if (status == recodEnd) {
LogUtils.e("錄音尚未開始");
? ? ? ? }else if(status == recodPasue){//暫停狀態(tài)下直接釋放資源
? ? ? ? ? ? audioRecord.stop();
? ? ? ? ? ? status = recodEnd;
? ? ? ? ? ? release();
? ? ? ? }else
? ? ? ? {
//正在錄音的狀態(tài)下會在writeDataTOFile方法中結(jié)束釋放
? ? ? ? ? ? status = recodEnd;
? ? ? ? }
}
/**
* 釋放資源
*/
? ? public void release() {
Log.d("AudioRecorder", "===release===");
? ? ? ? if (audioRecord !=null) {
audioRecord.release();
? ? ? ? ? ? audioRecord =null;
? ? ? ? }
if(audioStatusUpdateListener !=null)
{
audioStatusUpdateListener.onUpdate(0,System.currentTimeMillis() - startTime);
? ? ? ? ? ? audioStatusUpdateListener.onStop(fileName);
? ? ? ? }
status = recodEnd;
? ? }
/**
* 取消錄音
*/
? ? public void canel() {
fileName =null;
? ? ? ? if (audioRecord !=null) {
audioRecord.release();
? ? ? ? ? ? audioRecord =null;
? ? ? ? }
status = recodEnd;
? ? }
/**
* 將音頻信息寫入文件
*
*/
? ? private void writeDataTOFile(String currentFileName) {
RandomAccessFile mRandomAccessFile =null;
? ? ? ? try {
mRandomAccessFile =new RandomAccessFile(new File(
FileUtils.getRecordSdcardPCMFile(currentFileName)), "rw");
? ? ? ? ? ? byte[] b =new byte[bufferSizeInBytes /4];
? ? ? ? ? ? //開始錄制音頻
? ? ? ? ? ? audioRecord.startRecording();
? ? ? ? ? ? //判斷是否正在錄制
? ? ? ? ? ? status = recoding;
? ? ? ? ? ? while (status == recoding) {
audioRecord.read(b, 0, b.length);
? ? ? ? ? ? ? ? //向文件中追加內(nèi)容
? ? ? ? ? ? ? ? mRandomAccessFile.seek(mRandomAccessFile.length());
? ? ? ? ? ? ? ? mRandomAccessFile.write(b, 0, b.length);
? ? ? ? ? ? ? ? //LogUtils.e("buff大小:" + b.length);
? ? ? ? ? ? }
//停止錄制
? ? ? ? ? ? audioRecord.stop();
? ? ? ? ? ? mRandomAccessFile.close();
? ? ? ? ? ? //釋放資源
? ? ? ? ? ? if(status == recodEnd)
release();
? ? ? ? ? ? LogUtils.e("保存成功");
? ? ? ? }catch (FileNotFoundException e) {
// TODO Auto-generated catch block
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }catch (IOException e) {
// TODO Auto-generated catch block
? ? ? ? ? ? LogUtils.e("異常關(guān)閉文件流");
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
}
public interface OnAudioStatusUpdateListener {
/**
* 錄音中...
*
? ? ? ? * @param db? 當前聲音分貝
? ? ? ? * @param time 錄音時長
*/
? ? ? ? public void onUpdate(double db, long time);
? ? ? ? /**
* 停止錄音
*
? ? ? ? * @param filePath 保存路徑
*/
? ? ? ? public void onStop(String filePath);
? ? }
}
因為錄制出來的是PCM文件发乔,無法直接播放熟妓,為了和IOS端兼容并且考慮文件大小的問題,選用相同時間內(nèi)容較小的.aac格式栏尚。
這里使用的是android-aac-enc這個庫起愈,可以在git上搜索下載就行了。
因為是C庫译仗,需要NDK編譯jni文件夾才可以正常使用抬虽,這里附上生成jni中頭文件的命令好像在JDK10版本之后取消了javah的命令了,這里生成頭文件使用java xxxx.java -h . ? 命令生成.h頭文件纵菌,注意要在xxxx.java文件根目錄下運行阐污。
把上面生成的.h文件(下面紅色箭頭所指的文件)拖到jni根目錄下
生成的文件內(nèi)容是長這個樣的
之后再在jnl根目錄下創(chuàng)建c文件
這里我創(chuàng)建了和jnl原本庫里面文件名相同的aac-enc.c如果想自己創(chuàng)建文件名請修改Android.mk中下圖紅色箭頭的文件名。
到此就可以使用ndk-build命令編譯生成.os文件了咱圆。
之后使用以下方法就可以把PCM錄音文件轉(zhuǎn)成.aac文件了
encoder.init(32000, 2, 16000, 16, FileUtils.getRecordSdcardAACFile(currentRptBean.getVoiceFile()));
? ? ? ? //對二進制代碼進行編碼
? ? ? ? encoder.encode(b);
? ? ? ? //編碼完成
? ? ? ? encoder.uninit();