安卓實現(xiàn)AudioRecord暫停,繼續(xù)的錄音功能AudioRecord錄音

安卓錄音有兩種實現(xiàn)方式,分別用MediaRecorder和AudioRecord實現(xiàn),遺憾的是兩種實現(xiàn)方式都沒有暫停的api。

1.MediaRecorder實現(xiàn)暫停的思路是每次點擊暫停都生成一個文件,用數(shù)組保存該文件的路徑,在結(jié)束的時候把數(shù)組里所有的路徑對應(yīng)文件拼接起來生成一個完成的錄音文件,但是嘗試發(fā)現(xiàn)MediaRecorder錄制出來的每個文件都帶有頭文件,不知道頭文件的格式合并起來的語音無法正常播放。

最終選擇了AudioRecord的方式實現(xiàn),AudioRecord支持實時文件流處理,并且錄制出來的原始PCM格式文件是不含任務(wù)頭文件的裸文件,合并數(shù)據(jù)之后就簡單了。

2.主要的思路是在recoding正在錄音的時候不斷的讀取語音文件流,不斷地往文件中追加內(nèi)容,當(dāng)按了暫停之后停止向文件追加內(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;//暫停中

//音頻輸入-麥克風(fēng)

? ? private final static int AUDIO_INPUT = MediaRecorder.AudioSource.MIC;

? ? //采用頻率

//44100是目前的標(biāo)準(zhǔn),但是某些設(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? 當(dāng)前聲音分貝

? ? ? ? * @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文件(下面紅色箭頭所指的文件)拖到j(luò)ni根目錄下


生成的文件內(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()));

? ? ? ? //對二進(jìn)制代碼進(jìn)行編碼

? ? ? ? encoder.encode(b);

? ? ? ? //編碼完成

? ? ? ? encoder.uninit();

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容