第27章 车载多媒体场景:CarMediaService、多屏互动、音频分区

车载多媒体,说实话,是Android多媒体体系里最“拧巴”的一块。为什么?因为手机上的那套逻辑——一个屏幕、一对喇叭、一个用户——在车里完全不够用。你想想看,前排司机在导航,后排孩子在动画片,副驾在打电话,这怎么搞?

我最早接触车载项目时,以为就是把MediaSession和AudioManager搬过来就行。结果呢?被CarService的架构按在地上摩擦。今天我们就来聊聊CarMediaService、多屏互动和音频分区这三个核心话题。

27.1 CarMediaService:车载多媒体的“总管”

CarMediaService不是普通的MediaSessionService。它是CarService体系里专门管理媒体播放的组件。说白了,它要协调多个MediaSession、多个显示区域、多个音频焦点。

我习惯把CarMediaService理解成一个“媒体路由器”。它不直接播放,而是把媒体流路由到正确的屏幕和音频通道上。

核心职责:

  • 管理多个MediaSession(导航、音乐、视频、通话)
  • 处理音频焦点冲突(比如导航播报时压低音乐)
  • 分发媒体元数据到各个显示区域
  • 支持多用户场景(司机和乘客的媒体隔离)

来看一个典型的CarMediaService初始化代码:

public class CarMediaServiceImpl extends CarMediaService {
    private static final String TAG = "CarMediaService";

    @Override
    public void onCreate() {
        super.onCreate();
        // 注册多个MediaSession
        registerMediaSession("navigation", new NavigationMediaSession());
        registerMediaSession("music", new MusicMediaSession());
        registerMediaSession("video", new VideoMediaSession());
        
        // 设置音频分区策略
        AudioZoneManager zoneManager = getCar().getCarManager(AudioZoneManager.class);
        zoneManager.setZonePolicy(AudioZone.ZONE_DRIVER, AudioZone.POLICY_NAVIGATION_ONLY);
        zoneManager.setZonePolicy(AudioZone.ZONE_PASSENGER, AudioZone.POLICY_MEDIA_ONLY);
    }
}

嗯,这里要注意:registerMediaSession的key不能重复。我曾经踩过坑,两个MediaSession用了同一个key,结果后注册的把前一个覆盖了,导航声音死活出不来。

27.2 多屏互动:不是简单的“投屏”

车载多屏,不是手机投屏到车机那么简单。它涉及的是多个物理屏幕之间的媒体状态同步、控制权切换、以及内容隔离。

我参与过一个项目,后排屏幕想看视频,前排屏幕在导航。这时候如果后排点“播放”,前排的导航语音不能被打断。怎么办?

解决方案是:每个屏幕绑定一个独立的MediaSession,但共享同一个MediaController。CarMediaService负责仲裁哪个Session拥有“前台焦点”。

多屏互动设计要点:

  • 每个屏幕有独立的MediaSession实例
  • MediaController可以跨屏幕共享,但播放命令需要经过CarMediaService仲裁
  • 元数据更新要广播到所有注册的屏幕
  • 控制权切换需要用户确认(防止后排抢方向盘)

来看一个多屏状态同步的代码片段:

// 当后排屏幕开始播放视频时
public void onPlayRequested(int screenId, MediaSession session) {
    // 检查当前是否有更高优先级的播放
    if (currentActiveSession != null && 
        currentActiveSession.getPriority() > session.getPriority()) {
        // 拒绝请求,通知用户
        notifyUser(screenId, "当前导航播报中,无法播放视频");
        return;
    }
    
    // 暂停当前播放
    if (currentActiveSession != null) {
        currentActiveSession.getController().getTransportControls().pause();
    }
    
    // 切换到新会话
    currentActiveSession = session;
    broadcastMediaState(screenId, session);
}

我个人习惯在广播状态时加上一个序列号,防止旧状态覆盖新状态。这个坑我踩过两次,第一次是后排暂停了,但前排收到的是旧的状态,以为还在播放。

27.3 音频分区:让每个座位“各听各的”

音频分区是车载多媒体最硬核的部分。它要求系统能同时输出多路音频流到不同的扬声器区域。比如:导航声音只从驾驶位喇叭出,音乐从全车喇叭出,后排耳机独立一路。

Android原生的AudioManager不支持分区。CarService引入了AudioZone的概念。每个Zone对应一个物理区域,有自己的音频策略和音量控制。

音频分区注意事项:

  • 硬件必须支持多路DAC输出,否则分区是空谈
  • 每个Zone需要独立的AudioTrack实例
  • 音量调节要按Zone独立,不能全局联动
  • 蓝牙电话必须路由到所有Zone(法规要求)

来看一个音频分区的配置示例:

// 创建三个音频分区
AudioZone driverZone = new AudioZone.Builder(AudioZone.ZONE_DRIVER)
    .setOutputDevice(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER)
    .setVolumeGroup(VolumeGroup.GROUP_DRIVER)
    .setAllowedUsages(AudioAttributes.USAGE_NAVIGATION, 
                      AudioAttributes.USAGE_VOICE_COMMUNICATION)
    .build();

AudioZone passengerZone = new AudioZone.Builder(AudioZone.ZONE_PASSENGER)
    .setOutputDevice(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER)
    .setVolumeGroup(VolumeGroup.GROUP_PASSENGER)
    .setAllowedUsages(AudioAttributes.USAGE_MEDIA, 
                      AudioAttributes.USAGE_GAME)
    .build();

AudioZone rearZone = new AudioZone.Builder(AudioZone.ZONE_REAR)
    .setOutputDevice(AudioDeviceInfo.TYPE_WIRED_HEADSET)
    .setVolumeGroup(VolumeGroup.GROUP_REAR)
    .setAllowedUsages(AudioAttributes.USAGE_MEDIA)
    .build();

// 应用分区配置
AudioZoneManager zoneManager = getCar().getCarManager(AudioZoneManager.class);
zoneManager.setZones(driverZone, passengerZone, rearZone);

我曾经在调试分区时遇到一个问题:导航声音明明配置了只走驾驶位,但副驾也能听到。查了半天,发现是硬件混音器没配置好,DSP把两路音频混到一起了。嗯,软件配好了,硬件没跟上,白搭。

27.4 整体架构图

下面这张图展示了CarMediaService、多屏互动和音频分区之间的关系:

车载多媒体架构总览 CarMediaService MediaSession管理 多屏互动仲裁 音频分区管理 导航Session 音乐Session 视频Session 前排屏幕 后排屏幕 中控屏幕 驾驶位Zone 副驾Zone 后排Zone CarMediaService 统一管理 MediaSession、多屏状态和音频分区

27.5 实战中的坑与经验

做车载多媒体开发,有几个坑我不得不提:

  1. 音频焦点冲突:导航播报时,音乐要自动降低音量(不是暂停)。我见过有些方案直接暂停音乐,结果导航播完音乐不恢复了,用户投诉。
  2. 多屏延迟:后排屏幕的播放状态更新到前排,延迟不能超过200ms。否则用户会感觉“卡”。我建议用Binder直连,不要走广播。
  3. 音量同步:不同Zone的音量曲线要一致。我曾经遇到驾驶位音量调到10很响,副驾调到10几乎听不见。原因是两个Zone的DAC增益不同。
  4. 蓝牙电话:法规要求电话必须全车播放。你不能把电话只路由到驾驶位。这个在AudioZone配置里要特殊处理。

避坑指南:

我曾经在调试多屏互动时,发现后排屏幕的MediaSession状态更新总是慢半拍。后来定位到是用了Handler.postDelayed做同步。换成Binder直调后,延迟从500ms降到了50ms。所以,能直调就别用消息队列。

27.6 总结

车载多媒体场景,说白了就是解决“多”的问题:多屏幕、多音频流、多用户。CarMediaService是核心调度器,多屏互动是状态同步的艺术,音频分区是硬件和软件的联调。

我个人觉得,做车载开发最考验的是“边界意识”。什么时候该让,什么时候该抢,什么时候该隔离,这些策略比代码本身更重要。你想想看,如果导航和音乐抢起来了,用户会骂谁?肯定是车厂。所以,策略定好了,代码只是实现而已。

嗯,今天就聊到这里。如果你正在做车载项目,建议先把AudioZone的硬件能力摸清楚,再动手写代码。否则,软件写得再好,硬件不支持也是白搭。


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