Qcom Camx全栈开发

Android Camera 全栈课程(Qcom Camx):第 2 章:Android Camera 系统全景

本章导读

想象这个场景:

你打开手机上的相机 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 算法——都在一起。

问题

  • OEM 想加一个自定义功能(如美颜)→ 需要修改 mm-camera 核心代码

  • 高通升级版本 → OEM 要重新移植自己的修改

  • 集成第三方算法 → 很难,因为没有标准插件接口

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/ = 硬件配置

学完这章之后,你应该能回答这几个问题:

  1. 手上遇到一个 Camera 问题,logcat 没有输出——你应该先怀疑哪一层?

  2. Treble 架构前后的崩溃恢复路径有什么不同?

  3. 为什么 HAL3 只定义了 6 个接口?

  4. mm-camera 和 CamX-CHI 的本质区别是什么?

  5. 打开你的源码,找到 camxsession.h 和 chxsession.h,看它们分别在哪个目录?


动手验证

  1. 执行 adb shell ps -A | grep camera,列出所有相机相关进程。对照本章的进程架构图,确认每个进程的角色。

  2. 执行 adb shell dumpsys media.camera | head -50,确认:

    • 有几个摄像头

    • 每个摄像头的 facing 和 orientation

    • 是否支持 RAW 输出

  3. 在源码目录中搜索 HAL_MODULE_INFO_SYM,找到它的定义位置。确认它与本章描述的一致。

常见误解

  • "HAL3 有很多接口" — HAL3 只有 6 个核心接口(initialize / configure_streams / construct_default_request_settings / process_capture_request / dump / flush)。CamX 内部的几百个接口是高通自己的事,不暴露给 Google。


下章预告:第 3 章「开发环境与调试基础」——logcat、dumpsys、setprop 三板斧。

地图看完了,接下来要教会你在这张地图上走路的工具。

(课程相关内容,会陆续在下面专栏更新,感兴趣的同学可以扫码订阅)



欢迎扫码关注「小驰行动派」公众号 10 年Camera开发 | Camera技术干货 | 行业洞察 | Camera实战分享
小驰行动派公众号 扫一扫关注
分享到: 复制链接
← 上一篇 Android Camera 全栈课程(Qcom Camx):第 3 章:开发环境与调试基础 下一篇 → Android Camera 全栈课程(Qcom Camx):第 1 章:相机成像基础

相关文章

推荐课程

想系统学习 Camera 开发?看看这些课程

评论 (0)

暂无评论,快来抢沙发吧