前言
手头一台 Pixel 9 Pro XL(代号 komodo),想刷一个自己从源码编译的、尽量纯净的 AOSP 15当日用机。其他都好说——双击熄屏、root、蓝牙、5G 数据、电池百分比,一个个都搞定了。
唯独有一件事,几乎所有教程和论坛都告诉你”做不到”:
自编译的纯 AOSP 在国内打不了电话。
中国移动卡,插上去,数据 5G 满格,但一拨号——秒断。
这篇就是记录怎么把这块硬骨头啃下来的全过程。剧透:最后在 SELinux enforcing 模式下,自编译纯 AOSP 跑通了完整的双向中国移动 VoLTE 通话(AMR-WB,上下行都有声,通话稳定不掉线)。中间踩的坑可以说是一层套一层。
注:本文所有设备序列号、手机号、IMSI/ICCID、签名密钥指纹、内网地址等已脱敏。保留的
MCCMNC 46000、carrierid 1435是中国移动的公开标识。
一、为什么打不了?先搞清楚根因
国行 Pixel 不存在这个问题,海外版才有。逐层排查后,根因有两道墙:
第一道墙:Google 在非国行 Pixel 上默认关掉了中国移动的 VoLTE。
中国移动是 4G/5G 纯 IMS 语音,没有 2G/3G 的 CS 电路域回落。也就是说,语音只能走 VoLTE。而 Google 官方的运营商配置里:
1 | carrier_volte_available_bool = false // 对中国移动默认关闭 |
VoLTE 一关,又没有 CS 回落,结果就是拨号即断。
第二道墙:运营商配置本身在国内拉不下来。
Pixel 的运营商配置不是 AOSP 默认的 com.android.carrierconfig 提供的,而是闭源的 CarrierSettings(包名 com.google.android.carrier)。这玩意儿要连 Google 服务器做 checkin 才能下发对应运营商的配置——而这个 checkin 在国内被墙,拉不到,配置版本号是空的。
所以两件事叠加:配置下不来,就算下来了 VoLTE 也是关的。
二、先在原厂固件上验证可行性
在动手改 AOSP 之前,先证明这条路走得通——设备、SIM、基带本身有没有能力打中国移动 VoLTE?
在官方固件上做了两件事:
- 挂一个能连 Google 的代理。 设备一旦能连上 Google,CarrierSettings 立刻 checkin 成功,下发中国移动配置(版本号变成
chinamobile_cn-...,本地缓存出现对应的carrierconfig-*-1435.xml)。 - root + Pixel-IMS 强开 VoLTE。 Pixel 9 用 Magisk root(注意 Pixel 9 要patch 的是
init_boot.img,不是boot.img),再用 Pixel-IMS(dev.bluehouse.enablevolte)经 Shizuku 把carrier_volte_available_bool强行改成true。
小坑:
cmd phone cc set-value即使有 root 也会 “Permission denied”,所以得用 Pixel-IMS 这种走载体特权的 app,而不是命令行。
结果:官方固件上中国移动通话拨通,AMR-WB(codecType=2,16kHz 宽带语音)over VoLTE,无报错。
这就证明了:硬件和 modem 固件完全有能力,问题 100% 在 Android 软件层。 Google 只是默认配置把它关了。这给了我啃自编译版本的信心。
三、真正的目标:让自编译 AOSP 也能打
原厂能打不算本事。目标是:从 AOSP 源码编译出来的 ROM,自己也能打通中国移动电话。
我给自己定的原则是:除了 telephony 这一块允许引入闭源的 Google/三星组件,其余一切保持最纯的 AOSP。 毕竟电话栈(基带交互、IMS、媒体编解码)这种东西,指望纯开源实现去对接真实运营商核心网,是不现实的。
死路一条:手搓移植电话栈
最初的想法很朴素:把官方固件里跟电话相关的几个 APK/JAR 一个个挑出来,塞进 AOSP 源码树,重签平台 key。
结果撞上了经典报错:
1 | 1610 CODE_REJECT_UNSUPPORTED_SDP_HEADERS |
通话建立时,Shannon 基带生成的 SIP INVITE 里的 SDP 被中国移动核心网拒绝。
折腾了很久才想明白根因:手工挑选的电话栈是”不连贯”的。 整套 Pixel 电话栈是一个互相咬合的有机整体——
- Google RIL 主干:
grilservice+google-ril.jar+RilConfigService - IMS:ShannonIms(
com.shannon.imsservice) - 媒体:PixelImsMediaService + 原生库
libpixelimsmedia.so - 一堆
ril-extension、OemRilService、sitril 原生库……
手工挑,必然漏东西(尤其是那些藏在 lib/ 里的 .so 原生库),而且很容易混进版本不匹配的旧 blob。栈不连贯 → 生成的 SDP 不对 → 1610。
热补这条路验证了假设就果断放弃,没有在错误的方向上浪费几小时的编译时间。
转折点:adevtool 连贯提取
关键工具是 adevtool——GrapheneOS 用来从官方固件完整、连贯地提取专有 blob 的工具。它的价值在于:它知道整套电话栈的依赖关系,一次性把匹配同一套框架 ABI 的所有组件(包括原生库、sepolicy、各种 .pb 配置)成套提取出来,而不是让你手工去猜哪个该拿哪个不该拿。
做法:
vendor/adevtool克隆 GrapheneOS 版本,把版本对齐到我编译的 base(BP1A→android-15.0.0_r36)。- 跑
adevtool的 extract,成套拉出 grilservice、google-ril.jar、PixelImsMediaService(连libpixelimsmedia.so/ sitril 原生库一起)、ShannonIms,以及 427 个 CarrierSettings 的.pb配置文件。
这一步直接解决了 1610——栈连贯了,SDP 就对了。
但真正的折磨,从能编译出镜像之后才开始。
四、编出来了,但开不了机:连环 boot 坑
集成进源码树、m 编译通过、刷进设备——卡在 Google logo。 然后是连续好几个晚上的 bringup 排查。
坑 1:根本没生成 vendor.img
adevtool 生成的 BoardConfigVendor.mk 没有被安装(只躺在一个 .skip 文件里),而且 device-vendor.mk 被截断了,缺了 VINTF manifest 和 200 多个 HAL 包的声明。结果就是 vendor.img 压根没编出来。
修法:从 .skip 恢复 BoardConfigVendor.mk(它设置了 BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE := ext4),从骨架恢复被截断的 device-vendor.mk。
教训:用自己的 system + 官方的 vendor 会因为 VINTF/sepolicy 不匹配而 boot 失败。 必须有一套自己编出来的、连贯的 vendor.img。
坑 2:7 个 vendor HAL 二进制 SELinux 标签错了
komodo 的 product 配置没有引入 gs-common 那套 per-HAL 的 sepolicy,导致 7 个 vendor HAL 可执行文件的 file_contexts 漏标,全部落到了通用的 vendor_file 类型上。后果是 init 无法对它们做域转换(domain transition),开机就卡在音频 HAL 启动那一步。
最关键的一条:
1 | /vendor/bin/hw/android.hardware.audio.service-aidl.aoc u:object_r:hal_audio_default_exec:s0 |
补上正确的 file_contexts 标签后,HAL 才能被拉起来。
坑 3:service_contexts 缺了音频扩展服务 → HAL 直接 abort
补完上面那条,enforcing 下又陷入 boot loop。日志里:
1 | avc: denied { add } ... vendor.google.whitechapel.audio.extension.IAudioExtension ... |
音频 HAL 启动时要往 servicemanager 注册 IAudioExtension 服务,但 service_contexts 里没给这个服务定义类型,注册被拒,HAL 直接 SIGABRT 崩溃,于是 boot loop。
修法:补上 hal_audio_ext_service 类型 + 对应的 service_contexts 条目。后来发现引入 gs-common 的 aidl.mk 之后它会原样提供这些定义,就把手动加的删掉去重了(见下文)。
坑 4:刷机姿势——verity 与 super 不一致
中途为了省时间只单独 resize 重刷了 vendor 分区,结果 super 分区和 dm-verity 的哈希对不上,又卡 Google logo。
正确姿势:全量刷——super_empty 重置逻辑分区表 + 一次性刷入全部逻辑分区(system / system_ext / product / vendor / vendor_dlkm / system_dlkm)+ 匹配的 vbmeta。
verity 哈希对不上时,用:
1 | avbtool make_vbmeta_image --flags 3 ... # flags 3 = 同时禁用 verity 和 verification |
生成一个禁用校验的 vbmeta 刷进去。(这台设备上 fastboot --disable-verity 会报 “Failed to find AVB_MAGIC at offset 0”,对自编译的 vbmeta 不奏效,只能用 avbtool 这条路。)
闯过这四关,设备终于在 enforcing 下正常开机进系统了。试拨——拨通了,振铃,接起来……
五、最后一公里:接通了,但对方听不到我
通话能建立了,但出现了诡异的现象:
- 我能听到对方(下行 OK);
- 对方听不到我(上行无声);
- 几十秒后通话因为
RTP Timeout自动挂断。
抓媒体层日志,铁证如下:
1 | numRtpPacketsTransmitted = 0 ← 上行一个 RTP 包都没发出去 |
null-source -> voice-call-uplink——上行通道的音频源居然是个空源,麦克风根本没被路由进去。
根因:装了 HIDL 的音频配置,却跑着 AIDL 的 HAL
adevtool 提取出来的音频 HAL 是 AIDL 版本(android.hardware.audio.service-aidl.aoc)。但是 device 配置里,引入 AIDL 音频配置文件的那段被一个 release flag 门控着:
1 | ifeq ($(RELEASE_PIXEL_AIDL_AUDIO_HAL),true) |
而 RELEASE_PIXEL_AIDL_AUDIO_HAL 这个变量在公开的 AOSP 树里根本没定义。于是这段被跳过,系统装的是旧的 HIDL 音频配置(mixer_paths.xml),却跑着 AIDL 的 HAL。两者不匹配,AIDL HAL 找不到正确的混音路径(mixer path)把麦克风接到通话上行通道,于是上行成了空源。
修法:强制走 AIDL 音频路径
在 device/google/caimito/device-komodo.mk 里直接绕过那个未定义的 flag:
1 | USE_AUDIO_HAL_AIDL := true |
这样就会引入 gs-common/audio/aidl.mk,装上正确的 AIDL 音频配置:
mixer_paths_aidl.xml(AIDL 的混音路径表)- AoC(Always-on Compute,Tensor 的音频协处理器)的上行路由配置
uplink_*_config.pb - 与官方一致的
audio_platform_configuration.xml
顺手把之前手动加的、现在由 aidl.mk 原样提供的音频 sepolicy 删掉去重(否则会撞 “Multiple same specifications” 编译错误)。
六、收工:完整双向通话跑通
重新编译、刷入、enforcing 模式试拨。这次的媒体层日志:
1 | audio_route: Apply path: microphones -> voice-call-uplink-0 ← 麦克风接上了! |
无 1610,无 RTP Timeout,上下行都有声,通话稳定不掉。
至此目标达成:一个从源码自编译的、除 telephony 外保持纯净的 AOSP 15,在 SELinux enforcing 模式下,跑通了完整的双向中国移动 VoLTE 通话。
七、复盘:整条链路的坑
把整个过程串起来,能打通电话需要同时满足的条件,按从下到上的顺序:
| 层 | 问题 | 修法 |
|---|---|---|
| 配置 | Google 默认关闭中国移动 VoLTE + 配置 checkin 被墙 | 用 CarrierSettings + 正确的运营商配置 .pb |
| 电话栈 | 手搓移植不连贯 → 1610 | adevtool 成套连贯提取(含原生库) |
| 构建 | vendor.img 没生成 | 恢复 BoardConfigVendor.mk / device-vendor.mk |
| SELinux | HAL 二进制 file_contexts 漏标 | 补 hal_audio_default_exec 等标签 |
| SELinux | service_contexts 缺 IAudioExtension | 补 hal_audio_ext_service |
| 刷机 | super/verity 不一致 | 全量刷 + avbtool --flags 3 |
| 音频 | 装 HIDL 配置跑 AIDL HAL → 上行空源 | USE_AUDIO_HAL_AIDL := true |
几点最大的体会:
- 先在原厂验证可行性,再啃自编译。 否则你根本不知道是软件问题还是硬件/SIM 问题,方向都找不准。
- 不要手搓移植成套的、互相咬合的闭源栈。 电话栈这种东西,用 adevtool 这类工具成套连贯提取,比手工挑 APK 靠谱一万倍——手工挑必漏原生库。
- enforcing 下的 boot 失败,先怀疑 sepolicy。
file_contexts标签错、service_contexts缺服务,都会让 HAL 起不来从而卡 boot。permissive 能起、enforcing 起不来,基本就是 sepolicy 缺口。 - “接通了但没声音” 要看媒体层日志的 audio_route。
null-source -> voice-call-uplink这种就是音频路由没接上,往往是 HAL 类型(HIDL/AIDL)和配置文件不匹配。
下一步是把这套电话栈连同其他定制(双击熄屏、root、电池百分比、GApps)一起,折叠进一个自签名的 user 版,baked 进去做成日用机。那是另一篇的故事了。
整个过程跨了好几个晚上,一层坑套一层坑。但最后看到 numRtpPacketsTransmitted 从 0 开始往上涨、对方说”听得到了”的那一刻,还是挺爽的。所谓”做不到”,很多时候只是没找对工具和没把每一层都啃透而已。