6、音频架构与策略:车载音频路由、焦点管理、CarAudioService 工作原理

车载音频,说实话,是 Android Automotive 里最让我头疼的模块之一。手机上的音频逻辑拿到车上,基本得重写。你想想看,手机就一个扬声器、一个听筒、一个蓝牙耳机,但车上有十几个扬声器,还有导航、电话、媒体、语音助手、警告音……这些声音谁先谁后?谁该从哪个喇叭出来?这就是我们今天要聊的核心。

6.1 车载音频的独特挑战

手机上的音频策略很简单:谁最后请求焦点,谁就出声。但车上不行。我举个例子:你正在听音乐,突然导航说“前方300米右转”,音乐得自动降低音量,这叫“闪避”(Ducking)。如果这时候来了电话,音乐得暂停,导航也得闭嘴,电话优先。如果倒车雷达响了,那所有声音都得让路——安全第一。

所以,车载音频需要一套全新的策略。Google 为此设计了 CarAudioService,它是整个车载音频的大脑。

核心要点:车载音频不是简单的“谁抢到谁用”,而是基于“优先级”和“上下文”的动态路由。每个音频流都有明确的“角色”(Role),比如导航、媒体、通信、安全警告。

6.2 CarAudioService 架构概览

我们先看一张图,把整体结构理清楚。这是我画的一张简化版架构图,帮你理解各个模块的关系。

CarAudioService 架构图 应用层 媒体App | 导航App | 电话App | 语音助手 | 系统警告 CarAudioService 音频焦点管理 谁可以出声? 音频路由策略 声音从哪出来? 音量/闪避控制 多大声音? 音频 HAL + 硬件抽象层 音频设备映射 多通道路由 音量曲线校准 物理扬声器 / 功放 / DSP

这张图里,从上到下分了三层。应用层只管发出声音请求,不关心路由。CarAudioService 是中间人,它决定“谁出声、从哪出、多大声音”。最底层是硬件抽象层,负责把逻辑通道映射到物理扬声器。

6.3 音频焦点管理——谁说了算?

音频焦点,说白了就是“说话权”。在 Android Automotive 里,焦点管理比手机复杂得多。手机只有“获取焦点”和“放弃焦点”两种状态,但车上有好几种:

焦点状态 含义 典型场景
FOCUS_GAIN 完全获得焦点 媒体播放、电话通话
FOCUS_GAIN_TRANSIENT 临时获得焦点 导航语音播报
FOCUS_GAIN_TRANSIENT_MAY_DUCK 临时获得焦点,其他音频可闪避 导航提示音(音乐降低音量)
FOCUS_LOSS 失去焦点 电话来了,媒体暂停
FOCUS_LOSS_TRANSIENT 临时失去焦点 导航播报时,音乐暂停
FOCUS_LOSS_TRANSIENT_CAN_DUCK 临时失去焦点,但可以降低音量继续播放 导航播报时,音乐降低音量

我的经验:我曾经在一个项目里遇到导航和媒体互相抢焦点的问题。导航说“我播报时你必须暂停”,媒体说“我凭什么暂停,降低音量不行吗?”最后发现是焦点请求的 usage 属性没配对。导航应该用 USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,媒体用 USAGE_MEDIA,系统会根据 usage 自动决定闪避策略。

6.4 音频路由——声音从哪出来?

路由是车载音频最有趣的部分。手机上的路由很简单:听筒、扬声器、蓝牙耳机。车上呢?你可能想让导航从驾驶位附近的扬声器出来,媒体从全车扬声器出来,电话从驾驶位头枕扬声器出来,倒车雷达从后部扬声器出来……

CarAudioService 通过 AudioZoneAudioDevice 的映射来实现。每个 AudioZone 代表一个“听音区域”,比如驾驶位、副驾驶位、后排。每个 AudioDevice 代表一个物理输出设备。

举个例子,配置可能是这样的:

<!-- car_audio_configuration.xml -->
<carAudioConfiguration>
    <zones>
        <zone name="driver">
            <volumeGroup name="navigation">
                <device address="builtin_speaker_front_left" />
            </volumeGroup>
            <volumeGroup name="phone">
                <device address="headrest_speaker_driver" />
            </volumeGroup>
        </zone>
        <zone name="all">
            <volumeGroup name="media">
                <device address="builtin_speaker_front_left" />
                <device address="builtin_speaker_front_right" />
                <device address="builtin_speaker_rear_left" />
                <device address="builtin_speaker_rear_right" />
            </volumeGroup>
        </zone>
    </zones>
</carAudioConfiguration>

这段配置的意思是:导航声音只从驾驶位左前扬声器出来,电话从驾驶位头枕扬声器出来,媒体从全车四个扬声器出来。每个 volumeGroup 还绑定了独立的音量曲线,所以导航和媒体的音量可以分别调节。

注意:路由配置必须在系统启动时加载,而且不能动态修改(至少原生不支持)。如果你需要运行时切换路由,比如“后排乘客想用耳机听音乐”,那就得自己实现动态路由逻辑。我见过有些车厂在 CarAudioService 上层封装了一层路由管理器,专门处理这种需求。

6.5 音量管理与闪避策略

音量管理在车上是个精细活。手机上的音量条是线性的,但车上的扬声器功率大,人耳对音量的感知是非线性的。所以车厂通常会提供一套 音量曲线,把 0-100 的数值映射到实际的增益值。

闪避策略(Ducking)是另一个重点。当导航播报时,媒体音量应该降低多少?降低得太少,导航听不清;降低得太多,音乐体验又不好。我建议的闪避参数是:

  • 导航播报:媒体音量降低 50%,持续到播报结束
  • 电话来电:媒体音量降低 80%,如果接听则暂停
  • 倒车雷达:所有音频暂停,包括导航
  • 语音助手:媒体暂停,导航降低音量

这些参数在 car_audio_configuration.xml 里可以配置,但说实话,每个车厂都有自己的调校标准。我曾经参与过一个项目,客户要求导航播报时媒体只降低 30%,因为他们的用户喜欢“边听音乐边听导航”。嗯,这种需求就得靠定制了。

6.6 CarAudioService 的工作流程

我们来看一个完整的流程:当用户打开导航,导航 App 请求播放语音。

  1. 请求焦点:导航 App 调用 AudioManager.requestAudioFocus(),传入 AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE
  2. 焦点裁决:CarAudioService 收到请求,检查当前焦点持有者。如果媒体正在播放,系统决定让媒体闪避(Duck),导航获得 FOCUS_GAIN_TRANSIENT_MAY_DUCK
  3. 路由决策:CarAudioService 根据配置,找到导航对应的 volumeGroup,确定输出设备(比如驾驶位左前扬声器)。
  4. 音量调整:媒体音量自动降低到预设的闪避值,导航音量按曲线映射到实际增益。
  5. 播放:导航语音从指定扬声器输出,媒体继续以较低音量播放。
  6. 焦点释放:导航播报结束,释放焦点。媒体音量恢复到正常值。

整个过程在几百毫秒内完成。如果做得不好,你会听到“噗”的一声或者音量突变。嗯,这就是为什么车厂要花大量时间调校音频参数。

避坑指南:我曾经遇到过一个 bug,导航播报结束后媒体音量没有恢复。查了半天,发现是导航 App 在播报结束后没有正确释放焦点。记住,App 必须调用 abandonAudioFocus(),否则系统会一直认为导航还在占用焦点。这不是系统的问题,是 App 的锅。

6.7 总结

车载音频架构的核心就是三个词:焦点、路由、音量。焦点决定谁可以出声,路由决定声音从哪出来,音量决定多大声音。CarAudioService 把这三件事串起来,通过配置文件和运行时策略实现灵活控制。

说实话,车载音频的坑很多。每个车厂的硬件布局不一样,扬声器数量、位置、功率都不一样。你很难写一套通用的策略适配所有车型。我的建议是:先把焦点管理做扎实,这是基础;路由策略尽量用配置驱动,不要硬编码;音量曲线一定要和硬件团队一起调,光靠软件是调不好的。

最后说一句,如果你在车上听到某个声音“不对劲”,八成是音频策略没配好。别问我怎么知道的——我调过太多这样的 bug 了。


公众号:蓝海资料掘金营,微信deep3321