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

安卓錄音有兩種實現(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();

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末笛辟,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子序苏,更是在濱河造成了極大的恐慌手幢,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忱详,死亡現(xiàn)場離奇詭異围来,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門监透,熙熙樓的掌柜王于貴愁眉苦臉地迎上來桶错,“玉大人,你說我怎么就攤上這事才漆∨2埽” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵醇滥,是天一觀的道長黎比。 經(jīng)常有香客問我,道長鸳玩,這世上最難降的妖魔是什么阅虫? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮不跟,結(jié)果婚禮上颓帝,老公的妹妹穿的比我還像新娘。我一直安慰自己窝革,他們只是感情好购城,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著虐译,像睡著了一般瘪板。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上漆诽,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天侮攀,我揣著相機與錄音,去河邊找鬼厢拭。 笑死兰英,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的供鸠。 我是一名探鬼主播畦贸,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼楞捂!你這毒婦竟也來了家制?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤泡一,失蹤者是張志新(化名)和其女友劉穎颤殴,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鼻忠,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡涵但,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年杈绸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瞳脓。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖澈侠,靈堂內(nèi)的尸體忽然破棺而出劫侧,到底是詐尸還是另有隱情哨啃,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站魔吐,受9級特大地震影響酬姆,放射性物質(zhì)發(fā)生泄漏奥溺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一谚赎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧诱篷,春花似錦壶唤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至琳省,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蜒谤。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工赋兵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人桦他。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓蔫巩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子圆仔,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354