如何开发可同时录制系统与环境音频的AudioRecorder测试应用?
Android 双音频录制实现方案(系统音频+环境音频)
一、必要权限声明
在AndroidManifest.xml中添加以下权限:
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" android:minSdkVersion="33" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
注:Android 13+ 改用READ_MEDIA_AUDIO替代存储权限;系统音频录制需通过MediaProjection申请屏幕录制权限(无需实际录屏,仅获取系统音频流)
二、核心实现代码
1. 申请MediaProjection权限(系统音频录制必备)
private static final int REQUEST_CODE_MEDIA_PROJECTION = 1001; private MediaProjectionManager mediaProjectionManager; private MediaProjection mediaProjection; // 触发系统音频权限申请 private void requestMediaProjection() { mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); Intent intent = mediaProjectionManager.createScreenCaptureIntent(); startActivityForResult(intent, REQUEST_CODE_MEDIA_PROJECTION); } // 处理权限回调 @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE_MEDIA_PROJECTION && resultCode == RESULT_OK) { mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data); // 权限获取成功,初始化双轨录制 initDualAudioRecording(); } }
2. 初始化双音频录制(系统+麦克风)
private AudioRecord micAudioRecord; private AudioRecord systemAudioRecord; private MediaMuxer mediaMuxer; private int micTrackIndex = -1; private int systemTrackIndex = -1; private boolean isRecording = false; // 统一音频参数 private static final int SAMPLE_RATE = 44100; private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO; private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; private void initDualAudioRecording() { // 初始化麦克风录制(环境音频) int bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT); micAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, bufferSize * 2); // 初始化系统音频录制 AudioFormat systemAudioFormat = new AudioFormat.Builder() .setSampleRate(SAMPLE_RATE) .setEncoding(AUDIO_FORMAT) .setChannelMask(CHANNEL_CONFIG) .build(); AudioRecord.Builder systemRecordBuilder = new AudioRecord.Builder() .setAudioFormat(systemAudioFormat) .setAudioSource(MediaRecorder.AudioSource.REMOTE_SUBMIX) .setBufferSizeInBytes(bufferSize * 2); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { systemRecordBuilder.setMediaProjection(mediaProjection); } systemAudioRecord = systemRecordBuilder.build(); // 初始化MediaMuxer,合成双轨MP4文件 String outputPath = getExternalFilesDir(Environment.DIRECTORY_MUSIC) + "/dual_audio_record.mp4"; try { mediaMuxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); } catch (IOException e) { e.printStackTrace(); return; } // 启动录制线程 startRecordingThreads(); }
3. 启动双轨录制线程
private void startRecordingThreads() { isRecording = true; // 麦克风录制线程 new Thread(() -> { micAudioRecord.startRecording(); byte[] buffer = new byte[AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT) * 2]; // 初始化AAC编码器并添加麦克风轨道到Muxer initAacEncoder(); MediaFormat micMediaFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, SAMPLE_RATE, 2); micMediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); micMediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 128000); micTrackIndex = mediaMuxer.addTrack(micMediaFormat); systemTrackIndex = mediaMuxer.addTrack(micMediaFormat); // 系统音频轨道参数与麦克风一致 mediaMuxer.start(); while (isRecording) { int readSize = micAudioRecord.read(buffer, 0, buffer.length); if (readSize > 0) { ByteBuffer encodedData = encodePcmToAac(buffer, readSize); if (encodedData != null) { writeToMuxer(micTrackIndex, encodedData); } } } micAudioRecord.stop(); micAudioRecord.release(); }).start(); // 系统音频录制线程 new Thread(() -> { systemAudioRecord.startRecording(); byte[] buffer = new byte[AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT) * 2]; while (isRecording) { int readSize = systemAudioRecord.read(buffer, 0, buffer.length); if (readSize > 0 && systemTrackIndex != -1) { ByteBuffer encodedData = encodePcmToAac(buffer, readSize); if (encodedData != null) { writeToMuxer(systemTrackIndex, encodedData); } } } systemAudioRecord.stop(); systemAudioRecord.release(); }).start(); } // 停止录制 private void stopRecording() { isRecording = false; if (mediaMuxer != null) { mediaMuxer.stop(); mediaMuxer.release(); } if (aacEncoder != null) { aacEncoder.stop(); aacEncoder.release(); } } // 将编码后的数据写入Muxer private void writeToMuxer(int trackIndex, ByteBuffer encodedData) { MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); bufferInfo.offset = 0; bufferInfo.size = encodedData.remaining(); bufferInfo.presentationTimeUs = System.nanoTime() / 1000; bufferInfo.flags = MediaCodec.BUFFER_FLAG_KEY_FRAME; mediaMuxer.writeSampleData(trackIndex, encodedData, bufferInfo); }
4. PCM转AAC编码工具
private MediaCodec aacEncoder; private ByteBuffer[] inputBuffers; private ByteBuffer[] outputBuffers; private void initAacEncoder() { try { MediaFormat format = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, SAMPLE_RATE, 2); format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); format.setInteger(MediaFormat.KEY_BIT_RATE, 128000); aacEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC); aacEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); aacEncoder.start(); inputBuffers = aacEncoder.getInputBuffers(); outputBuffers = aacEncoder.getOutputBuffers(); } catch (IOException e) { e.printStackTrace(); } } private ByteBuffer encodePcmToAac(byte[] pcmData, int length) { int inputBufferIndex = aacEncoder.dequeueInputBuffer(-1); if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; inputBuffer.clear(); inputBuffer.put(pcmData, 0, length); aacEncoder.queueInputBuffer(inputBufferIndex, 0, length, System.nanoTime() / 1000, 0); } MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); int outputBufferIndex = aacEncoder.dequeueOutputBuffer(bufferInfo, 0); while (outputBufferIndex >= 0) { ByteBuffer outputBuffer = outputBuffers[outputBufferIndex]; ByteBuffer encodedData = ByteBuffer.allocate(bufferInfo.size); encodedData.put(outputBuffer); aacEncoder.releaseOutputBuffer(outputBufferIndex, false); outputBufferIndex = aacEncoder.dequeueOutputBuffer(bufferInfo, 0); return encodedData; } return null; }
三、关键注意事项
- 系统音频兼容性:
MediaRecorder.AudioSource.REMOTE_SUBMIX仅在Android 10+且通过MediaProjection授权后可用;Android 9及以下需root权限或第三方SDK实现系统音频录制。 - 参数一致性:两个录制源的采样率、声道数、编码格式必须完全一致,否则会出现音轨不同步或合成失败。
- 后台录制:Android 8+ 后台录制需启动前台服务,避免被系统强制回收。
- 性能优化:缓冲区大小建议设置为最小缓冲区的2倍,减少音频丢帧;录制线程需与UI线程分离,避免卡顿。
内容的提问来源于stack exchange,提问作者Ayushi-kaushik22
相关产品推荐
相关产品推荐

