← 返回课程

Usecase详解

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

第 12 章:Usecase — 场景调度中心


本章导读

当 App 调用 createCaptureSession() 时,Camera HAL 内部的第一个决策发生在 UsecaseSelector——它决定了"这次用什么方案来完成 App 的需求"。

Usecase 是 CHI 层中最靠近 App 的一层。理解它,你就理解了为什么打开相机 App 有时走预览路线、有时走 HDR 路线、为什么拍照时预览不会卡。


12.1 Usecase 是什么

12.1.1 直观理解

Usecase 翻译成"用例"或"场景"——它回答了"当前要做什么"这个问题。

App 传入的 Surface:              Usecase 做出的决定:
[previewSurface]                 → 预览模式(只用 RT Pipeline)
[previewSurface, jpegSurface]    → 拍照模式(RT + Offline)
[previewSurface, videoSurface]   → 录像模式

源码定义在 chi-cdk/core/chiframework/chxusecase.h。Usecase 的职责分为四个阶段:

① 选择 — UsecaseSelector 根据 Stream 配置决定用哪个 Usecase
② 创建 — 创建 Session + Pipeline + 分配 Buffer
③ 调度 — 接收 App 的 capture_request,分发给正确的 Pipeline
④ 汇聚 — 收集各 Pipeline 的 result,回调给 App

12.1.2 Usecase 的继承体系

Usecase(基类, chi-cdk/core/chiframework/chxusecase.h)
  │
  ├── UsecaseDefault       → 标准预览 + 拍照
  │     ├── 只传 previewSurface → 纯预览(1 个 Pipeline)
  │     └── 传 preview + jpeg  → 预览 + 拍照(2 个 Pipeline)
  │
  ├── UsecaseHDR           → HDR 多帧融合拍照
  ├── UsecaseMFNR          → 多帧降噪
  ├── UsecaseVideo         → 标准录像(30fps)
  ├── UsecaseHFR           → 高帧率录像(>60fps)
  ├── UsecaseRaw           → RAW 数据输出
  └── [OEM Custom]

12.2 从一个具体的场景看 Usecase 的选择过程

12.2.1 场景:打开相机 App(仅预览)

App: createCaptureSession([previewSurface])
  ↓
HAL: configure_streams(1 个 Stream, format=PRIVATE)
  ↓
UsecaseSelector 开始工作:
  ① 检查 Stream 数量 = 1
  ② 检查 format = PRIVATE(预览用)
  ③ 没有 JPEG、没有 RAW、没有视频
  ↓
决策结果: UsecaseDefault(预览模式)
  → 创建 1 个 RT Pipeline: IFE → IPE → Display
  → Offline Pipeline: 不需要

12.2.2 场景:用户点击拍照按钮

App 重建 Session: createCaptureSession([previewSurface, jpegSurface])
  ↓
HAL: configure_streams(2 个 Stream: PRIVATE + BLOB)
  ↓
UsecaseSelector 开始工作:
  ① Stream 数量 = 2
  ② 第二个 Stream 的 format = BLOB → 这是 JPEG 输出
  ↓
决策结果: UsecaseDefault(拍照模式)
  → RT Pipeline:    IFE → IPE → Display        (预览用,一直开)
  → Offline Pipeline: IFE → BPS → IPE → JPEG Node(拍照时激活)

12.2.3 场景:暗光下拍照(MFNR 开启)

App: createCaptureSession([previewSurface, jpegSurface])
  加上 vendor tag: enableMFNR=true
  ↓
UsecaseSelector 检查:
  ① 有 JPEG 输出
  ② 暗光 + MFNR 开启
  ↓
决策结果: UsecaseMFNR
  → RT Pipeline:  IFE → IPE → Display
  → MFNR 处理需要: 连拍 N 帧 + 对齐 + 融合 → JPEG
  → 需要多帧 Pipeline

12.3 UsecaseSelector——决策引擎

### 12.3.1 源码位置

文件: chi-cdk/core/chiusecase/chxusecaseutils.cpp (4166 行)
关键函数: UsecaseSelector::DefaultMatchingUsecase() (第 1564 行)
匹配函数: UsecaseMatches() → IsMatchingUsecase()

12.3.2 选择机制——表驱动匹配

UsecaseSelector 不使用 if-else 分支。它使用表驱动匹配(table-driven matching)

// 伪代码:DefaultMatchingUsecase 的核心逻辑
ChiUsecase* UsecaseSelector::DefaultMatchingUsecase(
    camera3_stream_configuration_t* pStreamConfig)
{
    ChiUsecase* pSelected = NULL;

    // Step 1: 特殊场景优先检查
    // 如果是 secure mode → 走 secure usecase
    // 如果是 GPU 旋转 → 走 GPU usecase
    // 如果是 HFR(高帧率)→ 走 HFR usecase

    // Step 2: 根据 Stream 数量从对应表中选择
    for (int i = 0; i < tableSize; i++) {
        if (UsecaseMatches(pStreamConfig, &candidateTable[i])) {
            pSelected = &candidateTable[i];
            break;
        }
    }

    return pSelected;
}

12.3.3 匹配顺序——优先级决定了一切

每个 Usecase 表(如 Usescases2Target[])中的 Usecase 是按优先级排好的。
匹配时从第一个开始依次尝试,第一个匹配成功的当选。

假设 2 Stream 的场景,优先级顺序大致是:

① Secure Usecase    (如果开启了安全模式)
② HFR Usecase       (如果帧率 > 60)
③ GPU Usecase       (如果开启了 GPU 处理)
④ MFNR Usecase      (暗光 + MFNR 开关)
⑤ HDR Usecase       (高对比场景 + HDR 开关)
⑥ UsecaseDefault    (普通预览 + 拍照)
⑦ RAW Usecase       (如果输出 RAW)

尝试的顺序决定了: 当多个 Usecase 都"可能匹配"时,谁优先。

12.3.4 IsMatchingUsecase——它到底在检查什么

每个 Usecase 预先定义了一组 Targets(目标 Surface 的描述)。
IsMatchingUsecase 检查 App 的 Stream 列表和 Usecase 的 Target 描述是否匹配:

① Stream 数量是否一致?
② 每个 Stream 的格式(PRIVATE / BLOB / RAW / YUV)是否匹配?
③ 每个 Stream 的用途(预览 / 拍照 / 录像)是否匹配?
④ 分辨率是否在 Usecase 支持的范围内?

如果有 vendor tag / operation_mode 的额外约束,也在这里检查。

12.4 RT Pipeline vs Offline Pipeline——核心区分

这是 Usecase 中最重要的概念,决定了拍照时预览会不会卡。

12.4.1 它们是什么

RT (Real-Time) Pipeline = 实时管线,用于预览和录像
  - 持续工作,从 Sensor 开始到 Display 结束
  - 严格截止时间:每帧必须按时完成(比如 33ms @30fps)
  - 超时后果:掉帧 → 预览卡顿
  - 全程 STREAM_ON

Offline Pipeline = 离线管线,用于拍照后处理
  - 只在需要时激活,比如 JPEG 编码
  - 没有严格截止时间(多花几 ms 没关系)
  - 平时 STREAM_OFF,拍照时 STREAM_ON

12.4.2 它们是怎么配合的

预览时(仅 RT 工作):
  Sensor → IFE → IPE → Display          [RT Pipeline, STREAM_ON]
  (Offline Pipeline 不存在或 STREAM_OFF)

拍照时(RT + Offline 同时工作):
  Sensor → IFE → IPE → Display            [RT Pipeline, 持续预览]
                       ↘ JPEG Node       [Offline Pipeline, 异步编码]
  → 预览不受影响!

12.4.3 为什么需要两种 Pipeline

如果 JPEG 编码在 RT Pipeline 中:
  Sensor → IFE → IPE → JPEG → Display
                               ↑
                       JPEG 编码时预览被阻塞 → 卡一帧

分离后:
  Sensor → IFE → IPE → Display            [RT,流畅预览]
                       ↘ JPEG Node        [Offline,慢慢编]

12.5 slowJpegMode——ISP 带宽不足时

当 ISP 负载过高,无法同时支持 Preview + JPEG 双路输出时,系统进入降级模式:

慢速 JPEG 模式的表现:
  - Offline Pipeline 无法获得 ISP 资源
  - JPEG 编码塞回 RT Pipeline
  - ZSL 不可用
  - 拍照时预览可能卡顿

12.6 本章总结

Usecase = 场景调度器
UsecaseSelector = 表驱动匹配引擎

RT Pipeline: 实时工作(预览/录像),严格截止时间
Offline Pipeline: 按需工作(JPEG 编码),无截止时间

匹配过程: Stream 配置 → 查表 → 逐个匹配 → 第一个匹配的当选

动手验证

  1. 打开相机仅预览,执行 adb shell dumpsys media.camera | grep -A 5 "Pipeline",看有几个 Pipeline。
  2. 拍一张照片,再次执行同样命令,看是否多了一个 Pipeline(Offline)。