7、车载音频系统:Android Automotive音频架构
车载音频,说实话,是IVI系统里最容易被低估的模块。很多人觉得不就是放个音乐、接个电话吗?等你真正上车调音、处理多路音频并发、搞定蓝牙和USB切换的时候,就知道这里面的坑有多深了。
我最早接触车载音频是在一个量产项目上,当时客户反馈导航提示音和音乐混在一起,听不清。我查了三天日志,最后发现是音频焦点管理没配好。嗯,从那以后,我对AudioPolicy和CarAudioService就格外上心。
这一章,我们就来拆解Android Automotive的音频架构。我会从底层AudioFlinger讲起,再到上层的CarAudioService,最后聊聊多区域音频和路由策略。你跟着走一遍,基本就能应付大部分车载音频开发场景了。
7.1 Android音频系统概览:AudioPolicy与AudioFlinger
Android的音频系统,核心就两个服务:AudioFlinger 和 AudioPolicyService。前者管音频数据的混音和输出,后者管音频策略——说白了就是「谁的声音该从哪个喇叭出来」。
我习惯把AudioFlinger比作一个调音台。各路音频流(音乐、导航、电话)进来,它负责混音,然后送到硬件设备(扬声器、耳机、蓝牙)。AudioPolicyService则是调音师,它决定哪路音频该被压低、哪路该被放大。
核心要点:
- AudioFlinger:负责音频数据的混音、重采样、格式转换,最终写入音频硬件抽象层(HAL)。
- AudioPolicyService:负责音频路由策略、设备选择、音量管理、焦点管理。
在车载场景下,AudioPolicyService的配置尤为重要。因为车上有多个音频输出设备——前门喇叭、后门喇叭、头枕喇叭、蓝牙耳机、USB音频设备。你想想看,如果导航音从后门喇叭出,音乐从前门喇叭出,那体验得多奇怪。
AudioPolicy的配置主要在 audio_policy_configuration.xml 里。这个文件定义了所有音频设备、端口、以及它们之间的连接关系。我贴一个简化版的车载配置示例:
<!-- audio_policy_configuration.xml 片段 -->
<audioPolicyConfiguration version="1.0">
<globalConfiguration speaker_drc_enabled="false"/>
<modules>
<module name="primary" halVersion="3.0">
<attachedDevices>
<item>Speaker</item>
<item>BuiltInMic</item>
</attachedDevices>
<devicePorts>
<devicePort tagName="Speaker" type="AUDIO_DEVICE_OUT_SPEAKER" role="source">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
</devicePort>
<devicePort tagName="WiredHeadset" type="AUDIO_DEVICE_OUT_WIRED_HEADSET" role="source">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
</devicePort>
</devicePorts>
<routes>
<route type="mix" sink="Speaker" sources="mixport_0,mixport_1"/>
<route type="mix" sink="WiredHeadset" sources="mixport_0"/>
</routes>
</module>
</modules>
</audioPolicyConfiguration>
这里要注意,车载系统通常会有多个音频模块(primary、usb、bluetooth等)。每个模块管理一组设备。路由规则定义了哪些混音端口(mixport)可以输出到哪些物理设备。
我的经验:调试音频路由问题时,先检查 dumpsys audio_policy 的输出。它能告诉你当前设备连接状态、路由策略、以及每个音频流的输出设备。我每次遇到「没声音」的问题,第一件事就是跑这个命令。
7.2 CarAudioService与音频焦点管理
CarAudioService是Android Automotive在音频方面的核心扩展。它不像手机那样简单——手机只有一个人在用,音频焦点冲突少。车上呢?驾驶员要导航,副驾要听歌,后排小孩要看动画片。音频焦点管理就变得复杂了。
CarAudioService主要做了几件事:
- 管理车载特有的音频上下文(AudioContext),比如导航、语音命令、电话、媒体、安全警告等。
- 实现多用户音频焦点管理(不同座位可能有不同用户)。
- 提供音量分组和独立调节能力。
- 与车辆硬件交互,控制音频路由。
音频焦点,说白了就是「谁有资格出声」。在车载场景下,焦点的优先级是硬编码的。比如安全警告的优先级最高,导航次之,媒体最低。我见过一个案例,第三方音乐App在导航播报时强行获取焦点,导致导航音被完全压制。后来我们在CarAudioService里加了焦点策略校验,才解决这个问题。
焦点管理的核心接口是 AudioManager.requestAudioFocus()。但在车载上,我们通常用 CarAudioManager 提供的扩展方法:
// 获取CarAudioManager实例
CarAudioManager carAudioManager = Car.createCar(context).getCarManager(CarAudioManager.class);
// 请求音频焦点,指定音频上下文为导航
AudioAttributes audioAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
.build();
AudioFocusRequest focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(audioAttributes)
.setOnAudioFocusChangeListener(focusChange -> {
if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
// 失去焦点,暂停导航播报
pauseNavigation();
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// 重新获得焦点,恢复播报
resumeNavigation();
}
})
.build();
int result = carAudioManager.requestAudioFocus(focusRequest);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// 焦点获取成功
startNavigationGuidance();
}
这里有个细节:车载的音频焦点是分区域的。驾驶员区域的导航焦点和副驾区域的媒体焦点可以共存。CarAudioService内部维护了一个焦点矩阵,按区域和上下文来管理。
注意:不要滥用 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK。在车载上,duck(降低音量)策略容易引起用户困惑。我建议导航和电话场景用 AUDIOFOCUS_GAIN_TRANSIENT,直接暂停其他音频,体验更清晰。
7.3 多区域音频(Zone Audio)配置
多区域音频,是车载系统区别于手机的最显著特征。一辆车可能有2到4个独立音频区域。每个区域可以播放不同的音频源,互不干扰。
Android Automotive通过 CarAudioZone 来抽象这个概念。每个Zone对应一个物理区域(比如前排、后排),有自己的音频设备列表、音量控制、焦点管理。
配置多区域音频,需要在 car_audio_configuration.xml 中定义:
<!-- car_audio_configuration.xml 片段 -->
<carAudioConfiguration>
<zones>
<zone name="前排区域" volumeGroup="0">
<deviceAddresses>
<address>bus_device_0_front_speakers</address>
<address>bus_device_1_front_headphones</address>
</deviceAddresses>
<audioContexts>
<context name="MEDIA" usage="AUDIO_USAGE_MEDIA"/>
<context name="NAVIGATION" usage="AUDIO_USAGE_NAVIGATION"/>
</audioContexts>
</zone>
<zone name="后排区域" volumeGroup="1">
<deviceAddresses>
<address>bus_device_2_rear_speakers</address>
<address>bus_device_3_rear_headphones</address>
</deviceAddresses>
<audioContexts>
<context name="MEDIA" usage="AUDIO_USAGE_MEDIA"/>
<context name="ENTERTAINMENT" usage="AUDIO_USAGE_GAME"/>
</audioContexts>
</zone>
</zones>
</carAudioConfiguration>
每个Zone可以绑定不同的音频上下文。比如前排支持媒体和导航,后排支持媒体和游戏。这样,前排导航播报时,后排的娱乐音频不受影响。
我在一个项目中遇到过这样的需求:驾驶员想听导航,副驾想听音乐,后排小孩要看动画片。三个区域互不干扰。实现方案就是给每个区域分配独立的AudioFlinger混音线程,通过硬件总线隔离。嗯,这个方案对硬件要求比较高,需要SoC支持多路I2S或TDM输出。
下面这张图展示了多区域音频的核心架构:
从图中可以看到,CarAudioService根据区域配置,将不同应用的音频流路由到对应的AudioFlinger混音线程,最终输出到不同的物理设备。每个区域的音频是独立处理的,互不干扰。
7.4 车载音频路由策略:蓝牙、USB、AUX
音频路由策略,说白了就是「声音走哪条路」。车载上常见的音频源有蓝牙(A2DP/HFP)、USB(Android Auto/CarPlay)、AUX(有线输入)。每种源的路由策略都不一样。
我整理了一个表格,方便你对比:
| 音频源 | 典型用途 | 路由策略 | 注意事项 |
|---|---|---|---|
| 蓝牙 A2DP | 手机音乐播放 | 默认路由到媒体Zone | 注意编解码器兼容性(SBC/AAC/aptX) |
| 蓝牙 HFP | 电话通话 | 路由到驾驶员Zone,优先级最高 | 需要处理语音上行和下行路径 |
| USB Audio | Android Auto / CarPlay | 路由到指定Zone,通常独占 | 需要处理USB热插拔和权限 |
| AUX IN | 外部音频设备 | 路由到所有Zone或指定Zone | 模拟信号,注意噪声和增益 |
路由策略的实现,主要在 CarAudioService 的 onAudioDeviceAdded() 和 onAudioDeviceRemoved() 回调中。当检测到新设备接入,系统会根据设备类型和当前配置,自动切换路由。
举个例子,蓝牙连接时的路由流程:
// 伪代码:蓝牙A2DP设备连接时的路由处理
void onA2dpDeviceConnected(BluetoothDevice device) {
// 1. 获取蓝牙设备的音频地址
String address = device.getAddress();
// 2. 检查当前是否有媒体正在播放
boolean isMediaPlaying = audioManager.isMusicActive();
// 3. 如果有媒体播放,将路由切换到蓝牙设备
if (isMediaPlaying) {
AudioDeviceInfo btDevice = findAudioDeviceByAddress(address);
if (btDevice != null) {
// 设置蓝牙设备为媒体输出
audioManager.setPreferredDeviceForUsage(
AudioAttributes.USAGE_MEDIA, btDevice);
}
}
// 4. 通知CarAudioService更新路由策略
carAudioService.updateAudioRouting();
}
这里有个坑:蓝牙设备断开时,系统需要自动切回扬声器。如果切得不及时,用户会听到「声音突然没了」。我建议在 onA2dpDeviceDisconnected 回调中,提前把路由切回默认设备,而不是等系统自动处理。
避坑指南:我曾经在一个项目上遇到蓝牙通话时声音从所有喇叭出来,导致回声严重。后来发现是HFP路由策略没配好,把通话音频路由到了全车Zone。正确的做法是只路由到驾驶员Zone,并且启用回声消除(AEC)。
USB音频路由稍微复杂一些。Android Auto和CarPlay通过USB传输音频,系统需要识别USB设备类型,然后建立独立的音频会话。我建议在 car_audio_configuration.xml 中为USB设备预留专用的音频总线,避免和系统音频冲突。
AUX输入的路由相对简单,但要注意模拟信号的噪声问题。我见过一些车,AUX输入时有明显的底噪。解决方案是在硬件层面增加噪声门限,或者在软件层面做静音检测。
好了,这一章的内容就到这里。车载音频系统涉及的面很广,从底层AudioFlinger到上层CarAudioService,再到多区域配置和路由策略,每一步都有细节。你把这些搞懂了,车载音频开发的基本功就算扎实了。