本章导读
想象这个场景:
你打开手机上的相机 App,按下快门。从这一刻开始,一个"数据包"穿过了 5 层架构(App → Framework → HAL → Kernel → Hardware)、4 个进程边界、经过数百个函数调用,最后到达 ISP 硬件。硬件处理完之后,结果再沿着原路返回。
整个旅程在 100ms 内完成(这个得视实际平台性能来看)。
这听起来像是工程设计上的一个奇迹。但要把这个奇迹变成日常工作,你需要一张"地图"——知道你在哪一层、上下层是谁、怎么通信、出了问题该去哪层查。
这一章就是这张地图。
2.1 五层架构:从 App 到硬件

先看完整分层:
纯文本
App 层 ┌─────────────────────────────────────┐
│ Camera App / 第三方 App │ ← 你写的或系统自带的 App
└─────────────────────────────────────┘
↑ Binder IPC
Framework 层 ┌─────────────────────────────────────┐
│ CameraService / CameraProvider │ ← Android 系统服务
└─────────────────────────────────────┘
↑ HIDL / AIDL
HAL 层 ┌─────────────────────────────────────┐
│ HAL3 Interface + CamX + CHI │ ← 相机 HAL 实现
│ ┌───────────────────────────────┐ │
│ │ HAL3 入口 (camxhal3entry) │ │
│ │ CHI CDK (Usecase/Feature2) │ │
│ │ CamX Core (Session/Pipeline) │ │
│ │ HWL / SWL (ISP 封装/算法) │ │
│ │ CSL (与 Kernel 的接口) │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
↑ CSL ioctl
Kernel 层 ┌─────────────────────────────────────┐
│ msm_camera / CSIPHY / CSID / Sensor │ ← 高通驱动的内核模块
└─────────────────────────────────────┘
↑ MIPI / I2C / GPIO
硬件层 ┌─────────────────────────────────────┐
│ Sensor / ISP / MIPI CSI │ ← 物理芯片和总线
└─────────────────────────────────────┘
一句话说清每层干什么
纯文本
App → "帮我拍张照"
Framework → "好的,我帮你叫 HAL"
HAL → "告诉 ISP 开始曝光"
Kernel → "配置寄存器,启动 MIPI"
硬件 → "感光 → ADC → 传数据"
越往下越靠近硬件,越往上越靠近用户。
每一层向下屏蔽细节,向上提供服务。这就是"分层架构"的核心思想。
💡 "分层"的代价
分层的代价是性能——每多一层,就多一次函数调用、可能还多一次进程切换。读到这里你可能会问:为什么不能把 HAL 直接做到 Kernel 里?那不是更快吗?
因为解耦的收益大于性能损失。如果 Camera 直接操作 Kernel,每次修改 Sensor 驱动都要重新编译整个内核。分层之后,App 开发者不需要懂 ISP、HAL 开发者不需要懂硬件寄存器、Kernel 开发者只需要关注自己的驱动接口。
而且,这个性能损失在大多数场景下可以忽略——一次 app→HAL→kernel 的调用链大约耗时几百微秒,相比一帧的 33ms(30fps)来说,只占 1~2%。
2.2 进程架构——谁在哪个进程里

分层的实现落地到操作系统上,变成了进程边界。每一层可能运行在不同的进程中。
纯文本
App 进程 system_server 进程 CameraProvider 进程
┌─────────┐ Binder ┌─────────────────┐ HIDL ┌──────────────────┐
│ 系统相机 │ ←───────→ │ CameraService │ ←─────→ │ CameraProvider │
│ 微信 / 抖音│ │ │ │ │
│ │ │ DeviceClient │ │ CamX / CHI │
└─────────┘ └─────────────────┘ └──────────────────┘
│
CSL ioctl
│
Kernel
每个进程里有什么
进程 | 包含的代码 | 崩溃后 |
|---|
App 进程 | App 自身代码、Camera2 API 调用 | 仅该 App 相机不可用 |
system_server | CameraService、CameraDeviceClient、权限管理 | 所有相机不可用 → 系统重启 |
CameraProvider | CamX、CHI CDK、HAL 实现 | HAL 层重启,Service 可重建 |
进程间的通信方式
纯文本
App ↔ CameraService: Binder IPC(ICameraService.aidl)
CameraService ↔ Provider: HIDL(Android 8~12) → AIDL(Android 13+)
Provider → HAL: 函数调用(同一进程内 dlopen)
HAL → Kernel: ioctl(CSL 层封装)
💡 一个信号可以告诉你这是哪一层的问题
在实际调试中,进程边界是你定位问题的"分水岭":
logcat 中 CameraService 报错 → 通常是 App 或 Framework 层问题
logcat 中 [CamX] 或 [CHI] 报错 → HAL 层问题
dmesg 报错 → Kernel 驱动层问题
App 无响应但其他 App 相机正常 → 该 App 的问题
所有 App 相机都用不了 → Service / Provider / Kernel 层问题
2.3 Treble 架构——最重要的架构分水岭
如果你问一个做了 10 年 Android Camera 的人"什么变化让你印象最深?"十有八九的回答是:Treble。
2.3.1 Treble 之前(Android 7-)
纯文本
system_server 进程
├── CameraService(Java)
└── Camera HAL(C++) ← 在同一个进程里!
└── dlopen camera.qcom.so
致命的耦合:
Camera HAL 和 CameraService 在同一个进程里运行。这意味着一件事:HAL 中的一个空指针崩溃 → 整个 system_server 进程挂掉 → 手机重启。
纯文本
用户场景:
你在用相机拍照 → HAL 闪退 → system_server 崩溃 → 手机重启
→ 你的未保存的数据可能丢失
→ OEM 接到大量投诉
2.3.2 Treble 之后(Android 8+)
纯文本
system_server 进程 CameraProvider 进程(独立!)
┌─────────────────────┐ ┌──────────────────────┐
│ CameraService │ HIDL │ CameraProvider@2.4 │
│ │ ←────→ │ │
│ ProviderManager │ │ camera.qcom.so │
└─────────────────────┘ └──────────────────────┘
│
dlopen
│
CamX / CHI
核心变化:HAL 运行在独立进程中。
纯文本
现在 HAL 崩溃时:
CameraProvider 进程死亡
→ CameraService 检测到 "binder death notification"
→ 标记该 Provider 为不可用
→ 如果有 App 正在用相机,App 收到 onError()
→ 系统可以重启 Provider 进程
→ 但 system_server 不受影响!
→ **手机不会重启!**
💡 这个变化对 OEM 意味着什么?
在 Treble 之前,OEM 每一次 Camera HAL 的代码改动都"压力山大"——一个内存越界可能让手机变成砖。
Treble 之后,Camera HAL 的开发变成了"普通驱动开发"——崩溃了最多相机打不开,重启 Provider 就好,不会影响系统稳定性。
这不仅仅是技术改进,更是工程效率的飞跃。
2.3.3 从 HIDL 到 AIDL 的演进
Android 版本 | 通信协议 | Service 名 |
|---|
8.0~11 | HIDL | android.hardware.camera.provider@2.4
|
12+ | AIDL | android.hardware.camera.provider.aidl
|
纯文本
HIDL → AIDL 的变化说明了什么?
Android 在统一 IPC 机制。HIDL 是 Android 8 专门为 HAL 新发明的语言,
AIDL 是 Android 已有的 Binder IPC 语言。
多一套工具链就多一份维护负担,所以 Google 最终回到了 AIDL。
这告诉我们:**接口定义的复杂度越低,生态越健康。**
2.4 Camera API 的演化——为什么现在的设计是这样的
2.4.1 Camera1(Android 1.0~5.0)
纯文本
// Camera1 的用法——一个极简但混乱的设计
Camera camera = Camera.open(); // 打开
Camera.Parameters params = camera.getParameters(); // 获取参数
params.setPictureSize(1920, 1080); // 设置大小
camera.setParameters(params); // 设置参数(一个 Map)
camera.setPreviewDisplay(surfaceHolder); // 设预览 Surface
camera.startPreview(); // 开始预览
camera.takePicture(null, null, jpegCallback); // 拍照
问题:
setParameters 接受的是一个巨大的 Map——你传什么它都吃,不对的静默忽略
没有异步回调模型——startPreview 后你无法知道帧什么时候到
没有多摄支持——一次只能开一个 Camera
没有独立的控制/结果路径——所有的参数都在一个 Map 里混着
Camera1 的设计哲学"看起来简单,用起来痛苦"——初期开发快,后期维护难。
2.4.2 Camera2(Android 5.0+)
纯文本
// Camera2 的用法——状态机 + 异步 Pipeline
CameraManager manager = (CameraManager) getSystemService(CAMERA_SERVICE);
manager.openCamera(cameraId, stateCallback, handler);
// ↑ 异步回调,不阻塞主线程
// 创建 Session
cameraDevice.createCaptureSession(surfaces, sessionCallback, handler);
// 发起请求(异步,结果通过 callback 回来)
captureSession.setRepeatingRequest(request, captureCallback, handler);
Camera2 的三大核心设计变化:
纯文本
① 从"方法调用"变成"状态机"
Camera1: open() → startPreview() → takePicture()
Camera2: open → onOpened → createSession → onConfigured → setRepeatingRequest → onCaptureCompleted
② 从"全局参数 Map"变成"结构化 Metadata"
Camera1: camera.setParameters(params) // String→String 的大杂烩
Camera2: builder.set(CaptureRequest.CONTROL_AE_MODE, AE_MODE_ON)
// 类型安全、枚举约束、可查询
③ 从"同步"变成"异步"
Camera1: 大部分操作同步 → 容易 ANR
Camera2: 全部是异步回调 → 不会阻塞 UI 线程
2.4.3 HAL3——Camera2 的底层实现
Camera2 API 的底层 HAL 版本是 HAL3(区别于早期 HAL1)。
HAL3 的接口定义在 camera3_device_ops_t 中。从源码 camx/src/core/hal/camxhal3entry.cpp 可以看到实际注册的函数表:
纯文本
static camera3_device_ops_t g_camera3DeviceOps =
{
.initialize = CamX::initialize,
.configure_streams = CamX::configure_streams,
.construct_default_request_settings = CamX::construct_default_request_settings,
.process_capture_request = CamX::process_capture_request,
.dump = CamX::dump,
.flush = CamX::flush,
};
6 个接口分两类:
纯文本
数据流接口:
initialize() — 连接回调,启动 HAL
configure_streams() — 配置输出 Stream
construct_default_request_settings() — 生成默认 Request 模板
process_capture_request() — 核心!处理拍照/预览
管理接口:
dump() — 输出调试信息(dumpsys)
flush() — 取消所有待处理请求
💡 HAL3 的设计哲学
核心数据流只有 4 个接口,最关键的是 process_capture_request——App 的请求从这里进去,结果通过异步 callback 回来。
Google 选择"少即是多"。如果定义 30 个接口,每个都要做 CTS 兼容性测试,复杂度会大增。只定义核心接口,把灵活性和复杂度留给厂商自己权衡。
只定义 6 个核心接口 → 接口稳定、CTS 覆盖简单、厂商自由度大。
代价是:CamX 内部自己又定义了几百个内部接口(Session/Pipeline/Node/Dependency...)——但这是高通的内部事务,不暴露给 Google。
2.5 Camera HAL 的演进:mm-camera → CamX-CHI
如果你在一家使用高通平台的手机公司做 Camera 开发,你一定会听到这两个名字:mm-camera 和 CamX-CHI。
2.5.1 mm-camera 时代(中低端平台,约 2016~2018)
纯文本
mm-camera 的架构:
┌────────────────────────────────────┐
│ mm-camera(一个大的独立进程) │
│ ├── ISP 控制(直接操作寄存器) │
│ ├── Sensor 控制(I2C 通信) │
│ ├── EEPROM 读取(OTP 校准) │
│ ├── 3A 算法(AE/AWB/AF) │
│ └── JPEG 编码 │
└────────────────────────────────────┘
特点:一个进程,全部 ISP 逻辑,全部 Sensor 控制,全部 3A 算法——都在一起。
问题:
2.5.2 CamX-CHI 时代(SM8150/8250/8350,2018+)
纯文本
CamX-CHI 的架构:
┌────────────────────────────────────┐
│ CHI CDK(定制层) │
│ ├── Usecase 框架(OEM 可改) │
│ ├── Feature2 配置(XML 控制) │
│ ├── External Node(第三方算法) │
├────────────────────────────────────┤
│ CamX(核心层) │
│ ├── Session / Pipeline / Node │
│ ├── ISP 控制 │
│ ├── CSL(Kernel 接口) │
└────────────────────────────────────┘
核心变化:分成了两层——CamX(高通维护,OEM 不修改)+ CHI(OEM 定制层)。
纯文本
mm-camera → CamX-CHI 的本质变化:
mm-camera: "你要定制?改我的核心代码。"
CamX-CHI: "你要定制?在 CHI 层写插件,别碰我的核心。"
这也是 Google Treble 设计哲学在 Camera HAL 中的体现——把核心和定制分开。
2.6 你的源码地图
前面讲了一大堆理论,现在落地到你的实际代码上。拿到 CamX-CHI 源码后,你会看到 3 个顶层目录,它们对应着不同的职责:
纯文本
camx_chi_root/
│
├── camx/ ──── CamX 核心层
│ └── src/
│ ├── core/hal/ HAL3 接口(camxhal3entry.cpp / camxhaldevice.cpp)
│ ├── core/ Session / Pipeline / Node
│ ├── hwl/ ISP 硬件封装(IFE / BPS / IPE)
│ ├── swl/ 软件算法(EIS / JPEG / Stats)
│ ├── csl/ 与 Kernel 的通信层
│ └── settings/kona/ SM8250 平台配置
│
├── chi-cdk/ ──── CHI 定制层
│ ├── core/chiframework/ Usecase 框架
│ ├── core/chifeature2/ Feature2 框架
│ └── core/chiusecase/ Usecase 实现
│
└── camera-devicetree/ ──── 设备树 dtsi
你的第一道练习题:
纯文本
用 Source Insight 打开工程,在 `camx/src/core/` 下搜索下面这些关键词。
每个关键词对应一个核心组件:
class Session → 第 13 章
class Pipeline → 第 13 章
class Node → 第 14 章
DependencyUnit → 第 14 章
ChiContext → 第 11 章
UsecaseSelector → 第 12 章
ChiFeature2GraphDesc → 第 16 章
💡 阅读顺序建议
如果你是第一次接触这个源码,不要试图从头到尾读,会迷失。按照这个顺序来:
① 先读 camxhal3entry.cpp(HAL3 入口)→ 知道"请求从哪里进来"
② 再读 camxsession.h 和 camxpipeline.h(核心数据结构)→ 知道"内部长什么样"
③ 然后读 camxnode.h(DependencyUnit)→ 知道"Node 之间怎么协作"
④ 最后读 chxusecase.cpp(Usecase 流程)→ 知道"Usecase 怎么管理这一切"
不要试图一次读懂全部。先建立地图,再分块深入。
2.7 一个请求的旅程——贯穿全书的线索
把前面所有内容串起来。一次按下快门的完整旅程:
纯文本
① App 层:
你点下拍照按钮 → Camera2 API 创建 CaptureRequest
Logcat 标签: [App]
② Framework 层:
CameraService 收到请求 → 通过 HIDL 发给 CameraProvider
Logcat 标签: [CameraService]
③ HAL 层:
camera3_device_t.process_capture_request() 被调用
Logcat 标签: [CamX] [HAL]
④ OEM 定制层:
Usecase::SubmitChiRequest() → Session::ProcessCaptureRequest()
Logcat 标签: [CHI]
⑤ CamX 核心层:
Pipeline → Node → Dependency Graph 解析 → CSLSubmit()
Logcat 标签: [CamX] [CORE] [CSL]
⑥ Kernel 层:
msm_camera 驱动配置 ISP 寄存器 → 启动 MIPI 传输
Log: dmesg
⑦ 硬件层:
Sensor 开始曝光 → ISP 处理 → DMA 写回内存 → 中断通知
Log: [CamX] CSLMessageHandler (HW Done)
每一层对应的 logcat 标志(记下来,调试时有用):
纯文本
你想查的问题 看哪个 log
──────────────────────────────────────────────────
App 或 Framework 层的问题 搜 App log / dumpsys
HAL3 接口调用是否正确 [CamX] [HAL ]
CHI 层的 Usecase 决策 [CHI]
Session/Pipeline 状态 [CamX] [CORE]
CSL 提交到 Kernel [CSL]
硬件处理完成 frameMessage / CSLMessageHandler
Kernel 驱动问题 dmesg
2.8 本章总结
纯文本
这张"地图"上有这些地标
──────────────────────────────────────────────────
① Android Camera 是 5 层架构
App → Framework → HAL → OEM → Kernel → FW → HW
每层向下屏蔽细节,向上提供服务
② 4 个进程 + 3 种 IPC
App (Binder) → system_server (HIDL/AIDL) → Provider (dlopen) → HAL (ioctl) → Kernel
③ Treble 架构最重要的变化
HAL 从 system_server 中移出 → HAL 崩溃不再导致手机重启
④ HAL3 有 6 个接口
initialize, configure_streams, construct_default_request_settings,
process_capture_request, dump, flush
——少即是多
⑤ mm-camera → CamX-CHI 的演进
从"一个庞大的单体"到"核心 + 定制分离"的两层架构
⑥ 你的源码中
camx/ = 核心层,chi-cdk/ = 定制层,camera-devicetree/ = 硬件配置
学完这章之后,你应该能回答这几个问题:
手上遇到一个 Camera 问题,logcat 没有输出——你应该先怀疑哪一层?
Treble 架构前后的崩溃恢复路径有什么不同?
为什么 HAL3 只定义了 6 个接口?
mm-camera 和 CamX-CHI 的本质区别是什么?
打开你的源码,找到 camxsession.h 和 chxsession.h,看它们分别在哪个目录?
动手验证
执行 adb shell ps -A | grep camera,列出所有相机相关进程。对照本章的进程架构图,确认每个进程的角色。
执行 adb shell dumpsys media.camera | head -50,确认:
在源码目录中搜索 HAL_MODULE_INFO_SYM,找到它的定义位置。确认它与本章描述的一致。
常见误解
下章预告:第 3 章「开发环境与调试基础」——logcat、dumpsys、setprop 三板斧。
地图看完了,接下来要教会你在这张地图上走路的工具。
(课程相关内容,会陆续在下面专栏更新,感兴趣的同学可以扫码订阅)
评论 (0)