← 返回课程

CamX-CHI架构

Camera全栈开发(Qcom Camx) 第 12 / 30 节

第 10 章:CamX-CHI 架构全景


本章导读

从这章开始,进入这门课程最核心的部分——CamX-CHI。

前面 9 章,你知道了 Camera 怎么成像(第 1 章)、App 怎么调用(第 46 章)、CameraService 怎么中转(第 7 章)、Metadata 和 Buffer 怎么传递(第 89 章)。现在终于要进入 HAL 层内部了。

这章的任务是建立全景认知。不是讲细节(细节在第 11~17 章逐一展开),而是让你先看清:CamX 和 CHI 是什么关系?Usecase、Session、Pipeline、Node 这些概念各是什么角色?它们之间怎么协作?

读完这章,你不一定能写出 CamX 代码,但你一定能看懂 CamX 的日志、理解 CamX 的目录结构、知道一个 Bug 要去哪一层查。


10.1 先看一个请求的旅程

不急着讲概念。先看一个具体的场景:

你打开相机 App,按下拍照按钮。

这个动作,在 CamX-CHI 内部经历了什么?

① App 的 capture() → Binder → CameraService → HIDL → Provider
     ↓
② HAL3 入口: camera3_device_t.process_capture_request()
     ↓
③ CHI 层: Usecase 收到请求 → "现在是拍照场景,用 UsecaseDefault"
     ↓
④ CHI → CamX: Usecase 把请求丢给 Session
     ↓
⑤ Session 内部: "我有两个 Pipeline,预览 Pipeline 和 JPEG Pipeline。
                拍照请求要给两个 Pipeline 各发一份。"
     ↓
⑥ Pipeline 内部: "我先检查各 Node 的依赖。IFE 需要 AE 参数...
                 都有了?好,开始处理。"
     ↓
⑦ Node: IFE / IPE / BPS / JPEG 各司其职——预览走 IFE→IPE(跳过 BPS),拍照/快照才经 BPS 做完整 demosaic
     ↓
⑧ CSL: 把处理请求通过 ioctl 发给 Kernel → ISP 硬件
     ↓
⑨ ISP 处理完 → 中断 → 结果原路返回 → App 收到照片

这 9 步中,Usecase、Session、Pipeline、Node 各司其职。 这一章就是要讲清——它们各自管什么、怎么协作。


10.2 一张图看清 CamX 和 CHI 是什么关系

CamX-CHI 的完整架构分四层(注意这不是 Android 系统分层,而是 HAL 内部的模块划分):

┌──────────────────────────────────────────────────┐
│                                                 │
│  ① HAL3 入口层(camxhal3entry / camxhaldevice)  │  ← Google 标准接口
│                                                 │
├──────────────────────────────────────────────────┤
│                                                 │
│  ② CHI 层(chi-cdk/)                            │  ← 策略层:决定"做什么"
│     Usecase、Feature2、CHI Session               │     OEM 主要在这里写代码
│                                                 │
├──────────────────────────────────────────────────┤
│                                                 │
│  ③ CamX 核心层(camx/src/core/)                 │  ← 机制层:执行"怎么做"
│     Session、Pipeline、Node、DependencyUnit      │     高通维护,一般不修改
│                                                 │
├──────────────────────────────────────────────────┤
│                                                 │
│  ④ HWL/SWL + CSL + Kernel/HW                     │  ← 底层:真正干活的硬件
│     IFE/BPS/IPE Node、EIS/SWMF Node、CSLSubmit   │
│                                                 │
└──────────────────────────────────────────────────┘

💡 关键认知:② 和 ③ 是最重要的两层。CHI 是"策略层"——决定当前要做什么(预览还是拍照?用哪个 Usecase?走哪些 Pipeline?)。CamX 是"机制层"——不管策略,只管把 Pipeline 跑起来、把 HW 驱动起来。

为什么这样分?因为 OEM 需要定制的是策略(比如自己的美颜算法、自己的 HDR 方式),而不是机制(Pipeline 怎么启动、ISP 怎么控制)。 分开了,OEM 只需要改 CHI 层的 Usecase/Feature2,不动 CamX 核心——这就是"两层设计"的初衷。


10.3 五个核心概念——Usecase、Session、Pipeline、Node、Feature

本节是整章最重要的一节。 下面这 5 个概念贯穿 CamX-CHI 的全部内容。先统一讲清楚,后面每一章用到的都对照这里。

概念 1:Usecase(用户场景)

Usecase 回答一个问题:"当前要干什么?"

例子:
  预览          → UsecaseDefault
  拍照          → UsecaseDefault(带 JPEG Stream)
  HDR 拍照      → UsecaseHDR
  多帧降噪      → UsecaseMFNR
  录像          → UsecaseVideo
  高帧率录像    → UsecaseHFR

Usecase 的决策逻辑:根据 App 的 createCaptureSession 中传入的 Surface 列表来判断。

如果 App 传入 [previewSurface] → 只是预览 → UsecaseDefault(预览模式)
如果 App 传入 [previewSurface, jpegSurface] → 预览 + 拍照 → UsecaseDefault(拍照模式)
如果 App 传入 [previewSurface, videoSurface] → 录像 → UsecaseVideo

UsecaseSelector 就是这个决策引擎——它检查 Stream 的格式/分辨率/帧率,匹配合适的 Usecase。

Usecase 管什么

概念 2:Session(会话)

Session 回答一个问题:"谁来执行这个场景?"

Session = 管理一组 Pipeline 的容器

一个 Usecase 通常有 1~2 个 Session:
  主摄工作时 → 1 个 Session(内建 1~2 个 Pipeline)
  多摄同时工作 → 可能需要 2 个 Session(主摄 + 广角各一个)

Session 管什么

CHI Session vs CamX Session

// CHI 层有 CHI Session —— 给 OEM 用的接口
// chi-cdk/core/chiframework/chxsession.h
class Session {
    CamX::Session* m_pCamxSession;   // 内部包了一层 CamX Session
};

// CamX 核心层有 CamX Session —— 真正的实现
// camx/src/core/camxsession.h
class Session {
    // 真正的 Pipeline 管理、Request 分发、Result 汇聚在这里
};

💡 为什么要包两层?

CHI Session 是"策略包装"——它决定 Request 怎么分发、Callback 怎么调用。CamX Session 是"机制实现"——它真正管理 Pipeline 状态机。OEM 通过 CHI Session 来控制行为,不需要接触 CamX Session 的复杂细节。

概念 3:Pipeline(处理管线)

Pipeline 回答一个问题:"需要哪些工序来完成这件事?"

Pipeline = 一组 Node 连成的流水线

典型的 Preview Pipeline:
  IFE → IPE → Display   (BPS 仅用于拍照/快照路径,见下)

典型的 Snapshot/JPEG Pipeline(拍照路径):
  IFE → BPS → IPE → JPEG

Pipeline 管什么

RT Pipeline vs Offline Pipeline——这是个重要区分:

RT(Real-Time)Pipeline:      Offline Pipeline:
  预览、录像                    JPEG 编码、多帧融合
  必须实时完成(16ms/帧)      可以慢慢做
  高优先级                     低优先级
  一直开着                     只在使用时激活

为什么需要 Offline Pipeline? 因为 JPEG 编码可能耗时 80ms,如果塞在预览 Pipeline 里,预览就会卡。单独分出来,预览流畅 + JPEG 慢慢编。

概念 4:Node(节点)

Node 回答一个问题:"流水线上的这一站,具体干什么?"

Node = Pipeline 中最小的处理单元

HW Node(硬件加速):          SW Node(CPU 上运行):
  IFE Node — RAW 预处理        EIS Node — 电子防抖
  BPS Node — Bayer 域处理      SWMF Node — 多帧融合
  IPE Node — YUV 域处理
  JPEG Node — JPEG 编码

每个 Node 都有

概念 5:Feature(功能组合)

Feature 回答一个问题:"能不能把多个功能像积木一样组合?"

传统方式的局限:
  拍照 + HDR → 写一个 UsecaseHDR
  拍照 + 虚化 → 写一个 UsecaseBokeh
  拍照 + HDR + 虚化 → 再写一个?
  ↑ 这不行——组合爆炸

Feature2 的解决:
  Feature A = Preview(预览积木)
  Feature B = HDR Merge(HDR 积木)
  Feature C = Bokeh Depth(虚化积木)
  Feature D = JPEG(编码积木)

  Graph 1: A + D           = 普通拍照
  Graph 2: A + B + D       = HDR 拍照
  Graph 3: A + C + D       = 人像模式
  Graph 4: A + B + C + D   = HDR 人像模式
  ↑ 用 XML 配置组合,不用写代码

10.4 五个概念的层次关系——一图胜千言

                    ┌─────────────────────────────┐
                    │        Usecase               │  ← "做什么"
                    │  (UsecaseDefault/UsecaseHDR) │
                    └──────────┬──────────────────┘
                               │ 管理 1~2 个
                    ┌──────────▼──────────────────┐
                    │        Session               │  ← "谁来做"
                    │  (CHI Session + CamX Session)│
                    └──────────┬──────────────────┘
                               │ 管理 1~N 个
                    ┌──────────▼──────────────────┐
                    │        Pipeline              │  ← "怎么做"
                    │  (RT / Offline, 8 状态机)    │
                    └──────────┬──────────────────┘
                               │ 管理 1~N 个
                    ┌──────────▼──────────────────┐
                    │        Node                  │  ← "具体谁做"
                    │  (HW: IFE/BPS/IPE, SW: EIS)  │
                    └──────────┬──────────────────┘
                               │ 通过 Port/Link 连接
                    ┌──────────▼──────────────────┐
                    │     Port / Link              │  ← "怎么连"
                    │  (输入/输出端口 + 连接关系)   │
                    └─────────────────────────────┘

  Feature2 = 对 Usecase 层的增强
    让功能的组合更灵活,通过 XML 配置而非硬编码

一张表总结

概念 一句话 谁维护 哪章细讲
Usecase 决定当前干什么 OEM(CHI 层) 第 12 章
Session 管理一组 Pipeline CamX 核心 第 13 章
Pipeline 一条处理流水线,有 8 状态状态机 CamX 核心 第 13 章
Node 流水线上的一台机器 HWL/SWL 第 14 章
Feature2 积木式功能组合 OEM(CHI 层) 第 16 章

10.5 CHI ExtensionModule——CHI 是怎么被加载的

CHI 层的代码不是编译时链接到 CamX 的。它是运行时动态加载的:

CameraProvider 进程启动
  → dlopen("camera.qcom.so")             // 加载 CamX
    → HAL_MODULE_INFO_SYM 初始化
      → ChiContext::Create()
        → 扫描 /vendor/lib64/camera/oem/{platform}/*chi*.*
          → 没找到 → 回退 /vendor/lib64/camera/qti/{platform}/*chi*.*
            → OsUtils::LibMap() = dlopen 加载
              → dlsym("chi_hal_override_entry")
                → 获取 CHIAppCallbacks 函数表
                  → CHI 层接管 Request 处理

这个函数的调用链

// camx/src/core/hal/camxhal3module.cpp
funcCHIHALOverrideEntry = (CHIHALOverrideEntry)
    OsUtils::LibGetAddr(m_hChiOverrideModuleHandle,
                        "chi_hal_override_entry");
funcCHIHALOverrideEntry(&m_ChiAppCallbacks);
// m_ChiAppCallbacks 中包含了:
//   chi_override_process_request → Usecase::SubmitChiRequest()
//   chi_override_flush           → Usecase::Flush()
//   chi_override_dump            → 输出调试信息

Request 在两层之间的流转

HAL3: process_capture_request()
  → m_ChiAppCallbacks.chi_override_process_request()
    → CHI ExtensionModule
      → Usecase::SubmitChiRequest()
        → Session::ProcessCaptureRequest()
          → CamX 核心处理

💡 为什么动态加载?

OEM 可以单独编译 CHI override .so,push 到 /vendor/lib64/camera/oem/{platform}/ 下,重启 Provider 进程就能生效,不重新编译 CamX。这就是"OEM 定制不需要改核心代码"的技术基础。


10.6 源码目录结构

了解了概念之后,你知道去哪儿找代码:

camx_chi_root/
├── camx/src/core/               ← CamX 核心
│   ├── hal/                        HAL3 入口、camera3_device_t
│   ├── camxsession.h/cpp           Session 实现
│   ├── camxpipeline.h/cpp          Pipeline 状态机
│   ├── camxnode.h/cpp              Node + DependencyUnit
│   └── chi/                        ChiContext
│
├── camx/src/hwl/                 ← 硬件 Node(IFE/BPS/IPE/JPEG)
├── camx/src/swl/                 ← 软件 Node(EIS/SWMF/Stats)
├── camx/src/csl/                 ← 与 Kernel 的通信层
│
├── chi-cdk/core/
│   ├── chiframework/               CHI 框架(Usecase、CHI Session)
│   ├── chifeature2/                Feature2 框架
│   └── chiusecase/                 Usecase 实现
│
└── chi-cdk/oem/qcom/             ← OEM 定制示例

10.7 本章总结

CamX-CHI 最核心的认知:

五个概念,从上到下:
  Usecase   → "做什么"   → CHI 层
  Session   → "谁来做"   → CHI 包装 + CamX 核心
  Pipeline  → "怎么做"   → CamX 核心(8 状态状态机)
  Node      → "具体谁做" → HWL/SWL
  Feature2  → "积木组合" → CHI 层的增强

两层设计:
  CHI(策略层)= Usecase + Feature2 → OEM 定制
  CamX(机制层)= Session + Pipeline + Node → 高通维护

关键:
  CHI 层代码运行时 dlopen 加载 → OEM 可独立编译推送

动手验证

  1. 在你的设备上,执行:

    adb shell ls /vendor/lib64/camera/oem/
    adb shell ls /vendor/lib64/camera/qti/
    

    查看你的设备上 CHI override .so 是否存在。.so 的文件名里是否包含 "chi"?

  2. 打开相机,在 logcat 中搜索 Usecase 选择日志:

    adb logcat | grep -i "usecase\|UsecaseSelector"
    

    当前用的是哪个 Usecase?

  3. 打开你的源码,在 chi-cdk/core/ 下找到 chxusecase.hchxsession.h。确认它们的路径和本章描述一致。

常见误解