8、车载多媒体框架:CarMediaService、CarAudioManager、音频焦点管理、音区控制

好,我们进入车载系统里最“热闹”的一个模块——多媒体框架。

说实话,我做了这么多年车载系统,多媒体这块是用户感知最强、也是出问题最多的区域。你想啊,导航在播报、音乐在播放、电话突然进来、后排小孩要看动画片……这些声音怎么协调?谁该停、谁该小声、谁该独占?这就是我们今天要聊的核心。

8.1 CarMediaService:多媒体应用的“调度中心”

CarMediaService 是什么?说白了,它就是车载系统里所有媒体应用的管家。它不直接播放音乐,但它管着谁可以播放、谁正在播放、播放状态怎么同步。

我个人习惯把 CarMediaService 理解成一个“媒体会话注册中心”。每个媒体应用(比如 Spotify、本地音乐播放器、USB 音乐)启动时,都要向它注册一个 MediaSession。CarMediaService 维护一个列表,记录当前所有活跃的媒体会话。

核心职责:

  • 管理媒体会话的注册与注销
  • 向系统 UI 提供当前播放信息(歌名、歌手、封面)
  • 处理媒体按键事件(上一曲、下一曲、暂停)
  • 协调多个媒体源之间的切换

我在项目中遇到过一个问题:某个第三方音乐 App 在后台被杀死后,CarMediaService 里的会话信息没有及时清理,导致 UI 上一直显示“正在播放”,但实际已经没声音了。后来我们加了一个心跳检测机制——每个媒体会话必须每隔 30 秒发一次存活信号,超时未响应的自动移除。

// 注册媒体会话的典型流程
MediaSession mediaSession = new MediaSession(context, "MyMusicPlayer");
mediaSession.setCallback(new MediaSession.Callback() {
    @Override
    public void onPlay() {
        // 开始播放
    }
    @Override
    public void onPause() {
        // 暂停播放
    }
});

// 通过 CarMediaService 注册
CarMediaService mediaService = car.getCarMediaService();
mediaService.registerMediaSession(mediaSession);

8.2 CarAudioManager:音频路由的“交通警察”

CarAudioManager 是 Android Automotive 里专门管理音频输出的服务。它和普通 Android 的 AudioManager 最大的区别在于——它知道车里有几个喇叭、每个喇叭在哪个位置、声音应该从哪个声道出去。

你想想看,一辆车可能有 6 个、8 个甚至 12 个扬声器。导航声音从主驾喇叭出,音乐从全车喇叭出,电话只从主驾喇叭出——这些路由逻辑,就是 CarAudioManager 在管。

音频上下文 默认输出设备 典型场景
AUDIO_CONTEXT_MUSIC 全车扬声器 音乐播放
AUDIO_CONTEXT_NAVIGATION 主驾扬声器 导航播报
AUDIO_CONTEXT_CALL 主驾扬声器 + 蓝牙 电话通话
AUDIO_CONTEXT_NOTIFICATION 全车扬声器(低音量) 消息提示音

嗯,这里要注意:CarAudioManager 的音频上下文(AudioContext)是 Android Automotive 特有的概念。普通 Android 用 StreamType(比如 STREAM_MUSIC),但车载系统需要更精细的控制,所以引入了 AudioContext。每个音频上下文可以独立设置音量、路由策略和焦点行为。

8.3 音频焦点管理:谁说话,谁闭嘴

音频焦点,说白了就是“谁有资格出声”。

我刚开始做车载开发时,觉得音频焦点不就是个 boolean 吗?后来发现完全不是那么回事。车载场景下,音频焦点是有“层级”的。导航的焦点优先级高于音乐,电话的优先级高于导航。而且,焦点可以“暂时让渡”——比如导航播报时,音乐可以暂停,也可以降低音量(闪避)。

我的经验:音频焦点管理最容易出问题的场景是“焦点丢失后恢复”。很多应用在失去焦点后直接停止播放,但焦点恢复时没有自动续播。用户体验就很差——导航说完了,音乐不响了。正确的做法是:失去焦点时暂停,恢复焦点时自动续播。

// 请求音频焦点的示例
AudioAttributes attributes = new AudioAttributes.Builder()
        .setUsage(AudioAttributes.USAGE_MEDIA)
        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        .build();

AudioFocusRequest focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
        .setAudioAttributes(attributes)
        .setOnAudioFocusChangeListener(focusChange -> {
            switch (focusChange) {
                case AudioManager.AUDIOFOCUS_GAIN:
                    // 获得焦点,开始播放
                    break;
                case AudioManager.AUDIOFOCUS_LOSS:
                    // 永久失去焦点,停止播放
                    break;
                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                    // 暂时失去焦点,暂停播放
                    break;
                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                    // 暂时失去焦点,但可以降低音量继续播放(闪避)
                    break;
            }
        })
        .build();

int result = audioManager.requestAudioFocus(focusRequest);

我曾经踩过一个坑:某个音乐 App 在收到 AUDIOFOCUS_LOSS_TRANSIENT 时直接调了 stop(),而不是 pause()。结果导航播报结束后,音乐没有恢复。用户投诉说“导航说完话,音乐就没了”。后来我们统一规范:所有媒体应用必须用 pause() 处理暂时焦点丢失,用 stop() 处理永久焦点丢失。

8.4 音区控制:让每个座位都有自己的声音

音区控制是车载系统最酷、也最复杂的功能之一。它允许不同座位的乘客听不同的内容——主驾听导航,副驾看电影,后排小孩看动画片,互不干扰。

实现原理其实不复杂:车内的扬声器被分成多个“音区”,每个音区对应一个物理位置(主驾、副驾、后排左、后排右)。CarAudioManager 允许为每个音区独立设置音频上下文、音量和路由策略。

注意:音区控制依赖硬件支持。如果车内的扬声器是物理上共享的(比如左右声道共用一组喇叭),那么软件层面的音区隔离效果有限。真正的音区隔离需要每个座位有独立的扬声器阵列,或者使用定向声学技术。

// 设置音区音频的示例
CarAudioManager carAudioManager = car.getCarAudioManager();

// 获取主驾音区
int zoneId = carAudioManager.getZoneIdForOccupant(
        VehicleSeat.SEAT_ROW_1_LEFT,
        VehicleSeat.SEAT_POSING_DRIVER);

// 设置该音区的音量
carAudioManager.setZoneVolume(
        zoneId,
        AudioAttributes.USAGE_MEDIA,
        0.5f,  // 音量 0.0 ~ 1.0
        0);    // 标志位,一般传 0

// 设置该音区的音频焦点策略
carAudioManager.setZoneAudioFocusPolicy(
        zoneId,
        CarAudioManager.ZONE_AUDIO_FOCUS_POLICY_INDEPENDENT);

音区的音频焦点策略有三种:

  • INDEPENDENT:每个音区独立管理焦点,互不影响。主驾导航播报时,副驾音乐不会闪避。
  • SHARED:所有音区共享焦点。任何一个音区获得焦点,其他音区都会失去焦点。
  • HIERARCHICAL:按优先级管理。比如主驾的导航焦点优先级高于副驾的音乐。

我个人建议,大多数场景用 INDEPENDENT 策略。因为车载场景下,每个座位的乘客是独立的个体,不应该因为主驾在听导航就让副驾的音乐暂停。但电话场景例外——电话应该用 SHARED 策略,因为通话是全车共享的。

车载多媒体框架核心结构 CarMediaService CarAudioManager 音频焦点管理 音区控制 焦点请求 / 释放 闪避(Ducking)策略 焦点恢复与状态同步 音区独立音量控制 音区焦点策略配置 扬声器路由映射

这张图把今天讲的内容串起来了。CarMediaService 在上层管理媒体会话,CarAudioManager 在下层管理音频路由和焦点,而音区控制则是 CarAudioManager 在车载场景下的核心扩展。

最后说一句:多媒体框架的调试,一定要在真车上做。模拟器里很多音频路由和焦点行为是模拟不出来的。我见过太多团队在模拟器上跑得好好的,一上真车就各种声音冲突。嗯,这个坑,希望你别踩。


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