android語(yǔ)音即時(shí)通訊之錄音、播放功能實(shí)現(xiàn)代碼
在android中,實(shí)現(xiàn)錄音與語(yǔ)音播放的功能算是比較簡(jiǎn)單的,但是作為參考,還是很有必要將語(yǔ)音相關(guān)的知識(shí)做一個(gè)簡(jiǎn)要的記錄。
首先,在android中,支持錄音支持兩種方式。主要包括:字節(jié)流模式和文件流模式。用文件流模式進(jìn)行錄音操作比較簡(jiǎn)單,而且相對(duì)來說,因?yàn)槠浞庋b性比較好,錄制下的文件也會(huì)比較小。但是相對(duì)于文件流模式,就沒有字節(jié)流模式那么靈活,但是想要用好字節(jié)流模式還是需要下一點(diǎn)功夫的。
下面開始介紹文件流模式的語(yǔ)音操作:
文件流模式
我們來看錄音部分的實(shí)現(xiàn),首先我們實(shí)現(xiàn)開始錄音的部分:
在正式編碼之前,還是需要對(duì)其進(jìn)行一個(gè)簡(jiǎn)要的說明。一般來說,錄音功能的實(shí)現(xiàn)是在jin層,而在這一層中,是用單線程實(shí)現(xiàn)的。如果我們?cè)诰幋a的對(duì)錄音api進(jìn)行多線程操作,會(huì)導(dǎo)致程序直接閃退,并且我們是無法在java層對(duì)其異常進(jìn)行捕獲的。所以,我們必須使用單線程以保證錄音的正常運(yùn)行。
一般來說,開始錄音的步驟也就三個(gè),代碼如下:
releaseRecord();//釋放可能沒釋放的錄音相關(guān)資源 if (!doStartRecord()) {//真正的開始錄音的函數(shù),開始錄音成功返回true,否則返回false recordFail(); //開始失敗,向用戶提示開始錄音失敗 }
接下來我們來看看上述三個(gè)方法的實(shí)現(xiàn):
實(shí)現(xiàn)是釋放相關(guān)資源的方法releaseRecord:
if (mMediaRecorder != null) { mMediaRecorder.release(); mMediaRecorder = null; }
其中的mMediaRecorder 的MediaRecorder的全局變量。
接下來是真正實(shí)現(xiàn)開始錄音的實(shí)現(xiàn)邏輯doStartRecord()
private boolean doStartRecord() { try { mMediaRecorder = new MediaRecorder(); mAudioFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/voice/" + System.currentTimeMillis() + ".m4a"); mAudioFile.getParentFile().mkdirs(); mAudioFile.createNewFile(); //設(shè)置從麥克風(fēng)采集聲音 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); //保存文件為mp4的格式 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); //設(shè)置所有android系統(tǒng)都支持的采樣頻率 mMediaRecorder.setAudioSamplingRate(44100); //設(shè)置acc的編碼方式 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); //設(shè)置比較好的音質(zhì) mMediaRecorder.setAudioEncodingBitRate(96000); mMediaRecorder.setOutputFile(mAudioFile.getAbsolutePath()); mMediaRecorder.prepare(); mMediaRecorder.start(); mStartRecordTime = System.currentTimeMillis(); } catch (IOException | RuntimeException e) { e.printStackTrace(); return false; }finally { if(mAudioRecord != null){ mAudioRecord.release(); } } return true; }
這一部分代碼比較多,但是關(guān)鍵部分我都給出了注釋,相信理解起來也不算難吧。這一部分我們實(shí)現(xiàn)的主要是在sdcrad根目錄下新建一個(gè)voice的目錄,然后在新建一個(gè)以==.m4a==為后綴名的文件。在配置mAudioRecord的相關(guān)參數(shù)后,將收集到的錄音存放到之前的文件中。如果一切都順利的話,就返回true ,表示開始錄音成功。
最后就是提示用戶錄音實(shí)現(xiàn)的邏輯recordFail
mAudioFile = null; mHandler.postDelayed(new Runnable() { @Override public void run() { Toast.makeText(VioceActivity.this, "錄音失敗", Toast.LENGTH_SHORT).show(); } }, 100);
這里的邏輯比較簡(jiǎn)單,但是值得注意的是,因?yàn)槲覀冮_始錄音方法是在一個(gè)非主線程的線程中執(zhí)行的,所以我們需要借助hander來實(shí)現(xiàn)界面提示的效果。這里的mHander是一個(gè)局部變量,其初始化放在OnCreate()方法中。
mHandler = new Handler(Looper.getMainLooper());
綜上,開始錄音的所有邏輯已經(jīng)全部實(shí)現(xiàn)。接下來實(shí)現(xiàn)的是結(jié)束錄音的實(shí)現(xiàn)邏輯:
主題的邏輯如下:
if (!doStopRecord()) {//實(shí)現(xiàn) 停止錄音的真正邏輯,成功返回true,否則返回false recordFail();//提示用戶錄音失敗 }
這里的doStopRecord實(shí)現(xiàn)邏輯如下:
mMediaRecorder.stop(); mEndRecordTime = System.currentTimeMillis(); final int seond = (int) ((mEndRecordTime - mStartRecordTime) / 1000); if (seond < 3) { recordFail(); return false; } else { mHandler.post(new Runnable() { @Override public void run() { mText.setText("錄音" + seond + "成功!"); } }); } } catch (RuntimeException e) { e.printStackTrace(); return false; } return true;
其實(shí)我們實(shí)現(xiàn)停止錄音的邏輯也很簡(jiǎn)單,首先調(diào)用mMediaRecorder.stop();停止錄音,然后對(duì)錄音時(shí)間是否大于3s進(jìn)行判斷,若大于3s,則表示錄音有效,提示用戶,錄音成功。
綜上,我們文件流的錄音的所有代碼已經(jīng)實(shí)現(xiàn)完畢。接下來我們實(shí)現(xiàn)對(duì)其進(jìn)行播放。如果需要參考全部的代碼,請(qǐng)戳這里。
private void doPlay(File mAudioFile) { //配置播放器 MediaPlayer mMediaPlayer = new MediaPlayer(); try{ //設(shè)置聲音文件 mMediaPlayer.setDataSource(mAudioFile.getAbsolutePath()); //設(shè)置監(jiān)聽回調(diào) mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { stopPlay(); } }); //設(shè)置出錯(cuò)的監(jiān)聽器 mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { @Override public boolean onError(MediaPlayer mp, int what, int extra) { playFail(); //提示用戶 stopPlay(); //釋放播放器 return true; } }); //配置音量,是否循環(huán) mMediaPlayer.setVolume(1,1); mMediaPlayer.setLooping(false); mMediaPlayer.prepare(); mMediaPlayer.start(); }catch (Exception e){ e.printStackTrace(); playFail(); stopPlay(); } }
整體來說,基于文件的錄音是比較容易實(shí)現(xiàn)的。下面介紹如何通過字節(jié)流模式實(shí)現(xiàn)錄音。
字節(jié)流模式錄音
開始錄音:主要邏輯startRecord2()的實(shí)現(xiàn)
private boolean startRecord2() { try { mAudioFile2 = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/voice/" + System.currentTimeMillis() + ".pcm"); mAudioFile2.getParentFile().mkdirs(); mAudioFile2.createNewFile(); mFileOutputStream = new FileOutputStream(mAudioFile2); //配置AudioRecord //從麥克風(fēng)采集數(shù)據(jù) int audioSource = MediaRecorder.AudioSource.MIC; //采集頻率 int sampleRate = 44100; //單聲道輸入 int channelConfig = AudioFormat.CHANNEL_IN_MONO; //設(shè)置pcm(脈沖編碼調(diào)制 Pulse Code Modulation)編碼格式 int audioFormat = AudioFormat.ENCODING_PCM_16BIT; //計(jì)算AudioRecord 內(nèi)存buffer最小的大小 int minBufferSize = AudioRecord.getMinBufferSize(sampleRate,channelConfig,audioFormat); //創(chuàng)建AudioRecord對(duì)象 mAudioRecord = new AudioRecord(audioSource,sampleRate,channelConfig,audioFormat,Math.max(BUFFER_SIZE,minBufferSize)); mAudioRecord.startRecording(); mStartRecordTime = System.currentTimeMillis(); //循環(huán)讀取數(shù)據(jù),寫到輸出流中 while(mIsRecord){ int read = mAudioRecord.read(mBuffer,0,BUFFER_SIZE); if(read >0 ){ //讀取文件寫到文件中 mFileOutputStream.write(mBuffer,0,read); }else{ return false; } } } catch (IOException | RuntimeException e) { e.printStackTrace(); return false; } //停止錄音 return true; }
停止錄音的doStopRecord()實(shí)現(xiàn):
private boolean doStopRecord() { try { mMediaRecorder.stop(); mEndRecordTime = System.currentTimeMillis(); final int seond = (int) ((mEndRecordTime - mStartRecordTime) / 1000); if (seond < 3) { recordFail(); return false; } else { mHandler.post(new Runnable() { @Override public void run() { mText.setText("錄音" + seond + "成功!"); } }); } } catch (RuntimeException e) { e.printStackTrace(); return false; } return true; }
對(duì)其中相關(guān)參數(shù)的說名:
private boolean mIsRecord = false; private final int BUFFER_SIZE = 2048;//緩存區(qū)的大小 private byte[] mBuffer; private FileOutputStream mFileOutputStream; private AudioRecord mAudioRecord; private File mAudioFile2;
接下來,實(shí)現(xiàn)的是對(duì)其字節(jié)流模式錄制的文件進(jìn)行播放:doPlay2()主題類的實(shí)現(xiàn):
private void doPlay2(File mAudioFile) { //聲音類型,揚(yáng)聲器播放 int steamType = AudioManager.STREAM_MUSIC; //采樣頻率 int sampleRate = 44100; //MONO 表示單聲道 錄音輸入單聲道 播放也使用單聲道 int channelConfig = AudioFormat.CHANNEL_OUT_MONO; //錄音使用16bit 所以播放也使用同樣的格式 int audioFormat = AudioFormat.ENCODING_PCM_16BIT; //流模式 int mode = AudioTrack.MODE_STREAM; //計(jì)算需要最小buffer的大小 int minBufferSize =AudioTrack.getMinBufferSize(sampleRate,channelConfig,audioFormat); AudioTrack audioTrack = new AudioTrack(steamType,sampleRate,channelConfig,audioFormat, Math.max(minBufferSize,BUFFER_SIZE),mode); //從文件流中讀取數(shù)據(jù) FileInputStream inputStream = null; try{ inputStream = new FileInputStream(mAudioFile2); int read; //循環(huán)讀取數(shù)據(jù),寫到播放器去播放 audioTrack.play(); while((read = inputStream.read(mBuffer)) > 0){ int ret = audioTrack.write(mBuffer,0,read); switch (ret){ case AudioTrack.ERROR: case AudioTrack.ERROR_BAD_VALUE: case AudioTrack.ERROR_INVALID_OPERATION: case AudioTrack.ERROR_DEAD_OBJECT: playFail(); return ; default: break; } } }catch (Exception e){ e.printStackTrace(); }finally { mIsPlaying = false; if(inputStream != null) closeQuietly(inputStream); resetQuietly(audioTrack); } }
千言萬(wàn)語(yǔ)肯定不如直接代碼來的直接了當(dāng),所以的代碼實(shí)現(xiàn)點(diǎn)這里
實(shí)現(xiàn)的效果如下:
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android一個(gè)類實(shí)現(xiàn)錄音與播放實(shí)例
- 詳解Android開發(fā)錄音和播放音頻的步驟(動(dòng)態(tài)獲取權(quán)限)
- Android實(shí)現(xiàn)語(yǔ)音播放與錄音功能
- Android編程實(shí)現(xiàn)錄音及保存播放功能的方法【附demo源碼下載】
- Android 錄音與播放功能的簡(jiǎn)單實(shí)例
- Android使用MediaRecorder實(shí)現(xiàn)錄音及播放
- Android錄音播放管理工具
- Android實(shí)現(xiàn)自制和播放錄音程序
- Android編程開發(fā)錄音和播放錄音簡(jiǎn)單示例
- Android實(shí)現(xiàn)音頻錄音與播放
相關(guān)文章
Android中Listview點(diǎn)擊item不變顏色及設(shè)置listselector 無效的解決方案
這篇文章主要介紹了Android中Listview點(diǎn)擊item不變顏色及設(shè)置listselector 無效的原因及解決方案,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09Android自定義View實(shí)現(xiàn)五星好評(píng)效果
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)五星好評(píng)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11Android簡(jiǎn)單實(shí)現(xiàn)動(dòng)態(tài)權(quán)限獲取相機(jī)權(quán)限及存儲(chǔ)空間等多權(quán)限
這篇文章主要介紹了Android簡(jiǎn)單實(shí)現(xiàn)動(dòng)態(tài)權(quán)限獲取相機(jī)權(quán)限及存儲(chǔ)空間等多權(quán)限,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的朋友可以參考一下2022-07-07Kotlin文件讀寫與SharedPreferences存儲(chǔ)功能實(shí)現(xiàn)方法
SharedPreferences是安卓平臺(tái)上一個(gè)輕量級(jí)的存儲(chǔ)類,用來保存應(yīng)用的一些常用配置,比如Activity狀態(tài),Activity暫停時(shí),將此activity的狀態(tài)保存到SharedPereferences中;當(dāng)Activity重載,系統(tǒng)回調(diào)方法onSaveInstanceState時(shí),再?gòu)腟haredPreferences中將值取出2022-12-12Android開發(fā)之時(shí)間日期操作實(shí)例
這篇文章主要介紹了Android開發(fā)之時(shí)間日期操作,是Android程序開發(fā)中常見的一個(gè)功能,需要的朋友可以參考下2014-08-08Android中截取當(dāng)前屏幕圖片的實(shí)例代碼
該篇文章是說明在Android手機(jī)或平板電腦中如何實(shí)現(xiàn)截取當(dāng)前屏幕的功能,并把截取的屏幕保存到SDCard中的某個(gè)目錄文件夾下面。實(shí)現(xiàn)的代碼如下:2013-08-08Android使用AIDL實(shí)現(xiàn)兩個(gè)App間通信
這篇文章主要為大家詳細(xì)介紹了Android使用AIDL實(shí)現(xiàn)兩個(gè)App間通信,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-04-04