从这章开始,进入这门课程最核心的部分——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 要去哪一层查。
不急着讲概念。先看一个具体的场景:
你打开相机 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 各司其职。 这一章就是要讲清——它们各自管什么、怎么协作。

┌──────────────────────────────────────────────────┐
│ │
│ ① 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 核心——这就是"两层设计"的初衷。
本节是整章最重要的一节。 下面这 5 个概念贯穿 CamX-CHI 的全部内容。先统一讲清楚,后面每一章用到的都对照这里。
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 管什么:
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 的复杂细节。
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 慢慢编。
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 都有:
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 配置组合,不用写代码
┌─────────────────────────────┐
│ 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 章 |
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 定制不需要改核心代码"的技术基础。
了解了概念之后,你知道去哪儿找代码:
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 定制示例
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 可独立编译推送
在你的设备上,执行:
adb shell ls /vendor/lib64/camera/oem/
adb shell ls /vendor/lib64/camera/qti/
查看你的设备上 CHI override .so 是否存在。.so 的文件名里是否包含 "chi"?
打开相机,在 logcat 中搜索 Usecase 选择日志:
adb logcat | grep -i "usecase\|UsecaseSelector"
当前用的是哪个 Usecase?
打开你的源码,在 chi-cdk/core/ 下找到 chxusecase.h 和 chxsession.h。确认它们的路径和本章描述一致。