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 在下层管理音频路由和焦点,而音区控制则是 CarAudioManager 在车载场景下的核心扩展。
最后说一句:多媒体框架的调试,一定要在真车上做。模拟器里很多音频路由和焦点行为是模拟不出来的。我见过太多团队在模拟器上跑得好好的,一上真车就各种声音冲突。嗯,这个坑,希望你别踩。