AudioRecord(基于字节流录音)
优点:可以实现语音的实时处理,进行边录边播,对音频的实时处理。
缺点:输出的是PCM的语音数据,如果保存成音频文件是不能被播放器播放的。要用到AudioTrack这个去进行处理。
实现 AudioRecord 录音的流程
构造一个 AudioRecord 对象,其中最小录音缓存 buffer 大小可以通过 getMinBufferSize 方法得到。如果 buffer 容量过小,将导致对象构造的失败。
初始化一个 buffer,该 buffer 大于等于 AudioRecord 对象用于写声音数据的 buffer 大小。
开始录音
创建一个数据流,一边从 AudioRecord 中读取声音数据到初始化的 buffer,一边将 buffer 中数据导入数据流。
关闭数据流
停止录音
释放对象
构造 AudioRecord 对象需要的参数
音频源:一般可以使用麦克风作为采集音频的数据源。
采样率:一秒内对声音数据的采样次数,采样率越高,音质越好。
通道:单声道,双声道等。
音频格式:一般选用 PCM 格式,即原始的音频样本。
缓冲区大小:音频数据写入缓冲区的总数,通过 AudioRecord.getMinBufferSize 获取最小的缓冲区。
(最后生成的音频文件是 PCM 格式的,也就是最原始的音频数据,它没有头信息,不能直接播放,必须转换成可识别的格式才行。若转成 WAV 格式,在文件的数据开头加入 WAVE HEAD。)
AudioRecord例子:录制音频
录音JNI函数不具有线程安全性,因此用单线程,以及设置缓存大小
1 2 3 4 5 6 7 8
| mExecutorService = Executors.newSingleThreadExecutor(); mHandler = new Handler(Looper.getMainLooper()); mAudioRecordTest = new AudioRecordTest.Builder(this) .setExecutorService(mExecutorService) .setHandler(mHandler) .setBtStreamRecorder(mBtStreamRecorder) .setBufferSize(BUFFER_SIZE) .build();
|
点击button进行录音,再次点击停止录音功能recorderAudio()
1 2 3 4 5 6 7 8 9 10 11 12
| public void recordAudio() { if (mIsRecording) { mIsRecording = false; mBtStreamRecorder.setText("开始录音"); } else { mIsRecording = true; mBtStreamRecorder.setText("停止录音"); mExecutorService.submit(this::startRecord); } }
|
开始录音startRecorder()
1 2 3 4 5
| private void startRecord() { if (!doStartRecord()) { recordFail(); } }
|
AudioRecord的配置及录音功能实现doStartRecord()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| private boolean doStartRecord() { try { startRecorderTime = System.currentTimeMillis(); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { mAudioRecordFile = new File(mContext.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + "/AudioRecordDemo/" + startRecorderTime + ".pcm"); if (!mAudioRecordFile.getParentFile().exists()) { mAudioRecordFile.getParentFile().mkdirs(); } } else { return false; } mAudioRecordFile.createNewFile(); FileOutputStream fos = new FileOutputStream(mAudioRecordFile); int audioSource = MediaRecorder.AudioSource.MIC; int sampleRate = 44100; int channelConfig = AudioFormat.CHANNEL_IN_MONO; int audioFormat = AudioFormat.ENCODING_PCM_16BIT; int minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat); mAudioRecord = new AudioRecord(audioSource, sampleRate, channelConfig, audioFormat, Math.max(minBufferSize, BUFFER_SIZE)); mAudioRecord.startRecording(); while (mIsRecording) { int read = mAudioRecord.read(mBuffer, 0, BUFFER_SIZE); if (read <= 0) { return false; } else { fos.write(mBuffer, 0, read); } } stopRecord(); } catch (Exception e) { e.printStackTrace(); return false; } finally { if (mAudioRecord != null) { mAudioRecord.stop(); mAudioRecord.release(); mAudioRecord = null; } } return true; }
|
停止录音stopRecorder()
1 2 3 4 5 6
| private void stopRecord() { mIsRecording = false; if (!doStopRecord()) { recordFail(); } }
|
再次点击停止录音doStopRecord()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| private boolean doStopRecord() { mAudioRecord.stop(); mAudioRecord.release(); mAudioRecord = null; long stopRecorderTime = System.currentTimeMillis(); final int second = (int) (stopRecorderTime - startRecorderTime) / 1000; if (second > 3) { mHandler.post(() -> { Toast.makeText(mContext, "录音成功:" + second + "秒", Toast.LENGTH_SHORT).show(); Log.d(TAG, "save in " + startRecorderTime + ".pcm"); mBtStreamRecorder.setText("开始录音"); }); } else { recordFail(); return false; } return true; }
|
录取失败,更新UI操作recorderFail()
1 2 3 4 5 6 7 8 9 10 11 12 13
| private void recordFail() { mHandler.post(() -> { Toast.makeText(mContext, "录制失败,录音请大于3秒!", Toast.LENGTH_SHORT).show(); mBtStreamRecorder.setText("开始录音"); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { File audioRecordFile = new File(mContext.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + "/AudioRecordDemo/" + startRecorderTime + ".pcm"); audioRecordFile.delete(); } mIsRecording = false; }); }
|
onDestroy()防止内存泄漏
1 2 3 4 5 6 7 8 9 10 11
| @Override protected void onDestroy() { super.onDestroy(); if (mAudioRecordTest.getAudioRecord() != null) { mAudioRecordTest.getAudioRecord().stop(); } if (mExecutorService != null) { mExecutorService.shutdownNow(); mExecutorService = null; } }
|
已集成了录音,编码,压缩等,支持少量的音频格式文件。
优点:封装度很高,操作简单
缺点:无法实现实时处理音频,输出的音频格式少。
建立 MediaRecorder 类的对象:new MediaRecorder()
设置音频来源:mMediaRecorder.setAudioSource()
设置音频编码方式:mMediaRecorder.setAudioEncoder()
设置音频文件的保存位置及文件名:mMediaRecorder.setOutputFile()
将录音器置于准备状态:mMediaRecorder.prepare()
启动录音器:mMediaRecorder.start()
音频录制完成,停止录音器:mMediaRecorder.stop()
释放录音器对象:mMediaRecorder.release()
录音JNI函数不具有线程安全性,因此用单线程
1 2 3 4 5 6 7
| mExecutorService = Executors.newSingleThreadExecutor(); mHandler = new Handler(Looper.getMainLooper()); mMediaRecorderTest = new MediaRecorderTest.Builder(this) .setExecutorService(mExecutorService) .setHandler(mHandler) .setBtMediaRecorder(mBtMediaRecorder) .build();
|
开启一个单线程去实现录音功能按下按钮recordMedia()
1 2 3 4 5 6 7 8 9 10 11 12
| public void recordMedia() { if (mIsRecording) { mIsRecording = false; mBtMediaRecorder.setText("开始录音"); mExecutorService.submit(this::stopRecorder); } else { mIsRecording = true; mBtMediaRecorder.setText("停止录音"); mExecutorService.submit(this::startRecorder); } }
|
开始录音startRecorder()
1 2 3 4 5 6 7 8
| private void startRecorder() { releaseRecorder(); if (!doStart()) { recorderFail(); } }
|
启动录音doStart()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| private boolean doStart() { try { mMediaRecorder = new MediaRecorder(); startRecorderTime = System.currentTimeMillis(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { mRecorderFile = new File(mContext.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + "/MediaRecorderDemo/" + startRecorderTime + ".m4a"); if (!mRecorderFile.getParentFile().exists()) { mRecorderFile.getParentFile().mkdirs(); } } else { return false; } mRecorderFile.createNewFile(); mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); mMediaRecorder.setAudioSamplingRate(44100); mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); mMediaRecorder.setAudioEncodingBitRate(96000); mMediaRecorder.setOutputFile(mRecorderFile.getAbsolutePath()); mMediaRecorder.prepare(); mMediaRecorder.start(); } catch (Exception e) { Toast.makeText(mContext, "录音失败,请重试", Toast.LENGTH_SHORT).show(); return false; } return true; }
|
停止录音stopRecorder()
1 2 3 4 5 6 7
| private void stopRecorder() { if (!doStop()) { recorderFail(); } releaseRecorder(); }
|
停止录音计算时长doStop()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| private boolean doStop() { try { mMediaRecorder.stop(); stopRecorderTime = System.currentTimeMillis(); final int second = (int) (stopRecorderTime - startRecorderTime) / 1000; if (second < 3) return false; mHandler.post(new Runnable() { @Override public void run() { Toast.makeText(mContext, "录音成功:" + second + "秒", Toast.LENGTH_SHORT).show(); Log.d(TAG, "save in " + startRecorderTime + ".m4a"); } }); } catch (Exception e) { e.printStackTrace(); } return true; }
|
释放MediaRecorder的releaseRecorder()
1 2 3 4 5 6
| private void releaseRecorder() { if (mMediaRecorder != null) { mMediaRecorder.release(); mMediaRecorder = null; } }
|
设置录音失败的逻辑recorderFail()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| private void recorderFail() { mRecorderFile = null; mHandler.post(new Runnable() { @Override public void run() { Toast.makeText(mContext, "录制失败,录音请大于3秒!", Toast.LENGTH_SHORT).show(); mBtMediaRecorder.setText("开始录音"); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { File audioRecordFile = new File(mContext.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + "/MediaRecorderDemo/" + startRecorderTime + ".m4a"); audioRecordFile.delete(); } } }); }
|
记得在onDestroy()防止内存泄漏
1 2 3 4 5 6 7 8 9 10 11
| @Override protected void onDestroy() { super.onDestroy(); if (mMediaRecorderTest.getMediaRecorder() != null) { mMediaRecorderTest.getMediaRecorder().stop(); } if (mExecutorService != null) { mExecutorService.shutdownNow(); mExecutorService = null; } }
|