← 返回课程

Feature定制

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

第 26 章:Feature 定制与扩展


本章导读

Camera 的架构是分层的。OEM 想加功能(比如美颜),可以在不同层面动手:

这一章的重点是帮你搞清楚这三个层面分别是什么、分别怎么改。我们会用美颜这个具体例子演示层次 1(加 ChiNode),因为这是最常用的定制方式;然后用高通官方例子 HDRDemo 演示层次 3(新增 Feature),让你理解 Feature 的真正用途。


26.1 必须理解的分层架构

在动手之前,你必须先搞清楚 Qualcomm CAMX/CHI 的两层 pipeline 结构。这是整个定制体系的基石。

📊 打开图表/api/course-files/camera-fullstack/documents/diagrams/26_two_layer_architecture.png — 两层 Pipeline 架构总览

26.1.1 两层 pipeline,两个不同的世界

┌─────────────────────────────────────────────────────────────┐
│  第 1 层(上层):Feature Graph —— "功能模块图"               │
│                                                             │
│  [RealTime Feature] → [Bayer2YUV Feature] → [JPEG Feature]  │
│         │                    │                    │         │
│     一个 Feature 是        一个 Feature 是       一个 Feature│
│     一个"逻辑容器"         一个"逻辑容器"        是"逻辑容器"│
└─────────────────────────────┬───────────────────────────────┘
                              │ 每个 Feature 内部都包含:
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  第 2 层(下层):CamX Pipeline —— "真正的 ISP 处理管线"      │
│                                                             │
│  例如 RealTime Feature 内部:                                 │
│  [Sensor] → [IFE] → [IPE] → [CVP] → [Display]              │
│      ↑         ↑        ↑       ↑                           │
│   图像传感器  前端处理  图像引擎 计算机视觉                    │
│              (Raw)     (YUV)   (DSP加速)                     │
│                                                             │
│  这些才是真正干活的硬件/软件处理单元,叫做 ChiNode             │
└─────────────────────────────────────────────────────────────┘

核心观点:当你听说"在 pipeline 里加一个 node"时,指的通常是第 2 层——在 CamX Pipeline 中加一个 ChiNode。而 Feature Graph 层的"Feature"更像是一个装有 pipeline 的容器。

26.1.2 一个更准确的类比:计算机主板

CAMX 概念 类比 说明
Usecase 你决定"用电脑做一件事" 预览、录像、拍照——使用场景
Feature Graph 主板上插了哪些板卡及连接方式 哪些 Feature 参与、怎么连
Feature 一块独立的板卡(显卡、声卡) 一个处理模块,内部有自己的电路
CamX Pipeline 板卡上的电路走线 Feature 内部的 Node 连接图
ChiNode 一个芯片(GPU 核、DSP 核) 真正的计算单元

所以:

26.1.3 为什么会有两层?——从 Feature Graph 到 Hardware Node

CAMX 这样设计是因为:

  1. 第 1 层(Feature Graph)解决"功能组合"问题

    • 预览时:只需要 [RealTime] 把数据推到 display
    • 拍照时:需要 [RealTime] → [B2Y] → [JPEG] 把 sensor raw 转为 JPEG
    • HDR 拍照时:需要 [RealTime] → [HDR] → [B2Y] → [JPEG]
    • Feature Graph 让你灵活拼装功能模块,不用每种组合写一套代码
  2. 第 2 层(CamX Pipeline + ChiNode)解决"真正的计算"问题

    • ISP 有 IFE(处理 Bayer raw)、IPE(处理 YUV)、CVP(计算机视觉加速)
    • 美颜算法可以放在 CVP 上跑 DSP 加速,也可以放在 IPE 里做 tuning
    • 这些都是 CamX Pipeline 内部的 ChiNode 做的事

总结:Feature Graph 定"谁来干",CamX Pipeline 定"怎么干"。

26.1.4 那为什么还要有 Feature 这一层?——有 ChiNode 不就够了吗?

这是一个很好的问题。既然 ChiNode 才是真正干活的,那为什么不直接往 Pipeline 里加 Node,还要多一层 Feature?

原因在于:ChiNode 只管一帧数据怎么处理,但 Camera 的处理流程远不止"处理一帧数据"。Feature 负责的是 ChiNode 搞不定的那些"脏活累活"。

Feature 管的四件事

职责 举例 ChiNode 能做吗?
① Session 生命周期管理 什么时候创建 CamX Session、什么时候销毁。Session 对应 ISP 硬件资源——不能随便创建,也不能一直占着 ❌ ChiNode 只是一个处理单元,没有 Session 的概念
② Pipeline 运行时选型 RealTime Feature 内部有 6 种 Pipeline 变体(ZSL 预览/非 ZSL 预览/带录像/不带录像/第三摄像头…),运行时根据 stream 配置选一个最合适的 ❌ ChiNode 是固定的,不会根据 stream 配置切换
③ 请求处理流程编排 收到一个 Capture Request 后,先等依赖的 buffer 到齐,然后送进 Pipeline 处理,处理完通知下游。这些调度逻辑都在 Feature 里 ❌ ChiNode 只管自己这步,不管上下游协调
④ 多 Pipeline 并发协调 有的 Feature(如双摄融合)需要同时管理 2 个 Pipeline,还要同步它们的帧率、做 buffer 对齐 ❌ ChiNode 属于一个 Pipeline,跨 Pipeline 的事管不了

一个具体例子说明 Feature 和 ChiNode 的分工

App 打开相机预览

Feature(RealTime 做的事):
  1. 看 stream 配置——有 preview + video 两个 stream
  2. 从 6 种 Pipeline 变体里选出 "ZSLPreviewRawYUVEIS"(因为带预览+录像+EIS)
  3. 创建 CamX Session,把这个 Pipeline 传进去
  4. 收到 Preview Request 后,等 ISP 的 buffer 就绪,送进 Pipeline
  5. 处理完通知 display 输出

ChiNode(Pipeline 里的 IPE 做的事):
  1. 收到输入 buffer(来自 IFE 的 RAW/RDI 数据)
  2. 做颜色转换、缩放、锐化
  3. 输出 YUV buffer 给下游
  * 它不知道自己在被预览还是录像,不管什么时候创建 Session,不关心几个 stream

所以 Feature = Manager(管理人、资源、流程),ChiNode = Worker(只管干好手里的活)。

如果没有 Feature 这一层会怎样?

假设把所有逻辑都堆在 Usecase 里,直接操作 Pipeline 和 ChiNode:

// 没有 Feature 的假想代码——Usecase 里所有细节都要自己管
void StillCaptureUsecase::ProcessRequest(Request* req) {
    // 自己创建 Session
    m_pSession = CamxSession::Create(..., req->streamConfig);
    // 自己选 Pipeline
    ChiPipeline* pPipeline = SelectPipeline(req->captureIntent, req->sceneMode);
    // 自己把 Node 串起来
    pPipeline->AddNode(myBeautyNode, pPipeline->IPE, Position::After);
    // 自己等 buffer
    WaitForBuffer(req->inputBuffer);
    // 自己送 Pipeline 处理
    pPipeline->Submit(req->inputBuffer, req->outputBuffer);
    // 处理完自己通知 framework
    NotifyResult(req);
}

有了 Feature 后:

// BeautyNode 只管处理
void BeautyNode_ProcessRequest(CHINODEPROCESSREQUESTINFO* pInfo) {
    MyBeautyAlgorithm(pInfo->pInputBuffers[0], pInfo->pOutputBuffers[0]);
}

// Feature 管流程
void RealTimeFeature::ProcessRequest(FRO* pFRO) {
    // Feature 基类 ChiFeature2Base 已经把 Session 创建好了、
    // Pipeline 选好了、buffer 依赖等好了
    OnExecuteProcessRequest(pFRO);  // → 最终走到 BeautyNode_ProcessRequest
}

核心区别一句话:ChiNode 管的是"这帧怎么算",Feature 管的是"整个请求怎么走"——Session 创建、Pipeline 选型、Buffer 调度、Metadata 传递。Feature 把上层流程管理中那些跟 ChiNode 无关的复杂性封装掉了。如果只用 ChiNode,那所有这些管理逻辑都得你自己写。


26.2 选择机制:一帧照片的旅程

理解了分层,我们再回头看整个流程:

📊 打开图表/api/course-files/camera-fullstack/documents/diagrams/26_graph_selector_flow.png — Feature Graph Selector 选择流程

App 打开相机 → Usecase 创建
                   │
                   ▼
         Feature Graph Selector 根据 stream 配置
         选出可用的 Feature Graph 列表
                   │
                   ▼
         App 下发 Capture Request
                   │
                   ▼
         Selector 根据 request 中的 metadata:
           - captureIntent    (PREVIEW / STILL_CAPTURE / ...)
           - sceneMode        (HDR / NIGHT / PORTRAIT / ...)
           - noiseReduction   (OFF / HQ / ...)
           - vendorTag        (MFNR / Burst / ...)
                   │
                   ▼
         选择匹配的 Feature Graph
                   │
                   ▼
         Feature Graph 里的每个 Feature 创建 CamX Session
                   │
                   ▼
         Session 里创建 CamX Pipeline
                   │
                   ▼
         Pipeline 中的 ChiNode 们开始处理
         (IFE → IPE → CVP → ...)

所以回答一个常见问题:正常拍照时,路径是 Usecase → Feature Graph Selector → Feature Graph → Feature → CamX Pipeline → ChiNode。不是 Usecase 直接处理,也不是"默认跑到 feature"——而是 Selector 根据 metadata 动态选择最合适的 Feature Graph。

26.2.1 选择表的实例

以 xxx 平台为例,在 chifeature2graphselectoroem.cpp 中:

// 当 App 拍一张普通照片时(StillCapture + 普通场景 + 任何降噪模式)
// Selector 会匹配到这张图:
{ RTBayer2YUVJPEGFeatureGraphDescriptor.pFeatureGraphName,
    SINGLE_CAMERA,
    { ControlCaptureIntentStillCapture,
      ControlCaptureIntentVideoSnapshot,
      ControlCaptureIntentZeroShutterLag,
      ControlCaptureIntentManual },           // ← 这些 Capture Intent
    { ControlSceneModeDisabled,
      ControlSceneModeFacePriority,
      ... },                                  // ← 这些 Scene Mode
    { noiseReductionmodeAll },                // ← 任何降噪模式
    { 0 },
    0
},

匹配到的图是:[RealTime] → [Bayer2YUV] → [JPEG]


26.3 定制的四个层次

📊 打开图表/api/course-files/camera-fullstack/documents/diagrams/26_feature_custom_flow.png — 四种定制层次及使用频率

层次 改动位置 典型场景 难度
1. 加 ChiNode CamX Pipeline 内部 美颜、滤镜、HDR 合成等逐帧算法(本章重点) ⭐⭐⭐
2. 改 Feature Graph Feature Graph 配置 重新拼装已有 Feature(不写新代码)
3. 新增 Feature Feature2 代码 复杂多帧算法(SuperNight、MFSR) ⭐⭐⭐⭐
4. 新增 Usecase Usecase 代码 全新工作模式(VR、超级慢动作) ⭐⭐⭐⭐⭐

实际项目中:层次 1 和 2 覆盖了 90% 以上的 OEM 定制需求。层次 3 只用于复杂多帧算法,层次 4 极少用到。


26.4 实战:添加"美颜"处理

下面用两种方式对比,让你感受它们的区别:方式一(ChiNode)用美颜做例子,方式二(Feature)用高通官方 HDRDemo 做例子。

26.4.1 方式一(推荐):在 Pipeline 中添加自定义 ChiNode

📊 打开图表/api/course-files/camera-fullstack/documents/diagrams/26_feature_graph.png — ChiNode 方式:在 Pipeline 中插入 Beauty Node

这是最常用、最高效的方式,适合美颜这种逐帧图像处理算法。

设计思路

美颜算法通常工作在 YUV 空间(磨皮、美白),所以在 IPE(输出 YUV)之后、display/编码之前最合适。在 RealTime Feature 的 CamX Pipeline 中,典型的节点顺序是:

修改前(一个拍照帧的硬件处理路径):
  [Sensor] → [IFE] → [IPE] → [CVP] → [Display/JPEG]
     raw       raw     YUV      直通

修改后(插入美颜 node):
  [Sensor] → [IFE] → [IPE] → [Beauty] → [Display/JPEG]
     raw       raw     YUV     美颜YUV

这个 [Beauty] 就是一个自定义 ChiNode。它可以运行在:

添加自定义 ChiNode 有两个步骤:先实现 Node,再串到 Pipeline 拓扑里。下面分开说。

Step 1: 写自定义 Node——实现 ChiNodeEntry 入口

高通为自定义 Node 定义了标准接口(chi-cdk/api/node/chinode.h)。每个自定义 Node 是一个独立的 .so 动态库,通过导出 ChiNodeEntry() 函数向 Chi driver 注册自己。

ChiNodeEntry() 是 Chi driver 加载 Node 时调用的入口。Node 在这里填充 CHINODECALLBACKS 结构体,告诉 Chi driver "我能干什么、怎么调用我":

// 路径: chi-cdk/oem/qcom/node/beauty/chinodebeauty.cpp

// Chi driver 加载 Node .so 后,首先调用这个入口函数
// Node 在这里填充自己的回调函数指针
CDK_VISIBILITY_PUBLIC VOID ChiNodeEntry(CHINODECALLBACKS* pNodeCallbacks) {
    pNodeCallbacks->pGetCapabilities    = BeautyNode_GetCaps;
    pNodeCallbacks->pCreate             = BeautyNode_Create;
    pNodeCallbacks->pDestroy            = BeautyNode_Destroy;
    pNodeCallbacks->pQueryBufferInfo    = BeautyNode_QueryBufferInfo;
    pNodeCallbacks->pSetBufferInfo      = BeautyNode_SetBufferInfo;
    pNodeCallbacks->pProcessRequest     = BeautyNode_ProcessRequest;
    pNodeCallbacks->pChiNodeSetNodeInterface = BeautyNode_SetNodeInterface;
    pNodeCallbacks->pQueryMetadataPublishList = BeautyNode_QueryMetadataPublishList;
}

各个回调的职责如下:

回调 作用
pGetCapabilities 返回 Node 的能力(支持哪些格式、缩放等)
pCreate 创建 Node 实例,分配资源
pDestroy 销毁 Node 实例,释放资源
pQueryBufferInfo 告诉框架 Node 的输入输出 buffer 需求(宽高、格式等)
pSetBufferInfo 框架把实际 buffer 信息告诉 Node
pProcessRequest 每帧处理入口——这里放你的美颜算法
pChiNodeSetNodeInterface 框架把 ChiNodeInterface 传给 Node,Node 通过它回调框架
pQueryMetadataPublishList 返回 Node 会发布的 metadata tag 列表

核心的处理回调 pProcessRequest 的实现:

// 每帧美颜处理
CDKResult BeautyNode_ProcessRequest(CHINODEPROCESSREQUESTINFO* pRequestInfo) {
    // 从请求信息中获取输入/输出 buffer
    // 输入:来自 IPE 的 YUV 图像
    // 输出:处理后的 YUV 图像,送往 display 或编码器
    ChiBufferInfo* pInputBuffer  = &pRequestInfo->pInputBuffers[0];
    ChiBufferInfo* pOutputBuffer = &pRequestInfo->pOutputBuffers[0];

    // 执行美颜算法(磨皮、美白等)
    BeautyAlgorithm_Process(pInputBuffer, pOutputBuffer);

    // 处理完成,通知 Chi driver
    pRequestInfo->pNodeInterface->pProcessRequestDone(pRequestInfo->hNodeSession,
                                                       pRequestInfo->pFrameInfo);
    return CDKResultSuccess;
}

编译后,生成的 .so(如 libchinodebeauty.so)会放到设备上的 /vendor/lib64/camera/components/ 目录。Chi driver 启动时扫描该目录,按需加载 Node。

注意区分CHINODECALLBACKS(Node 实现给 Chi 的回调)和 ChiNodeInterface(Chi 提供给 Node 的回调接口)是两个方向相反的结构体。Node 通过 ChiNodeInterface 回调框架(如报告处理完成 pProcessRequestDone),框架通过 CHINODECALLBACKS 回调 Node(如 pProcessRequest)。


Step 2: 串到 Pipeline 里——把 Node 加入 Pipeline 拓扑

Node 实现好了,还要告诉 CamX "这个 Node 应该放在 Pipeline 的哪个位置、跟谁连"。这一步叫串 Node(wiring)。

现代 Qualcomm Camera 平台的 Pipeline 拓扑通过 XML usecase component 文件描述,放在:

chi-cdk/oem/qcom/topology/usecase-components/usecases/<UsecaseName>/camx<UsecaseName>.xml

在 XML 中,你需要做两件事:

① 在 Pipeline 的 Node 列表中加入 Beauty Node

<!-- 在 <Pipeline> 段的 <Nodes> 中加入新节点 -->
<Node>
    <NodeId>65537</NodeId>          <!-- OEM 自定义 ID,避开高通保留 ID -->
    <NodeInstanceId>0</NodeInstanceId>
    <NodeName>Beauty</NodeName>
    <Property>
        <PropertyId>2</PropertyId>   <!-- NodePropertyProfileId -->
        <PropertyValue>1</PropertyValue>
    </Property>
</Node>

高通保留的 Node ID 范围:

Node ID 硬件模块
65536 IFE(Image Front-End)
65538 IPE(Image Processing Engine)
65540 FD(Face Detection)
65543 CVP(Computer Vision Processor)
≥ 65544 或 < 65536 OEM 自定义 Node

② 在 Link 表中把 Beauty Node 串联到 IPE 之后、CVP 之前

<!-- Link 定义:数据从哪个 Node 的哪个 Port 流到哪个 Node 的哪个 Port -->

<!-- IPE 的输出 Port 8(YUV)→ Beauty 的输入 Port 0 -->
<Link>
    <SrcNodeId>65538</SrcNodeId>
    <SrcNodeInstanceId>0</SrcNodeInstanceId>
    <SrcPortId>8</SrcPortId>
    <DstNodeId>65537</DstNodeId>
    <DstNodeInstanceId>0</DstNodeInstanceId>
    <DstPortId>0</DstPortId>
</Link>

<!-- Beauty 的输出 Port 0 → CVP 的输入 Port 0 -->
<Link>
    <SrcNodeId>65537</SrcNodeId>
    <SrcNodeInstanceId>0</SrcNodeInstanceId>
    <SrcPortId>0</SrcPortId>
    <DstNodeId>65543</DstNodeId>
    <DstNodeInstanceId>0</DstNodeInstanceId>
    <DstPortId>0</DstPortId>
</Link>

XML 配置完成后,通过高通的 usecase composer 工具(tools/usecaseconverter/)生成最终的 g_pipelines.h。这个过程叫 topology compilation,具体的编译方法见下面 Step 3。

补充:高通 ISV 指南(80-p9301-20_c_camera_quickstart_guide_for_isv)中也明确说明了自定义 node 的命名规范为 com.<vendor>.node.<algorithm>.so,放在 components/ 目录下。


Step 3: 部署与验证

# 编译 ChiNode 动态库
cd chi-cdk/oem/qcom/node/beauty/
mmm .

# 重新编译 topology XML(如果改了 XML 的话)
cd chi-cdk/oem/qcom/topology/usecase-components/
mmm .

# push 到 components 目录(Chi driver 从这里动态加载 Node)
adb root && adb remount
adb push libchinodebeauty.so /vendor/lib64/camera/components/
adb reboot

# 验证
adb logcat | grep -E "beauty|Beauty|ChiNode|components"

方式一的优缺点

优点 缺点
无需额外的 buffer 拷贝,延迟最低 需要理解 CamX Pipeline 拓扑
可以利用硬件加速(DSP/CVP) 调试复杂,出问题可能影响整个 pipeline
和 ISP tuning 无缝配合 对 pipeline 的依赖较强,移植性低

26.4.2 方式二(备选):新增 Feature——高通官方例子 HDRDemo

📊 打开图表/api/course-files/camera-fullstack/documents/diagrams/26_hdrdemo_feature.png — HDRDemo Feature 结构与 ChiNode 对比

注意:美颜这种逐帧 ISP 算法用 ChiNode 就够了,不需要 Feature。本节对应层次 3(新增 Feature),用高通官方 Feature2 文档中的 HDRDemo 来演示什么场景下才需要 Feature。

什么场景需要新增 Feature?——高通文档中的 HDRDemo 例子

高通 Feature2 入门文档(kba-210324010650)中带了一个完整的 Feature 实现示例:HDRDemo

这个 Feature 做的事情:

输入:同一场景、不同曝光的 8 帧 P010 图像
                ↓
        用自己的 Pipeline(SWMFMergeYuv)
        做多帧对齐 → 融合 → 色调映射
                ↓
输出:1 帧 HDR 融合后的 YUV 图像

为什么这个不能用 ChiNode 做?

需求 ChiNode 的方式 Feature(HDRDemo)的方式
输入 每帧来一次 pProcessRequest,一次处理一帧 先等 8 帧全部到齐,再一起提交给算法
处理 Pipeline 只能利用所在 Pipeline 的已有节点拓扑 有自己的 Pipeline SWMFMergeYuv,专为融合设计
帧同步 没有"等 N 帧"的机制,每帧独立 Feature 基类的 ChiFeature2RequestFlowType::Type1 天然支持多帧依赖

关键区别:ChiNode 是"插在别人的 Pipeline 里干活"的,用的是宿主 Pipeline 的数据流。Feature 是"我自己起一个 Pipeline",有完全独立的 Session、Pipeline、Node 拓扑。

HDRDemo 的实现要点(摘自高通官方文档)

HDRDemo 的 Descriptor 定义了 8 个输入端口 + 1 个输出端口:

// 8 帧 P010 输入端口
CDK_VISIBILITY_PUBLIC extern const ChiFeature2PortDescriptor HDRDemoInputPortDescriptors[] = {
    { {0, 0, 0, ChiFeature2PortDirectionType::ExternalInput,
       ChiFeature2PortType::ImageBuffer}, "YUV_In0", &HDRDemoInputTargetDescriptors[0] },
    { {0, 0, 1, ChiFeature2PortDirectionType::InternalInput,
       ChiFeature2PortType::ImageBuffer}, "YUV_In1", &HDRDemoInputTargetDescriptors[0] },
    // ... 直到 YUV_In7
};

// 1 帧输出端口
CDK_VISIBILITY_PUBLIC extern const ChiFeature2PortDescriptor HDRDemoOutputPortDescriptors[] = {
    { {0, 0, 0, ChiFeature2PortDirectionType::ExternalOutput,
       ChiFeature2PortType::ImageBuffer}, "YUV_Out", &HDRDemoOutputTargetDescriptors[0] },
};

Pipeline 用的是高通的多帧融合 pipeline SWMFMergeYuv

static const ChiFeature2PipelineDescriptor HDRDemoPipelineDescriptors[] = {
    { 0, 0, "SWMFMergeYuv", ChiFeature2PipelineType::CHI,
      CHX_ARRAY_SIZE(HDRDemoInputPortDescriptors), &HDRDemoInputPortDescriptors[0],
      CHX_ARRAY_SIZE(HDRDemoOutputPortDescriptors), &HDRDemoOutputPortDescriptors[0] },
};

Feature 基类提供了三种 request flow,HDRDemo 选择 Type1——先等第一帧(external input),再等剩下的 7 帧(internal input),全部到齐后一起提交:

ChiFeature2RequestFlowType ChiFeature2HDRDemo::OnSelectFlowToExecuteRequest(
    ChiFeature2RequestObject* pRequestObject) const {
    return ChiFeature2RequestFlowType::Type1;
}

这个逻辑 ChiNode 做不到——ChiNode 每帧来一次 pProcessRequest,没有"等齐 N 帧再处理"的机制。

方式二的关键特征总结

Feature Graph 中的位置:
  [RealTime] → [Bayer2YUV] → [HDRDemo Feature] → [JPEG]
                                  ↓
                         独立的 Session
                         独立的 Pipeline: SWMFMergeYuv
                         8 帧 P010 输入 → 1 帧 HDR YUV 输出

"新增一个 Feature"本质上是:在 Feature Graph 中新增一个独立的处理节点,它有自己的 Session/Pipeline,可以处理 ChiNode 无法处理的场景(多帧输入、独立 Pipeline 拓扑、Session 级生命周期管理)。

方式二的优缺点

优点 缺点
有独立的 Session/Pipeline,拓扑不受限 比 ChiNode 多了跨 Feature 的 buffer 拷贝
适合多帧处理(等 N 帧 → 处理 → 输出) 延迟更高(多一次 Feature 调度)
Feature Graph 层面可插拔 需要更多的内存和资源(独立 Session)
不需要修改 CamX Pipeline XML 拓扑 代码量比 ChiNode 大得多

26.4.3 两种方式怎么选?

场景 推荐方式 原因
逐帧处理 ISP Pipeline 内的 YUV/Raw 数据(美颜、滤镜等) 方式一:ChiNode 延迟低,利用硬件加速,你之前做的方式就是对的
算法有外部依赖(第三方库、CPU 密集) 方式一:ChiNode 可以用 CVP/DSP 卸载
多帧输入→单帧输出(HDR 融合、多帧降噪) 方式二:Feature 需要管理多帧 buffer 和请求队列,ChiNode 是逐帧的
需要独立 Pipeline 拓扑(不是 ISP 路径上的处理) 方式二:Feature Feature 内部可以定义自己的 Session/Pipeline
不想碰 CamX Pipeline 拓扑 方式二:Feature XML pipeline 配置不改,只在 Feature Graph 层面加节点

经验之谈:对于大多数 ISP Pipeline 路径上的图像处理算法(包括美颜),方式一(ChiNode)是更自然的选择——你之前做过的方式就是对的。方式二(Feature)只在算法需要独立 Pipeline 拓扑(如多帧融合、超夜)时才需要考虑。


26.5 编译和部署

两种方式的部署略有不同:

方式一(ChiNode)的部署

# 编译 ChiNode 动态库
cd chi-cdk/oem/qcom/node/beauty/
mmm .

# 推送到 components 目录(ChiNode 的默认加载路径)
adb root && adb remount
adb push libchinodebeauty.so /vendor/lib64/camera/components/
adb reboot

# 验证
adb logcat | grep -E "beauty|Beauty|ChiNode"

方式二(Feature)的部署

# 编译 Feature 动态库
cd chi-cdk/oem/qcom/feature2/hdrdemo/
mmm .

# 推送到 camera 目录
adb root && adb remount
adb push libfeature2hdrdemo.so /vendor/lib64/camera/
adb reboot

# 验证
adb logcat | grep -E "HDRDemo|FeatureGraph|GraphSelector"

26.6 常见误区

误区 1:"在 pipeline 里加节点"和"加 Feature"是一回事

不是。"pipeline 里的节点"指的是 CamX Pipeline 中的 ChiNode(硬件/软件处理单元),而"Feature"是 Feature2 框架中的一个逻辑模块。Feature 内部可以承载一整个 CamX Pipeline(包含多个 ChiNode)。

误区 2:Feature = 一个算法

不是。一个 Feature 可以包含多个 Session/Pipeline/Node,它更像一个"处理单元容器"。比如 RealTime Feature 内部有 6 种不同的 pipeline 配置(ZSL 预览、非 ZSL 拍照、第三摄像头等),每种 pipeline 包含 IFE、IPE、CVP 等多个 ChiNode。

误区 3:新增处理功能一定要改核心代码

不是。ChiNode 接口(chinode.h)是厂商可以实现的扩展接口,Feature2 的 Descriptor 放在 chi-cdk/oem/ 目录下。这些都是高通特意留给 OEM 的定制层。

误区 4:Feature Graph Selector 的选择是写死的

不是。选择表以 captureIntent + sceneMode + noiseReduction + vendorTag 的组合为条件,每帧都会根据 metadata 重新选择。同一台手机上,拍普通照片和拍 HDR 照片走的是不同的 Feature Graph。

误区 5:OEM 加算法只能二选一(ChiNode vs Feature)

可以结合:一个 Feature 内部的 Pipeline 中可以含有多个 ChiNode。Feature 和 ChiNode 不是互斥的,而是不同层次上的概念


26.7 本章总结

CAMX 架构的两层 pipeline:
  第 1 层(Feature Graph):逻辑功能模块的连接图
  第 2 层(CamX Pipeline):真正的硬件/软件处理节点(ChiNode)拓扑

定制层次(从易到难):
  层次 1: 在 CamX Pipeline 中加 ChiNode  ← 本章重点,适合逐帧算法
  层次 2: 改 Feature Graph 连接          ← 不写代码,重新组合
  层次 3: 新增 Feature                  ← 适合复杂/多帧算法
  层次 4: 新增 Usecase                  ← 极少需要

两种方式对比:
  方式一(ChiNode):
    适用:美颜等逐帧 ISP 算法
    做法:实现 ChiNodeEntry → 在 XML 中串到 Pipeline 里
    优点:低延迟、硬件加速
    缺点:需要理解 CamX Pipeline 拓扑

  方式二(Feature):
    适用:多帧融合(如 HDRDemo,8帧→1帧)
    做法:派生 ChiFeature2Base → 定义 Descriptor → 注册到 Selector
    优点:独立 Session/Pipeline,多帧管理
    缺点:额外 buffer 拷贝,延迟更高

选择机制一句话:
  Usecase 定"模式",Feature Graph Selector 根据 metadata
  动态选"图",图里的每个 Feature 创建 CamX Pipeline,
  Pipeline 里的 ChiNode 真正干活。

关键源码路径:
  ChiNode 接口:       chi-cdk/api/node/chinode.h
  CamX Pipeline:      chi-cdk/core/lib/common/g_pipelines.h
  Feature2:           chi-cdk/core/chifeature2/
  选择器:             chi-cdk/oem/qcom/feature2/chifeature2graphselector/
  OEM 选择表:         chifeature2graphselectoroem.cpp + 平台目录
  OEM Graph:          chifeature2oemgraphdescriptors.cpp
  XML Topology:       chi-cdk/oem/qcom/topology/usecase-components/

附录 :ChiNode 与 Feature 的核心差异速查

维度 ChiNode Feature (ChiFeature2Base)
层次 CamX Pipeline 内部 Feature2 Graph 节点
粒度 单个处理单元(硬件/软件) 包含 Session/Pipeline 的容器
入口函数 ChiNodeEntry() 填充 CHINODECALLBACKS CreateFeature() + 派生虚函数
处理粒度 逐帧 pProcessRequest 支持多帧依赖的 request flow
输入 每帧 1 次回调 可配置多帧输入(Type1/Type2)
自己的 Pipeline ❌ 使用宿主 Pipeline ✅ 独立 Session + Pipeline
buffer 管理 框架管理 框架管理或自行管理
适用场景 简单的逐帧处理 复杂的多帧/多 Session 处理
所在目录 chi-cdk/oem/qcom/node/ chi-cdk/oem/qcom/feature2/