从虚拟化音频架构到板端故障定位:车载 Audio 阶段性工程实践整理

从虚拟化音频架构到板端故障定位:车载 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_driveramfs2_libcsd2gsl_bemcm_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.xmlbackend_conf.xmlcard-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"的反查方法:

  1. audio_servicegsl_open、AGM 或 gsl_fe 日志中提取 GKV。
  2. 根据键族区分 stream、instance、device、device processing 和 VMID。
  3. 在源码或设备侧的 usecaseKvManager.xml 中按十六进制 key 搜索名称。
  4. 用名称组合回到 QACT/ACDB 中定位对应 use case、subgraph 和端点。
  5. 修改前先保留原 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_masklane_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 音频块。记录中的平台地址分别属于两个独立寄存器区域,不能把一种工具的"默认值"解释为另一块已经生效。

验证路径是:

  1. 用通用 GPIO 工具检查 SoC 编号范围的存在性和基本状态。
  2. 检查 audio_service 是否执行了 vaudio_gpio_config
  3. 通过 QNX /dev/gpio/legacydevctl 接口读取 LPASS 模块。
  4. 使用逻辑模块号(例如 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 工作区/辅助参数文件

验收至少包含四个维度:

  1. 文件内容与本地源一致;
  2. 两个文件成对更新,不能只替换其中一个;
  3. 板端属主、权限和挂载位置正确;
  4. 重启后服务、图、会话和目标功能都能按预期运行。

只比较文件大小不够,建议在主机端用 md5sumsha256sum,在板端没有这些工具时用 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

板端没有 rebootmd5sum 或完整 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 makeqccdumpifs QNX 可执行文件、库、IFS
Android QSSI 通用系统与 Soong/Kati/Ninja 依赖 build/envsetup.shlunchm system.img
Android Kernel GKI、设备树、内核模块 Bazel/Kleaf、Clang Image.ko.dtb/.dtbo
Vendor/OEM 厂商服务、驱动、非 HLOS 打包 Soong、build.py vendor.imgNON-HLOS.bin
Meta Build 汇总各分区和刷机包 build.py super.imgrawprogram_unsparse*.xml
DSP/固件 ADSP、CDSP、AOP、Boot、TZ SCons、build_variant.pybuildex.py adsp.mbncdsp*.mbnaop.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 会截断真正的错误上下文;构建日志应完整保存,展示时再筛选。
  • 临时放在 /tmpfilepp 或链接在重启后可能消失,应把依赖放在可追溯目录并在构建前检查。

直接构建中曾出现 fileppdtclibxml2.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

sourcelunchmbash build.sh 必须在同一 shell 中完成;与 QNX 的"脚本可能带警告但仍需继续"不同,这里通常应让 && 在环境初始化失败时及时停止。若 bcc_strip_attr 在中段失败,常见原因是主机输出目录中缺少 libncurses.so.5libtinfo.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.hlibdrmMinimalfs 等头文件或库找不到。适配时可以在目标 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_ntoaarch64legcc_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.7ruamel.yaml==0.17.21swig,并设置:

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 构建的失败原因经常来自磁盘或内存,编译错误属于另一类问题。建议把问题拆成三类:

  1. 删除可重建中间目录前,先保留失败日志、配置和最后一次成功产物摘要。
  2. 为编译机配置足够的交换空间,例如受控的 32 GiB swap,并根据实际内存调整并发度。
  3. 合并多套树时使用 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 用户态进程现场 vmlinuxcrash、GDB、capstone/pyelftools
ADSP/CDSP SSR dump DSP 线程、堆栈、硬件服务状态 hansei.pyadspcrashman.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,重定向位置和文件生命周期会改变。完整原始日志应保留,grepSelect-String 或脚本只用于本地分析,不应覆盖原始证据。

9.3 退出码是弱证据

agmplayaudio_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 失败或服务缺失。处理原则是:

  1. 先备份整棵配置树;
  2. 将确认为空且用于占位的文件移到单独目录;
  3. 保留尚未匹配的占位文件,不要批量删除;
  4. 重新生成 IFS 并用 dumpifs 检查内容;
  5. 按服务启动顺序验证,不能只看 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 diffgit apply --check --reversegit 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 的标签同时带入了 0x47930x4AB8 两个同类模块,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。标准 agmplayagmcapagmhostless 并没有提供可靠的任意 key 注入能力;该版本静默忽略命令行中的 -stxkv-btpkv。程序返回 0,但目标 BT 图没有建立。

这类问题的正确验证方法是:

  1. 从资源管理器配置确认 key 的来源和 use case;
  2. 检查 XML → TKV → ACDB 的生成链;
  3. 从 AGM/PAL 日志确认 session、graph、endpoint;
  4. 用 QXDM 检查 DSP 侧端口、格式和错误;
  5. 最后才判断音频是否从物理 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 每次改动都要留下四类记录

  1. 输入:源码/配置/ACDB/固件的版本、时间戳和摘要。
  2. 动作:具体命令、构建入口、清理范围、刷写方式和重启方式。
  3. 观察:stdout、slog2、logcat、QXDM、文件大小、frame 数、设备节点。
  4. 结论:已验证、已观察、推测、待验证,以及下一步如何证伪。

可直接复用的实验记录模板:

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 设备诊断日志采集与分析工具链
相关推荐
爱学习的小白柏1 小时前
【AI问数技术】多Agent协同架构:查询规划/SQL生成/洞察分析/报告生成
java·网络·人工智能·windows·sql·架构·llama
Larcher2 小时前
SDD 项目开发步骤与提示词
人工智能·设计模式·架构
ZGIAI3 小时前
ZGI 父子分块:连接检索片段与完整上下文
人工智能·架构
ZGIAI3 小时前
ZGI 文件解析:知识入库前的质量门
人工智能·架构
martindelophy4 小时前
使用 Timeline Studio 制作 AI 视频二创:从高光分析、音乐卡点到 15 秒成片
人工智能·音视频
空堂与归5 小时前
实时目标检测怎么做到又快又准?YOLOv12架构深度解析
人工智能·yolo·目标检测·计算机视觉·架构
Larcher6 小时前
从 SDD 到可交付:我如何用规范驱动 AI 做出一个 Chrome 翻译插件
前端·后端·架构
tachibana26 小时前
RAGAS 指标解读
数据库·人工智能·算法·机器学习·架构·大模型·llm
Python私教7 小时前
创业团队做管理系统,别先堆页面:我把 14 个问题做成了需求门禁
后端·python·架构