網站首頁 編程語言 正文
前言
最近接到個需求,不使用第三方SDK的情況下實現IM通訊,文字聊天已經通過MQTT實現,而語音功能目前想到的較好解決方案就是進行錄音文件的上傳下載。可能還有更好解決方案,但我目前沒想到,有建議的小伙伴勞煩指導下。
前提 :
1、權限申請: 清單文件中加上:
<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" />
對應讀寫文件和錄音權限。
2、錄音文件要寫到相應文件夾中,此文件夾要先創建,丟到Application或Activity的onCreate()中都可以,但一定要先創建。
代碼實現流程 :
1、創建MediaRecorder對象;
2、調用setAudioSource()方法設置聲音的來源,一般傳入MediaRecorder.MIC;
3、調用setOutputFormat()設置所錄制的音頻文件的格式;
4、調用setAudioRncoder()、setAudioEncodingBitRate(int bitRate)、setAudioSamlingRate(int SamplingRate)設置所錄音的編碼格式、編碼位率、采樣率等,當然不是每個都需要,根據具體業務調整(setAudioEncodingBitRate(96000),編碼位率一般是96000);
5、調用setOutputFile(String path)方法設置錄制的音頻文件的保存位置;
6、調用MediaRecoder對象的Prepare()方法準備錄制;
7、調用MediaRecoder對象的start()方法開始錄制;
8、結束后調用MediaRecoder對象的stop()方法停止錄制,并調用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"; //聲明存儲路徑,用絕對路徑什么都可以
//btnTalk就是個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機型上MediaRecorder.stop會報錯,這里建議抓取一下異常
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) {
//文件夾一定要先創建,不然報錯的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);//設置麥克風
/*
* 設置保存輸出文件的格式: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);//設置音頻文件編碼格式
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();
}
}
/**
* 簡單的權限申請邏輯
*/
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();
}
}
}
相關API差異已經寫的很詳細了,布局很簡單,這里就不貼出來了。這里要特別注意的是調用順序不能改變,否則容易報錯,且因為調用順序不對而報錯的提示信息也不一定足夠去定位問題。
踩坑
按照前面的提示,覺得避開所有坑能愉快的玩耍了,結果運行報錯,有的機型還不打印特定日志,只能自己去鼓搗。
1、Android?Q:
有的時候根據報錯分類,還是能抓到點蛛絲馬跡。如果是Android?Q的設備,報IO異常或者Permission?Denied錯誤,則要檢查下清單文件中application標簽里有沒有這句:
android:requestLegacyExternalStorage="true"
沒有的話一定要加上。
原因在于安卓10開始,要想訪問外部存儲的所有文件,除了動態申請權限 和 權限申明外,必須在主工程AndroidManifest.xml中加上這句,用于申請外部存儲所有文件的權限。
2、RuntimeException:setAudioSource failed
如果程序運行看到
RuntimeException: setAudioSource failed
報錯,請確保申請權限相關邏輯正確,還有清單文件中相關權限的申請,但如果(雖然是極少概率,但我碰到了)添加權限后,依舊還報這個錯,請進入手機設置-應用,找到你發布上去的應用,給其授權。部分機型在調試過程中除了第一次會提示授權外,再次安裝則不會再提示,這就相當于用戶沒有授予相關的錄音和sdcard讀寫權限,程序依然會報錯。所以,建議每次開始進行錄音等邏輯前,進行一次邏輯判斷。
此外還有一種情況會出現此報錯,在錄音結束后沒有調用mediaRecorder.release()去釋放資源,而又處于stop狀態,這時候再去prepare、start容易報此錯,此時報錯打印堆棧與原先堆棧報錯信息差別不大,較難定位,因此要格外注意。
3、MediaRecorder: stop failed
在調用start()后,馬上進行調用stop()的操作,由于沒有生成有效的音頻或是視頻數據,會報此錯誤。這個情景在即時通訊過程中很常見,可以通過讓其線程睡眠小段時間(建議最少1秒),再stop()。官方文檔注釋對此也有解釋:Note that a RuntimeException is intentionally thrown to the application, if no valid audio/video data has been received when stop() is called.
總結
原文鏈接:https://juejin.cn/post/7103565497425199140
相關推薦
- 2023-12-15 log4j.properties自定義日志配置
- 2022-10-24 C++??STL?_?Vector使用及模擬實現_C 語言
- 2022-10-13 解析批處理命令call和start_DOS/BAT
- 2022-05-03 Python?PyQt5學習之自定義信號_python
- 2022-10-27 scrollview?tableView嵌套解決方案示例_IOS
- 2023-04-04 C/C++關于實現CAN信號的獲取方法_C 語言
- 2022-04-20 Xamarin渲染器移植到.NET?MAUI項目中_實用技巧
- 2022-07-18 linux引導和服務過程
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支