You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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音频数据,同时完成两个任务:

  1. 把PCM数据实时喂给AudioTrack实现播放
  2. 将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:58:20