车载媒体系统:MediaSession 与 CarMediaService、音频焦点管理、蓝牙电话与媒体集成
车载媒体系统,说白了就是整个座舱里最核心的娱乐交互模块。用户坐进车里,第一件事往往不是调空调,而是连蓝牙、放音乐、接电话。我这些年做车载系统,光是媒体相关的 crash 就修了不下几十个,很多问题都出在 MediaSession 和音频焦点的配合上。
今天这一章,我们就把车载媒体系统的几个关键模块拆开揉碎了讲。你想想看,一个完整的车载媒体体验,至少需要解决三个问题:
- App 怎么把自己的播放状态告诉系统?—— MediaSession
- 系统怎么统一管理多个音源?—— CarMediaService
- 多个 App 同时发声时谁说了算?—— 音频焦点管理
再加上蓝牙电话和媒体流的集成,嗯,这就是本章要啃的硬骨头。
MediaSession:App 与系统之间的桥梁
MediaSession 是 Android 官方提供的一套媒体控制框架。它的核心作用,就是让音乐 App 把自己的播放状态、控制能力暴露给系统。系统层面的 CarMediaService、语音助手、甚至方向盘按键,都可以通过 MediaSession 来操控 App。
我习惯把 MediaSession 理解成一个「遥控器接收器」。App 内部维护一个 MediaSession 对象,系统通过 MediaController 来连接它,发送播放、暂停、下一曲等指令。
核心要点:每个媒体 App 必须且只能有一个 MediaSession。多个 Session 会导致系统无法确定当前应该控制哪个 App。
来看一个最简单的 MediaSession 初始化代码:
MediaSession mediaSession = new MediaSession(context, "MyMusicPlayer");
mediaSession.setCallback(new MediaSession.Callback() {
@Override
public void onPlay() {
// 开始播放
player.start();
}
@Override
public void onPause() {
// 暂停播放
player.pause();
}
@Override
public void onSkipToNext() {
// 下一曲
player.next();
}
});
mediaSession.setActive(true);
注意 setActive(true) 这一步。我曾经遇到过一个问题:App 初始化了 MediaSession 但忘记激活,结果方向盘按键怎么按都没反应。排查了半天才发现是 Session 没激活。嗯,这个坑我替你们踩过了。
CarMediaService:车载系统的媒体管家
CarMediaService 是 Android Automotive 中专门为车载场景设计的媒体服务。它不像手机那样让多个媒体 App 各自为政,而是统一管理所有音源。
我个人觉得,CarMediaService 最巧妙的设计是「音源优先级」机制。车载系统里,音源有很多种:FM 收音机、USB 音乐、蓝牙音乐、在线流媒体、导航提示音……这么多音源同时存在,谁该出声?
CarMediaService 定义了一套优先级规则:
| 优先级 | 音源类型 | 说明 |
|---|---|---|
| 1(最高) | 紧急广播 | 交通安全信息、紧急警报 |
| 2 | 蓝牙电话 | 来电、通话中 |
| 3 | 导航提示 | 转弯提示、电子眼提醒 |
| 4 | 媒体播放 | 音乐、播客、收音机 |
| 5(最低) | 系统音效 | 按键音、通知音 |
当高优先级音源出现时,CarMediaService 会自动降低或暂停低优先级音源的音量。比如来电话时音乐自动暂停,这就是 CarMediaService 在背后协调。
避坑指南:我曾经在集成第三方音乐 App 时,发现它播放时导航提示音被完全静音了。原因是 App 的 MediaSession 没有正确声明自己的音频类型。解决方案是在 MediaSession 的 setMetadata() 中设置正确的 MediaMetadata.METADATA_KEY_MEDIA_TYPE。
音频焦点管理:谁出声,谁闭嘴
音频焦点(Audio Focus)是 Android 原生就有的机制,但在车载场景下尤为重要。手机上的音频焦点冲突顶多就是两个 App 打架,但在车上,音频焦点管理不当会直接影响驾驶安全。
举个例子:导航正在说「前方 500 米右转」,这时候音乐突然大声播放,把导航提示盖过去了——司机很可能错过路口。这就是音频焦点没处理好。
Android 的音频焦点机制有几个关键概念:
- 请求焦点:App 在播放前调用
requestAudioFocus() - 焦点变化监听:当其他 App 抢走焦点时,当前 App 会收到回调
- 焦点释放:播放结束后释放焦点
车载系统里,我建议使用 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 这种「闪避」模式。什么意思呢?就是当导航提示音出现时,音乐不是完全暂停,而是降低音量,让导航声音能清晰听到。用户体验会好很多。
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
AudioAttributes attrs = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build();
int result = audioManager.requestAudioFocus(
focusChange -> {
if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
// 永久失去焦点,暂停播放
player.pause();
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
// 暂时失去焦点,暂停播放
player.pause();
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
// 降低音量
player.setVolume(0.3f);
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// 重新获得焦点,恢复播放
player.start();
player.setVolume(1.0f);
}
},
attrs,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
0
);
注意:不要在所有 App 里都写死 duck 逻辑。蓝牙电话必须使用 AUDIOFOCUS_GAIN_TRANSIENT(完全暂停),因为通话过程中如果音乐还在小声播放,会严重影响通话质量。这个区别我在项目评审时反复强调过。
蓝牙电话与媒体集成
蓝牙电话是车载媒体系统里最复杂的一块。它涉及两个协议栈:
- HFP(Hands-Free Profile):用于蓝牙电话通话
- A2DP(Advanced Audio Distribution Profile):用于蓝牙音乐播放
这两个 Profile 在 Android Automotive 中由 BluetoothDevice 和 BluetoothAdapter 管理,但真正和上层交互的是 CarService 中的 CarBluetoothService。
我遇到过最典型的问题是:蓝牙电话接通后,音乐没有自动暂停。原因出在音频焦点的传递链上。蓝牙电话的音频焦点请求是通过 HFP 通道触发的,但音乐 App 监听的是 MediaSession 的焦点变化。如果 HFP 和 MediaSession 之间没有桥接,音乐 App 就收不到焦点丢失的通知。
解决方案是在 CarService 层做一层适配:
// 伪代码:CarService 中监听蓝牙电话状态
BluetoothHeadset headset = ...;
headset.registerCallback(new BluetoothHeadset.Callback() {
@Override
public void onConnectionStateChanged(BluetoothDevice device, int state) {
if (state == BluetoothProfile.STATE_CONNECTED) {
// 蓝牙电话连接,请求音频焦点
audioManager.requestAudioFocus(focusListener, ...);
}
}
@Override
public void onAudioStateChanged(BluetoothDevice device, int audioState) {
if (audioState == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
// 通话开始,通知 CarMediaService 暂停音乐
carMediaService.pauseCurrentMedia();
}
}
});
蓝牙音乐的集成相对简单一些。A2DP 连接成功后,系统会自动把蓝牙设备注册为一个媒体音源。CarMediaService 会把它和其他音源统一管理。但要注意一点:蓝牙音乐的采样率和编码格式会影响音质。我建议在 BluetoothA2dp 的 onCodecConfigChanged 回调中动态调整音频参数。
整体架构图
下面这张图展示了车载媒体系统的核心模块和它们之间的交互关系。你可以看到 MediaSession、CarMediaService、音频焦点管理、蓝牙模块是如何协同工作的。
从这张图可以看得很清楚:应用层的 App 通过 MediaSession 向 Framework 层注册自己的播放能力;CarMediaService 作为统一的管理者,协调各个音源的优先级和路由;音频焦点管理则负责处理并发播放时的冲突。最底层是蓝牙芯片和音频硬件,它们负责实际的音频数据传输。
实战建议:如果你正在开发车载媒体 App,我建议先实现 MediaSession 的基本回调,然后用 MediaController 写一个测试工具,模拟系统发送各种控制指令。这样可以提前发现回调逻辑中的漏洞,避免集成到 CarService 后才发现问题。
好了,车载媒体系统的核心内容就讲到这里。MediaSession 是 App 和系统沟通的桥梁,CarMediaService 是统一管理的调度中心,音频焦点是避免冲突的交通规则,蓝牙集成则是连接外部设备的通道。把这四块吃透了,车载媒体系统这块基本就稳了。