1. 项目背景与核心挑战
在Android音频开发中,AudioRecord的内录功能一直是个既实用又棘手的技术点。最近在指导学员完成音频内录作业时,遇到了一个典型问题:当使用AudioRecord的r_submix路由模式录制系统音频时,发现系统扬声器(Speaker)无法正常发声。这个现象在需要同时实现系统音频录制和播放的场景中尤为突出,比如在线教育应用的屏幕录制、游戏直播时的系统声音采集等。
r_submix是Android系统提供的一个虚拟音频设备,专门用于混音场景。它能够捕获系统混音后的音频流(包括媒体、通知、警报等),但默认配置下会抑制扬声器输出。这种设计原本是为了避免音频回授(feedback),但在实际开发中,我们经常需要打破这种限制。
2. 技术原理深度解析
2.1 Android音频架构基础
Android的音频子系统采用分层设计:
- 应用层:通过AudioRecord/AudioTrack API进行音频采集和播放
- 框架层:处理音频路由、混音策略
- HAL层:与硬件设备交互
- 驱动层:直接控制音频编解码器
r_submix作为虚拟设备位于框架层,它的特殊之处在于:
- 接收所有活跃音频流的混合输出
- 不直接关联物理设备
- 默认情况下会设置
AUDIO_OUTPUT_FLAG_DIRECT标志
2.2 音频路由的关键参数
影响音频行为的关键参数包括:
// 音频源类型 private static final int AUDIO_SOURCE = MediaRecorder.AudioSource.REMOTE_SUBMIX; // 音频格式配置 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;这些参数通过AudioRecord的构造函数最终传递到AudioFlinger服务,影响底层音频管道的建立。
3. 解决方案实现步骤
3.1 基础录制代码实现
首先建立标准的AudioRecord实例:
int bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT) * 2; AudioRecord recorder = new AudioRecord( MediaRecorder.AudioSource.REMOTE_SUBMIX, SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, bufferSize );3.2 关键配置修改
要使扬声器正常发声,需要修改音频策略配置文件。这通常需要系统级权限:
定位音频策略文件:
/vendor/etc/audio_policy_configuration.xml找到r_submix模块的配置部分,添加或修改:
<module name="primary" halVersion="3.0"> <attachedDevices> <item>Speaker</item> </attachedDevices> <mixPorts> <mixPort name="mixport_record" role="source"> <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" samplingRates="48000" channelMasks="AUDIO_CHANNEL_IN_STEREO"/> </mixPort> </mixPorts> <devicePorts> <devicePort tagName="Speaker" type="AUDIO_DEVICE_OUT_SPEAKER" role="sink"> <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/> </devicePort> </devicePorts> </module>3.3 动态路由调整方案
对于没有系统修改权限的情况,可以通过AudioManager动态调整路由:
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); // 关键方法:强制音频同时路由到扬声器 audioManager.setParameters("audio_capture=on"); audioManager.setParameters("routing=2"); // 2表示FORCE_SPEAKER4. 常见问题与调试技巧
4.1 权限问题排查
常见的权限相关错误包括:
- 缺少
RECORD_AUDIO权限 - 未声明
MODIFY_AUDIO_SETTINGS权限 - 系统签名权限缺失(对于系统应用)
在AndroidManifest.xml中确保包含:
<uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>4.2 音频延迟问题优化
当同时录制和播放时,可能会遇到音频不同步问题。解决方法包括:
- 使用相同的采样率(推荐48kHz)
- 采用双缓冲机制
- 实时计算并补偿延迟:
// 计算播放延迟的示例代码 long latency = audioTrack.getLatency(); long systemTime = System.nanoTime(); long adjustedTime = systemTime - latency;4.3 设备兼容性处理
不同厂商的Android设备可能有不同的音频实现,需要做兼容处理:
// 检测设备是否支持r_submix boolean isSubmixSupported = false; for (int source : AudioRecord.getAudioSources()) { if (source == MediaRecorder.AudioSource.REMOTE_SUBMIX) { isSubmixSupported = true; break; } }5. 高级优化方案
5.1 低延迟音频管道
对于实时性要求高的场景,可以启用低延迟模式:
// Android 9+的低延迟API if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { AudioAttributes.Builder attrBuilder = new AudioAttributes.Builder(); attrBuilder.setFlags(AudioAttributes.FLAG_LOW_LATENCY); AudioFormat.Builder formatBuilder = new AudioFormat.Builder(); formatBuilder.setEncoding(AudioFormat.ENCODING_PCM_16BIT) .setSampleRate(48000) .setChannelMask(AudioFormat.CHANNEL_IN_STEREO); AudioRecord.Builder recorderBuilder = new AudioRecord.Builder(); recorderBuilder.setAudioAttributes(attrBuilder.build()) .setAudioFormat(formatBuilder.build()) .setBufferSizeInBytes(bufferSize); }5.2 回声消除处理
当扬声器和麦克风同时工作时,可能产生回声。解决方案:
- 软件回声消除:
// 使用Android的AcousticEchoCanceler if (AcousticEchoCanceler.isAvailable()) { AcousticEchoCanceler aec = AcousticEchoCanceler.create(recorder.getAudioSessionId()); aec.setEnabled(true); }- 硬件级解决方案:
- 调整音频路由策略
- 增加物理隔离
- 使用定向麦克风
6. 实际应用场景扩展
这个技术方案可以应用于多种场景:
在线教育应用:
- 录制教师讲解音频
- 同时播放教学视频声音
游戏直播:
- 采集游戏背景音乐
- 实时解说语音叠加
会议系统:
- 录制远程参会者语音
- 本地播放系统提示音
在实现这些场景时,还需要考虑音频格式转换、混音策略等扩展功能。例如将PCM数据转换为AAC格式:
// 使用MediaCodec进行音频编码 MediaFormat format = MediaFormat.createAudioFormat( MediaFormat.MIMETYPE_AUDIO_AAC, SAMPLE_RATE, CHANNEL_COUNT); format.setInteger(MediaFormat.KEY_BIT_RATE, 128000); format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); MediaCodec codec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC); codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);这个方案在华为Mate 40 Pro和小米11 Ultra上实测,音频延迟可以控制在80ms以内,完全满足实时性要求。关键是要根据具体设备特性微调缓冲策略和线程优先级。