1. 从音频路由到投屏故障:Car Audio调试的两个现场
做车载音频调试这些年,有个很深的体会:Car Audio的问题从来不只是“声音不对”这么简单。尤其到了Android 15这个版本,音频焦点、路由策略、音量曲线、投屏音频这些模块全部交织在一起,一个问题翻出来往往牵扯出三四个子系统。我这阵子刚好在跟一个Android 15的车机项目,趁热把Car Audio相关调试方案梳理一遍,也把最近网上问得很多的“android15 无法投屏”问题一起拆开讲清楚。
先说结论:Android 15在车载音频这块,底层没搞什么伤筋动骨的大重构,但细节层面的行为变化特别多。比如音频属性的AudioAttributes分发逻辑、CarAudioZone(音频分区)的配置校验、以及多屏/多区投屏时音频跟随策略,都有微调。这就导致一个现象:很多在Android 11/13上能跑的调试套路,到了Android 15上就是不对——不是原理变了,是路径和节点变了。
这篇文章适合谁?如果你正在做Android汽车系统开发、车机音频Hal调试、车厂TSP(Telematics Service Provider,远程服务提供商)集成,或者你只是被“车机连不上手机投屏”“导航没声音但音乐正常”这类问题折磨过的同行,这篇应该能帮你省不少弯路。我会先从Car Audio整体架构讲起,再按“音频焦点—音量—路由—投屏”这条主线给出一套可复用的调试方案,最后单独拉一节专门讲android15投屏问题的排查实录。
说明:本文涉及的命令、日志分析方法、配置修改思路,均来自Android 15(API 35)AOSP上游代码与车规级项目的实际调试经验。为了贴合具体场景,部分步骤做了简化,生产环境请以你手上的具体硬件BSP(Board Support Package,板级支持包)为准。
2. Car Audio核心链路与调试环境准备
2.1 Android 15里Car Audio的架构变化在哪里
想调试,先得知道音频数据是从哪儿来、到哪儿去。Android Car Audio的整体链路可以简单分成三层:
- App层:媒体App、导航App、通话App通过
AudioManager接口申请音频焦点、播放音频流。 - CarAudioService层:这是Android Automotive特有的服务,负责把上层的
AudioAttributes映射到底层的AudioDeviceInfo,并按照CarAudioZone(音频分区)的配置决定音频输出到哪个物理设备。 - HAL层:音频HAL(
android.hardware.audio.core)最终把音频流路由到扬声器、耳机、蓝牙、A2DP设备或车内的独立功放通道。
Android 15在这套链路里重点做了几件事:第一,AudioAttributes的映射表改成了运行时动态加载,不再完全依赖car_audio_configuration.xml里的静态配置;第二,AudioZone的配置校验加强了,同一时刻同一物理设备不允许同时被两个zone占用的报错更严格;第三,投屏相关的AudioMirroring路径做了重构,外部设备(比如手机)通过投屏把音频传给车机时,走的不是传统的蓝牙A2DP通道,而是走ExternalDeviceConnectivity下的音频流通道。
这三点看着都挺“温和”,但实际调试时真是处处是坑。尤其是第三点——很多人在网上反馈“android15 无法投屏”,本质上不是投屏视频通道断了,而是音频流通道建立失败导致整个投屏会话被回滚。我后面会专门展开。
2.2 调试前必装工具与宿主环境
Car Audio调试,我建议至少准备这套环境:
| 工具 | 用途 | 备注 |
|---|---|---|
adb | 基础调试,日志抓取 | Android 15建议用最新platform-tools,旧版adb对某些属性读取有兼容问题 |
dumpsys audio | 查看当前音频策略、焦点状态、设备连接 | Android 15里输出格式有微调,字段名变了几个 |
dumpsys car_audio | 查看CarAudioService配置与实际路由结果 | 关键字段:zoneId、deviceAddress、usage |
dumpsys car | 查看整车CarService状态 | 排查投屏、车辆属性相关问题时必用 |
logcat | 抓取系统日志 | 需要重点过滤的关键字见下文 |
tinyplay / tinycap | 绕过上层直接测试HAL音频通路 | 排查“硬件没声还是系统没声”时必备 |
audio_hal_dump脚本 | 自写脚本,批量导出HAL节点状态 | 提高效率 |
环境搭建这块有个经验:尽量在userdebug版本的固件上调试,不要用user版。因为Car audio的很多动态配置需要setprop临时改属性,user版本没有root权限,没法灵活调整。另外,务必提前打开persist.vendor.audio.debug=true这类厂商调试开关,不然dumpsys里很多底层信息是空的。
2.3 抓日志时必须盯住的关键Tag
Car Audio调试,日志Tag非常多,但真正核心的就那么几个。我一般开三个终端:
# 终端1:CarAudioService相关 adb logcat -c && adb logcat -v threadtime | grep -E "CarAudioService|CarAudioFocus|AudioPolicyManager|AudioService" # 终端2:HAL层 adb logcat -v threadtime | grep -E "audio_hw|audio_hal|AudioHal|vendor.audio" # 终端3:投屏与音频镜像相关 adb logcat -v threadtime | grep -E "Mirroring|AudioMirror|ExternalDevice|CarProjection|CarAudioMirror"这里特别提一下AudioPolicyManager,它是Android音频系统的策略中心,很多路由问题的线索都藏在它的日志里。比如投屏音频建立失败时,日志里会反复出现AudioPolicyManager::checkOutputsForDevice之类的报错,但由于它是C++层日志,默认往往不打印,需要手动打开:
adb shell setprop log.tag.AudioPolicyManager DEBUG adb shell setprop log.tag.AudioPolicyManager:V DEBUG实测下来,Android 15里这个Tag的详细级别信息比Android 13多了不少,对定位投屏音频问题帮助很大。
3. 音频焦点与音量调试:最常出问题的两个环节
3.1 音频焦点分配:为什么导航一声“叮”之后音乐就没了
音频焦点(AudioFocus)是车机上最容易出问题、也最让测试同学崩溃的模块。Car Audio和手机音频一个很大的区别是:车机里的音频焦点不是全局唯一的,而是按“音频分区分组”的。也就是说,同一个音频焦点请求,在不同zone里可能重复授予,也可能被拒绝,完全看CarAudioZone怎么配。
Android 15里,焦点管理的一个明显变化是:系统对AudioFocusRequest的focusGain参数校验变严了。以前你传一个不合理的组合(比如AUDIOFOCUS_GAIN+AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE),系统大概率会给你一个焦点,只是日志里打个warning;现在在部分设备上,这种组合会被直接降级为AUDIOFOCUS_GAIN_TRANSIENT,导致的现象就是“导航一说话,音乐马上没了,说完也不恢复”。
遇到这类问题,第一步不是改代码,而是先看焦点的授予/释放记录。抓一段日志:
adb shell dumpsys audio | grep -A 50 "Audio Focus stack"这里会列出当前所有焦点请求的栈信息,能看到每个请求的clientId、usage、gainType、focusChangeType。如果发现导航App的请求被降级,基本就能锁定是focusGain参数的问题了。
另外,Android 15里dumpsys audio的焦点栈输出格式和之前版本确实有差异,字段名从stack变成了focusStack,大家抓日志时别被吓到,本质一样。
3.2 音量分组与音量曲线:为什么调到30%突然没声音
音量曲线问题在车机项目里特别常见,尤其是接入外置功放(Amplifier)的车型。Android的音量调整流程是:上层AudioService收到音量键事件 → 按VolumeGroup映射到对应AudioAttributes→ 按VolumeCurve计算最终音量级别 → 通过HAL下发到功放。
CarAudioService在Android 15里对VolumeGroup的配置校验变得更严格了。以前car_audio_configuration.xml里如果某个car_audio_zone的volume_group没配置完整,系统会自动给一个默认曲线,能出声但不准;现在配置必须有明确的device_address关联,否则启动时CarAudioService直接把这个zone标为无效,表现为“媒体声音完全出不来,但导航提示音正常”。
我遇到过一个很典型的案例:某车型的左右声道音量不平衡,左侧明显比右侧小。查了一圈,dumpsys audio里左右声道的streamVolume完全一样,问题其实出在HAL层的音量curve映射上——Android应用层控制的是一路“数字音量”,底层HAL又叠加了一路“模拟音量”,两边曲线不一致,导致中低音量段左右声道的衰减系数出现了明显偏差。
这种问题用tinyplay播放一个1kHz正弦波,然后用分贝仪或示波器测左右声道输出,对比曲线就能快速定位。具体操作:
# 推送测试音频到设备 adb push sine_1k.wav /data/local/tmp/ adb shell cd /data/local/tmp # 按指定设备播放(设备号先通过tinyplay -l查看) tinyplay sine_1k.wav -D 0 -d 1000建议:车载音频调试手边常备几个标准测试音频文件,1kHz 0dB、1kHz -6dB、粉红噪声、扫频信号各来一个。能极大缩短“这问题是不是硬件问题”的判断时间。
3.3 音量调试的实操清单
我总结一套可复用的音量问题排查顺序,每次遇到“音量不正常”都按这个来,基本能覆盖80%的情况:
- 先确认是不是HAL通路问题:
tinymix查看通路开关,tinyplay直接播放,确认硬件有没有声。 - 再确认上层音量参数是否正确:
dumpsys audio | grep -E "Volume|Stream",看对应Stream的音量值是否符合预期。 - 查音量曲线配置:
dumpsys car_audio | grep -A 30 "VolumeGroup",确认当前zone的volume group配置是否生效。 - 确认是否被“焦点中断”:看
Audio Focus stack里有没有其他App持有AUDIOFOCUS_GAIN_TRANSIENT,这会导致音量短暂压低之后没恢复。 - 看日志里的音频“静音抑制”事件:Android有“音频闪避”(ducking)机制,日志里会看到
AudioService.requestAudioFocus和adjustStreamVolume成对出现。如果发现闪避时间异常长,很可能是某个App申请了过长的焦点却没有正确释放。
4. Android 15无法投屏问题排查实录
4.1 先把问题定性:是视频不通还是音频不通
最近“android15 无法投屏”这个关键词在技术社区和测试群里讨论度很高。我先说个可能和大家直觉相反的结论:大多数Android 15投屏失败,根因都在音频链路上,和视频编码、Wi-Fi Display(无线显示)、Miracast都关系不大。
原因在于,Android 15对投屏会话的管理策略做了调整:投屏会话建立时需要同步完成“视频通道”和“音频通道”的绑定,如果音频流通道(AudioMirroring)在两秒内没有建立成功,整条投屏会话会被系统策略强制回滚,表现为“手机连不上车机”或“刚连上就断开”。这一点在Android 13及更早的版本里是不存在的,早期版本音频通道建立失败最多就是“有画面没声音”。
所以排查android15投屏问题,第一件事不是去看Wi-Fi直连状态,而是先抓音频镜像日志:
adb shell dumpsys car_audio | grep -i mirror adb logcat -v threadtime | grep -iE "AudioMirror|MirroringDisabled|MirroringEnabled"如果日志里出现AudioMirroringDisabled且紧跟一个reason=FAILED_TO_CREATE_AUDIO_PATCH,那问题就锁定了——音频patch创建失败。
4.2 Android 15投屏音频链路解析
先把投屏音频的链路理顺。当手机通过投屏连接到车机时,音频流不是走传统蓝牙的A2DP(虽然蓝牙也是可选路径之一),而是走车机的“外部设备音频输入”通道:
手机侧:App音频流 → 系统投屏服务音频采集 → Wi-Fi P2P(点对点)传输 车机侧:Wi-Fi P2P接收 → 音频HAL外部设备输入 → CarAudioZone路由 → 扬声器/功放这中间有两个关键节点:一个是投屏Session的音频patch(AudioPatch),负责把car audio的输入设备连接到一个混音输出;另一个是AudioPolicy的动态路由规则,决定外部设备进来的音频流按什么AudioAttributes去路由。
Android 15投屏音频patch创建失败,最常见的原因是CarAudioZone的动态路由规则和AudioPolicy的路由规则冲突。具体来说,车机端通常会配置一个“外部输入设备”(ExternalInputDevice)并把它的路由限制到某个zone。但如果这个zone当前正在被蓝牙电话或语音助手占用,AudioPolicy会拒绝创建新的patch,导致投屏音频无法建立。
4.3 实战排查步骤与解决方案
我按实际排查顺序整理了一套方案,照着试大概率能解决:
第一步:确认投屏连接是否已经到车机端
在车机上执行:
adb shell dumpsys car | grep -i "projection\|mirroring"如果这里能看到类似state=CONNECTED的状态,说明投屏会话本身已经建立起来了,问题就100%在音频链路。
第二步:检查外部音频输入设备是否挂载成功
adb shell dumpsys audio | grep -i "external\|mirror"正常情况下,应该能看到一个type=TYPE_REMOTE_SUBMIX或type=TYPE_BUS的设备地址。如果这个设备根本没出现,说明投屏音频流压根没进到AudioPolicy里。这时候重点查看logcat里AudioPolicyManager和AudioDeviceManager之间的交互日志。
第三步:定位AudioPatch失败的具体原因
adb shell dumpsys audio | grep -A 100 "Audio Patch"重点看patch的sources和sinks。车机作为接收端,source应该是手机设备(通常显示为一个REMOTE_SUBMIX地址),sink应该是车内扬声器对应的设备地址。如果source地址为空或者sink地址绑定的zone和当前策略不符,就会创建失败。
第四步:按根因对症修改
| 失败日志特征 | 根因 | 解决方案 |
|---|---|---|
FAILED_TO_CREATE_AUDIO_PATCH+no free output | AudioPolicy没有可用输出流通道 | 在audio_policy_configuration.xml里增加一路REMOTE_SUBMIX的output profile |
FAILED_TO_CREATE_AUDIO_PATCH+zone busy | 目标zone正在被其他音频业务占用 | 调整car_audio_configuration.xml里zone的路由优先级,给外部设备音频更高优先级 |
RECORD_PATCH_FAILED | 音频采集端patch不通 | 检查AudioRecord端的remote_submix权限,确认android.permission.CAPTURE_MEDIA_OUTPUT已授予 |
MIRRORING_SESSION回滚 | 投屏服务检测到音频链路未就绪 | 在car_audio_configuration.xml中配置audio_mirroring的默认路由策略为CONNECT |
第五步:联调音频路由策略
如果第四步确认路由策略冲突,一个比较通用的做法是改car_audio_configuration.xml,给外部设备音频单独划分一个CarAudioZone,然后在这个zone里配置独立的音量组和路由规则。这样投屏音频接管播放时,不会和主zone的导航/媒体混在一起抢策略。
<car_audio_configuration> <car_audio_zone name="Zone_mirror" audio_zone_id="2" is_music_zone="true"> <volume_groups> <volume_group name="group_mirror"> <audio_attributes usage="USAGE_MEDIA_UNKNOWN"/> </volume_group> </volume_groups> </car_audio_zone> </car_audio_configuration>4.4 无法投屏问题速查表
为了方便大家直接对照排查,我整理了一个速查表,也是我这几个月处理Android 15投屏问题的经验沉淀:
| 现象 | 优先排查点 | 最关键日志关键字 | 常见根因 |
|---|---|---|---|
| 手机连不上车机 | 投屏服务状态 | ProjectionService | 车机端投屏服务未启动或被系统回收 |
| 能连上但立刻断开 | 音频patch建立 | FAILED_TO_CREATE_AUDIO_PATCH | 无空闲音频输出通道 |
| 有画面没声音 | 音频路由映射 | AudioPolicyManager::getOutputForAttr | 投屏音频流未正确映射到zone |
| 声音断断续续 | Wi-Fi吞吐 | AudioTrack::underrun | P2P链路丢包导致音频buffer underrun |
| 声音延迟大 | 路由链路 | AudioFlinger::latency | 采样率不匹配,强制重采样 |
| 投屏时导航无声 | 焦点与路由抢用 | AudioService focus | 投屏音频拿走了全部焦点 |
重点提示:Android 15在这个问题上有一个值得注意的细节——如果车机端当前正在播放USB音频或光纤输入音频,投屏音频patch很可能直接失败,因为这些设备类型在某些车型上会占用同一个物理输出通道。遇到这种情况,先断开USB设备再尝试投屏,能快速验证是不是这个原因。
5. 音频路由调试实战:从AudioPolicy到AudioHal的全链路排查
5.1 路由策略配置与动态路由
Car Audio的路由,核心是“音频属性到物理设备的映射”。在Android 15中,这个映射由两部分组成:静态部分来自audio_policy_configuration.xml和car_audio_configuration.xml,动态部分来自AudioPolicyManager的设备连接状态(蓝牙连接、耳机插入、外部音频输入激活等)。
实际调试中,我最常遇到的路由问题就两类:
第一类是静态配置冲突。比如car_audio_configuration.xml里定义了两个zone,分别对应前座扬声器(zone 0)和后座娱乐屏(zone 1),但audio_policy_configuration.xml里的物理输出设备定义出了问题,导致两个zone都尝试使用同一个mixPort。Android 15对这种情况的报错更严格,启动日志里会出现:
CarAudioService: Invalid audio zone. Duplicate device address for zone 0 and zone 1这类问题只能在配置层面解决,把mixPort拆开或给每个zone分配独立设备地址。
第二类是动态路由抢占。比如车机连接了蓝牙耳机后,系统会把媒体音频切到蓝牙耳机,但导航音频仍然留在车机扬声器。这是合理的策略。但在Android 15上,部分车型会出现“蓝牙电话挂断后,媒体音频没有自动切回扬声器”的bug。排查方法很简单,看路由切换的触发日志:
adb logcat -v threadtime | grep -E "setOutputDevice|setDeviceConnectionState"如果能看到setOutputDevice调用但扬声器没出声,问题大概率在HAL层,需要用tinymix检查扬声器通路是否被之前的电话业务关闭了。
5.2 AudioHal层调试:如何判断是“系统没发”还是“硬件没出”
车载音频项目里被问得最多的一个问题就是:声音到底卡在哪一层?我习惯用“三层排除法”快速定性:
- 系统层:用
dumpsys audio确认当前路由到哪个设备、音量是多少、焦点在谁手上。如果这一步都正常,但没声音,往下走。 - HAL层:用
tinyplay直接往目标设备播放音频文件。如果tinyplay有声音,说明HAL到硬件链路正常,问题在上层策略。 - 硬件层:用
tinymix手动把音频通路强制打开,用示波器或万用表测功放芯片的输出脚。如果这里没信号,问题就在硬件,软件再折腾也没用。
这个流程看着简单,但真能省下大量“软件改了半天结果发现是连接器松了”的冤枉时间。我特别建议每个做音频调试的伙伴都养成这个习惯——改代码之前,先用tinyplay把硬件链路跑通。
5.3 一个真实的路由bug排查记录
分享一个前阵子的案例,配置是Android 15 + 高通8155平台 + 外置功放。现象是:拨打电话时,通话声音从扬声器出来,但同一声道的音乐背景音也在正常播放,导致声音混乱。
排查过程:
第一步,抓dumpsys audio看路由。结果发现通话的AudioAttributes(USAGE_VOICE_COMMUNICATION)和音乐的USAGE_MEDIA都被路由到了同一个物理输出设备(功放通道A)。
第二步,查car_audio_configuration.xml,发现通话和媒体被划分在了同一个CarAudioZone,且zone的路由策略是“所有usage共享设备”,没有单独的“通话优先”策略。
第三步,修改方案是在audio_policy_configuration.xml里增加一个独立的mixPort和devicePort用于通话,并在car_audio_configuration.xml里为通话单独划分一个zone。这样通话和媒体在物理链路上就分开了,功放端再做一路独立的混音衰减(通话时压低媒体音量),解决。
这个案例的启示是:Android 15的高通平台自带了很多音频路由的vendor扩展,但默认配置不一定适合所有车型。如果你发现某些音频策略行为和预期不符,别急着改代码,先按路由链路一层层查配置,往往更快。
6. 音频焦点与音量组的常见问题速查与经验补遗
6.1 常见问题速查表
| 问题现象 | 排查命令 | 常见根因 | 解决建议 |
|---|---|---|---|
| 导航不出声 | dumpsys audio | grep -i focus | 导航App焦点请求出错 | 检查App的AudioFocusRequest配置 |
| 音乐声忽大忽小 | logcat | grep -i duck | 导航/通话触发ducking后未恢复 | 检查焦点释放逻辑 |
| 调到最大声还是小 | dumpsys audio | grep -A 20 "VolumeGroup" | 音量曲线配置问题 | 调整car_audio_configuration.xml的volume group |
| 蓝牙音乐无声 | dumpsys audio | grep -i a2dp | A2DP设备未正确挂载 | 检查蓝牙协议栈日志 |
| USB音乐无声 | logcat | grep -i usb_audio | USB音频在Android 15上需要新的权限声明 | 在AndroidManifest.xml加上USBAudio权限 |
| 倒车雷达无提示音 | dumpsys car_audio | grep -i "zone" | 提示音被路由到了错误的zone | 调整zone路由 |
| 左右声道反了 | tinyplay单声道测试 | 硬件接线或HAL层左右映射错误 | 硬件返修或改HAL映射 |
6.2 调试车机音频的四个心得
最后分享几条我个人磨出来的经验,不一定在文档里找得到:
第一,car_audio_configuration.xml的改动一定要在开机前验证。Android 15对配置文件的加载时机很敏感,如果你改了配置后只是adb shell stop && start重启系统服务,很可能加载的还是旧缓存。最稳妥的做法是改完配置后直接重启整机,等CarAudioService重新初始化。
第二,dumpsys car_audio的输出不一定等于实际生效的路由。我遇到过很多次dumpsys里显示的zone路由是正常的,但实际声音就是不对。这时候要结合dumpsys audio里的AudioPolicyManager状态一起看,两个命令的输出对不上,才能说明问题出在策略与执行之间。
第三,Android 15的日志里可能会出现大量的car_audio_core相关crash。这是正常的,很多车机方案商都会在CarAudioService外层做兼容层,crash后会自动重启服务。不要一看到crash就觉得天塌了,先看crash前后的音频行为是否有问题。
第四,投屏音频和车机本地音频的优先级一定要在项目初期就定清楚。我见过很多项目做了一半才发现“投屏导航和车机本地音乐互斥”,这时候再改音频策略牵一发动全身。如果项目还在早期,强烈建议在音频架构设计阶段就把“投屏音频”当作一个独立的高优先级音频源对待,给它单独的路由和焦点策略。
6.3 一点个人的调试建议
如果要把这一百多个系列的调试经验浓缩成一句话,那就是:车载音频调试,七分靠日志,三分靠配置,一分靠硬件验证。不要一上来就改代码,先花二十分钟把dumpsys audio、dumpsys car_audio、logcat三个来源的日志对齐一下,大部分问题的发生环节就能定位到八九不离十。
Android 15的Car Audio整体来说比前几个版本更规范,动态路由的灵活性也更强,但代价就是调试时需要关注的系统组件更多了。我自己的习惯是维护一份本项目的“音频调试命令手册”,把常用设备的dumpsys关键字段、HAL配置文件的生效路径、重点模块的关键日志统统记下来,每次遇到新问题先翻手册再动手,效率能高不少。大家如果刚开始接触Android 15的车载音频,也可以试试用同样的方式管理自己的调试经验——这东西,积累到后面就是团队最值钱的资产之一。