从虚拟化音频架构到板端故障定位:车载 Audio 阶段性工程实践整理
本文由一组车载 Audio 研发日报重新整理而成,重点保留可复用的架构认知、构建经验、调试命令、日志证据、失败路径和验证方法。
本文属于阶段性技术积累文档,记录当前阶段的已验证结果、观察现象和待解决问题。项目仍在推进,文档不代表最终交付、全链路验收或全部问题关闭。
文中的路径、主机和设备名统一使用
<project_root>、<qnx_prebuilt>、<board_ip>等占位符,表示相应工程位置或连接对象,不代表可直接复制的实际路径。文中状态标签的含义如下:
- 已验证:原始记录中有编译结果、板端输出、日志或文件校验作为证据。
- 已观察:记录了现象,但还不足以证明根因。
- 推测:基于现有证据提出的方向,不等同于结论。
- 待验证:原始记录明确没有完成验证。
1. 这篇文章解决什么问题
车载音频问题很少只属于某一层。一个"没有声音"的现象,可能来自 GPIO 复用错误、时钟没有打开、TDM slot 映射错误、ACDB use case 不匹配、Android 音频图没有建起来、QNX 侧服务没有启动、DSP 图下游持续丢数据,甚至只是测试文件路径写错。
技术经验的复用性依赖一条可追溯的证据链;单条命令只能解决局部问题:
text
硬件接口与时序
↓
GPIO / Pin Mux / Clock
↓
QNX audio_service / CSD2 / GSL
↓
Hypervisor 跨虚拟机通道
↓
Android HAL / PAL / AGM / GSL FE
↓
AudioReach Graph / ADSP / AFE / TDM
↓
外部 Codec、DSP、功放、扬声器或蓝牙设备
这条链路还要和另一条"软件交付链"同时成立:源码下载、宿主机依赖、组件编译、镜像打包、刷机、板端文件替换、重启和回归验证,任何一环不完整,都会导致"改了但板端没有运行新内容"。
本文先建立整体模型,再讨论典型问题的缩小过程。代码、参数和日志片段按工程记录整理;其中平台相关的数值是实测实例,不应直接当作所有硬件的默认值。文中把已完成的阶段性验证和仍待闭环的工作分开描述,后续结果需要在此基础上继续补充。
2. 先建立系统整体认知
2.1 虚拟化音频系统的角色划分
在一类基于 QNX Hypervisor 的车载平台中,可以把系统粗略分为以下角色:
| 角色 | 运行内容 | 音频职责 |
|---|---|---|
| PVM / QNX 主虚拟机 | QNX 实时操作系统 | 直接管理音频硬件、时钟、Codec、GPIO、DSP 加载和原生音频服务 |
| LA GVM | Android 客户虚拟机 | 运行 Android Audio HAL、PAL、TinyALSA、AGM 等用户态音频栈 |
| LV GVM | Linux 客户虚拟机 | 运行 ALSA UCM、PulseAudio 等 Linux 音频栈;是否存在取决于产品配置 |
| Hypervisor | 虚拟机监控与隔离层 | 提供虚拟机之间的消息、共享内存或硬件抽象通道 |
| ADSP / 其他 DSP | 专用信号处理器 | 执行 AudioReach 图、格式处理、后处理、TDM/PCM 驱动协同和数据搬运 |
典型的软件数据流可以表示为:
#mermaid-svg-ysaWk7CcaznhW9Rj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ysaWk7CcaznhW9Rj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ysaWk7CcaznhW9Rj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ysaWk7CcaznhW9Rj .error-icon{fill:#552222;}#mermaid-svg-ysaWk7CcaznhW9Rj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ysaWk7CcaznhW9Rj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ysaWk7CcaznhW9Rj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ysaWk7CcaznhW9Rj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ysaWk7CcaznhW9Rj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ysaWk7CcaznhW9Rj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ysaWk7CcaznhW9Rj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ysaWk7CcaznhW9Rj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ysaWk7CcaznhW9Rj .marker.cross{stroke:#333333;}#mermaid-svg-ysaWk7CcaznhW9Rj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ysaWk7CcaznhW9Rj p{margin:0;}#mermaid-svg-ysaWk7CcaznhW9Rj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ysaWk7CcaznhW9Rj .cluster-label text{fill:#333;}#mermaid-svg-ysaWk7CcaznhW9Rj .cluster-label span{color:#333;}#mermaid-svg-ysaWk7CcaznhW9Rj .cluster-label span p{background-color:transparent;}#mermaid-svg-ysaWk7CcaznhW9Rj .label text,#mermaid-svg-ysaWk7CcaznhW9Rj span{fill:#333;color:#333;}#mermaid-svg-ysaWk7CcaznhW9Rj .node rect,#mermaid-svg-ysaWk7CcaznhW9Rj .node circle,#mermaid-svg-ysaWk7CcaznhW9Rj .node ellipse,#mermaid-svg-ysaWk7CcaznhW9Rj .node polygon,#mermaid-svg-ysaWk7CcaznhW9Rj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ysaWk7CcaznhW9Rj .rough-node .label text,#mermaid-svg-ysaWk7CcaznhW9Rj .node .label text,#mermaid-svg-ysaWk7CcaznhW9Rj .image-shape .label,#mermaid-svg-ysaWk7CcaznhW9Rj .icon-shape .label{text-anchor:middle;}#mermaid-svg-ysaWk7CcaznhW9Rj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ysaWk7CcaznhW9Rj .rough-node .label,#mermaid-svg-ysaWk7CcaznhW9Rj .node .label,#mermaid-svg-ysaWk7CcaznhW9Rj .image-shape .label,#mermaid-svg-ysaWk7CcaznhW9Rj .icon-shape .label{text-align:center;}#mermaid-svg-ysaWk7CcaznhW9Rj .node.clickable{cursor:pointer;}#mermaid-svg-ysaWk7CcaznhW9Rj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ysaWk7CcaznhW9Rj .arrowheadPath{fill:#333333;}#mermaid-svg-ysaWk7CcaznhW9Rj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ysaWk7CcaznhW9Rj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ysaWk7CcaznhW9Rj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ysaWk7CcaznhW9Rj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ysaWk7CcaznhW9Rj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ysaWk7CcaznhW9Rj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ysaWk7CcaznhW9Rj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ysaWk7CcaznhW9Rj .cluster text{fill:#333;}#mermaid-svg-ysaWk7CcaznhW9Rj .cluster span{color:#333;}#mermaid-svg-ysaWk7CcaznhW9Rj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ysaWk7CcaznhW9Rj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ysaWk7CcaznhW9Rj rect.text{fill:none;stroke-width:0;}#mermaid-svg-ysaWk7CcaznhW9Rj .icon-shape,#mermaid-svg-ysaWk7CcaznhW9Rj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ysaWk7CcaznhW9Rj .icon-shape p,#mermaid-svg-ysaWk7CcaznhW9Rj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ysaWk7CcaznhW9Rj .icon-shape .label rect,#mermaid-svg-ysaWk7CcaznhW9Rj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ysaWk7CcaznhW9Rj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ysaWk7CcaznhW9Rj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ysaWk7CcaznhW9Rj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Android 应用或测试命令
Audio HAL / PAL
AGM 音频图管理
GSL FE / 虚拟音频节点
Hypervisor 跨 VM 通道
QNX GSL BE / CSD2
ACDB 加载与 Graph 配置
GPR / FastRPC
ADSP AudioReach Graph
AFE / TDM / PCM / I2S
Codec / 外部 DSP / 功放 / 蓝牙
这张图表达的是逻辑关系,不意味着每个产品都会拥有完全相同的模块或命名。排查时应以实际源码、运行日志和板端设备节点为准。
2.2 软件目录与模块边界
原始记录中反复出现的模块,可以按职责归纳为:
| 层级 | 常见模块 | 作用 |
|---|---|---|
| QNX 音频服务 | audio_service |
用户态服务,负责初始化、Codec/GPIO 控制、ACDB 等待和音频会话入口 |
| QNX 驱动与适配 | audio_driver、amfs2_lib、csd2、gsl_be、mcm_lib |
驱动、会话、消息文件系统、图服务后端和时钟管理 |
| Android 音频适配 | Audio HAL、PAL、AGM、TinyALSA | 将 Android 音频请求转换为平台音频图和后端参数 |
| 跨 VM 链路 | GSL FE/BE、MMHAB、VMM IPC | 将 GVM 侧音频请求转发到 PVM |
| DSP 框架 | AudioReach、SPF、MDF、AMS | 组织图、容器、模块和 DSP 任务 |
| 配置数据 | ACDB、usecaseKvManager.xml、backend_conf.xml、card-defs.xml、资源管理配置 |
描述 use case、后端、格式、接口和校准数据 |
第一次接手代码时,建议先建立"模块 / 路径 / 运行进程 / 配置文件 / 日志前缀"的表,再修改具体参数。这样可以避免把 Android 侧配置、QNX 侧配置和 DSP 侧运行状态混成一个问题。
2.3 AudioReach 图的基本层级
原始技术分享中使用了如下四层模型来解释 AudioReach 图:
text
Graph
└── Subgraph
└── Container
└── Module
一条普通播放链路可能由 stream、stream processing、device processing 和 device 等子图构成。每个子图通常对应自己的执行线程;记录中提到的常见模式包括低功耗和低延迟,前者以毫秒级帧间隔运行,后者需要更短的处理链路。具体帧间隔和容器限制仍应以目标平台文档为准。
一个很重要的排查原则是:
Graph open 成功,只能证明图结构和部分配置可以建立;它不等价于 DSP 已经把完整 PCM 数据送到物理 TDM 线上。
2.4 GKV、CKV、TKV:不要把三类键值混为一谈
音频日志里经常看到十六进制键值。它们至少可以分为三类:
| 类型 | 主要阶段 | 作用 | 常见误区 |
|---|---|---|---|
| GKV(Graph Key Vector) | graph open | 选择 stream、device、后处理和输出图 | 以为它本身就是完整校准参数 |
| CKV(Calibration Key Vector) | set config | 传递采样率、音量、通道数等核心索引 | 以为 Android 侧要组装全部 ACDB 参数 |
| TKV(Tag Key Vector) | set config | 通过标签批量匹配模块参数 | 以为每个模块都必须显式写一份标签 |
在一类双虚拟机架构中,Android 侧通常只传递图和配置的关键标识,真正的校准参数匹配由 QNX 侧 ACDB 完成。原始记录中还验证过:GVM 启动时可能检查 ACDB 是否存在,但实际生效的校准文件位于 PVM/QNX 侧;因此不能只替换 Android 文件后就认为 QNX 音频已经改变。
2.5 从日志中的 GKV 反查 ACDB
实际定位时可以采用"日志键值 → XML 名称 → ACDB use case"的反查方法:
- 从
audio_service、gsl_open、AGM 或gsl_fe日志中提取 GKV。 - 根据键族区分 stream、instance、device、device processing 和 VMID。
- 在源码或设备侧的
usecaseKvManager.xml中按十六进制 key 搜索名称。 - 用名称组合回到 QACT/ACDB 中定位对应 use case、subgraph 和端点。
- 修改前先保留原 ACDB 和对应的 QWSP,修改后用同一命令做 A/B 对比。
常见键族可以按如下方式理解:
| 典型前缀 | 语义 | 说明 |
|---|---|---|
0xA1000000 |
Stream RX | 播放流类型 |
0xB1000000 |
Stream TX | 录音流类型 |
0xAB000000 |
Instance | 区分实例或虚拟机 |
0xA2000000 |
Device RX | 播放设备 |
0xA3000000 |
Device TX | 录音设备 |
0xAC000000 |
Device PP RX | 播放设备后处理 |
0xAD000000 |
Device PP TX | 录音设备后处理 |
0xFF000000 一类没有在当前 XML、AGM 或 PAL 源码中找到定义的附加键,不应在没有证据时强行赋予含义。
3. TDM、PCM、I2S 与时钟:先算清楚再改参数
3.1 三种接口的差异
三者都是芯片之间传输 PCM 音频数据的串行接口,但典型用途不同:
| 特性 | I2S | PCM 接口 | TDM |
|---|---|---|---|
| 常见通道 | 左、右 2 通道 | 少量 slot | 多 slot、多通道 |
| 帧同步 | 高低电平区分左右 | 短脉冲或长帧 | 短脉冲或长帧 |
| 典型场景 | 立体声 Codec | 蓝牙语音 | 多路音频、外部 DSP |
| 灵活性 | 较低 | 中等 | 较高 |
共同的物理信号通常包括:
| 信号 | 含义 |
|---|---|
| BCLK / SCK | 位时钟,每传输一个 bit 推进一次 |
| FS / SYNC / WS | 帧同步,通常与采样率相关 |
| DATA / SD | 串行数据线,承载各个 slot |
TDM 可以用 Frame → Slot → Channel 理解:
text
Frame(一帧,持续 1 / Fs)
├── Slot 0 → 逻辑通道 0
├── Slot 1 → 逻辑通道 1
├── ...
└── Slot N → 逻辑通道 N
基本计算式为:
text
BCLK = slot 数 × 每 slot bit 数 × 采样率
3.2 一个四路播放 TDM 示例
原始硬件资料中有一组"8 slot、每 slot 32 bit、48 kHz、有效数据占前 4 个 slot"的设计。该实例的计算为:
text
8 × 32 × 48000 = 12.288 MHz
如果只使用 DATA3 上的 Slot0~Slot3,则常见配置组合为:
text
nslots_per_frame = 8
slot_width = 32
slot_mask = 0x0000000F # Slot0~Slot3
lane_mask = 0x0008 # DATA3
sync_mode = Long Sync
data_delay = 1 BCLK
这里的 slot_mask 和 lane_mask 属于不同维度:前者选择帧内的 slot,后者选择数据线。把 DATA0 录音用的 mask 和 DATA3 播放用的 mask 混用,是后续 TDM 排查中反复出现的风险。
另一组蓝牙语音接口实例使用 4 slot、32 bit slot、16 kHz 宽带语音,BCLK 为:
text
4 × 32 × 16000 = 2.048 MHz
窄带语音 8 kHz 时,BCLK 会随采样率下降。具体是 8 kHz 还是 16 kHz,必须与 HFP profile、Codec 和对端设备协商结果一致。
3.3 硬件数据流示例
下面用"公开芯片型号 + 通用连接关系 + 技术参数"描述一组硬件示例:
#mermaid-svg-nydPxw6Ey4cdBYgM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-nydPxw6Ey4cdBYgM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-nydPxw6Ey4cdBYgM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-nydPxw6Ey4cdBYgM .error-icon{fill:#552222;}#mermaid-svg-nydPxw6Ey4cdBYgM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nydPxw6Ey4cdBYgM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-nydPxw6Ey4cdBYgM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nydPxw6Ey4cdBYgM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nydPxw6Ey4cdBYgM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-nydPxw6Ey4cdBYgM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nydPxw6Ey4cdBYgM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nydPxw6Ey4cdBYgM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nydPxw6Ey4cdBYgM .marker.cross{stroke:#333333;}#mermaid-svg-nydPxw6Ey4cdBYgM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nydPxw6Ey4cdBYgM p{margin:0;}#mermaid-svg-nydPxw6Ey4cdBYgM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-nydPxw6Ey4cdBYgM .cluster-label text{fill:#333;}#mermaid-svg-nydPxw6Ey4cdBYgM .cluster-label span{color:#333;}#mermaid-svg-nydPxw6Ey4cdBYgM .cluster-label span p{background-color:transparent;}#mermaid-svg-nydPxw6Ey4cdBYgM .label text,#mermaid-svg-nydPxw6Ey4cdBYgM span{fill:#333;color:#333;}#mermaid-svg-nydPxw6Ey4cdBYgM .node rect,#mermaid-svg-nydPxw6Ey4cdBYgM .node circle,#mermaid-svg-nydPxw6Ey4cdBYgM .node ellipse,#mermaid-svg-nydPxw6Ey4cdBYgM .node polygon,#mermaid-svg-nydPxw6Ey4cdBYgM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-nydPxw6Ey4cdBYgM .rough-node .label text,#mermaid-svg-nydPxw6Ey4cdBYgM .node .label text,#mermaid-svg-nydPxw6Ey4cdBYgM .image-shape .label,#mermaid-svg-nydPxw6Ey4cdBYgM .icon-shape .label{text-anchor:middle;}#mermaid-svg-nydPxw6Ey4cdBYgM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-nydPxw6Ey4cdBYgM .rough-node .label,#mermaid-svg-nydPxw6Ey4cdBYgM .node .label,#mermaid-svg-nydPxw6Ey4cdBYgM .image-shape .label,#mermaid-svg-nydPxw6Ey4cdBYgM .icon-shape .label{text-align:center;}#mermaid-svg-nydPxw6Ey4cdBYgM .node.clickable{cursor:pointer;}#mermaid-svg-nydPxw6Ey4cdBYgM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-nydPxw6Ey4cdBYgM .arrowheadPath{fill:#333333;}#mermaid-svg-nydPxw6Ey4cdBYgM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-nydPxw6Ey4cdBYgM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-nydPxw6Ey4cdBYgM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nydPxw6Ey4cdBYgM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-nydPxw6Ey4cdBYgM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nydPxw6Ey4cdBYgM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-nydPxw6Ey4cdBYgM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-nydPxw6Ey4cdBYgM .cluster text{fill:#333;}#mermaid-svg-nydPxw6Ey4cdBYgM .cluster span{color:#333;}#mermaid-svg-nydPxw6Ey4cdBYgM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-nydPxw6Ey4cdBYgM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-nydPxw6Ey4cdBYgM rect.text{fill:none;stroke-width:0;}#mermaid-svg-nydPxw6Ey4cdBYgM .icon-shape,#mermaid-svg-nydPxw6Ey4cdBYgM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nydPxw6Ey4cdBYgM .icon-shape p,#mermaid-svg-nydPxw6Ey4cdBYgM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-nydPxw6Ey4cdBYgM .icon-shape .label rect,#mermaid-svg-nydPxw6Ey4cdBYgM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nydPxw6Ey4cdBYgM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-nydPxw6Ey4cdBYgM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-nydPxw6Ey4cdBYgM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} TDM0 DATA3:4 路播放
模拟或数字音频
PCM3:蓝牙语音 RX/TX
TDM0 DATA0:反馈/麦克风/其他输入
车载 SoC / LPASS
外部音频 Hub
Class-D 功放
扬声器
蓝牙连接芯片
工程中还会遇到音频 Hub、蓝牙连接芯片、功放和电源诊断器件等不同组合。它们属于硬件背景,具体连接关系仍需以板级原理图和设备树为准。
4. GPIO、Pin Mux 与 LPASS:数字编号不足以描述全部行为
4.1 GPIO 配置项
音频引脚配置通常同时包含:
- 引脚或逻辑编号;
- 复用功能号(FCN/Function);
- 输入或输出方向;
- 上拉、下拉或无上下拉;
- 驱动能力;
- 与时钟、帧同步、数据线的物理角色。
QNX 侧的配置结构可以抽象成:
c
{
.gpioNum = AUDIO_DATA_GPIO,
.gpioFunc = AUDIO_TDM_FUNCTION,
.gpioDir = DAL_GPIO_OUTPUT,
.gpioPull = DAL_GPIO_PULL_DOWN,
.gpioDrive = DAL_GPIO_2MA,
},
gpio_config.c 里的宏名、芯片变体条件和实际硬件原理图必须同时核对。不能只看到一个 GPIO 数字就直接改数组。
4.2 RX/TX 方向的视角陷阱
硬件设计文档常以外部 DSP 或 Codec 为视角,代码通常以 SoC 为视角。于是:
| 外部器件视角 | SoC 引脚视角 |
|---|---|
| 文档 RX:外部器件接收 | SoC TX,GPIO 输出 |
| 文档 TX:外部器件发送 | SoC RX,GPIO 输入 |
| SoC 为 BCLK/FS Master | 时钟线、同步线由 SoC 输出 |
这类视角反转曾导致"文档写 RX,代码却应该配 INPUT/OUTPUT 哪一个"的争论。解决方法是把每条线画成 谁发送 → 谁接收,再映射到 SoC 端 GPIO 方向。
4.3 TLMM 与 LPASS 是不同的 GPIO 配置块
在一次板端排查中,通用 GPIO 工具能读取 TLMM 块,却读不到 LPASS 音频块。记录中的平台地址分别属于两个独立寄存器区域,不能把一种工具的"默认值"解释为另一块已经生效。
验证路径是:
- 用通用 GPIO 工具检查 SoC 编号范围的存在性和基本状态。
- 检查
audio_service是否执行了vaudio_gpio_config。 - 通过 QNX
/dev/gpio/legacy的devctl接口读取 LPASS 模块。 - 使用逻辑模块号(例如
LPASS_TLMM_MODULE | lpass_pin)解析方向、驱动、上下拉和 Function。
读取工具的伪代码流程如下:
c
fd = open("/dev/gpio/legacy", O_RDWR);
module_gpio = LPASS_TLMM_MODULE | lpass_pin;
devctl(fd, GPIO_GET_CONFIG, &module_gpio, sizeof(module_gpio), NULL);
parse_config(module_gpio.cfg, &dir, &drive, &pull, &function);
这解释了为什么同一组物理线可能同时出现两套编号:文档/芯片资料使用 SoC GPIO 编号,而 LPASS 驱动和调试工具使用 LPASS 块内的逻辑编号。
4.4 GPIO 修复的完整认知演进
这是一个很适合保留失败路径的案例。
初始现象
板端读取一组音频相关 GPIO 时,多个引脚保持 In / 2mA / Pull down / ALT0,与硬件接口要求不符。源码中对应芯片变体的旧表却配置了另一段编号范围。
方案一:直接替换旧条目(初测有效,后续废弃)
第一版把旧条目的编号直接改成目标编号,并按照时钟线、同步线、数据线分别设置方向和驱动能力。刷机后部分引脚确实显示为预期值,audio_service 也能启动。
但这个方案修改了原始条目,无法证明其他功能是否仍依赖旧条目。即使板端当前测试通过,也可能破坏默认关联。因此按评审意见撤回,不把"板端暂时可用"当成"设计正确"。
方案二:只新增、不改旧表
第二版保留原始芯片变体段落,在表尾新增目标平台专用条目,并用变体宏包围:
c
#if defined(VARIANT_TARGET_AUDIO)
/* 仅新增目标平台的音频接口引脚,不改原有条目 */
{ .gpioNum = AUDIO_CLK, .gpioFunc = 1, .gpioDir = OUTPUT,
.gpioPull = NO_PULL, .gpioDrive = 2MA },
{ .gpioNum = AUDIO_FS, .gpioFunc = 1, .gpioDir = OUTPUT,
.gpioPull = NO_PULL, .gpioDrive = 2MA },
{ .gpioNum = AUDIO_DATA, .gpioFunc = 1, .gpioDir = INPUT,
.gpioPull = NO_PULL, .gpioDrive = 2MA },
#endif
这样做的好处是:变更范围窄、回滚容易、不会改变旧平台配置。刷机验证证明目标引脚方向和 Function 生效。
方案三:根据最终硬件方向收口
拿到更明确的 TDM0/PCM3 方向表后,最终只固化需求明确的线:时钟、同步和需要使用的数据线按 SoC 视角设置,未列出的线保留原状态。最终记录中,一组 TDM0 线表现为:
text
数据线 0:SoC 输入
数据线 3:SoC 输出
时钟线、同步线:SoC 输出
未使用数据线:保留原始配置
这条演进说明了一个通用原则:
先用实验验证"代码路径确实执行",再用硬件需求决定"哪些条目应该存在";验证工具成功不代表所有候选 GPIO 都应进入修改范围。
4.5 PCM3 / 蓝牙接口扩展
另一组 PCM3 引脚按以下逻辑配置:
| 逻辑角色 | SoC 方向 |
|---|---|
| PCM clock | 输出 |
| frame sync | 输出 |
| BT 下行数据 | 输入 |
| BT 上行数据 | 输出 |
原始板端实测显示 8 个引脚(TDM0 与 PCM3 合计)均为 2mA / No pull / ALT1 或符合目标表格的状态,其中输入数据线保持高电平是蓝牙空闲态的合理现象,不应直接判断为异常。
5. 时钟与 Codec:音频链路的隐藏前置条件
5.1 MCLK、BCLK 与接口时钟
如果外部 Codec 或 Hub 需要持续 BCLK/MCLK,而系统只在播放会话中按需开时钟,开机阶段或低功耗切换时可能出现"接口存在但无数据"。主时钟管理代码通常集中在 mcm.c 一类文件中,可以抽象为:
c
static const audio_clock_cfg_t clock_cfg[] = {
{
.clock_id = PRIMARY_TDM_CLOCK,
.enable = true,
.graph_desc = &primary_tdm_clock_graph,
},
};
排查时要区分:
- ACDB 中配置的 clock ID;
- DSP 运行时真正 request/enable 的 clock ID;
- 物理时钟频率;
- 时钟属性、根域和数据延迟。
频率相同不代表 clock ID 一定正确。后文 TDM0 案例中,静态配置和运行时日志分别显示两个 ID,这正是导致定位不能提前收口的证据。
5.2 Codec 上电控制
某些硬件通过动态加载 Codec 控制库并调用入口函数完成上电。如果硬件设计为 SoC 直接连接 Codec,中间没有 Hub,则必须先确认 Codec 的 MCLK 输入需求。关闭自动上电只能作为隔离实验,缺少电源、复位和时钟证据时不能直接采用该方案。
6. ACDB:从"文件替换"到"运行时建图"
6.1 ACDB 不只是一个文件
原始记录中实际使用的是一对相互配套的文件:
text
acdb_cal.acdb # 校准数据库
workspaceFile.qwsp # QACT 工作区/辅助参数文件
验收至少包含四个维度:
- 文件内容与本地源一致;
- 两个文件成对更新,不能只替换其中一个;
- 板端属主、权限和挂载位置正确;
- 重启后服务、图、会话和目标功能都能按预期运行。
只比较文件大小不够,建议在主机端用 md5sum 或 sha256sum,在板端没有这些工具时用 cksum 或把文件拉回主机再计算摘要。
6.2 ACDB 的静态和运行时位置
一种常见的系统设计是:启动镜像中的 /ifs 只读,ACDB 在启动时从可写系统分区 /mnt 读取。要让它成立,至少需要同时改变:
- 构建模板:不再把目标 ACDB 放进只读音频 IFS;
- QNX audio_service:初始化时等待
/mnt下的 ACDB 文件; - CSD2/AMFS:把 ACDB、workspace 和可能的 delta 路径基座指向
/mnt; - META/整包构建:把 QNX 修改后的内容重新打包进可刷入镜像;
- 板端启动流程:保证
/mnt已挂载,并有正确的权限。
一轮移植中,涉及的五类修改可以概括为:
| 文件类型 | 变更 |
|---|---|
| ADSP 库路径代码 | 把 /mnt 放在 IFS 路径前,便于动态替换 |
| CSD2 会话代码 | ACDB、QWSP 和 delta 的路径基座改为 /mnt |
| audio_service 初始化 | 以固定间隔轮询等待 ACDB 可读,再继续初始化 |
| 音频构建 fileset | 注释只读 IFS 中的旧 ACDB 拷贝 |
| QNX system build 模板 | 以目标变体保护块把两个实体文件放入系统分区 |
这里有两个常见遗漏:
- 某个平台的 ACDB 只有主数据库和 QWSP,没有
acdbdelta,不能机械照搬其他平台的三文件 patch。 - 同一份源码可能同时生成多个硬件变体,必须用变体宏保护目标 ACDB,否则会把一个平台的校准数据带入另一个平台的镜像。
6.3 ACDB 动态加载的验证闭环
#mermaid-svg-yZX9kObfCpe0M7lw{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yZX9kObfCpe0M7lw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yZX9kObfCpe0M7lw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yZX9kObfCpe0M7lw .error-icon{fill:#552222;}#mermaid-svg-yZX9kObfCpe0M7lw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yZX9kObfCpe0M7lw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yZX9kObfCpe0M7lw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yZX9kObfCpe0M7lw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yZX9kObfCpe0M7lw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yZX9kObfCpe0M7lw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yZX9kObfCpe0M7lw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yZX9kObfCpe0M7lw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yZX9kObfCpe0M7lw .marker.cross{stroke:#333333;}#mermaid-svg-yZX9kObfCpe0M7lw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yZX9kObfCpe0M7lw p{margin:0;}#mermaid-svg-yZX9kObfCpe0M7lw .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-yZX9kObfCpe0M7lw .cluster-label text{fill:#333;}#mermaid-svg-yZX9kObfCpe0M7lw .cluster-label span{color:#333;}#mermaid-svg-yZX9kObfCpe0M7lw .cluster-label span p{background-color:transparent;}#mermaid-svg-yZX9kObfCpe0M7lw .label text,#mermaid-svg-yZX9kObfCpe0M7lw span{fill:#333;color:#333;}#mermaid-svg-yZX9kObfCpe0M7lw .node rect,#mermaid-svg-yZX9kObfCpe0M7lw .node circle,#mermaid-svg-yZX9kObfCpe0M7lw .node ellipse,#mermaid-svg-yZX9kObfCpe0M7lw .node polygon,#mermaid-svg-yZX9kObfCpe0M7lw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yZX9kObfCpe0M7lw .rough-node .label text,#mermaid-svg-yZX9kObfCpe0M7lw .node .label text,#mermaid-svg-yZX9kObfCpe0M7lw .image-shape .label,#mermaid-svg-yZX9kObfCpe0M7lw .icon-shape .label{text-anchor:middle;}#mermaid-svg-yZX9kObfCpe0M7lw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-yZX9kObfCpe0M7lw .rough-node .label,#mermaid-svg-yZX9kObfCpe0M7lw .node .label,#mermaid-svg-yZX9kObfCpe0M7lw .image-shape .label,#mermaid-svg-yZX9kObfCpe0M7lw .icon-shape .label{text-align:center;}#mermaid-svg-yZX9kObfCpe0M7lw .node.clickable{cursor:pointer;}#mermaid-svg-yZX9kObfCpe0M7lw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-yZX9kObfCpe0M7lw .arrowheadPath{fill:#333333;}#mermaid-svg-yZX9kObfCpe0M7lw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-yZX9kObfCpe0M7lw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-yZX9kObfCpe0M7lw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yZX9kObfCpe0M7lw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-yZX9kObfCpe0M7lw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yZX9kObfCpe0M7lw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-yZX9kObfCpe0M7lw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-yZX9kObfCpe0M7lw .cluster text{fill:#333;}#mermaid-svg-yZX9kObfCpe0M7lw .cluster span{color:#333;}#mermaid-svg-yZX9kObfCpe0M7lw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-yZX9kObfCpe0M7lw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-yZX9kObfCpe0M7lw rect.text{fill:none;stroke-width:0;}#mermaid-svg-yZX9kObfCpe0M7lw .icon-shape,#mermaid-svg-yZX9kObfCpe0M7lw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yZX9kObfCpe0M7lw .icon-shape p,#mermaid-svg-yZX9kObfCpe0M7lw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-yZX9kObfCpe0M7lw .icon-shape .label rect,#mermaid-svg-yZX9kObfCpe0M7lw .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yZX9kObfCpe0M7lw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-yZX9kObfCpe0M7lw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-yZX9kObfCpe0M7lw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
备份板端 ACDB 与 QWSP
主机计算源文件摘要
重挂 /mnt 为可写
成对上传两个文件
root:root + 0555 + sync
人工复位或进入 fastboot 后重启
主机回读并比较摘要
检查 /dev/audio_service
检查 audio_chime / agmplay graph/session 日志
功能是否符合预期?
保留日志与文件版本,回滚备份
执行负向验证或 A/B 回归
一组已验证的板端操作可以整理成模板:
sh
# 板端 QNX shell
mount -uw /mnt
mkdir -p /mnt/etc/acdb/<target>_backup_vN
cp /mnt/etc/acdb/<target>/* /mnt/etc/acdb/<target>_backup_vN/
# 主机通过受控文件传输服务上传两个文件后,回到板端
chown root:root /mnt/etc/acdb/<target>/*
chmod 0555 /mnt/etc/acdb/<target>/*
sync
# 重启方式取决于板卡
reset -f
# 主机端回到系统后
fastboot reboot
板端没有 reboot、md5sum 或完整 Linux 工具集时,不要假设常见命令一定存在。把"板端能执行什么"作为环境基线记录下来。
6.4 负向验证比一次成功更有价值
把 ACDB 主文件临时改名移走,再重启:
sh
mount -uw /mnt
mv /mnt/etc/acdb/<target>/acdb_cal.acdb \
/mnt/etc/acdb/<target>/acdb_cal.acdb.bak
sync
reset -f
原始板端验证得到的现象是:
/dev/audio_service不再出现;audio_chime只能打出开头日志并等待;- 没有后续 CSD2 会话流量;
- 恢复文件原名、权限并重启后,服务节点和播放恢复。
这组负向结果证明 audio_service 确实依赖 /mnt 下的 ACDB;单凭"文件恰好上传成功"无法得到这个结论。
6.5 ACDB 版本问题的真实边界
一次新版 ACDB 替换后,QNX 侧 audio_chime 报出:
text
csd2_cgm_build_hw_ep_tdm_intf_payload:
failed to get tagged data for endpoint tag [0xc0000004]
csd2_cgm_stream_config_device_hw_ep_cfg:
build hw endpoint interface payload failure
当时 audio_service 能启动,Android 侧同一类 agmplay 仍能成功打开 GSL FE 图。日志表明新 ACDB 已经加载,但缺失或不匹配的 TDM 端点数据影响了 QNX CSD2 建图路径:
运行时已加载新 ACDB,但缺失或不匹配的 TDM 端点数据只影响 QNX CSD2 建图路径;Android GVM 与 QNX PVM 的使用路径需要分开验证。
后续另一版 ACDB 在 QNX chime 上又通过,说明前一个版本的问题来自具体 use case/端点配置差异;动态加载机制本身仍然有效。
7. 构建、打包与环境治理
车载 Audio 的问题经常归因于"代码改完重新编译即可"。实际工程链至少包含 QNX 应用与 IFS、Android QSSI、Android Kernel、Vendor、OEM 非 HLOS、Meta Build,以及 ADSP/CDSP/AOP/Boot/TZ 等固件组件。每层都有自己的编译器、依赖、产物目录和打包入口;只改源码而不确认产物是否进入下一层,最终刷到板上的可能仍是旧文件。
7.1 先把环境问题和代码问题分开
环境分层可以按以下方式理解:
| 层次 | 主要职责 | 常见工具或入口 | 典型产物 |
|---|---|---|---|
| Host | 容器、交叉编译器、许可证、调试工具 | Docker、CMake、GCC、Python、QPM/QXDM/QACT | 构建环境与日志 |
| QNX | PVM 音频服务、GSL BE、CSD2、IFS | make、qcc、dumpifs |
QNX 可执行文件、库、IFS |
| Android QSSI | 通用系统与 Soong/Kati/Ninja 依赖 | build/envsetup.sh、lunch、m |
system.img 等 |
| Android Kernel | GKI、设备树、内核模块 | Bazel/Kleaf、Clang | Image、.ko、.dtb/.dtbo |
| Vendor/OEM | 厂商服务、驱动、非 HLOS 打包 | Soong、build.py |
vendor.img、NON-HLOS.bin |
| Meta Build | 汇总各分区和刷机包 | build.py |
super.img、rawprogram_unsparse*.xml |
| DSP/固件 | ADSP、CDSP、AOP、Boot、TZ | SCons、build_variant.py、buildex.py |
adsp.mbn、cdsp*.mbn、aop.mbn 等 |
在 Ubuntu 22.04 上,最先暴露的问题通常来自宿主机状态:Docker 默认数据目录所在分区不足、构建并发过高导致 OOM、许可证工具启动时访问网络超时、Python 版本由系统包管理器替换,以及旧工具依赖的共享库已经不再随发行版提供。处理顺序应当是先记录磁盘、内存、交换分区、容器数据目录、Python/编译器版本,再判断具体报错属于环境还是源码。
例如 Docker 构建目录接近满盘时,继续删除随机中间文件容易把可复用缓存和源码混在一起。更稳妥的方案是把 Docker data-root 迁移到明确的用户数据盘,并在迁移后用 docker info、镜像列表和一次最小构建确认新目录已经生效。清理时只清理可重建的中间产物,不把源码、预编译库、许可证文件和刷机包一并删除。
QPM、QXDM、QCAT、QACT 也应当分别记录版本和运行平台。QACT 主要依赖 Windows 图形环境;QXDM 负责采集设备侧诊断日志;QPM 在非 TTY 或后台环境中可能挂起;访问 CDN 时还可能出现大量 CLOSE-WAIT 连接并造成 CPU 空转。此类现象应先收集进程、网络连接和日志,再考虑重启工具或改用本地缓存。QNX SDK 的许可证流程同样应使用有效授权和官方预编译包,本文不展开任何凭据或绕过授权的操作。
7.2 容器镜像兼容性:每个修复都要有边界
一个旧构建镜像在新 Ubuntu/软件源上连续遇到多类兼容性问题。将其整理成表格,便于下一次复现和评估风险:
| 现象 | 原因判断 | 处理方向 |
|---|---|---|
| 软件源返回 403 | 旧镜像中的源或签名已失效 | 清理失效源,跳过不再需要的源,并保留修改记录 |
| Microsoft 源不可用但构建只需要 .NET | 外部源访问失败,不等于运行时不存在 | 使用经过验证的本地 .NET 包或镜像内已有运行时 |
python 是虚拟包 |
新发行版不再自动提供旧命令 | 对明确要求 Python 2 的遗留工具使用隔离解释器;其他工具使用 python3 |
gcc-8/g++-8/filepp 找不到 |
包版本和发行版不匹配 | 使用兼容的系统 GCC,或移除已不再参与构建的过滤器;先验证生成物 |
| Python readline 包名变化 | 发行版拆包规则变化 | 按当前发行版包名安装,并记录解释器版本 |
| Kitware CMake 下载慢 | 外部 CDN 不稳定 | 优先使用发行版 CMake,确认项目最低版本 |
| Launchpad 或 LLVM CDN 超时 | 远端依赖不可达 | 使用系统包或内部已审计缓存,不在脚本中无限重试 |
DTC 与 GCC 11 的 -Werror=array-bounds 冲突 |
老代码把新编译器警告当错误 | 对已确认无实际越界的目标使用 CFLAGS="-Wno-error=array-bounds",并保留完整日志 |
| Ninja 源码克隆超时 | 构建不需要从源码拉取 | 使用发行版 Ninja,验证版本和并行行为 |
这类修复不能直接写成"把所有错误都忽略"。每一项都要回答三个问题:修复是否只影响构建工具而不改变目标代码;是否有最小编译或链接验证;是否会让后续产物变成不可追溯的混合版本。
7.3 QNX 直接构建:shell 语义和依赖比命令本身更容易出错
一个可复用的 QNX 构建骨架如下,路径使用占位符:
sh
cd <qnx_root>/apps/qnx_ap
source <qnx_prebuilt>/qnxsdp-env.sh
source ./setenv_qhs220.sh --external <qnx_prebuilt>
export HOST_OS=linux
export SECTOOLS_V2_ROOT=<sectools_path>
export PATH=<filepp_dir>:$PATH
make -j8
这里有几个常见的 shell 规则:
source <script> | grep ...会在子 shell 中执行,脚本导出的变量不会回到当前 shell。需要记录输出时,用source <script> >env.log 2>&1,再单独查看日志。- 某些
setenv脚本会因为兼容性警告返回非零,但实际已经设置了大部分变量。若把它写进source ... && export ...,shell 会跳过后面的导出。对已知行为的 QNX 环境,按脚本顺序使用分号并在最后单独检查关键变量。 - 每次终端调用都重新
cd到目标目录;不要依赖上一次命令留下的工作目录。 make | tail会截断真正的错误上下文;构建日志应完整保存,展示时再筛选。- 临时放在
/tmp的filepp或链接在重启后可能消失,应把依赖放在可追溯目录并在构建前检查。
直接构建中曾出现 filepp、dtc、libxml2.so、.NET runtime、sshd-session 等依赖问题,也出现过大量 IFS 配置占位文件。解决时应先按缺失项建立依赖清单,随后逐项验证;不要在一个命令里不断追加软链接。libxml2.so 的兼容链接、SSH 会话程序的路径兼容等操作只应对已确认 ABI 相容的文件执行。
目录操作本身也会制造假问题:
sh
# 需要覆盖已有链接时,优先使用不会跟随目录的方式
ln -snf <real_target> <link_name>
反复使用 cp -raf source dest 可能把目录复制成 dest/source 的嵌套结构;用 rsync 合并时,--no-links 适合避免"指向非空目录的符号链接"覆盖目标树,但会改变链接语义,必须在构建后检查。手工把文件放进 out 目录也不等于构建系统会把它打进镜像;Android 需要 PRODUCT_COPY_FILES 或对应的 Make/Soong 规则,Ninja 依赖图中没有该文件就不会自动更新。
7.4 六段构建链:先分层,再追产物
典型依赖关系可以抽象成:
#mermaid-svg-Ypo9BwEzrkmWbQyb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Ypo9BwEzrkmWbQyb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Ypo9BwEzrkmWbQyb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Ypo9BwEzrkmWbQyb .error-icon{fill:#552222;}#mermaid-svg-Ypo9BwEzrkmWbQyb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Ypo9BwEzrkmWbQyb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Ypo9BwEzrkmWbQyb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Ypo9BwEzrkmWbQyb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Ypo9BwEzrkmWbQyb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Ypo9BwEzrkmWbQyb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Ypo9BwEzrkmWbQyb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Ypo9BwEzrkmWbQyb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Ypo9BwEzrkmWbQyb .marker.cross{stroke:#333333;}#mermaid-svg-Ypo9BwEzrkmWbQyb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Ypo9BwEzrkmWbQyb p{margin:0;}#mermaid-svg-Ypo9BwEzrkmWbQyb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Ypo9BwEzrkmWbQyb .cluster-label text{fill:#333;}#mermaid-svg-Ypo9BwEzrkmWbQyb .cluster-label span{color:#333;}#mermaid-svg-Ypo9BwEzrkmWbQyb .cluster-label span p{background-color:transparent;}#mermaid-svg-Ypo9BwEzrkmWbQyb .label text,#mermaid-svg-Ypo9BwEzrkmWbQyb span{fill:#333;color:#333;}#mermaid-svg-Ypo9BwEzrkmWbQyb .node rect,#mermaid-svg-Ypo9BwEzrkmWbQyb .node circle,#mermaid-svg-Ypo9BwEzrkmWbQyb .node ellipse,#mermaid-svg-Ypo9BwEzrkmWbQyb .node polygon,#mermaid-svg-Ypo9BwEzrkmWbQyb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Ypo9BwEzrkmWbQyb .rough-node .label text,#mermaid-svg-Ypo9BwEzrkmWbQyb .node .label text,#mermaid-svg-Ypo9BwEzrkmWbQyb .image-shape .label,#mermaid-svg-Ypo9BwEzrkmWbQyb .icon-shape .label{text-anchor:middle;}#mermaid-svg-Ypo9BwEzrkmWbQyb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Ypo9BwEzrkmWbQyb .rough-node .label,#mermaid-svg-Ypo9BwEzrkmWbQyb .node .label,#mermaid-svg-Ypo9BwEzrkmWbQyb .image-shape .label,#mermaid-svg-Ypo9BwEzrkmWbQyb .icon-shape .label{text-align:center;}#mermaid-svg-Ypo9BwEzrkmWbQyb .node.clickable{cursor:pointer;}#mermaid-svg-Ypo9BwEzrkmWbQyb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Ypo9BwEzrkmWbQyb .arrowheadPath{fill:#333333;}#mermaid-svg-Ypo9BwEzrkmWbQyb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Ypo9BwEzrkmWbQyb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Ypo9BwEzrkmWbQyb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ypo9BwEzrkmWbQyb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Ypo9BwEzrkmWbQyb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ypo9BwEzrkmWbQyb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Ypo9BwEzrkmWbQyb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Ypo9BwEzrkmWbQyb .cluster text{fill:#333;}#mermaid-svg-Ypo9BwEzrkmWbQyb .cluster span{color:#333;}#mermaid-svg-Ypo9BwEzrkmWbQyb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Ypo9BwEzrkmWbQyb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Ypo9BwEzrkmWbQyb rect.text{fill:none;stroke-width:0;}#mermaid-svg-Ypo9BwEzrkmWbQyb .icon-shape,#mermaid-svg-Ypo9BwEzrkmWbQyb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ypo9BwEzrkmWbQyb .icon-shape p,#mermaid-svg-Ypo9BwEzrkmWbQyb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Ypo9BwEzrkmWbQyb .icon-shape .label rect,#mermaid-svg-Ypo9BwEzrkmWbQyb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ypo9BwEzrkmWbQyb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Ypo9BwEzrkmWbQyb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Ypo9BwEzrkmWbQyb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} QNX make / qcc
QNX IFS 与音频服务
LA QSSI: Soong / Kati / Ninja
system/product/system_ext
LA Kernel: Bazel / Clang
Image / dtb / ko
LA Vendor: Soong / Kati / Ninja
vendor 与模块
OEM build.py
NON-HLOS 与厂商包
ADSP/CDSP/AOP/Boot/TZ
固件 mbn/elf
Meta Build / build.py
刷机包与设备验证
QSSI 的入口示例:
sh
source build/envsetup.sh && lunch qssi_au-userdebug
export ALLOW_MISSING_DEPENDENCIES=true
export QCPATH=vendor/qcom/opensource
bash build.sh dist -j8 --qssi_only
source、lunch、m 或 bash build.sh 必须在同一 shell 中完成;与 QNX 的"脚本可能带警告但仍需继续"不同,这里通常应让 && 在环境初始化失败时及时停止。若 bcc_strip_attr 在中段失败,常见原因是主机输出目录中缺少 libncurses.so.5 或 libtinfo.so.5 兼容链接。修复后要重新执行失败目标,并检查产物时间戳,不要仅凭命令退出码判断整个 QSSI 已完成。
Kernel 使用 Kleaf/Bazel 时,参数边界也很重要:
sh
python3 build_with_bazel.py -t autogvm gki
-t 后面是目标变体,gki 是构建目标,不能把两者拼成一个参数。GKI 设备树若需要引入厂商绑定,通常要创建受控的 vendor 目录,补上 qcom/bindings/Makefile 的映射,并在 BUILD.bazel 中声明 kernel_dtstree。所有新增路径都应在 Bazel graph 中出现,否则"目录里有源码"仍然不会参与构建。使用 ln -snf 更新链接后,要确认链接目标没有自指向。
Vendor 树通常由 QSSI、Kernel 和 Vendor 三部分合并。除了符号链接处理,还会遇到预编译 Android.mk 相对深度不一致,导致 c2d2.h、libdrmMinimalfs 等头文件或库找不到。适配时可以在目标 Makefile 中增加受保护的 include:
make
ifneq ($(shell grep -q "<required_header>" $(LOCAL_PATH)/<prebuilt_mk> && echo yes),)
LOCAL_C_INCLUDES += $(LOCAL_PATH)/<relative_include>
endif
真实项目中更推荐直接修正预编译包的目录描述;若必须兼容多种树,至少用 grep -q 或变量存在性做幂等保护。m 成功而 m dist 失败并不矛盾:dist 还会执行额外归档和校验,必须分别记录"目标镜像是否生成"和"发布归档是否完成"。HIDL manifest 缺失接口、OTA 工具覆盖 QSSI 中的 host_init_verifier,也都是"局部构建成功、整包失败"的典型边界。
OEM 层的入口更接近打包:
sh
cd <oem_root>/common/build
python3 ./build.py --nonhlos --variant <target_variant> --imf
它不能替代 QNX、Kernel 或 DSP 的源代码编译。--imf 只表示在当前本地树中采用对应的镜像文件检查策略,是否可以使用要结合发布流程和树的完整性确认。Meta Build 类似:
sh
cd <meta_root>
python3 build.py --hlos --variant <variant> --imf
当它报缺少 build info 时,问题可能是组件目录或 snap_release 元数据不存在;HLOS 代码编译失败属于另一类问题。某些低版本组件没有完整目录,可以补建最小的占位树供打包器读取;但 LV 这类组件的 dummy tree 只能解决目录遍历,无法证明真实功能存在。若解析 sparse 镜像,应先识别标准 sparse magic 0xED26FF3A,再按工具支持的格式处理,避免把稀疏头误当成原始文件头。
7.5 清理、预编译与许可证边界
make clean 在 QNX 工程里具有破坏性,可能删除 install/、预编译头和中间依赖。一次许可证加载失败后,错误文本甚至覆盖了 DALDeviceId.h,后续编译错误看起来像源码损坏。遇到这种事故,优先从版本控制、备份或同版本干净树恢复,再用 git diff、文件类型检查和最小 qcc 编译验证,不要在损坏的树上继续堆补丁。
有效的当前预编译包与旧的许可证环境绑定 host 工具混用时,也不能简单地把整套 host 目录覆盖掉。不同版本的 target/host 布局、gcc_ntoaarch64le 与 gcc_ntoaarch64 目标别名可能不同;实际适配应在受控、有授权的环境中按精确目录替换,并保留原始目录和版本记录。无论采用哪种替换方式,都要用最小编译验证工具链是否真正可用:
sh
qcc -V<target_alias> -c <small_source.c> -o <small_object.o>
file <small_object.o>
7.6 DSP/BP 构建:路径、解释器与时间戳
ADSP 与 CDSP 的源码目录并不总是相同:ADSP 使用 dsp_ivi/dsp_proc,CDSP 使用 dsp/dsp_proc;某些发布包把 ADSP 作为预编译固件提供,源码树存在并不代表当前变体可以重新生成。工具链版本也经常按组件分裂,例如 Hexagon 8.6/8.7、不同代的 LLVM ARM,以及 Python 2.7、3.8、3.10/3.11 分别服务 AOP、Boot、DSP/CDSP。先按组件隔离环境,再执行入口:
sh
# ADSP / CDSP
python3 ./build_variant.py <adsp_variant>
python3 ./build_variant.py <cdsp_variant>
# AOP
./aop_proc/build/build_monaco.sh [-c]
# Boot
python3 boot_images/boot_tools/buildex.py \
--variant AU -r RELEASE -t Monaco,QcomToolsPkg
# TZ
python3 build_all.py -b TZ.XF.5.0 CHIPSET=<chipset> --config=<config>
AOP 的 Python 2 必须单独隔离,不能因为当前终端默认 Python 3 就改写全部脚本;同时检查 PATH 中的 Python、签名工具链接和 libtinfo 兼容库。Boot 构建通常要求 Python 3.8 及相应的 distutils,DSP/CDSP 则可能依赖 Python 3.10/3.11。CDSP 设备树工具还需要固定版本,例如 dtschema==2022.7、ruamel.yaml==0.17.21 和 swig,并设置:
sh
export HEXAGON_ROOT=<hexagon_root>
export SECTOOLS=<sectools_path>
export DTC_PATH=<dtc_path>
export LD_LIBRARY_PATH=<toolchain_lib>:$LD_LIBRARY_PATH
有些 build_variant.py 会把底层 SCons 的退出码转换成 0,或者在旧日志存在时复用上一次结果。因此要同时检查:日志末尾是否真的完成、目标文件时间戳是否更新、文件大小是否合理、签名/打包步骤是否执行。相同变体不要并发构建;如果必须刷新时间戳,使用项目支持的 --clean,并提前确认它会删除哪些输出。
7.7 资源不足和可复现构建
大型 Android/Kernel 构建的失败原因经常来自磁盘或内存,编译错误属于另一类问题。建议把问题拆成三类:
- 删除可重建中间目录前,先保留失败日志、配置和最后一次成功产物摘要。
- 为编译机配置足够的交换空间,例如受控的 32 GiB swap,并根据实际内存调整并发度。
- 合并多套树时使用
rsync --link-dest等硬链接策略减少重复占用,同时避免把源码内部名为out的目录通过过宽的--exclude out误排除。
Kernel LTO 可能瞬间消耗大量空间;应在构建前检查工作区、临时目录和缓存目录的剩余容量。Kleaf 还会缓存 *_env.sh 一类环境脚本。如果沙箱或临时目录曾经只读,clang 可能报 unable to make temporary file。可在确认权限后设置:
sh
export TMPDIR=/tmp
并移走或删除由旧环境生成的缓存脚本,让 Kleaf 重新生成。这里的"移走"必须限定在明确的构建缓存目录,不能对整个工作区做递归删除。
7.8 固件替换链:编译完成不代表板端已经更新
ADSP 固件替换至少要经过下面的链路:
text
编译变体
→ 检查 adsp.mbn / mdt 时间戳与摘要
→ 替换 prebuilt_NHLOS 中对应文件
→ QNX clean + build,生成新的 IFS
→ Meta Build 重新打包
→ 检查 pil_split_bins 与刷机包清单
→ 刷写后确认板端文件和版本
→ 再做功能、QXDM、物理链路验证
其中每个箭头都可能断开。只在 DSP 源码目录看到新文件,不能证明 NON-HLOS.bin 已更新;只看到刷机工具退出 0,也不能证明设备没有回退到旧分区。构建日志、产物摘要、刷机包清单、板端启动日志应形成同一条可追溯记录。
8. QNX 崩溃转储与符号化
8.1 先建立符号环境,再打开 core
QNX coredump 分析最容易卡在"core 有了但符号不对"。一个可复用的准备流程是:
sh
cd <qnx_root>
ln -snf <install_aarch64le> install/aarch64le
ln -snf <prebuilt_target> <target_link>
随后在 .gdbinit 中配置完整的 solib-search-path,覆盖 QNX SDK、目标 IFS 解包目录、组件安装目录和对应的 .sym 文件目录。某次环境检查到 11 个搜索路径、约 1400 个符号文件;另一个基线约 1500 个。数量不能作为成功条件,关键是 core 中加载的库能否在搜索路径中找到同版本符号。
可以为项目维护一个小型分析脚本 analyze_core.sh,将常用动作固定下来:
sh
./analyze_core.sh verify
./analyze_core.sh env
./analyze_core.sh info <binary>
./analyze_core.sh core <core_file>
./analyze_core.sh bt <core_file>
./analyze_core.sh log <slog_file> --tail 50
./analyze_core.sh log <slog_file> -p audio_service --tail 20 "14:30:00"
./analyze_core.sh log <slog_file> -k "SIGABRT\|SIGSEGV"
verify 应至少检查 GDB 版本、目标架构、11/11 的共享库搜索路径和 .sym 文件是否可见。分析时使用目标 SDK 提供的 GDB:
sh
source <qnx_sdk>/qnxsdp-env.sh
ntoaarch64-gdb <binary_or_binary.sym> -c <core_file>
不要把不同构建树的 .sym 混在一个目录里仅靠文件名匹配。若 backtrace 中出现函数名但行号、局部变量和库基址不一致,优先检查符号版本,暂时不要怀疑 GDB。
8.2 三类 dump 要分开处理
| 来源 | 适合回答的问题 | 典型工具 |
|---|---|---|
QNX ramdump |
PVM/系统进程、线程栈、共享库状态 | QNX GDB、QNX ramdump 工具 |
Guest gcore |
Android/Linux 用户态进程现场 | vmlinux、crash、GDB、capstone/pyelftools |
| ADSP/CDSP SSR dump | DSP 线程、堆栈、硬件服务状态 | hansei.py、adspcrashman.py、Trace32 或对应分析器 |
QNX 日志先导出再分析:
sh
slog2info -b > /var/log/slog.txt
./analyze_core.sh log /var/log/slog.txt --tail 50
Linux Guest 的 gcore 触发方式应遵守目标系统的安全策略;在允许的调试环境中,常见入口是 echo c > /proc/sysrq-trigger,但它会触发系统级崩溃行为,不应在生产设备上直接执行。ADSP/CDSP 的 SSR 采集也要按平台文档和受控设备状态操作,文章只保留工具链和判断方法,不发布特定板卡的救援细节。
8.3 "没有符号"是一个可验证的阻塞结论
ADSP/CDSP ELF 调查曾经遇到一个典型边界:有源码树,但当前发布包没有对应平台的构建变体;手里的 .mbn 是固件镜像,不含带调试信息的 ELF;分析工具版本也没有目标平台条目。源码存在不足以支持符号化。应把阻塞拆成证据:
- 当前树中没有匹配的
build_variant或平台配置; - 预编译目录中只有无符号镜像,没有匹配的 debug ELF;
- 分析器的目标平台列表与板端固件不一致;
- 因而目前只能做字符串、时间线、模块状态和寄存器层面的有限分析。
曾使用过的 hansei.py 还暴露了脚本实现问题:混用 Tab 和空格会触发 Python 缩进错误,Linux 会把 Windows 反斜杠路径按普通字符处理。局部修复后仍可能因深层依赖缺失而无法完成解析。报告应分别记录"脚本可运行""能读到 dump""能完成目标平台解析";第三层结论需要独立证据。
9. 日志采集、QUTS/QXDM 与证据质量
9.1 调试补丁要追踪到最终链接
一次调试任务同时涉及三类修改:QNX CSD2/GSL BE 侧补丁已经落地;ADSP GSL FE 侧因源码不可用,补丁不适用;ADSP SPL container 侧在完成路径映射后落地。这个结论本身就比"调试补丁已全部应用"准确。
更隐蔽的问题是静态库增量构建。只执行 make install 时,可能只重新生成共享库;已经静态链接进 audio_service 的旧 .a 并没有更新。结果是源码和日志宏看起来已经改变,但运行进程仍没有新日志。稳妥流程是:
sh
make -C <component> clean install
make -C <another_component> clean install
make -C audio_service clean install
重新链接后,再比较 slog2info 中各模块计数或关键字符串。一次实际前后对比为:GSL 日志约从 15 条增至 31 条,GPR 从 0 条增至 182 条,CSD2 从 73 条增至 77 条。数字不构成通用阈值;"清理静态库 → 重新链接 → 比较计数"这条证据链可以复用。
9.2 GUI、API 和 mask 属于不同采集链
QUTS 图形界面连接 QXDM 时,HDF 文件可以持续增长;同一设备上用脚本 API 时,队列却长期为 0。进一步发现某个 DMC mask 没有包含目标 SSID;补上 mask 后,目标 SPL 消息仍未出现。这里应保留为未解决的采集链问题,不能宣称"加 mask 已修复"。
实时双机采集可以采用以下流程:Windows 上运行 QUTS TCP 服务(示例端口 2500),设备侧通过受控网络连接,主机端播放一段足够长的 WAV,让实时图和文件写入有时间展开。
QNX 侧的最小日志流程:
sh
slog2info -c
audio_chime -g 21 /ifs/etc/car2-chime.wav
slog2info > /data/<session>.txt
Android 侧:
sh
adb root
adb shell logcat -c
adb shell "logcat > /data/<session>.txt"
adb pull /data/<session>.txt <local_dir>
注意 adb logcat -d > <local_file> 中的重定向发生在主机 shell;如果先进入 adb shell,重定向位置和文件生命周期会改变。完整原始日志应保留,grep、Select-String 或脚本只用于本地分析,不应覆盖原始证据。
9.3 退出码是弱证据
agmplay、audio_chime 或自定义测试程序返回 0,只能说明程序流程没有以非零状态退出。仍需检查:
- 标准输出和
logcat/slog2是否出现 graph、session、endpoint、underrun 错误; - 实际写入字节数、frame 数、写入次数是否符合 WAV 的采样率、声道数和时长;
- 输出文件的头部、格式和大小是否正确;
- QXDM 是否看到对应的 clock、TDM、DMA、CSD2/GPR 活动;
- 物理端是否真的有电平、波形或可听输出。
因此,测试报告应同时写"程序返回值"和"功能证据",不能只贴一行 RC=0。
10. 从 ACDB 到板端的典型故障路径
10.1 空占位文件会遮蔽真实启动内容
一次 IFS 排查发现启动配置目录中存在大量空 .build 文件,这些占位项遮蔽了实际文件。系统只能启动到很早阶段,出现最小 QNX、spawn 失败或服务缺失。处理原则是:
- 先备份整棵配置树;
- 将确认为空且用于占位的文件移到单独目录;
- 保留尚未匹配的占位文件,不要批量删除;
- 重新生成 IFS 并用
dumpifs检查内容; - 按服务启动顺序验证,不能只看 IFS 文件大小。
相关修复让核心服务配置从约 150 行扩展到近 500 行,说明"文件存在"与"启动内容完整"是两件事。
10.2 空 shell 脚本和挂载顺序会制造假死
另一次移植中有若干空 shell 脚本,导致 /data 相关函数不存在;模板还缺少一个引号,FDE 路径和持久化挂载顺序进一步触发 smcinvoke 等待。局部适配采用了三项措施:未加密 userdata 路径绕开不必要的 FDE 步骤;当 /dev/qtd 不存在时使用直接的 dsplib fallback;把空占位脚本移出启动路径并修复模板语法。
这类修复属于板卡和系统版本相关的适配,不应写成通用"删掉 FDE"方案。报告必须注明适用前提,并在加密设备、冷启动、重启和数据持久化场景分别回归。
10.3 文件权限是动态加载的一部分
ACDB 文件存在但服务仍读不到时,除了路径和版本,还要检查所有者、模式位、挂载状态和重启后的持久性。常见的受控修复是:
sh
chown root:root /mnt/etc/acdb/<target>/*
chmod 0555 /mnt/etc/acdb/<target>/*
修复后用 git diff、git apply --check --reverse、git diff --check 检查补丁状态;用 dumpifs 确认文件进入 IFS;用 strings、时间戳和主机摘要确认板端加载的是预期文件。动态替换、负向测试、恢复测试必须放在同一份记录里。
10.4 9008、EDL、fastboot 和串口锁要按状态命名
设备出现在 USB 9008 类枚举状态,不等于已经进入可执行刷写的 EDL;某些状态实际是 QDSS 或诊断模式。遇到 fastboot 中断时,先用设备状态、枚举信息和官方工具确认当前模式,再选择有效的 QFIL/刷机包或平台规定的硬件恢复方法。
串口工具也有类似的状态问题:minicom lock 只表示已有会话占用设备,不代表板端异常。先确认没有其他终端使用该端口,再按工具规则释放锁,避免两个会话同时发送复位或刷机命令。
11. TDM0 深入案例:从"图能打开"到"数据真的到达引脚"
TDM0 是最能体现分层排查价值的一条案例。它同时涉及 QACT 拓扑、ACDB、Android AGM/GSL FE、QNX CSD2、ADSP AFE、时钟、DMA、TDM lane 和外部硬件。任何一层显示成功,都不能替代下一层的证据。
11.1 先写出接口契约
本案例中,TDM0 RX 的目标接口契约为:
| 项目 | 目标值 |
|---|---|
| 采样率 | 48 kHz |
| 逻辑声道 | 4 ch |
| slot 宽度 | 32 bit |
| slot 数 | 8 |
| slot mask | 0xF |
| 数据 lane | DATA3,lane_mask=0x8 |
| 同步方式 | Long Sync |
| 数据延迟 | 1 BCLK |
| BCLK | 12.288 MHz |
| 目标时钟配置 | 0x200 |
采样率、slot 宽度和 slot 数之间要先做算术检查:
text
BCLK = sample_rate × slot_width × nslots
= 48,000 × 32 × 8
= 12.288 MHz
slot_mask=0xF 表示逻辑通道使用低四个 slot;lane_mask=0x8 表示数据位于第四条 lane。两者属于不同维度,不能因为 slot mask 正确就推断 lane 也正确。Long Sync、1 BCLK delay、采样沿、FS 极性和 master/slave 方向也应列入同一张接口契约。
11.2 ACDB 图拓扑错误会先于物理时序暴露
一版 ACDB 中,TDM Sink Graph 的标签同时带入了 0x4793 和 0x4AB8 两个同类模块,QACT 导出的 num_modules=2 与预期拓扑不符。Android 侧 graph/session 可以开始,但这并不能说明 endpoint 配置正确。后来通过 QACT 删除重复模块,只保留 TDM Sink Module 0x4793,图拓扑恢复为单模块,Android graph/session 日志也变得一致。
这一步证明的是"ACDB 图结构从无效变为可建图",还没有证明声学输出。验证图结构时,至少要同时检查:
- graph、subgraph、container、module 的数量;
- 每个 module 的 instance ID、端口数和端口方向;
- TDM Sink 的声道、位宽、lane 和时钟参数;
- QACT 工程导出文件与最终 ACDB 的生成时间和摘要;
- QNX 与 Android 两条消费路径是否使用同一组 tagged data。
测试文件不存在时,失败范围限于文件路径或部署流程:若 /data/48k.wav 打不开,不能据此判断 TDM link 失败。使用已有的 /ifs/etc/car2-chime.wav 可以验证 graph 入口,但仍只能证明软件路径有输入。
11.3 QXDM 把"启动成功"拆成多个时间点
QXDM 观察到运行时 clock ID 变成了 0x206,而 QACT 中配置的是 0x200。进一步检查发现两者对应的频率都为 12.288 MHz,因此这里至少要区分"配置 ID 不同"和"实际频率错误"两个问题。不要只比较十六进制 ID 就判定时钟频率错了。
同一段播放中,0x41FA Splitter Renderer 的外部输出 port1 连接到 0x4793 port2;随后出现大量 overrun/drop,约每通道丢失 960 个采样,累计接近两千次。TDM sink 同时出现 underrun,某些数据包的实际长度只有 96,而最大长度是 384。值得继续检查的方向包括:
- Splitter 输出与 TDM Sink 的声道数是否一致;
- internal MFC 是否把 4 ch 转成了 2 ch,或把 32 bit 转成了 16 bit;
- zero insertion、port map 和 channel map 是否匹配;
- DMA period、buffer 深度与播放写入节奏是否匹配;
- graph 是否有不必要的 Mixing/Demuxing 实例。
一个旧基线也出现过类似 drop,所以回退后仍有 drop 不能证明问题只由新 ACDB 引入。删除 0x4AB9 的 Mixing and Demuxing 实例后,drop 消失,但短数据包、underrun 和 zero insertion 仍在。这个结果应记录为"局部改善",不能称为"已修复"。
11.4 AVSync 和格式转换要以拓扑为准
AVSync 页面处于启用状态,且 num_ctrl_ports=0 与当前拓扑一致;这更像是配置状态的确认,不足以成为根因。没有新的端口控制证据时,不要为了消除一个字段就修改 AVSync。
图内部的 MFC 常见为 2 ch、单个 16-bit 数据格式,而 TDM Sink 要求 4 ch、32-bit。此时必须在 QXDM 或模块参数中找出明确的 2→4、16→32 转换证据。仅凭"graph 打开了"和"Sink 参数正确"无法证明转换已经发生。
11.5 Android 字节统计只能封闭上游范围
为隔离 AGM/GSL FE,曾用如下方式构建测试工具:
sh
source build/envsetup.sh
lunch gen4_gvm-userdebug
m libagm libagm_pcm_plugin agmplay -j8
一次长数据测试写入约 7,681,920 字节、960,240 frames,完成约 235 次写入,Android 日志中 AGM/GSL FE 没有 underrun。这组数字证明了:输入文件解析、AGM session、GSL FE 写入节奏在该次测试中成立。它不能证明下游 AFE、DMA、TDM、外部 Codec 或扬声器输出成立,因为该统计尚未覆盖这些路径。
11.6 最小驱动修复也要避免 ID 生命周期错误
时钟相关驱动的一处最小修复思路是:在 invert-couple 属性中缓存 ACDB 返回的实际 clock ID;只有 ACDB 没有提供有效 ID 时,才回退到 devcfg;停止时释放缓存的实际 request ID,避免重新使用默认 ID。伪代码为:
c
clock_id = acdb_clock_id;
if (clock_id == INVALID_CLOCK_ID)
clock_id = devcfg_clock_id;
start_clock(clock_id);
...
stop_clock(clock_id);
后续需要完整执行 ADSP 编译、Meta 打包和刷机:
sh
python3 ./build_variant.py <adsp_variant>
python3 build.py --flavors=<flavors> --variant=<variant> --imf
python3 fastboot_complete.py --st=ufs --pf=<platform_family>
完整刷写后 ACDB 已恢复,但播放在 GSL FE 的 gsl_write 阶段返回 -131,没有看到对应的 QXDM clock/overrun 新证据。最终结论是:驱动 ID 生命周期修复已经进入验证链,但因此仍不能认定 TDM0 端到端问题已解决。
11.7 TDM0 案例的结论矩阵
| 观察 | 能证明什么 | 不能证明什么 |
|---|---|---|
| QACT 无重复 module | 图拓扑更接近预期 | 物理引脚有波形 |
| Android graph/session 成功 | 上游软件图能建立 | 下游 DMA/TDM 正常 |
| clock 频率为 12.288 MHz | 运行时频率正确 | ID 映射和停止释放全正确 |
| GSL FE 字节数正确 | Android 上游写入成立 | AFE/外部 Codec 收到数据 |
| drop 消失 | 某个 graph 节点的丢包减少 | underrun、格式转换、听感全正常 |
RC=0 |
程序没有主动报错 | 物理输出或声学结果 |
12. 蓝牙 HFP:键值、格式与时序必须同时成立
12.1 从资源键一路追到 use case
BT 播放和 HFP 的问题,不能只看 agmplay 命令。完整链路应当是:
text
GKV
→ resource manager XML
→ usecase XML
→ kvh2xml 生成 TKV/ACDB
→ AGM/PAL session
→ GSL FE / ADSP graph
→ PCM/TDM3 或 HFP endpoint
先确认物理通路使用的是 PCM/TDM3/PCM3 哪一组资源,再确认 GKV 是否映射到了对应 use case。某个键只存在于 XML 并不代表最终 ACDB 带上了它;最终应检查生成文件、模块参数和运行日志。
12.2 stock 工具无法证明任意键已经生效
一次 BT 播放尝试发现缺少 SCO 相关 key。标准 agmplay、agmcap、agmhostless 并没有提供可靠的任意 key 注入能力;该版本静默忽略命令行中的 -stxkv、-btpkv。程序返回 0,但目标 BT 图没有建立。
这类问题的正确验证方法是:
- 从资源管理器配置确认 key 的来源和 use case;
- 检查 XML → TKV → ACDB 的生成链;
- 从 AGM/PAL 日志确认 session、graph、endpoint;
- 用 QXDM 检查 DSP 侧端口、格式和错误;
- 最后才判断音频是否从物理 BT/PCM 端点出去。
12.3 hostless 和 HFP 的失败顺序
Hostless WSA-TX → VA-RX 测试中,MFC 不存在,PCM 返回 -19。HFP 使用 AUD-TX 时返回 -131,同时配置尚未验证,说明失败发生在 endpoint/格式准备阶段,早于稳定播放数据流动。
QXDM 的时间线中,最早的硬错误是:
text
CAPI_PCM_TDM ... Cannot start, configuration not validated
Intf cfg received: 0
Media fmt received: 1
随后 FvXiii 报告 primary input 的 bits per sample 为 32,且该配置无效。期间观察到 sample slip,但 sample slip 可能是配置错误的后果,不能直接当作根因。排查顺序应该是先解决 interface config、media format 和 endpoint validate,再分析时钟漂移或 buffer slip。
12.4 采集不到 PCM 数据不等于 ACDB 没生效
QACT Data Logging 中选择 0x1981、Tap 0x0001 后没有得到 PCM 数据。这个结果可能来自 payload 映射错误、tap 点不在实际数据流、日志 mask 未覆盖或下游尚未启动。没有把 QXDM payload 与目标模块端口一一对应前,不能反推 ACDB 没有加载。
BT_Rx TDM Sink 的一次修改方向是 48 kHz、4 ch、32 bit、fixed point,clock 0x200,lane 0x8,8 slots,slot mask 0xF。但板端 Audio、HFP、声音链路和回归尚未完成,所以这些参数应标记为"配置尝试",不宜写成"最终已验证"。还要特别检查后端命名:代码期待 AUD/PRIMARY,ACDB 里可能写成 LPAIF/SECONDARY;这是值得验证的映射假设,不能直接改名后宣布修复。
12.5 文件名不足以描述媒体格式
一次测试文件名带有"24bit/16ch"字样,但实际 WAV 头解析出的格式是 4 ch、32-bit float。文件名、命令参数和容器头部三者不一致时,优先以 WAV header 和播放器解析结果为准。建议在测试脚本开头打印:采样率、声道数、bits per sample、sample format、总帧数和文件字节数,再决定是否进入 TDM/HFP 链路。
13. 一套可复用的音频问题定位方法
13.1 把现象写成可证伪假设
"没有声音"只是现象,下面这种写法才可验证:
| 假设 | 最小证据 | 反证 |
|---|---|---|
| ACDB 没有加载 | 文件摘要/时间戳、服务节点、负向测试 | 移走文件后服务消失,恢复后出现 |
| graph 拓扑错误 | module 数量、instance、端口和 tagged data | QACT 与最终 ACDB 一致且 graph 建立 |
| clock 频率错误 | QXDM 实测频率、父时钟、ID 映射 | 频率正确但仍有 drop |
| TDM lane 错误 | lane/slot 配置、寄存器、波形或采集数据 | 端口配置与实际引脚观测一致 |
| 下游吞吐不足 | overrun/underrun、period、写入节奏 | 上游写入正常且下游无丢包 |
| 测试文件问题 | header、路径、大小、frame 数 | 文件正确后故障仍复现 |
每个假设都要指定"什么结果会推翻它"。这样可以避免看到一条符合预期的日志就提前结束排查。
13.2 推荐的分层检查顺序
#mermaid-svg-JgjRmDRo6r5sAl7e{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-JgjRmDRo6r5sAl7e .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-JgjRmDRo6r5sAl7e .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-JgjRmDRo6r5sAl7e .error-icon{fill:#552222;}#mermaid-svg-JgjRmDRo6r5sAl7e .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-JgjRmDRo6r5sAl7e .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-JgjRmDRo6r5sAl7e .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JgjRmDRo6r5sAl7e .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JgjRmDRo6r5sAl7e .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-JgjRmDRo6r5sAl7e .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JgjRmDRo6r5sAl7e .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JgjRmDRo6r5sAl7e .marker{fill:#333333;stroke:#333333;}#mermaid-svg-JgjRmDRo6r5sAl7e .marker.cross{stroke:#333333;}#mermaid-svg-JgjRmDRo6r5sAl7e svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JgjRmDRo6r5sAl7e p{margin:0;}#mermaid-svg-JgjRmDRo6r5sAl7e .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-JgjRmDRo6r5sAl7e .cluster-label text{fill:#333;}#mermaid-svg-JgjRmDRo6r5sAl7e .cluster-label span{color:#333;}#mermaid-svg-JgjRmDRo6r5sAl7e .cluster-label span p{background-color:transparent;}#mermaid-svg-JgjRmDRo6r5sAl7e .label text,#mermaid-svg-JgjRmDRo6r5sAl7e span{fill:#333;color:#333;}#mermaid-svg-JgjRmDRo6r5sAl7e .node rect,#mermaid-svg-JgjRmDRo6r5sAl7e .node circle,#mermaid-svg-JgjRmDRo6r5sAl7e .node ellipse,#mermaid-svg-JgjRmDRo6r5sAl7e .node polygon,#mermaid-svg-JgjRmDRo6r5sAl7e .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-JgjRmDRo6r5sAl7e .rough-node .label text,#mermaid-svg-JgjRmDRo6r5sAl7e .node .label text,#mermaid-svg-JgjRmDRo6r5sAl7e .image-shape .label,#mermaid-svg-JgjRmDRo6r5sAl7e .icon-shape .label{text-anchor:middle;}#mermaid-svg-JgjRmDRo6r5sAl7e .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-JgjRmDRo6r5sAl7e .rough-node .label,#mermaid-svg-JgjRmDRo6r5sAl7e .node .label,#mermaid-svg-JgjRmDRo6r5sAl7e .image-shape .label,#mermaid-svg-JgjRmDRo6r5sAl7e .icon-shape .label{text-align:center;}#mermaid-svg-JgjRmDRo6r5sAl7e .node.clickable{cursor:pointer;}#mermaid-svg-JgjRmDRo6r5sAl7e .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-JgjRmDRo6r5sAl7e .arrowheadPath{fill:#333333;}#mermaid-svg-JgjRmDRo6r5sAl7e .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-JgjRmDRo6r5sAl7e .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-JgjRmDRo6r5sAl7e .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JgjRmDRo6r5sAl7e .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-JgjRmDRo6r5sAl7e .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JgjRmDRo6r5sAl7e .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-JgjRmDRo6r5sAl7e .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-JgjRmDRo6r5sAl7e .cluster text{fill:#333;}#mermaid-svg-JgjRmDRo6r5sAl7e .cluster span{color:#333;}#mermaid-svg-JgjRmDRo6r5sAl7e div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-JgjRmDRo6r5sAl7e .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-JgjRmDRo6r5sAl7e rect.text{fill:none;stroke-width:0;}#mermaid-svg-JgjRmDRo6r5sAl7e .icon-shape,#mermaid-svg-JgjRmDRo6r5sAl7e .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JgjRmDRo6r5sAl7e .icon-shape p,#mermaid-svg-JgjRmDRo6r5sAl7e .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-JgjRmDRo6r5sAl7e .icon-shape .label rect,#mermaid-svg-JgjRmDRo6r5sAl7e .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JgjRmDRo6r5sAl7e .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-JgjRmDRo6r5sAl7e .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-JgjRmDRo6r5sAl7e :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 确认测试文件与实际格式
确认服务、进程和设备节点
确认 ACDB/QWSP 版本与权限
确认 graph/subgraph/module/port
确认 session 与 GSL FE
确认 CSD2/GPR 与 Hypervisor
确认 ADSP graph/AFE/DMA
确认 clock、TDM、GPIO 与引脚
确认外部 Codec、设备和声学输出
做反向测试与 A/B 回归
推荐顺序可以根据故障层次调整。若 QNX 服务节点都没有出现,应先处理 ACDB、IFS、启动和权限;若 Android graph 已成功但 QXDM 出现 TDM underrun,应把重点移到 ADSP/AFE/DMA,暂缓修改 Android 文件路径。
13.3 每次改动都要留下四类记录
- 输入:源码/配置/ACDB/固件的版本、时间戳和摘要。
- 动作:具体命令、构建入口、清理范围、刷写方式和重启方式。
- 观察:stdout、slog2、logcat、QXDM、文件大小、frame 数、设备节点。
- 结论:已验证、已观察、推测、待验证,以及下一步如何证伪。
可直接复用的实验记录模板:
text
实验编号:<id>
目标假设:<one falsifiable hypothesis>
输入版本:<acdb/qnx/android/adsp/build summary>
测试媒体:<rate/channels/width/format/frames>
动作命令:<commands>
软件证据:<graph/session/service/log>
DSP/硬件证据:<clock/tdm/dma/gpio/waveform>
结果:<pass/fail/partial>
推翻条件:<what would disprove the hypothesis>
后续动作:<next experiment>
13.4 常见的"成功假象"
| 表面现象 | 实际可能只说明 |
|---|---|
RC=0 |
进程没有主动退出失败 |
| graph/session success | 软件图对象已创建 |
| clock ID 有值 | 有请求或配置记录,不一定频率和生命周期正确 |
| QUTS 文件持续增长 | 某条采集链在写文件,不一定包含目标 SSID |
| Android GSL FE 有字节 | 上游写入完成,不一定到达 TDM |
| QNX 服务启动 | 服务能加载部分配置,不一定每个 endpoint 都匹配 |
| 回退版本仍有问题 | 不能证明新版本是唯一根因 |
| 物理端暂时有声音 | 不能证明长时间吞吐、静音控制和异常恢复都正确 |
13.5 用 A/B 和负向测试缩小结论
A/B 测试要一次只改变一个主要变量:ACDB、驱动、固件、图拓扑、输入文件或构建基线。若同时替换 ACDB 和 ADSP,就算结果改善,也不能确定是哪一项起作用。
负向测试尤其适合验证依赖关系:移走 ACDB 看服务是否消失;使用故意错误的 graph tag 看是否出现预期的 endpoint 错误;用短文件和长文件分别观察 buffer 行为;关闭一条日志 mask 确认采集链是否真的覆盖目标模块。负向测试完成后必须恢复文件、权限和配置,并重新做一次正向验证。
13.6 跨日报的推理演化
这批记录中最具复用性的内容是几条逐步收敛的推理链:
- ACDB 链 :动态替换 →
/mnt权限与重启 → 移走文件的负向验证 → QNX 与 Android 路径分化 → 端点 tag 版本差异。 - GPIO 链:直接替换方案验证失败 → 采用只增不删的演进策略 → TLMM 与 LPASS 分区 → 自定义 GPIO 工具的命令解析和权限边界。
- TDM0 链 :重复 module → 图拓扑修复 → Splitter overrun/drop → Mixing/Demuxing 局部改善 → clock ID 生命周期修复 → GSL FE
-131仍待解释。 - 构建链:宿主依赖缺失 → QNX IFS/占位文件 → QSSI/Kernel/Vendor 分层 → DSP 固件替换 → Meta 打包和板端验证。
- 日志链:补丁应用 → 静态库未重链 → clean install → QUTS GUI/API 分裂 → 采集结论保留边界。
- 恢复链:USB 9008 枚举 → 区分 QDSS/EDL → fastboot 中断 → 按设备状态确定恢复动作,不根据设备名称做判断。
14. 阶段性结论与后续工作
车载 Audio 的问题会穿过多个进程、虚拟机、编译系统和硬件时序。当前阶段形成的工程习惯可以概括为:
- 先画路径,再看日志;
- 先确认输入格式和版本,再改参数;
- 先确认文件进入产物,再确认产物进入刷机包;
- 先把 graph/session、GSL FE、CSD2/GPR、ADSP/AFE、TDM/DMA 分开验证;
- 用负向测试证明依赖关系,用 A/B 测试隔离变量;
- 把失败路径和未完成验证保留下来;
- 把"软件链路成立"和"物理输出成立"明确分开。
截至本文覆盖的阶段,已经形成了虚拟化 Audio 架构、构建交付链、ACDB 动态加载和多层日志排查的技术记录。ACDB 负向验证、部分 Android 上游链路、QNX 构建问题和若干故障定位路径已有证据支持。TDM0 下游物理输出、BT/HFP 完整回归、SPL/QUTS API 采集、ADSP/CDSP 完整符号化等工作仍需继续。
后续验收需要同时满足:输入可复现,构建可追溯,镜像可确认,板端版本可回读,软件和 DSP 证据一致,时钟与 TDM 参数匹配,长时间数据没有异常丢失,并且在真实外部设备上完成独立的功能验证。达到这些条件前,本文应继续作为阶段性记录使用,并随实验结果更新。
15. 术语速查
| 缩写 | 含义 |
|---|---|
| ACDB | Audio Calibration Database,音频校准数据库 |
| QWSP | QACT/校准工程工作区文件,通常与 ACDB 配套 |
| AGM | Audio Graph Manager,Android 侧音频图管理层 |
| GSL FE/BE | Graph Service Layer 的前端/后端路径 |
| CSD2 | QNX/跨虚拟机侧的音频服务与设备配置通路 |
| GPR | DSP Graphite Packet Router,图模块间消息通路 |
| ADSP/CDSP | 音频 DSP/计算 DSP 固件域 |
| AFE | Audio Front End,DSP 侧硬件端点与数据通路 |
| TDM | Time Division Multiplexing,时分复用接口 |
| PCM | Pulse Code Modulation,音频数据接口/格式统称 |
| MCLK/BCLK | 主时钟/位时钟 |
| GKV/CKV/TKV | 图、容器/模块以及最终转换链中的键值集合 |
| IFS | QNX Image Filesystem,启动镜像文件系统 |
| GKI | Generic Kernel Image,通用内核镜像 |
| QSSI | Qualcomm System Software Image,Android 通用系统层 |
| HFP | Hands-Free Profile,蓝牙免提协议 |
| QXDM/QUTS | 设备诊断日志采集与分析工具链 |