實現(xiàn)錄音功能

我們知道AndroidApI提供了MediaRecorder和AudioRecord兩個類給開發(fā)者來很方便地實現(xiàn)音視頻的錄制(前者可以實現(xiàn)音頻和視頻的錄制计维,后者只能實現(xiàn)音頻的錄制)刘陶。這兩個類都提供了start()和stop()方法用于開始和結(jié)束音頻或視頻的錄制锅风,但令人費解的是這兩個類都沒有提供pause()方法用于暫停錄制音視頻,因為在實際應(yīng)用當(dāng)中淮逻,暫停錄制的功能是非常有必要的琼懊,暫不清楚Google工程師們在設(shè)計API時是如何考量的而沒有添加這個方法,可能另有玄機吧爬早。那既然android自身沒有提供這樣一個方法哼丈,就只有我們自己來實現(xiàn)了,那么問題就來了筛严,就是到底如何實現(xiàn)音頻錄制的暫停方法呢醉旦?別急,先講一下我在工作中所遇到的需求,如下:需實現(xiàn)音頻錄制的暫停功能车胡,并且生成的音頻文件格式必須是m4a格式檬输。為什么項目中音頻文件一定要采用m4a格式的呢?有以下幾點原因:

1. 錄制相同時間的音頻匈棘,使用m4a格式存儲的文件的大小要比使用其它格式類型存儲的文件的大小要小(通過實驗多次丧慈,在相同采樣率16000的情況下,一般錄制5分鐘的音頻主卫,采用m4a格式存儲的音頻文件只有1.2Mb逃默,而采用arm、mp3及其它格式的一般都有2-5Mb)簇搅,這樣當(dāng)用戶需要下載或上傳錄制的音頻文件時完域,可以節(jié)省流量,并且相同壓縮率的前提下瘩将,m4a格式音頻的音質(zhì)相比其它格式的也更高吟税;

2.產(chǎn)品同時擁有Android客戶端和iOS客戶端,那為了避免使用Android客戶端的用戶錄制的音頻上傳到服務(wù)器之后鸟蟹,使用ios客戶端的用戶下載下來發(fā)生無法播放的問題乌妙,我們需統(tǒng)一錄制音頻的存儲格式使兔。由于Iphone手機官方推薦的音頻格式是m4a且對m4a格式的音頻文件支持度較高建钥,再綜合第一點來看,于是我們選擇m4a格式作為音頻文件的存儲格式虐沥。

好了熊经,解釋了為什么音頻錄制文件必須使用m4a存儲格式之后,接下來我們來解決如何實現(xiàn)音頻的錄制的暫停功能欲险。前面講了镐依,Android SDK

API提供了MediaRecorder和AudioRecord兩個類來完成音視頻的錄制方法,我們看下它們兩者之間的特點和區(qū)別:

MediaRecorder:

特性:該類集成了錄音天试、編碼和壓縮等功能槐壳,可根據(jù)設(shè)置的編碼格式的參數(shù)直接生成各種格式的音頻文件(如arm、

mp3或m4a等)喜每,由于集成度較高务唐,因此使用起來簡單,但靈活度不高带兜,不能實現(xiàn)像AudioRecord那樣進行音

頻的實時處理枫笛。

AudioRecord:

特性:該類錄制的音頻為原始的PCM二進制音頻數(shù)據(jù),沒有文件頭和文件尾刚照,生成的PCM文件不能直接使用

Mediaplayer播放刑巧,只能使用AudioTrack播放。使用AudioRecord可以實現(xiàn)邊錄邊播的音頻實時處理。

了解了這兩個類的特性之后啊楚,起初我決定使用MediaRecorder類來解決錄制暫停的問題吠冤,具體的思路如下:

(1)每次觸發(fā)開始錄制和暫停錄制音頻的事件時都單獨保存一個m4a格式的音頻文件,直到最后觸發(fā)停止錄制音頻的事件時恭理,將之前錄制的若干m4a格式的音頻文件合并成一個文件咨演。如圖下:

這種方法比較好理解,也容易想到蚯斯,不過在實現(xiàn)過程中遇到了一個技術(shù)難點薄风,那就是多個m4a格式的音頻文件的合并并不是簡單地將文件的內(nèi)容拷貝到一個文件中,而是要通過分析每一個m4a格式的音頻文件拍嵌,計算出每個文件頭的結(jié)構(gòu)大小遭赂,并將文件頭去掉,再將文件進行拷貝合并横辆。通過查閱資料撇他,發(fā)現(xiàn)m4a格式的音頻文件頭是由多個包含關(guān)系的ATOM結(jié)構(gòu)組成,且每個不同的m4a格式的音頻文件的文件頭的大小都不一樣狈蚤,這樣使得多個m4a文件頭文件解析和合并變得較為復(fù)雜困肩,若有多個m4a文件需要合并,那么會變得較為耗時脆侮。再者锌畸,對于沒有足夠音視頻文件解析和編解碼經(jīng)驗的開發(fā)者來講,要精準地得解析一個m4a文件靖避,挑戰(zhàn)性太大(網(wǎng)上這方面的資料也寥寥無幾),有興趣的讀者可以進行深入研究潭枣。

上述方法行不通,于是只好作罷幻捏,后來又想到了另外一種方法盆犁,也是我解決問題的最終方案,具體的思路如下:

(2)由于使用AudioRecord類提供的方法錄制的音頻是原始的PCM格式的二進制數(shù)據(jù)篡九,該格式的文件沒有文件頭信息谐岁,那么我們在進行文件合并時就就無需解析文件結(jié)構(gòu)去掉對應(yīng)的文件頭,這樣就變成了二進制數(shù)據(jù)地簡單拷貝和合并榛臼。我在這里實現(xiàn)的方式是在錄制音頻的過程中采用邊錄制邊寫入的方式不斷地向同一個文件寫入錄制的二進制音頻數(shù)據(jù)伊佃。當(dāng)觸發(fā)暫停錄音事件時,停止錄制停止寫入二進制數(shù)據(jù)讽坏,當(dāng)觸發(fā)繼續(xù)錄音事件時锭魔,則繼續(xù)錄制和向文件中寫入數(shù)據(jù)。最后停止寫入數(shù)據(jù)時路呜,將PCM二進制音頻文件編碼成m4a格式的音頻文件迷捧。如圖下:

上面方法描述中织咧,實現(xiàn)邊錄制邊寫入的功能倒比較簡單,關(guān)鍵難點是如何將PCM二進制數(shù)據(jù)編碼成目標的m4a格式的音頻數(shù)據(jù)漠秋,要實現(xiàn)音視頻的編解碼笙蒙,一般都是使用第三方開源的編解碼庫,比較著名的有FFMpeg和Speex庆锦,這些庫都提供了錄制捅位、轉(zhuǎn)換以及流化音視頻的完整解決方案,不過在此我的需求只是需要簡單地實現(xiàn)編碼工作搂抒,使用這些開源庫體積太大艇搀,有點殺雞用牛刀的感覺。因此求晶,通過研究和查閱資料焰雕,我在github上找到了一個非常有用的編解碼開源項目android-aac-enc(地址:https://github.com/timsu/android-aac-enc),該開源項目能完美地實現(xiàn)將原始的pcm格式的二進制數(shù)據(jù)編碼成m4a格式的數(shù)據(jù)文件芳杏,相比于FFmpeg庫矩屁,這個庫有以下幾點優(yōu)點:

1. aac-enc庫的體積比FFmpeg庫的體積更小爵赵;

2. 相比FFMpeg, aac-enc實現(xiàn)格式轉(zhuǎn)換更加簡單和快速吝秕;

3. aac-enc比FFmpeg需要編譯更少的底層的代碼。

該開源項目使用起來也非常地簡單空幻,通過分析其示例代碼我們可以通過以下四個步驟來實現(xiàn)音頻的編碼工作烁峭,代碼如下:

/**

* 1.初始化編碼配置

*

* 32000 : 音頻的比特率

* 2 : 音頻的聲道

* sampleRateInHz : 音頻采樣率

* 16 :音頻數(shù)據(jù)格式,PCM 16位每個樣本

* FileUtils.getAAcFilePath(mAudioRecordFileName) : aac音頻文件的存儲路徑

*/

encoder.init(32000, 2, sampleRateInHz, 16, FileUtils.

getAAcFilePath(mAudioRecordFileName));

/**

* 2.對二進制代碼進行編碼

*

* b :需要編碼的二進制音頻流

*/

encoder.encode(b);

/**

* 3. 從pcm二進制數(shù)據(jù)轉(zhuǎn)aac音頻文件編碼完成

*

*/

encoder.uninit();

/**

* 4. 將aac文件轉(zhuǎn)碼成m4a文件

*

* FileUtils.getAAcFilePath(mAudioRecordFileName) :需要編碼的aac文件路徑

* FileUtils.getM4aFilePath(mAudioRecordFileName) :編碼成m4a文件的目標路徑

*/

new AACToM4A().convert(mContext, FileUtils.getAAcFilePath(mAudioRecordFileName),

FileUtils.getM4aFilePath(mAudioRecordFileName));

使用起來是不是很簡單方便氛悬,我們無需對音頻文件格式和文件頭進行判斷和解析则剃,只需要通過該開源項目封裝的api方法直接調(diào)用就可以很快速的將原始的二進制PCM音頻數(shù)據(jù)轉(zhuǎn)換成m4a格式的音頻數(shù)據(jù)文件耘柱。感興趣的讀者可以去研究一下該項目的源碼如捅,了解一下其內(nèi)部的實現(xiàn),這里暫且不深入探究调煎。

基本上明確好思路和編碼的實現(xiàn)方法后镜遣,接下來就是具體的實現(xiàn)過程了,我們將依據(jù)上面的思路和方法來實現(xiàn)一個具有暫停功能的音頻錄制Demo

如何使用AudioRecord類來實現(xiàn)音頻的錄制士袄,這方面的資料很多悲关,讀者可以先學(xué)習(xí),簡單地入一下門娄柳。接下來我們先運行一下Demo寓辱,來看一下效果圖

(1)初始界面
(2)正在錄制界面
(3)暫停界面

?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

(4)播放界面
?????????? (5)暫停播放界面?? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

粗略看了Demo的運行效果圖后,接下來我們就要來實現(xiàn)赤拒,這里由于要使用aac-encode項目來實現(xiàn)音頻的編碼秫筏,則需將該項目以library的形式集成到我們的Demo中诱鞠,做完該項工作后,我們就可以在Demo工程? 中寫其它相關(guān)的邏輯代碼了这敬,下面看一下實現(xiàn)demo的關(guān)鍵代碼航夺,首先是RecordAct.Java文件中的代碼,該類為主界面類崔涂,主要實現(xiàn)了界面的初始化阳掐、音頻的錄制和音頻播放的功能,具體的代碼如下:

publicclassRecordActextendsActivityimplementsOnClickListener{

/**

*?Status:錄音初始狀態(tài)

*/

privatestaticfinalintSTATUS_PREPARE?=0;

/**

*?Status:正在錄音中

*/

privatestaticfinalintSTATUS_RECORDING?=1;

/**

*?Status:暫停錄音

*/

privatestaticfinalintSTATUS_PAUSE?=2;

/**

*?Status:播放初始狀態(tài)

*/

privatestaticfinalintSTATUS_PLAY_PREPARE?=3;

/**

*?Status:播放中

*/

privatestaticfinalintSTATUS_PLAY_PLAYING?=4;

/**

*?Status:播放暫停

*/

privatestaticfinalintSTATUS_PLAY_PAUSE?=5;

privateintstatus?=?STATUS_PREPARE;

/**

*?錄音時間

*/

privateTextView?tvRecordTime;

/**

*?錄音按鈕

*/

privateImageView?btnRecord;//?錄音按鈕

privatePopupWindow?popAddWindow;

/**

*?試聽界面

*/

privateLinearLayout?layoutListen;

/**

*?錄音長度

*/

privateTextView?tvLength;

privateTextView?recordContinue;

/**

*?重置按鈕

*/

privateView?resetRecord;

/**

*?結(jié)束錄音

*/

privateView?recordOver;

privateImageView?audioRecordNextImage;

privateTextView?audioRecordNextText;

/**

*?音頻播放進度

*/

privateTextView?tvPosition;

longstartTime?=0;

/**

*?最大錄音長度

*/

privatestaticfinalintMAX_LENGTH?=300*1000;

privateHandler?handler?=newHandler();

privateRunnable?runnable;

/**

*?音頻錄音的總長度

*/

privatestaticintvoiceLength;

/**

*?音頻錄音幫助類

*/

privateAudioRecordUtils?mRecordUtils;

/**

*?播放進度條

*/

privateSeekBar?seekBar;

/**

*?音頻播放類

*/

privatePlayer?player;

/**

*?錄音文件名

*/

privateString?audioRecordFileName;

@Override

protectedvoidonCreate(Bundle?savedInstanceState)?{

//?TODO?Auto-generated?method?stub

super.onCreate(savedInstanceState);

setContentView(R.layout.pop_add_record);

initView();

}

publicvoidinitView(){

//音頻錄音的文件名稱

audioRecordFileName?=?TimeUtils.getTimestamp();

//初始化音頻錄音對象

mRecordUtils?=newAudioRecordUtils(this,audioRecordFileName);

View?view?=?LayoutInflater.from(this).inflate(R.layout.pop_add_record,null);

tvRecordTime?=?(TextView)findViewById(R.id.tv_time);

btnRecord?=?(ImageView)findViewById(R.id.iv_btn_record);

btnRecord.setOnClickListener(this);

recordContinue?=?(TextView)findViewById(R.id.record_continue_txt);

resetRecord?=?findViewById(R.id.btn_record_reset);

recordOver?=?findViewById(R.id.btn_record_complete);

resetRecord.setOnClickListener(this);

recordOver.setOnClickListener(this);

audioRecordNextImage?=?(ImageView)findViewById(R.id.recrod_complete_img);

audioRecordNextText?=?(TextView)findViewById(R.id.record_complete_txt);

layoutListen?=?(LinearLayout)findViewById(R.id.layout_listen);

tvLength?=?(TextView)findViewById(R.id.tv_length);

tvPosition?=?(TextView)findViewById(R.id.tv_position);

seekBar?=?(SeekBar)findViewById(R.id.seekbar_play);

seekBar.setOnSeekBarChangeListener(newSeekBarChangeEvent());

seekBar.setEnabled(false);

player?=newPlayer(seekBar,?tvPosition);

player.setMyPlayerCallback(newMyPlayerCallback()?{

@Override

publicvoidonPrepared()?{

seekBar.setEnabled(true);

}

@Override

publicvoidonCompletion()?{

status?=?STATUS_PLAY_PREPARE;

seekBar.setEnabled(false);

seekBar.setProgress(0);

tvPosition.setText("00:00");

recordContinue.setBackgroundResource(R.drawable.record_audio_play);

}

});

popAddWindow?=newPopupWindow(view,?LayoutParams.MATCH_PARENT,

LayoutParams.MATCH_PARENT);

popAddWindow.setFocusable(true);

popAddWindow.setAnimationStyle(R.style.pop_anim);

popAddWindow.setBackgroundDrawable(newBitmapDrawable());

}

publicvoidhandleRecord(){

switch(status){

caseSTATUS_PREPARE:

mRecordUtils.startRecord();

btnRecord.setBackgroundResource(R.drawable.record_round_red_bg);

status?=?STATUS_RECORDING;

voiceLength?=0;

timing();

break;

caseSTATUS_RECORDING:

pauseAudioRecord();

resetRecord.setVisibility(View.VISIBLE);

recordOver.setVisibility(View.VISIBLE);

btnRecord.setBackgroundResource(R.drawable.record_round_blue_bg);

recordContinue.setVisibility(View.VISIBLE);

status?=?STATUS_PAUSE;

break;

caseSTATUS_PAUSE:

mRecordUtils.startRecord();

resetRecord.setVisibility(View.INVISIBLE);

recordOver.setVisibility(View.INVISIBLE);

btnRecord.setBackgroundResource(R.drawable.record_round_red_bg);

recordContinue.setVisibility(View.INVISIBLE);

status?=?STATUS_RECORDING;

timing();

break;

caseSTATUS_PLAY_PREPARE:

player.playUrl(FileUtils.getM4aFilePath(audioRecordFileName));

recordContinue.setBackgroundResource(R.drawable.record_audio_play_pause);

status?=?STATUS_PLAY_PLAYING;

break;

caseSTATUS_PLAY_PLAYING:

player.pause();

recordContinue.setBackgroundResource(R.drawable.record_audio_play);

status?=?STATUS_PLAY_PAUSE;

break;

caseSTATUS_PLAY_PAUSE:

player.play();

recordContinue.setBackgroundResource(R.drawable.record_audio_play_pause);

status?=?STATUS_PLAY_PLAYING;

break;

}

}

/**

*?暫停錄音

*/

publicvoidpauseAudioRecord(){

mRecordUtils.pauseRecord();

if(handler?!=null&&?runnable?!=null)?{

handler.removeCallbacks(runnable);

runnable?=null;

}

}

/**

*?停止錄音

*/

publicvoidstopAudioRecord(){

pauseAudioRecord();

mRecordUtils.stopRecord();

status?=?STATUS_PLAY_PREPARE;

showListen();

}

/**

*?重新錄音參數(shù)初始化

*/

@SuppressLint("NewApi")

publicvoidresetAudioRecord(){

//停止播放音頻

player.stop();

pauseAudioRecord();

mRecordUtils.reRecord();

status?=?STATUS_PREPARE;

voiceLength?=0;

tvRecordTime.setTextColor(Color.WHITE);

tvRecordTime.setText(TimeUtils.convertMilliSecondToMinute2(voiceLength));

recordContinue.setText(R.string.record_continue);

recordContinue.setBackground(null);

recordContinue.setVisibility(View.GONE);

layoutListen.setVisibility(View.GONE);

tvRecordTime.setVisibility(View.VISIBLE);

audioRecordNextImage.setImageResource(R.drawable.btn_record_icon_complete);

audioRecordNextText.setText(R.string.record_over);

btnRecord.setBackgroundResource(R.drawable.record_round_blue_bg);

resetRecord.setVisibility(View.INVISIBLE);

recordOver.setVisibility(View.INVISIBLE);

}

/**

*?計時功能

*/

privatevoidtiming()?{

runnable?=newRunnable()?{

@Override

publicvoidrun()?{

voiceLength?+=100;

if(voiceLength?>=?(MAX_LENGTH?-10*1000))?{

tvRecordTime.setTextColor(getResources().getColor(

R.color.red_n));

}else{

tvRecordTime.setTextColor(Color.WHITE);

}

if(voiceLength?>?MAX_LENGTH)?{

stopAudioRecord();

}else{

tvRecordTime.setText(TimeUtils.convertMilliSecondToMinute2(voiceLength));

handler.postDelayed(this,100);

}

}

};

handler.postDelayed(runnable,100);

}

@Override

publicvoidonClick(View?v)?{

//?TODO?Auto-generated?method?stub

switch(v.getId())?{

caseR.id.iv_btn_record:

handleRecord();

break;

caseR.id.btn_record_reset:

resetAudioRecord();

break;

caseR.id.btn_record_complete:

stopAudioRecord();

break;

default:

break;

}

}

/**

*?顯示播放界面

*/

privatevoidshowListen()?{

layoutListen.setVisibility(View.VISIBLE);

tvLength.setText(TimeUtils.convertMilliSecondToMinute2(voiceLength));

tvRecordTime.setVisibility(View.GONE);

resetRecord.setVisibility(View.VISIBLE);

recordOver.setVisibility(View.INVISIBLE);

recordContinue.setVisibility(View.VISIBLE);

seekBar.setProgress(0);

tvPosition.setText("00:00");

btnRecord.setBackgroundResource(R.drawable.record_round_blue_bg);

recordContinue.setText(null);

recordContinue.setBackgroundResource(R.drawable.record_audio_play);

}

/**

*

*?SeekBar進度條改變事件監(jiān)聽類

*/

classSeekBarChangeEventimplementsSeekBar.OnSeekBarChangeListener?{

intprogress;

@Override

publicvoidonProgressChanged(SeekBar?seekBar,intprogress,

booleanfromUser)?{

if(null!=?player?&&?player.mediaPlayer?!=null)?{

this.progress?=?progress?*?player.mediaPlayer.getDuration()

/?seekBar.getMax();

tvPosition.setText(TimeUtils

.convertMilliSecondToMinute2(player.currentPosition));

}

}

@Override

publicvoidonStartTrackingTouch(SeekBar?seekBar)?{

}

@Override

publicvoidonStopTrackingTouch(SeekBar?seekBar)?{

if(player.mediaPlayer?!=null)?{

player.mediaPlayer.seekTo(progress);

}

}

}

@Override

protectedvoidonDestroy()?{

//?TODO?Auto-generated?method?stub

super.onDestroy();

player.stop();

}

}

上面代碼注釋比較清楚冷蚂,且好理解缭保,因此不多分析,讀者自行學(xué)習(xí)蝙茶。下面再來看一下AudioRecordUtils類的代碼涮俄,該類是音頻錄制功能的主要實現(xiàn)代碼,里面簡單地封裝了開始錄音尸闸、暫停錄音彻亲、停止錄音和重新錄音幾個方法,在開發(fā)中只要調(diào)用就行吮廉,來看看具體的實現(xiàn)代碼苞尝,如下:

[java]view plaincopy

publicclassAudioRecordUtils?{

privatefinalintaudioSource?=?MediaRecorder.AudioSource.MIC;

//?設(shè)置音頻采樣率,44100是目前的標準宦芦,但是某些設(shè)備仍然支持22050,16000,11025

privatefinalintsampleRateInHz?=16000;

//?設(shè)置音頻的錄制的聲道CHANNEL_IN_STEREO為雙聲道宙址,CHANNEL_CONFIGURATION_MONO為單聲道

privatefinalintchannelConfig?=?AudioFormat.CHANNEL_IN_STEREO;

//?音頻數(shù)據(jù)格式:PCM?16位每個樣本。保證設(shè)備支持调卑。PCM?8位每個樣本抡砂。不一定能得到設(shè)備支持。

privatefinalintaudioFormat?=?AudioFormat.ENCODING_PCM_16BIT;

privateintinBufSize?=0;

privateAudioRecord?audioRecord;

privateAACEncoder?encoder?=null;

privateProgressDialog?mProgressDialog?=null;

privatebooleanisRecord?=false;

privateContext?mContext;

/**

*?錄制的音頻文件名稱

*/

privateString?mAudioRecordFileName;

privatestaticfinalintRECORDED_INIT_DELETE?=0;

privatestaticfinalintRECORDED_COMPLETED_DELETE?=1;

publicAudioRecordUtils(Context?context,String?audioRecordFileName){

mContext?=?context;

mAudioRecordFileName?=?audioRecordFileName;

initAudioRecord();

}

/**

*?初始化對象

*/

privatevoidinitAudioRecord(){

inBufSize?=?AudioRecord.getMinBufferSize(

sampleRateInHz,

channelConfig,

audioFormat);

audioRecord??=newAudioRecord(

audioSource,

sampleRateInHz,

channelConfig,

audioFormat,

inBufSize);

encoder?=newAACEncoder();

deleteAllFiles(RECORDED_INIT_DELETE);

mProgressDialog?=newProgressDialog(mContext);

mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);

mProgressDialog.setCanceledOnTouchOutside(false);

mProgressDialog.setCancelable(false);

mProgressDialog.setTitle("提示");

mProgressDialog.setMessage("正在保存錄音恬涧,請耐心等候......");

}

/**

*?開始錄音

*/

publicvoidstartRecord(){

newAudioRecordTask().execute();

}

/**

*?暫停錄音

*/

publicvoidpauseRecord(){

isRecord?=false;

}

/**

*?停止錄音

*/

publicvoidstopRecord(){

newAudioEncoderTask().execute();

}

/**

*?重新錄制

*/

publicvoidreRecord(){

//重新錄制時注益,刪除錄音文件夾中的全部文件

deleteAllFiles(RECORDED_INIT_DELETE);

}

privatevoidencodeAudio(){

try{

//讀取錄制的pcm音頻文件

DataInputStream?mDataInputStream?=newDataInputStream(newFileInputStream(

FileUtils.getPcmFilePath(mAudioRecordFileName)));

byte[]?b?=newbyte[(int)newFile(FileUtils.

getPcmFilePath(mAudioRecordFileName)).length()];

mDataInputStream.read(b);

//初始化編碼配置

encoder.init(32000,2,?sampleRateInHz,16,?FileUtils.

getAAcFilePath(mAudioRecordFileName));

//對二進制代碼進行編碼

encoder.encode(b);

//編碼完成

encoder.uninit();

//關(guān)閉流

mDataInputStream.close();

try{

//將aac文件轉(zhuǎn)碼成m4a文件

newAACToM4A().convert(mContext,?FileUtils.getAAcFilePath(mAudioRecordFileName),

FileUtils.getM4aFilePath(mAudioRecordFileName));

}catch(IOException?e)?{

Log.e("ERROR","error?converting",?e);

}

deleteAllFiles(RECORDED_COMPLETED_DELETE);

}catch(FileNotFoundException?e)?{

//?TODO?Auto-generated?catch?block

e.printStackTrace();

}catch(IOException?e1)?{

//?TODO?Auto-generated?catch?block

e1.printStackTrace();

}

}

classAudioRecordTaskextendsAsyncTask{

@Override

protectedVoid?doInBackground(Void...?params)?{

//?TODO?Auto-generated?method?stub

if(audioRecord?==null){

initAudioRecord();

}

RandomAccessFile?mRandomAccessFile?=null;

try{

mRandomAccessFile?=newRandomAccessFile(newFile(

FileUtils.getPcmFilePath(mAudioRecordFileName)),"rw");

byte[]?b?=newbyte[inBufSize/4];

//開始錄制音頻

audioRecord.startRecording();

//判斷是否正在錄制

isRecord?=true;

while(isRecord){

audioRecord.read(b,0,?b.length);

//向文件中追加內(nèi)容

mRandomAccessFile.seek(mRandomAccessFile.length());

mRandomAccessFile.write(b,0,?b.length);

}

//停止錄制

audioRecord.stop();

mRandomAccessFile.close();

}catch(FileNotFoundException?e)?{

//?TODO?Auto-generated?catch?block

e.printStackTrace();

}catch(IOException?e)?{

//?TODO?Auto-generated?catch?block

e.printStackTrace();

}

returnnull;

}

}

classAudioEncoderTaskextendsAsyncTask{

@Override

protectedvoidonPreExecute()?{

//?TODO?Auto-generated?method?stub

super.onPreExecute();

if(mProgressDialog?!=null&&?!mProgressDialog.isShowing()){

mProgressDialog.show();

}

}

@Override

protectedLong?doInBackground(Void...?params)?{

//?TODO?Auto-generated?method?stub

encodeAudio();

returnnull;

}

@Override

protectedvoidonPostExecute(Long?result)?{

//?TODO?Auto-generated?method?stub

super.onPostExecute(result);

if(mProgressDialog.isShowing()){

mProgressDialog.cancel();

mProgressDialog.dismiss();

}

}

}

/**

*?清空音頻錄制文件夾中的所有文件

*?@param?isRecorded

*/

publicvoiddeleteAllFiles(intisRecorded){

File[]?files?=newFile(FileUtils.getAudioRecordFilePath()).listFiles();

switch(isRecorded)?{

caseRECORDED_INIT_DELETE:

for(File?file:?files){

file.delete();

}

break;

caseRECORDED_COMPLETED_DELETE:

for(File?file:?files){

if(!file.getName().equals(mAudioRecordFileName?+?Constants.M4A_SUFFIX)){

file.delete();

}

}

break;

default:

break;

}

}

}

上面代碼關(guān)鍵處都有注釋,讀者可自行學(xué)習(xí)溯捆。自此丑搔,我們基本熟悉了實現(xiàn)能夠暫停錄音功能的關(guān)鍵代碼,代碼沒有全部貼出提揍,想要完整的Demo可在文章末尾下載來仔細研究啤月。最后我再補充一點,就是若讀者對錄制的音頻格式?jīng)]有嚴格的要求話劳跃,如錄制的音頻格式是arm格式谎仲,則沒有必要考慮到音頻的編解碼問題,因為arm格式的音頻文件的文件頭信息固定是6個字節(jié)的大小刨仑,那這種情況讀者可以采用文章開頭所說的第一種方法郑诺,就是每次點擊暫停事件都錄制成一個arm文件绞呈,在最后合并的時候,只需要去掉第2至n個文件的前6個字節(jié)间景,然后進行文件的拷貝合并就行佃声。

demo 下載地址?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市倘要,隨后出現(xiàn)的幾起案子圾亏,更是在濱河造成了極大的恐慌,老刑警劉巖封拧,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件志鹃,死亡現(xiàn)場離奇詭異,居然都是意外死亡泽西,警方通過查閱死者的電腦和手機曹铃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捧杉,“玉大人陕见,你說我怎么就攤上這事∥抖叮” “怎么了评甜?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長仔涩。 經(jīng)常有香客問我忍坷,道長,這世上最難降的妖魔是什么熔脂? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任佩研,我火速辦了婚禮,結(jié)果婚禮上霞揉,老公的妹妹穿的比我還像新娘旬薯。我一直安慰自己,他們只是感情好零聚,可當(dāng)我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布袍暴。 她就那樣靜靜地躺著,像睡著了一般隶症。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上岗宣,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天蚂会,我揣著相機與錄音,去河邊找鬼耗式。 笑死胁住,一個胖子當(dāng)著我的面吹牛趁猴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播彪见,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼儡司,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了余指?” 一聲冷哼從身側(cè)響起捕犬,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎酵镜,沒想到半個月后碉碉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡淮韭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年垢粮,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片靠粪。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蜡吧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出占键,到底是詐尸還是另有隱情斩跌,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布捞慌,位于F島的核電站耀鸦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏啸澡。R本人自食惡果不足惜袖订,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嗅虏。 院中可真熱鬧洛姑,春花似錦、人聲如沸皮服。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽龄广。三九已至硫眯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間择同,已是汗流浹背两入。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留敲才,地道東北人裹纳。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓择葡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親剃氧。 傳聞我的和親對象是個殘疾皇子敏储,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容