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




