Android录音功能的实现以及踩坑实战记录

目录
  • 前言
  • 前提 :
  • 代码实现流程 :
  • 踩坑
    • 1、Android Q:
    • 2、RuntimeException:setAudioSource failed
    • 3、MediaRecorder: stop failed
  • 总结

前言

最近接到个需求,不使用第三方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.

总结

到此这篇关于Android录音功能的实现以及踩坑的文章就介绍到这了,更多相关Android录音功能踩坑内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android编程实现通话录音功能的方法

    本文实例讲述了Android编程实现通话录音功能的方法.分享给大家供大家参考,具体如下: 因受系统限制,只能录自已麦的声音,录不到对方的声音,可能需要改内核才能实现双向录音: 接通电话和挂断电话时,震动一下: 使用广播接收者实现自启动: 服务代码: package com.eboy.phoneListener; import java.io.File; import android.app.Service; import android.content.Context; import andro

  • Android 录音与播放功能的简单实例

     Android 录音与播放功能的简单实例 最近在研究Android中一些常用的功能,像地图.拍照.录音和播放的实现等等,还有一些侧滑.动画等是如何实现的. 今天就把录音和播放的实现分享一下,录音和播放比较简单,利用android内部的类即可实现. 1.先看下运行后的界面: 以下三张图分别是进入.录音.播放时的. 2.Layout布局文件 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout

  • Android实现录音功能实现实例(MediaRecorder)

    本文介绍了Android实现录音的实例代码(MediaRecorder),分享给大家,具体如下: Android提供了两个API用于录音的实现:MediaRecorder 和 AudioRecord,各有优劣. 1.MediaRecorder 已经集成了录音.编码.压缩等,支持少量的录音音频格式,大概有.aac(API = 16) .amr .3gp 优点:大部分已经集成,直接调用相关接口即可,代码量小 缺点:无法实时处理音频:输出的音频格式不是很多,例如没有输出mp3格式文件 2.AudioR

  • Android仿微信语音对讲录音功能

    自微信出现以来取得了很好的成绩,语音对讲的实现更加方便了人与人之间的交流.今天来实践一下微信的语音对讲的录音实现,这个也比较容易实现.在此,我将该按钮封装成为一个控件,并通过策略模式的方式实现录音和界面的解耦合,以方便我们在实际情况中对录音方法的不同需求(例如想要实现wav格式的编码时我们也就不能再使用MediaRecorder,而只能使用AudioRecord进行处理). 效果图: 实现思路: 1.在微信中我们可以看到实现语音对讲的是通过点按按钮来完成的,因此在这里我选择重新自己的控件使其继承

  • Android实现语音播放与录音功能

    本文实例为大家分享了Android实现语音播放与录音的具体代码,供大家参考,具体内容如下 项目用到的技术点和亮点 语音录音 (单个和列表) 语音播放(单个和列表) 语音录音封装 语音播放器封装 语音列表顺序播放 语音列表单个播放 复用问题处理 因为安装原生录音不能录mp3格式文件 而mp3格式是安卓和ios公用的,所以我们需要的是能直接录取mp3文件或者录完的格式转成mp3格式 下面添加这个库 能直接录mp3文件,我觉得是最方便的 compile 'com.czt.mp3recorder:lib

  • android语音即时通讯之录音、播放功能实现代码

    在android中,实现录音与语音播放的功能算是比较简单的,但是作为参考,还是很有必要将语音相关的知识做一个简要的记录. 首先,在android中,支持录音支持两种方式.主要包括:字节流模式和文件流模式.用文件流模式进行录音操作比较简单,而且相对来说,因为其封装性比较好,录制下的文件也会比较小.但是相对于文件流模式,就没有字节流模式那么灵活,但是想要用好字节流模式还是需要下一点功夫的. 下面开始介绍文件流模式的语音操作: 文件流模式 我们来看录音部分的实现,首先我们实现开始录音的部分: 在正式编

  • Android仿微信录音功能

    提要:需求是开发类似微信发语音的功能,没有语音转文字.网上看了一些代码,不能拿来直接用,部分代码逻辑有问题,所以想把自己的代码贴出来,仅供参考. 功能: a.设置最大录音时长和录音倒计时(为了方便测试,最大时长设置为15秒,开始倒计时设置为7秒) b.在录音之前检查录音和存储权限 源码: 1.录音对话框管理类DialogManager: /** * 功能:录音对话框管理类 */ public class DialogManager { private AlertDialog.Builder bu

  • Android录音功能的实现以及踩坑实战记录

    目录 前言 前提 : 代码实现流程 : 踩坑 1.Android Q: 2.RuntimeException:setAudioSource failed 3.MediaRecorder: stop failed 总结 前言 最近接到个需求,不使用第三方SDK的情况下实现IM通讯,文字聊天已经通过MQTT实现,而语音功能目前想到的较好解决方案就是进行录音文件的上传下载.可能还有更好解决方案,但我目前没想到,有建议的小伙伴劳烦指导下. 前提 : 1.权限申请: 清单文件中加上: <uses-perm

  • 使用Pyinstaller的最新踩坑实战记录

    前言 将py编译成可执行文件需要使用PyInstaller,之前给大家介绍了关于利用PyInstaller将python程序.py转为.exe的方法,在开始本文之前推荐大家可以先看下这篇文章,本文主要给大家介绍了Pyinstaller最新踩坑实战记录,现在网上关于pyinstaller的问题充斥着各种copy过来copy过去的答案,这大概就是各种无脑博客爬虫站最让人讨厌的地方. 而且这方面的问题,stackoverflow也是回答的千奇百怪. 强烈推荐官方文档 http://pythonhost

  • Go使用proto3的踩坑实战记录

    开发环境:windows10,golang1.18.2,goland2022.2 最近在写项目时,一些数据类的结构以protobuf文件给定.因此,需要将这些protobuf文件转换为golang代码. 首先,在下载解析protobuf的包的时候就碰到了第一个问题... go get -u github.com/golang/protobuf/protoc-gen-go 在我用上述命令后,终端提示该包已弃用 go: module github.com/golang/protobuf is dep

  • nginx反向代理踩坑实战记录(容器方式)

    目录 一.简述 1.1 什么是反向代理? 1.2 看图理解 1.3 错误总结 二.正确案例 2.1 启动nginx 2.3 配置nginx 2.4 重启所有服务 2.5 测试 三.云服务器上跑的nginx怎么代理本地项目 总结 一.简述 1.1 什么是反向代理? 这很重要,反向代理就是代理服务器代理真实服务器.客户端以为代理服务器就是真实服务器,所以就会把要请求的==资源(URL)==发给代理服务器. 代理服务器一般是由nginx来充当,代理功能由配置文件来完成. 1.2 看图理解 画的仓促,大

  • 一次mysql迁移的方案与踩坑实战记录

    目录 背景 方案一:老数据备份 方案二:分表 方案三:迁移至tidb 重点说下同步老数据遇到的坑 最终同步脚本方案 总结 背景 由于历史业务数据采用mysql来存储的,其中有一张操作记录表video_log,每当用户创建.更新或者审核人员审核的时候,对应的video_log就会加一条日志,这个log表只有insert,可想而知,1个video对应多条log,一天10w video,平均统计一个video对应5条log,那么一天50w的log, 一个月50 * 30 = 1500w条记录, 一年就

  • springboot整合log4j的踩坑实战记录

    目录 1.依赖添加 1.1.添加依赖 1.2.剔除依赖 2.配置日志 2.1.日志打印记录 2.2.指定配置文件 补充:log4j调优和注意事项 总结 1.依赖添加 1.1.添加依赖 需要引入 log4j 的依赖支持,推荐自己确定使用的版本. <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-to-slf4j</artifactId> <ve

  • vue3搭配pinia的踩坑实战记录

    目录 前言 An Object could not be cloned? VUE 3的toRaw PINIA与VUE 3可以混合搭配? 同样的操作在VUE 3下的结果 最后的解决方式 总结 前言 最近接手了一个新项目,用的是VUE3+pinia的组合.由于之前没有用过这2个库,只能现学现做.幸运的是这两者的上手难度都不大,项目可以正常开发.但这其中也遇到了一些坑,今天就来讲其中我印象最深的一个. An Object could not be cloned? 不知道有多少开发者遇到过这个报错——A

  • Java切割字符串的踩坑实战记录

    目录 坑出现的环境 问题的解决 补充:java分割字符串常见语法 一.java.lang.String.split() 二.java.util.StringTokenizer() 总结 坑出现的环境 一般情况下切割字符串会使用split或者StringTokenizer,如下代码 String s = ",,o,,"; String[] split = s.split(","); 期望得到数组["","","o&qu

  • C++踩坑实战之构造和析构函数

    目录 前言 构造函数 通过构造函数实现的类型转换 派生类的构造函数 析构函数 继承中的析构函数 应用 总结 前言 我是练习时长一年的 C++ 个人练习生,喜欢野指针.模板报错和未定义行为(undefined behavior).之前在写设计模式的『工厂模式』时,一脚踩到了构造.继承和 new 组合起来的坑,现在也有时间来整理一下了. 构造函数 众所周知:在创建对象时,防止有些成员没有被初始化导致不必要的错误,在创建对象的时候自动调用构造函数(无声明类型),完成成员的初始化.即: Class c

  • Swift踩坑实战之一个字符引发的Crash

    最近因为一个字符引发了 Crash,因为实际的业务场景不便描述,这里便用一段测试代码作说明. 话不多说,直接上代码: let testCharacters: Set<Character> = ["!", "\"", "$", "%", "&", "'", "+", ",", "<", &quo

随机推荐