Android中MediaRecorder与AudioRecorder无法共享麦克风资源求助
解决Android中MediaRecorder与AudioRecord麦克风冲突问题
这个问题我做音频相关开发时也碰到过,核心原因很明确:Android的麦克风硬件默认是独占式访问的,MediaRecorder和AudioRecord都以MIC作为输入源的话,两者无法同时占用麦克风资源,必然会出现冲突。
冲突对应原因拆解
- 先启动
MicrophoneStream(依赖AudioRecord)再启动MediaRecorder:麦克风已被AudioRecord占用,MediaRecorder.start()会直接抛出IllegalStateException - 先启动
MediaRecorder再启动流传输:麦克风被MediaRecorder占用,AudioRecord.startRecording()返回错误码-38(对应ERROR_INVALID_OPERATION,表示操作无效)
解决方案:用单一AudioRecord搞定双需求
既然两者不能同时使用,我们可以放弃MediaRecorder,只用AudioRecord获取原始PCM音频数据,同时完成两个任务:
- 把PCM数据实时喂给
AudioTrack实现播放 - 将PCM数据编码成你需要的格式(比如原来的AMR-NB)保存到文件
下面是整合后的完整实现:
1. 整合式音频处理类
public class AudioRecordAndPlayManager { private static final String TAG = "AudioRecordAndPlay"; private AudioRecord mAudioRecord; private AudioTrack mAudioTrack; private Thread mWorkThread; private boolean isRunning = false; // 和你原来配置对齐的参数 private static final int SAMPLE_RATE = 44100; private static final int CHANNEL_IN = AudioFormat.CHANNEL_IN_MONO; private static final int CHANNEL_OUT = AudioFormat.CHANNEL_OUT_MONO; private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; private MediaCodec mAmrEncoder; private FileOutputStream mOutputStream; public void start(String outputFilePath) { if (isRunning) return; isRunning = true; // 初始化AudioRecord和AudioTrack,缓冲区设为最小的2倍,避免卡顿 int minBuffer = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_IN, AUDIO_FORMAT); mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE, CHANNEL_IN, AUDIO_FORMAT, minBuffer * 2); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLE_RATE, CHANNEL_OUT, AUDIO_FORMAT, minBuffer * 2, AudioTrack.PERFORMANCE_MODE_LOW_LATENCY); } else { mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLE_RATE, CHANNEL_OUT, AUDIO_FORMAT, minBuffer * 2, AudioTrack.MODE_STREAM); } // 初始化AMR-NB编码器,和你原来MediaRecorder的编码格式一致 try { mAmrEncoder = MediaCodec.createEncoderByType("audio/AMR-NB"); MediaFormat amrFormat = MediaFormat.createAudioFormat("audio/AMR-NB", SAMPLE_RATE, 1); amrFormat.setInteger(MediaFormat.KEY_BIT_RATE, 16000); // 对应你设置的16kbps mAmrEncoder.configure(amrFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mAmrEncoder.start(); // 打开输出文件,记得写入AMR文件头,不然播放器识别不了 mOutputStream = new FileOutputStream(outputFilePath); mOutputStream.write(new byte[]{0x23, 0x21, 0x41, 0x4D, 0x52, 0x0A}); // 对应#!AMR\n } catch (IOException e) { e.printStackTrace(); stop(); return; } // 启动录制和播放 mAudioRecord.startRecording(); mAudioTrack.play(); // 启动工作线程处理数据 mWorkThread = new Thread(() -> { byte[] pcmBuffer = new byte[minBuffer]; while (isRunning) { int readBytes = mAudioRecord.read(pcmBuffer, 0, pcmBuffer.length); if (readBytes > 0) { // 1. 实时播放PCM数据 mAudioTrack.write(pcmBuffer, 0, readBytes); // 2. 编码PCM为AMR并写入文件 encodePcmToAmr(pcmBuffer, readBytes); } } // 停止后统一释放资源 releaseAllResources(); }); mWorkThread.start(); } private void encodePcmToAmr(byte[] pcmData, int dataSize) { try { // 给编码器喂PCM数据 int inputBufIdx = mAmrEncoder.dequeueInputBuffer(1000); if (inputBufIdx >= 0) { ByteBuffer inputBuf = mAmrEncoder.getInputBuffer(inputBufIdx); inputBuf.clear(); inputBuf.put(pcmData, 0, dataSize); mAmrEncoder.queueInputBuffer(inputBufIdx, 0, dataSize, System.nanoTime() / 1000, 0); } // 获取编码后的AMR数据并写入文件 MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); int outputBufIdx = mAmrEncoder.dequeueOutputBuffer(bufferInfo, 1000); while (outputBufIdx >= 0) { ByteBuffer outputBuf = mAmrEncoder.getOutputBuffer(outputBufIdx); if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { // 跳过编码器的配置数据 mAmrEncoder.releaseOutputBuffer(outputBufIdx, false); outputBufIdx = mAmrEncoder.dequeueOutputBuffer(bufferInfo, 1000); continue; } // 写入编码后的AMR数据 byte[] amrBytes = new byte[bufferInfo.size]; outputBuf.get(amrBytes); mOutputStream.write(amrBytes); mAmrEncoder.releaseOutputBuffer(outputBufIdx, false); outputBufIdx = mAmrEncoder.dequeueOutputBuffer(bufferInfo, 1000); } } catch (IOException e) { e.printStackTrace(); } } public void stop() { isRunning = false; if (mWorkThread != null) { try { mWorkThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } private void releaseAllResources() { if (mAudioRecord != null) { mAudioRecord.stop(); mAudioRecord.release(); mAudioRecord = null; } if (mAudioTrack != null) { mAudioTrack.stop(); mAudioTrack.release(); mAudioTrack = null; } if (mAmrEncoder != null) { mAmrEncoder.stop(); mAmrEncoder.release(); mAmrEncoder = null; } if (mOutputStream != null) { try { mOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } mOutputStream = null; } } }
2. 调用示例
// 启动录制+实时播放,文件保存到应用私有目录(Android 10+无需外部存储权限) AudioRecordAndPlayManager audioManager = new AudioRecordAndPlayManager(); String outputPath = getExternalFilesDir(null) + "/record.amr"; audioManager.start(outputPath); // 停止时调用 // audioManager.stop();
关键说明
- 只用
AudioRecord获取PCM数据,同时处理播放和编码,彻底避免麦克风资源冲突 - 用
MediaCodec实现AMR-NB编码,和你原来MediaRecorder的配置完全对齐,生成的文件可以正常播放 - AMR格式必须写入特定文件头,否则播放器无法识别这个文件
- 权限方面:需要申请
RECORD_AUDIO权限,Android 10及以上用应用私有目录保存文件的话,不需要WRITE_EXTERNAL_STORAGE权限
额外优化建议
- 如果需要AAC等其他编码格式,只需要修改
MediaCodec的类型和MediaFormat配置即可 - 可以根据设备性能调整缓冲区大小,平衡延迟和稳定性
- 可以增加错误回调,方便业务层处理异常情况
内容的提问来源于stack exchange,提问作者TABZ
相关产品推荐
相关产品推荐

