Android錄音功能的實(shí)現(xiàn)以及踩坑實(shí)戰(zhàn)記錄
前言
最近接到個(gè)需求,不使用第三方SDK的情況下實(shí)現(xiàn)IM通訊,文字聊天已經(jīng)通過(guò)MQTT實(shí)現(xiàn),而語(yǔ)音功能目前想到的較好解決方案就是進(jìn)行錄音文件的上傳下載??赡苓€有更好解決方案,但我目前沒(méi)想到,有建議的小伙伴勞煩指導(dǎo)下。
前提 :
1、權(quán)限申請(qǐng): 清單文件中加上:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.RECORD_AUDIO" />
對(duì)應(yīng)讀寫文件和錄音權(quán)限。
2、錄音文件要寫到相應(yīng)文件夾中,此文件夾要先創(chuàng)建,丟到Application或Activity的onCreate()中都可以,但一定要先創(chuàng)建。
代碼實(shí)現(xiàn)流程 :
1、創(chuàng)建MediaRecorder對(duì)象;
2、調(diào)用setAudioSource()方法設(shè)置聲音的來(lái)源,一般傳入MediaRecorder.MIC;
3、調(diào)用setOutputFormat()設(shè)置所錄制的音頻文件的格式;
4、調(diào)用setAudioRncoder()、setAudioEncodingBitRate(int bitRate)、setAudioSamlingRate(int SamplingRate)設(shè)置所錄音的編碼格式、編碼位率、采樣率等,當(dāng)然不是每個(gè)都需要,根據(jù)具體業(yè)務(wù)調(diào)整(setAudioEncodingBitRate(96000),編碼位率一般是96000);
5、調(diào)用setOutputFile(String path)方法設(shè)置錄制的音頻文件的保存位置;
6、調(diào)用MediaRecoder對(duì)象的Prepare()方法準(zhǔn)備錄制;
7、調(diào)用MediaRecoder對(duì)象的start()方法開始錄制;
8、結(jié)束后調(diào)用MediaRecoder對(duì)象的stop()方法停止錄制,并調(diào)用release()方法釋放資源。 示例如下:
public class TestActivity extends BaseActivity { private ActivityChatBinding testBinding; private MediaRecorder mediaRecorder; private boolean isRecorded; @Override public void initView() { testBinding = ActivityTestBinding.inflate(getLayoutInflater()); setContentView(testBinding.getRoot()); initMsgAndSth(); checkPermission(); } private void initMsgAndSth(){ String record_Home = this.getFilesDir()+"/Sample"; //聲明存儲(chǔ)路徑,用絕對(duì)路徑什么都可以 //btnTalk就是個(gè)Button testBinding.btnTalk.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (isRecorded) stopRecordAudio(); else startRecordAudio(record_Home); } }); } private void stopRecordAudio() { //有的5.0機(jī)型上MediaRecorder.stop會(huì)報(bào)錯(cuò),這里建議抓取一下異常 if(mediaRecorder !=null){ try { mediaRecorder.stop();//停止錄音 mediaRecorder.release();//釋放資源 mediaRecorder =null; }catch (Exception exception){ mediaRecorder.reset();//重置 mediaRecorder.release();//釋放資源 mediaRecorder =null; } Toast.makeText(this,"停止錄音",Toast.LENGTH_SHORT).show(); } } private void startRecordAudio(String path) { //文件夾一定要先創(chuàng)建,不然報(bào)錯(cuò)的bug信息中是找不到這里的 File audioFile = new File(path); if (!audioFile.exists()) { audioFile.mkdirs(); } else if (!audioFile.isDirectory()) { audioFile.delete(); audioFile.mkdirs(); } File file = new File(path + "Sample.amr"); if(!file.exists()){ try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } if(mediaRecorder == null){ mediaRecorder = new MediaRecorder(); mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//設(shè)置麥克風(fēng) /* * 設(shè)置保存輸出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default THREE_GPP(3gp格式 * ,H263視頻/ARM音頻編碼)、MPEG-4、RAW_AMR(只支持音頻且音頻編碼要求為AMR_NB) */ mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB); mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);//設(shè)置音頻文件編碼格式 mediaRecorder.setOutputFile(path+"Sample.amr"); } try { mediaRecorder.prepare(); //start之前要先prepare mediaRecorder.start(); isRecorded = true; Toast.makeText(this,"開始錄音",Toast.LENGTH_SHORT).show(); } catch (IllegalStateException el){ el.printStackTrace(); } catch (RuntimeException e){ e.printStackTrace(); } catch (Exception e){ e.printStackTrace(); } } /** * 簡(jiǎn)單的權(quán)限申請(qǐng)邏輯 */ private void checkPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { String[] permissions = new String[]{Manifest.permission.RECORD_AUDIO,Manifest.permission.WRITE_EXTERNAL_STORAGE}; for (String permission : permissions) { if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, permissions, 200); return; } } } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode,permissions,grantResults); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && requestCode == 200) { for (int i = 0; i < permissions.length; i++) { if (grantResults[i] != PackageManager.PERMISSION_GRANTED) { Intent intent = new Intent(); intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", getPackageName(), null); intent.setData(uri); startActivityForResult(intent, 200); return; } } } } @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK && requestCode == 200) { checkPermission(); } } }
相關(guān)API差異已經(jīng)寫的很詳細(xì)了,布局很簡(jiǎn)單,這里就不貼出來(lái)了。這里要特別注意的是調(diào)用順序不能改變,否則容易報(bào)錯(cuò),且因?yàn)檎{(diào)用順序不對(duì)而報(bào)錯(cuò)的提示信息也不一定足夠去定位問(wèn)題。
踩坑
按照前面的提示,覺(jué)得避開所有坑能愉快的玩耍了,結(jié)果運(yùn)行報(bào)錯(cuò),有的機(jī)型還不打印特定日志,只能自己去鼓搗。
1、Android Q:
有的時(shí)候根據(jù)報(bào)錯(cuò)分類,還是能抓到點(diǎn)蛛絲馬跡。如果是Android Q的設(shè)備,報(bào)IO異常或者Permission Denied錯(cuò)誤,則要檢查下清單文件中application標(biāo)簽里有沒(méi)有這句:
android:requestLegacyExternalStorage="true"
沒(méi)有的話一定要加上。
原因在于安卓10開始,要想訪問(wèn)外部存儲(chǔ)的所有文件,除了動(dòng)態(tài)申請(qǐng)權(quán)限 和 權(quán)限申明外,必須在主工程AndroidManifest.xml中加上這句,用于申請(qǐng)外部存儲(chǔ)所有文件的權(quán)限。
2、RuntimeException:setAudioSource failed
如果程序運(yùn)行看到
RuntimeException: setAudioSource failed
報(bào)錯(cuò),請(qǐng)確保申請(qǐng)權(quán)限相關(guān)邏輯正確,還有清單文件中相關(guān)權(quán)限的申請(qǐng),但如果(雖然是極少概率,但我碰到了)添加權(quán)限后,依舊還報(bào)這個(gè)錯(cuò),請(qǐng)進(jìn)入手機(jī)設(shè)置-應(yīng)用,找到你發(fā)布上去的應(yīng)用,給其授權(quán)。部分機(jī)型在調(diào)試過(guò)程中除了第一次會(huì)提示授權(quán)外,再次安裝則不會(huì)再提示,這就相當(dāng)于用戶沒(méi)有授予相關(guān)的錄音和sdcard讀寫權(quán)限,程序依然會(huì)報(bào)錯(cuò)。所以,建議每次開始進(jìn)行錄音等邏輯前,進(jìn)行一次邏輯判斷。
此外還有一種情況會(huì)出現(xiàn)此報(bào)錯(cuò),在錄音結(jié)束后沒(méi)有調(diào)用mediaRecorder.release()去釋放資源,而又處于stop狀態(tài),這時(shí)候再去prepare、start容易報(bào)此錯(cuò),此時(shí)報(bào)錯(cuò)打印堆棧與原先堆棧報(bào)錯(cuò)信息差別不大,較難定位,因此要格外注意。
3、MediaRecorder: stop failed
在調(diào)用start()后,馬上進(jìn)行調(diào)用stop()的操作,由于沒(méi)有生成有效的音頻或是視頻數(shù)據(jù),會(huì)報(bào)此錯(cuò)誤。這個(gè)情景在即時(shí)通訊過(guò)程中很常見(jiàn),可以通過(guò)讓其線程睡眠小段時(shí)間(建議最少1秒),再stop()。官方文檔注釋對(duì)此也有解釋:Note that a RuntimeException is intentionally thrown to the application, if no valid audio/video data has been received when stop() is called.
總結(jié)
到此這篇關(guān)于Android錄音功能的實(shí)現(xiàn)以及踩坑的文章就介紹到這了,更多相關(guān)Android錄音功能踩坑內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android控件FlowLikeView實(shí)現(xiàn)點(diǎn)贊動(dòng)畫
這篇文章主要為大家詳細(xì)介紹了一個(gè)點(diǎn)贊動(dòng)畫的優(yōu)雅控件FlowLikeView,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06Android編程之TabWidget選項(xiàng)卡用法實(shí)例分析
這篇文章主要介紹了Android編程之TabWidget選項(xiàng)卡用法,結(jié)合實(shí)例形式較為詳細(xì)的分析了TabWidget選項(xiàng)卡的具體實(shí)現(xiàn)技巧與使用注意事項(xiàng),需要的朋友可以參考下2015-12-12Android 自定義View的構(gòu)造函數(shù)詳細(xì)介紹
這篇文章主要介紹了Android 自定義View的構(gòu)造函數(shù)詳細(xì)介紹的相關(guān)資料,這里對(duì)構(gòu)造函數(shù)進(jìn)行了對(duì)比按需使用,需要的朋友可以參考下2016-12-12談?wù)凙ndroid中的Divider是個(gè)什么東東
在Android應(yīng)用開發(fā)中會(huì)經(jīng)常碰到一個(gè)叫divider的東西,就是兩個(gè)View之間的分割線,本文主要給大家介紹android中的divider相關(guān)知識(shí),需要的朋友可以參考下2016-03-03Android 自定義view和屬性動(dòng)畫實(shí)現(xiàn)充電進(jìn)度條效果
近期項(xiàng)目中需要使用到一種類似手機(jī)電池充電進(jìn)度的動(dòng)畫效果,以前沒(méi)學(xué)屬性動(dòng)畫的時(shí)候,是用圖片+定時(shí)器的方式來(lái)完成的,下面給大家分享android自定義view和屬性動(dòng)畫實(shí)現(xiàn)充電進(jìn)度條2016-12-12Android實(shí)現(xiàn)懸浮對(duì)話框代碼
這篇文章主要介紹了Android實(shí)現(xiàn)懸浮對(duì)話框代碼的相關(guān)資料,需要的朋友可以參考下2016-03-03Android編程實(shí)現(xiàn)拍照功能的2種方法分析
這篇文章主要介紹了Android編程實(shí)現(xiàn)拍照功能的2種方法,結(jié)合具體實(shí)例形式對(duì)比分析了Android通過(guò)調(diào)用系統(tǒng)攝像頭及程序調(diào)用照相機(jī)功能兩種實(shí)現(xiàn)技巧與相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-07-07Android開發(fā)之電話撥號(hào)器實(shí)例詳解
這篇文章主要介紹了Android開發(fā)之電話撥號(hào)器,結(jié)合實(shí)例形式詳細(xì)分析了Android電話撥號(hào)器的實(shí)現(xiàn)步驟與具體代碼,并附帶了一個(gè)Android開放電話撥號(hào)器的學(xué)習(xí)筆記,需要的朋友可以參考下2015-12-12解決Fedora14下eclipse進(jìn)行android開發(fā),ibus提示沒(méi)有輸入窗口的方法詳解
本篇文章是對(duì)Fedora14下eclipse進(jìn)行android開發(fā),ibus提示沒(méi)有輸入窗口的解決方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05