You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android如何同时录制麦克风与媒体播放器音频及耳机适配问题

搞定同时录制麦克风与媒体音频的问题

嘿,这个需求挺常见的,咱们来一步步解决它!

为啥你的现有代码不行?

你当前的代码里用了recorder.setAudioSource(MediaRecorder.AudioSource.MIC),这个设置只会让录制器捕获麦克风的声音。当你插上耳机后,媒体播放器的音频输出路径切换到了耳机,系统默认不会把媒体音频混入MIC的捕获流里,所以自然只能录到麦克风的声音,媒体音频完全没被捕获到。

正确的实现思路

要同时录到两者的声音,核心是分别捕获系统媒体音频和麦克风音频,然后在代码里把两个音频流混合起来。Android 10(API 29)及以上提供了官方的MediaProjection API来捕获系统音频,这是最靠谱的方案;低版本的话因为系统限制,普通APP很难做到稳定捕获,所以咱们重点讲API 29+的实现。

第一步:先搞定权限

首先得在AndroidManifest.xml里加必要的权限:

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Android 10+ 捕获系统音频需要这个 -->
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />

还要记得在运行时动态申请RECORD_AUDIO和存储权限,另外还得请求用户同意屏幕/音频捕获权限——因为系统音频捕获是和MediaProjection绑定的,必须用户授权才行。

第二步:核心代码实现

咱们用两个AudioRecord分别捕获系统音频和麦克风音频,然后在后台线程里把它们的PCM数据混合,最后写入文件。下面是核心代码示例:

// 先请求MediaProjection权限(这里省略权限申请的弹窗逻辑,你需要自己实现)
MediaProjectionManager projectionManager = 
    (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
Intent captureIntent = projectionManager.createScreenCaptureIntent();
startActivityForResult(captureIntent, REQUEST_CODE_AUDIO_CAPTURE);

// 在onActivityResult里拿到授权后的MediaProjection
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_CODE_AUDIO_CAPTURE && resultCode == RESULT_OK) {
        MediaProjection mediaProjection = projectionManager.getMediaProjection(resultCode, data);
        startMixedRecording(mediaProjection);
    }
}

private void startMixedRecording(MediaProjection mediaProjection) {
    // 统一音频格式:采样率44100,单声道,16位PCM
    AudioFormat audioFormat = new AudioFormat.Builder()
            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
            .setSampleRate(44100)
            .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
            .build();

    int bufferSize = AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);

    // 1. 创建捕获系统音频的AudioRecord
    AudioRecord systemAudioRecord = new AudioRecord.Builder()
            .setAudioSource(MediaRecorder.AudioSource.REMOTE_SUBMIX) // 系统音频源
            .setAudioFormat(audioFormat)
            .setBufferSizeInBytes(bufferSize)
            .setMediaProjection(mediaProjection)
            .build();

    // 2. 创建捕获麦克风音频的AudioRecord
    AudioRecord micAudioRecord = new AudioRecord.Builder()
            .setAudioSource(MediaRecorder.AudioSource.MIC)
            .setAudioFormat(audioFormat)
            .setBufferSizeInBytes(bufferSize)
            .build();

    // 启动两个录制器
    systemAudioRecord.startRecording();
    micAudioRecord.startRecording();

    // 开启后台线程处理音频混合
    new Thread(() -> {
        byte[] systemBuffer = new byte[bufferSize];
        byte[] micBuffer = new byte[bufferSize];
        File outputFile = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), 
                                  System.currentTimeMillis() + ".wav");
        
        try (FileOutputStream fos = new FileOutputStream(outputFile);
             DataOutputStream dos = new DataOutputStream(fos)) {
            // 先写入WAV文件的头信息(这个需要你自己实现,网上有很多现成的代码)
            writeWavHeader(dos, 44100, 1, 16);

            // 循环读取并混合音频数据
            while (isRecording) {
                int systemReadLen = systemAudioRecord.read(systemBuffer, 0, bufferSize);
                int micReadLen = micAudioRecord.read(micBuffer, 0, bufferSize);

                if (systemReadLen > 0 && micReadLen > 0) {
                    // 混合两个PCM音频:简单平均,避免音量过大失真
                    for (int i = 0; i < systemReadLen; i += 2) {
                        // 把字节转成16位的音频采样值
                        short systemSample = (short) ((systemBuffer[i] & 0xFF) | (systemBuffer[i+1] << 8));
                        short micSample = (short) ((micBuffer[i] & 0xFF) | (micBuffer[i+1] << 8));
                        // 混合:可以调整比例,比如micSample * 0.6 + systemSample * 0.4
                        short mixedSample = (short) ((systemSample + micSample) / 2);
                        // 再转成字节写回缓冲区
                        systemBuffer[i] = (byte) (mixedSample & 0xFF);
                        systemBuffer[i+1] = (byte) ((mixedSample >> 8) & 0xFF);
                    }
                    // 把混合后的数据写入文件
                    dos.write(systemBuffer, 0, systemReadLen);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 停止并释放资源
            systemAudioRecord.stop();
            systemAudioRecord.release();
            micAudioRecord.stop();
            micAudioRecord.release();
        }
    }).start();
}

// 辅助方法:写入WAV文件头,需要自己实现哦
private void writeWavHeader(DataOutputStream dos, int sampleRate, int channels, int bitsPerSample) throws IOException {
    // 这里放标准的WAV头写入逻辑,比如设置ChunkID、ChunkSize、Format等字段
}

几个关键注意点

  • 版本限制REMOTE_SUBMIX这个音频源只能在Android 10及以上用,而且必须配合MediaProjection,这是系统硬限制,没法绕过。
  • 音频混合:示例里用了简单的平均混合,你可以根据需求调整比例(比如让麦克风声音更突出),但要注意别让采样值超过short的范围,不然会出现破音。
  • 文件格式:示例用了WAV格式,因为PCM数据可以直接写入;如果要生成AMR格式,你需要在混合后用MediaCodec把PCM编码成AMR。
  • 权限问题:一定要确保用户同意了所有必要的权限,不然系统会直接拒绝音频捕获请求。

内容的提问来源于stack exchange,提问作者user9933014

火山引擎 最新活动