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

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

  • 20
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android实现录音功能,可以使用 `MediaRecorder` 类或 `AudioRecord` 类。这里给出 `AudioRecord` 的实现示例。 首先,在 AndroidManifest.xml 文件中添加录音权限: ```xml <uses-permission android:name="android.permission.RECORD_AUDIO" /> ``` 然后,创建一个 `AudioRecord` 对象并开始录音: ```java import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaRecorder; import android.os.Handler; import android.os.Looper; import android.util.Log; public class AudioRecorder { private static final String TAG = AudioRecorder.class.getSimpleName(); private static final int SAMPLE_RATE = 16000; private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO; private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; private static final int BUFFER_SIZE = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT); private AudioRecord audioRecord; private byte[] buffer; private boolean isRecording; private Handler handler; private Callback callback; public AudioRecorder() { audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, BUFFER_SIZE); buffer = new byte[BUFFER_SIZE]; handler = new Handler(Looper.getMainLooper()); } public void setCallback(Callback callback) { this.callback = callback; } public void start() { if (isRecording) { return; } audioRecord.startRecording(); isRecording = true; new Thread(() -> { while (isRecording) { int bytes = audioRecord.read(buffer, 0, buffer.length); if (bytes > 0 && callback != null) { handler.post(() -> callback.onAudioDataReceived(buffer, bytes)); } } audioRecord.stop(); audioRecord.release(); audioRecord = null; }).start(); } public void stop() { isRecording = false; } public interface Callback { void onAudioDataReceived(byte[] data, int size); } } ``` 在此示例中,我们首先创建一个 `AudioRecord` 对象,并使用 `MediaRecorder.AudioSource.MIC` 作为录音源。然后,我们在一个新线程中读取录音数据,并调用回调函数将数据传递给主线程。最后,我们在 `stop` 方法中停止录音。 使用示例: ```java AudioRecorder recorder = new AudioRecorder(); recorder.setCallback((data, size) -> { // 处理录音数据 }); recorder.start(); // 录音中... recorder.stop(); ``` 请注意,此示例中的 `Callback` 接口仅用于演示目的,您应该将其替换为您自己的接口,并在其中处理录音数据。另外,如果您需要将录音保存到文件中,可以使用 `AudioRecord` 的 `read` 方法读取录音数据,并将其写入文件中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值