
简介本资源是一份面向Android相机驱动开发工程师与高通平台Camera框架研究者的深度技术解析文档聚焦高通Chi-CDK Feature2架构设计与运行机制解决Camera HAL层多pipeline协同调度、缓冲区高效管理及实时图像处理链路构建等核心难题。文档以PDF格式呈现共1个文件大小约1000KB内容涵盖FeatureGraph与FeatureNode建模逻辑、CHITargetBufferManager缓冲区管理策略、Request-Execute-Result三阶段处理流程、RealTime/RTBay2YUV/RTMFSRYUV实时数据流组件作用、Camx XML pipeline定义方式、FeatureInternalLink跨节点连接机制及Link状态机实现细节并辅以Simple Model图解与完整执行时序分析。目前已有8067人学习下载读者可直接掌握Feature2框架的全局架构视图、关键组件职责划分、Stage依赖配置方法及典型场景如RTBayer2YUV的端到端执行路径为定制化Camera功能开发与性能调优提供扎实理论支撑与可复用的设计参考。1. Feature2 不是“更高级的 CamX”而是把 pipeline 的控制权从 XML 静态描述里硬生生抢回来的一次架构重构你写完一个camxoverrides.xml定义好 sensor → IFE → IPE → FD → Stats 的链路编译进镜像重启生效——这曾是高通 Camera 开发者最熟悉的“确定性”。但当某天需求变成“同一帧里Preview 走 Bayer2YUV DENOISEZSL 回写走 MFSR TONEMAP而 Metadata 又要单独走一条低延迟路径”XML 就卡死了它无法在 runtime 动态拆分、复用、重组 pipeline 片段。Feature2 正是为解决这个根本矛盾而生它把原本固化在 XML 中的 pipeline 拓扑抽象成可编程的FeatureGraph把每个处理单元如 Bayer2YUV从CamX Node升级为可独立生命周期管理的FeatureNode最关键的是它让Link不再是 XML 里link srcife0 dstipe0/这种静态字符串而是一个带状态机NotVisited/Disabled/OutputPending、可被 runtime 动态遍历与裁剪的有向边。这不是语法糖升级而是把 camera pipeline 从“电路板布线”推进到“可编程逻辑阵列”阶段。理解 Feature2本质是理解高通如何用 C 对象模型在底层驱动层实现一套轻量级的、面向图像流的 DAG 调度引擎——它不替代 CamX而是架在 CamX 之上做 CamX 做不了的动态决策。2. FeatureGraph不是容器而是带状态传播能力的拓扑调度器FeatureGraph 表面看是 FeatureNode 的集合但它的核心价值远不止于此。它不存储节点实例本身而是通过全局m_linkData维护所有 Link 的元数据并赋予 Link 三种语义明确的类型ExtSrcLink上层 App 输入、ExtSinkLink上层 App 输出、InternalLinkFeatureNode 间数据流转。这种设计让 Graph 具备了两个关键能力一是状态可传播——Link 状态如 OutputPending会沿 InternalLink 向上游反向触发WalkBackFromLink自动标记依赖未满足的节点为InputResourcePending二是拓扑可裁剪——当某个 SinkPort 未被上层请求时其对应的 ExtSinkLink 置为 Disabled整条下游链路包括所有关联的 InternalLink会被递归禁用避免无谓的 buffer 分配与 pipeline 提交。这才是 Feature2 实现“按需激活”的底层机制。2.1 Link 状态机从 NotVisited 到 OutputPending 的语义跃迁Link 的状态不是简单的布尔开关而是承载了明确的调度语义NotVisited初始状态表示该 Link 尚未被任何 Request 扫描到此时pSrcGraphNode和pSinkGraphNode均不参与本次调度。Disabled显式禁用可能因上层未请求该输出端口或 FeatureNode 被整体 bypass。此时portToInputLinkIndexMap和portToOutputLinkIndexMap中对应索引被置空WalkBackFromLink会跳过此 Link。OutputPending最关键的活跃态表示该 Link 已被当前 Request 选中其pSinkGraphNode的 output port 必须产出数据且该数据将作为pSrcGraphNode的 input dependency。此时pSinkGraphNode进入ReadyToExecute状态的前置条件之一。提示OutputPending并不保证数据已就绪只表示“该 Link 被激活等待数据生产”。真正的数据就绪由ProcessResultMessage触发状态跃迁。2.2 WalkAllExtSinkLinks从输出端反向驱动的拓扑扫描引擎整个 FeatureGraph 的调度起点不是入口Sensor而是出口SinkPort。WalkAllExtSinkLinks()函数执行以下三步原子操作标记 SinkLink遍历所有ExtSinkLink若其portId匹配上层传入的m_extSinkPortIdToChiStreamMap.portId或为 Metadata 类型则置linkState OutputPending注册上游请求对每个OutputPendingLink调用ProcessInputDepsForUpstreamFeatureRequest()将requestIndex和该 Link 的portId写入pSrcGraphNode-requestTable[requestIndex]同时递增numRequestOutputs触发反向遍历调用WalkBackFromLink(pOutputLinkData)从pSrcGraphNode开始检查其所有output link是否全为Disabled若否则继续向上游递归直至找到首个CheckAllOutputLinksReadyToProcessRequest()返回 true 的节点为其创建FeatureRequestObject并启动ProcessRequest()。这段逻辑彻底颠覆了传统 pipeline 的正向流水线思维——它让调度决策从“我有什么就推什么”变成“我要什么才拉什么”。// chi_feature2_graph.cpp 关键片段 VOID ChiFeature2Graph::WalkAllExtSinkLinks( ChiFeature2UsecaseRequestObject* pUsecaseRequestObj) { for (UINT32 extSinkLinkIndex 0; extSinkLinkIndex m_numExtSinkLinks; extSinkLinkIndex) { ChiFeature2GraphLinkData* pExtSinkLinkData m_pLinkData[extSinkLinkIndex]; pExtSinkLinkData-linkState ChiFeature2GraphLinkState::Disabled; // Step 1: Mark OutputPending for matched SinkPorts if (IsSinkPortMatched(pExtSinkLinkData, pUsecaseRequestObj)) { pExtSinkLinkData-linkState ChiFeature2GraphLinkState::OutputPending; // Step 2: Register upstream request ProcessInputDepsForUpstreamFeatureRequest(pExtSinkLinkData, 0); // Step 3: Trigger backward walk WalkBackFromLink(pExtSinkLinkData); } } }这段代码后必须跟一句机制说明IsSinkPortMatched()的判定逻辑直接决定了 FeatureGraph 的“可见性边界”——只有被上层明确请求的 SinkPort 才能激活整条链路这是 Feature2 支持多路并发、ZSL 与 Preview 解耦的底层保障。ProcessInputDepsForUpstreamFeatureRequest()写入的requestTable是后续ChiFeature2RequestObject::Create()分配内存的唯一依据requestTable.size()直接决定m_numRequests而requestTable[i].size()决定第 i 个 Request 的numRequestOutputs。没有这一步整个 Graph 就是死的。2.3 FeatureNode 的 Ready 状态判定一个被严重低估的“就绪门控”FeatureNode的ReadyToExecute状态并非简单地“所有 input dependency 满足”而是基于其所有output link的状态组合判定BOOL ChiFeature2Graph::CheckAllOutputLinksReadyToProcessRequest( ChiFeature2GraphNode* pGraphNode) { BOOL isReady FALSE; UINT32 numOutputLinks pGraphNode-numOutputLinks; // 至少有一个 output link 处于 OutputPending 状态 BOOL hasOutputPending FALSE; for (UINT32 i 0; i numOutputLinks; i) { ChiFeature2GraphLinkData* pOutputLinkData GetOutputLinkData(pGraphNode, i); if (pOutputLinkData-linkState ChiFeature2GraphLinkState::OutputPending) { hasOutputPending TRUE; break; } } // 所有 output link 都不能是 NotVisited即必须被扫描过 BOOL allVisited TRUE; for (UINT32 i 0; i numOutputLinks; i) { ChiFeature2GraphLinkData* pOutputLinkData GetOutputLinkData(pGraphNode, i); if (pOutputLinkData-linkState ChiFeature2GraphLinkState::NotVisited) { allVisited FALSE; break; } } isReady hasOutputPending allVisited; return isReady; }这个判定逻辑暴露了 Feature2 的核心设计哲学Node 的就绪取决于它“能输出什么”而非“需要输入什么”。只要有一个OutputPendingLink且所有 Link 都已被扫描即拓扑已明确该 Node 就具备了被调度的资格。它的 input dependency 是否满足是在Sequence_-1阶段由GetDependency()动态查询的——这正是 Feature2 支持跨 FeatureNode 数据复用如 RealTimeNode 的 output 直接作为下游 input的前提。如果误以为Ready等价于input ready就会在调试时陷入“为什么 Node 已 Ready 却卡在 InputResourcePending”的困惑。3. CHITargetBufferManager不是缓冲区分配器而是跨 FeatureNode 的引用计数协处理器CHITargetBufferManagerCTBM常被误解为一个简单的 buffer pool 管理器但它真正的角色是 Feature2 架构中实现“零拷贝数据共享”的信任锚点。它不负责物理内存分配那是ChiBufferManager或上层ImportExternalTargetBuffer的事而是通过ImportList/ProducerList/ConsumerList三个队列和精确到字节的refCount为每个TargetBuffer建立全生命周期的引用关系图。当一个FeatureNode的 output port 产出 bufferCTBM 将其 node 从ProducerList移至ConsumerListrefCount--当下游FeatureNode将其作为 input port 使用时refCount。只有当refCount归零且队列中 buffer 数量超过minNumOfBuffers时才真正释放物理内存。这种设计让RealTimeDisplay_OutRaw_OutMeta_OutRDI_InMeta_InMeta_OutYUV_Outyuvmetadata这类复杂链路中的 buffer 复用成为可能——同一块 raw buffer既被 RealTimeNode 读取又被 MFSRNode 读取CTBM 用refCount精确跟踪着每一处引用。3.1 三队列模型Import/Producer/Consumer 的语义隔离CTBM 的三个队列不是性能优化技巧而是语义契约ImportList存放上层 App 通过ImportExternalTargetBuffer()传入的 buffer。这些 buffer 的生命周期完全由 App 控制CTBM 只负责登记pData指向外部内存和引用计数。refCount初始为 1表示 App 持有一份引用。ProducerList存放由 CTBM 自身通过ChiBufferManager申请的 internal buffer。这些 buffer 的物理内存由 CamX 管理CTBM 仅负责逻辑归属。refCount初始为 1表示 Producer当前 FeatureNode持有一份引用。ConsumerList存放已被下游 Consumer其他 FeatureNode获取的 buffer。每次UpdateTarget()调用都会将 node 从ProducerList移出加入ConsumerList并refCount--。refCount在此处代表“还有多少个 Consumer 未完成消费”。注意refCount的增减严格遵循“谁使用谁增加谁释放谁减少”原则。SetupTargetBuffer()调用时refCountProducer 增加自身引用ReleaseTargetBuffer()调用时refCount--Producer 释放自身引用。下游GetInputDependency()获取 buffer 时CTBM 内部会refCountConsumer 增加引用而ProcessResultMessage()中ReleaseInputDependencyMessagge则refCount--Consumer 释放引用。3.2 Buffer 生命周期的四个关键钩子CTBM 的 buffer 管理完全由四个函数驱动每个都对应明确的状态跃迁函数触发时机队列操作refCount 变化语义ImportExternalTargetBuffer()上层传入 sink buffernode 加入ImportListrefCount 1App 声明对该 buffer 的所有权SetupTargetBuffer()FeatureNode 准备 output portnode 从ImportList移至ProducerListrefCountProducer 接管 buffer准备写入UpdateTarget()result 返回buffer 成为下游 inputnode 从ProducerList移至ConsumerListrefCount--Producer 完成写入buffer 可被下游读取ReleaseTargetBuffer()FeatureNode 显式释放 buffernode 从ConsumerList移除pData释放refCount--Producer 放弃对该 buffer 的所有权// chi_target_buffer_manager.cpp 关键逻辑 CDKResult CHITargetBufferManager::UpdateTarget( ChiTargetBufferInfo* pTargetBufferInfo, ChiFeature2RequestObject* pRequestObject) { // 1. 从 ProducerList 移除 node DoublyLinkedNode* pNode FindNodeInProducerList(pTargetBufferInfo-hBuffer); RemoveNodeFromProducerList(pNode); // 2. 加入 ConsumerList AddNodeToConsumerList(pNode); // 3. refCount 减一Producer 完成生产 pNode-refCount--; // 4. 若下游需要refCount 加一内部隐式 if (pRequestObject-IsDownstreamConsumer()) { pNode-refCount; } return CDKResultSuccess; }这段代码揭示了一个关键细节UpdateTarget()不仅是 Producer 的收尾动作更是 Consumer 的启始动作。IsDownstreamConsumer()的判断逻辑决定了该 buffer 是否会被下游 FeatureNode 的GetInputDependency()获取——这正是RTBayer2YUV中RealTimeNode的 output buffer 能直接作为Bayer2YUV的 input buffer 的技术基础。如果refCount在UpdateTarget()后变为 0意味着该 buffer 已无任何 ConsumerCTBM 会立即触发ReleaseTargetBuffer()清理资源避免内存泄漏。3.3 避坑FeatureNode 缓冲区管理的五大血泪经验Feature2 的 buffer 管理是高频翻车区以下是某开发者在调试RTMFSRYUV链路时踩过的五个典型坑每条都附带可验证的排查路径现象ProcessResultMessage()中ProcessBufferCallback(yuv)被调用但yuvbuffer 数据全黑。原因Bayer2YUVFeatureNode 的output port在SetupTargetBuffer()时未正确设置bufferFormat和bufferSize导致 CamX 分配的 internal buffer 尺寸错误实际写入越界。解决在OnProcessRequest()的Sequence_0阶段检查pRequestInfo-pRequestOutput[i].bufferMetadataInfo.bufferFormat是否与ChiFeature2Descriptor::StageDescriptor中定义的outputPortFormat严格一致用ChiBufferManager::GetBufferSize()校验bufferSize。现象RealTimeDisplay正常显示但MFSR处理帧率骤降log 显示CHITargetBufferManager::Allocate failed。原因RealTimeNode的output port被多个 downstream FeatureNode如 Display MFSR同时引用但minNumOfBuffers设置过小如设为 2导致ConsumerList中 buffer 不足Allocate时无法复用。解决在CHITargetBufferManager::Create()初始化时将minNumOfBuffers设为max(downstream_count, 3)或在ProbeChiFeature2Features()阶段根据FeatureDescriptor中numDownstreamNodes动态计算。现象Preview流正常但ZSL回写 buffer 偶发花屏且仅在高帧率下出现。原因ZSL的ExtSinkLink与Preview的ExtSinkLink共享同一SinkTarget但WalkAllExtSinkLinks()未对ZSLLink 设置OutputPending导致其requestTable为空FeatureRequestObject未创建buffer 未被SetupTargetBuffer()预分配。解决检查IsSinkPortMatched()的判定逻辑确保ZSL的portId在m_extSinkPortIdToChiStreamMap中有独立映射在configure_streams()阶段为ZSL显式调用ImportExternalTargetBuffer()。现象Metadatacallback 频繁丢失ProcessMetadataCallback(meta)调用次数远少于预期。原因Metadata的ExtSinkLink被误设为Disabled因为portId.portType比较时未区分ChiMetadataPortType::Stats与ChiMetadataPortType::App导致IsSinkPortMatched()返回 false。解决在IsSinkPortMatched()中对MetaData类型 Link增加portId.portType ChiMetadataPortType::Stats的精确匹配打印pExtSinkLinkData-portId.portType与m_extSinkPortIdToChiStreamMap.portId.portType进行比对。现象RTBay2YUV链路中RealTimeNode的output portbuffer 地址在ProcessResultMessage()中与GetInputDependency()获取的地址不一致。原因RealTimeNode的output port未在FeatureDescriptor的StageDescriptor中声明为isRealTime TRUE导致 CTBM 未启用virtual InternalLink机制而是为每个 Request 重新分配新 buffer。解决检查ChiFeature2Descriptor::StageDescriptor结构体确认isRealTime字段为TRUE在DoFeatureCreate()的PrepareStageData()阶段校验pStageData-isRealTime是否被正确赋值。4. ProcessRequest 状态机Sequence_-1 与 Sequence_0 的双阶段解耦是 Feature2 支持动态依赖的核心Feature2 的ProcessRequest()不是一个线性函数而是一个由FeatureRequestObjectFRO状态机驱动的双阶段协议。Sequence_-1Dependency Setup与Sequence_0Buffer Submit的严格分离使得 Feature2 能在 runtime 动态解析跨 Node 依赖这是它区别于 CamX 静态 pipeline 的本质特征。Sequence_-1阶段只做两件事1遍历所有output link收集上游FeatureNode的output port信息构建dependency list2根据dependency list的完备性决定该 FRO 是进入Executing状态无依赖还是InputResourcePending状态有依赖。Sequence_0阶段则纯粹是 buffer 绑定与 pipeline 提交不涉及任何依赖决策。这种解耦让RealTimeNode的 output 可以在Sequence_-1中被识别为dependency而在Sequence_0中直接复用其物理地址无需 memcpy。4.1 FRO 状态机从 Initialized 到 OutputResourcePending 的七步闭环FeatureRequestObject的状态流转严格遵循以下路径每一步都对应明确的函数调用与资源操作Initialized→ReadyToExecuteOnPrepareRequest()被调用初始化requestTable为每个OutputPendingLink 分配RequestOutputInfoReadyToExecute→InputResourcePendingGetDependency()发现上游output port未就绪SetCurRequestState(InputResourcePending)InputResourcePending→ExecutingProcessDependency()收到上游ProcessResultMessage()dependency满足SetCurRequestState(Executing)Executing→OutputResourcePendingOnExecuteProcessRequest()完成SubmitRequestToSession()SetCurRequestState(OutputResourcePending)OutputResourcePending→InitializedHandleOutputResourcePending()调用ReleaseTargetBuffer()清理本 Node 的 buffer 引用SetCurRequestState(Initialized)ReadyToExecute→ExecutingGetDependency()无依赖直接SetCurRequestState(Executing)Executing→OutputResourcePending同第 4 步。提示CanRequestContinue()函数只返回TRUE给Initialized、ReadyToExecute、Executing三种状态这意味着InputResourcePending和OutputResourcePending是阻塞态FRO 在此状态下不会被ProcessRequest()循环再次处理直到外部事件如ProcessResultMessage()触发状态跃迁。4.2 Sequence_-1GetDependency() 如何动态发现上游 output portGetDependency()的核心逻辑是“向上游 FeatureNode 查询其output port的 handle”而非静态配置。它通过pUpstreamGraphNode-requestTable查找对应requestIndex的RequestOutputInfo从中提取hBufferCDKResult ChiFeature2Base::GetDependency( ChiFeature2RequestObject* pRequestObject, ChiFeature2GraphNode* pUpstreamGraphNode, UINT8 requestIndex, ChiTargetBufferInfo* pTargetBufferInfo) { // 1. 从上游 Node 的 requestTable 中获取 output info std::vectorstd::vectorChiFeature2RequestOutputInfo requestTable pUpstreamGraphNode-requestTable; if (requestIndex requestTable.size()) { return CDKResultEFailed; } std::vectorChiFeature2RequestOutputInfo requestOutputs requestTable[requestIndex]; if (requestOutputs.empty()) { return CDKResultEFailed; } // 2. 取第一个 output port实际可能有多个需遍历 ChiFeature2RequestOutputInfo requestOutputInfo requestOutputs[0]; pTargetBufferInfo-hBuffer requestOutputInfo.bufferMetadataInfo.hBuffer; // 3. 关键增加 refCount表示本 Node 开始引用该 buffer CHITargetBufferManager::IncrementRefCount(pTargetBufferInfo-hBuffer); return CDKResultSuccess; }这段代码的关键在于IncrementRefCount()调用——它确保了上游RealTimeNode的 output buffer 在被下游Bayer2YUV引用期间不会被ReleaseTargetBuffer()提前释放。requestTable的存在使得GetDependency()不需要知道上游 Node 的具体类型RealTime / Bayer2YUV / MFSR只需按requestIndex索引即可这是 Feature2 实现“功能无关依赖”的基石。4.3 Sequence_0SubmitRequestToSession() 如何绑定 CamX pipelineSequence_0阶段的SubmitRequestToSession()是 Feature2 与 CamX 的胶水层。它不创建新 pipeline而是将已存在的CamX Session与当前 FRO 的 buffer 绑定CDKResult ChiFeature2Base::SubmitRequestToSession( ChiFeature2RequestObject* pRequestObject, ChiFeature2GraphNode* pGraphNode) { // 1. 为每个 output port 设置 buffer info for (UINT32 i 0; i pRequestObject-GetNumRequestOutputs(); i) { ChiFeature2RequestOutputInfo* pOutputInfo pRequestObject-GetRequestOutputInfo(i); SetBufferInfo(pOutputInfo); // 调用 CHITargetBufferManager::SetBufferInfo() } // 2. 为每个 input port 获取 dependency buffer for (UINT32 i 0; i pGraphNode-numInputPorts; i) { ChiFeature2PortDescriptor* pPortDesc pGraphNode-inputPorts[i]; ChiTargetBufferInfo targetBufferInfo; GetInputDependency(pPortDesc, targetBufferInfo); // 内部调用 GetDependency() SetInputBufferInfo(targetBufferInfo); } // 3. 提交到 CamX Session return m_pSession-SubmitRequest(pRequestObject-GetCamXRequest()); }SetBufferInfo()和SetInputBufferInfo()的作用是将 CTBM 管理的hBufferhandle 注入 CamX 的CamXRequest结构体。m_pSession-SubmitRequest()最终调用 CamX 的CamXSession::SubmitRequest()至此Feature2 的调度完成控制权移交 CamX。Sequence_0的纯粹性只做绑定不做决策保证了 Feature2 的轻量与高效。5. FeatureInternalLink虚拟链路与物理链路的二象性是 ZSL 和多路并发的底层支点FeatureInternalLink是 Feature2 中最具迷惑性的概念。它表面是FeatureNode间的连接线实则是两种截然不同机制的统一体物理 InternalLink对应 CamX 中真实存在的 pipeline 间数据通路如 IFE 的 RDI 输出连到 IPE 的 Raw 输入而虚拟 InternalLinkvirtual InternalLink则完全由 Feature2 在内存中模拟不消耗任何硬件资源专为RealTimeNode这类“不走 CamX pipeline”的特殊节点设计。RTBayer2YUV链路中RealTimeNode的output port与Bayer2YUV的input port之间就是虚拟 Link——RealTimeNode直接将 buffer 地址写入requestTableBayer2YUV在GetDependency()时读取该地址全程无硬件传输。这种二象性让 Feature2 既能兼容传统 CamX pipeline又能无缝接入自定义算法模块是其实现“软硬协同”的关键设计。5.1 物理 InternalLinkCamX pipeline 的拓扑投影物理InternalLink的存在是为了将 CamX 的 pipeline 拓扑“投影”到 FeatureGraph 中。ChiFeature2Descriptor::InternalLinkDesc结构体中srcPipelineId和dstPipelineId必须与camxoverrides.xml中定义的 pipeline ID 严格一致。FeatureGraph在CreateFeatureGraph()阶段会遍历所有InternalLinkDesc为每个 Link 创建ChiFeature2GraphLinkData并将其pSrcGraphNode和pSinkGraphNode指向对应的FeatureNode。此时WalkBackFromLink()沿物理 Link 的遍历等价于 CamX 中DRQ队列的 dependency 传递——IFE的 output pending会触发IPE的 input dependency 检查。5.2 虚拟 InternalLinkRealTimeNode 的零拷贝魔法RealTimeNode的 magic 在于它不提交任何 CamX Request却能产出output port数据。其output port的 buffer 由CHITargetBufferManager::ImportExternalTargetBuffer()从上层 App 获取ProcessResultMessage()中ProcessBufferCallback()直接将该 buffer 的hBufferhandle 写入requestTable。下游FeatureNode的GetDependency()读取此 handleSetInputBufferInfo()将其注入 CamX Request。整个过程绕过了 CamX 的DRQ与ready/deferred队列实现了真正的零拷贝。virtual InternalLink的存在让RealTimeNode成为 FeatureGraph 中的“第一公民”而非 CamX 的附属品。5.3 Link 状态同步物理 Link 与虚拟 Link 的统一调度无论物理还是虚拟InternalLink的linkState都由WalkAllExtSinkLinks()统一管理。OutputPending状态的传播不区分 Link 类型——当RealTimeNode的output port被标记为OutputPending其下游Bayer2YUV的input port就会进入InputResourcePending状态触发GetDependency()查询。这种统一调度抹平了硬件 pipeline 与软件算法模块的界限让RTMFSRYUV这样的混合链路成为可能RealTimeNode虚拟→MFSRNode物理→TonemapNode物理→SinkPort虚拟。6. 从 Chi-cdk 到 Feature2一次对“谁该为调度负责”的权力重定义Chi-cdkCamera Hardware Interface - Common Development Kit在 Feature2 架构中早已不是早期文档里那个“封装 CamX API 的工具包”。它的核心职责已演变为FeatureGraph 的编译器与加载器。configure_streams()阶段ChiFeaturePoolManager::Create()会扫描/vendor/lib64/camera/feature2/下所有com.qti.feature2.*.so通过DoFeatureCreate()加载每个 SO 的ChiFeature2Descriptor并将其SessionDescriptor、StageDescriptor、InternalLinkDesc解析为内存中的FeatureGraph元数据。Chi-cdk不执行任何调度逻辑它只提供ChiFeature2Graph::Create()、ChiFeature2Graph::WalkAllExtSinkLinks()等纯虚函数接口真正的调度引擎是Feature2Wrapper中的ExecuteProcessRequest()。这种分层让 OEM 可以在不修改 Chi-cdk 的前提下通过编写新的feature2.*.so来扩展功能而无需触碰 CamX 底层。6.1 ChiFeature2DescriptorFeature2 的“中间语言”ChiFeature2Descriptor是 Feature2 的 ABI 契约其结构体定义了 Feature2 的全部语义struct ChiFeature2Descriptor { ChiFeature2SessionDescriptor sessionDescriptor; // 所有 pipeline 的 ID、port 信息 ChiFeature2StageDescriptor* pStageDescriptors; // 每个 stage 使用哪些 pipeline、port、dependency ChiFeature2InternalLinkDesc* pInternalLinkDescs; // pipeline 间 link 描述 UINT32 numStageDescriptors; UINT32 numInternalLinkDescs; };sessionDescriptor是 Feature2 的“符号表”pStageDescriptors是“指令集”pInternalLinkDescs是“控制流图”。DoFeatureCreate()加载 SO 时Chi-cdk会校验sessionDescriptor.pipelineId是否存在于当前 CamX Session 中pStageDescriptors[i].inputPortId是否在sessionDescriptor中定义——这是 Feature2 实现“安全加载”的关键校验。Chi-cdk本身不包含任何算法逻辑它只是一个运行时链接器。6.2 UsecaseRequestObjectURO与 FeatureRequestObjectFRO跨层请求的桥接器URO是 App 层请求的镜像FRO是 FeatureGraph 层的执行单元二者通过CreateUsecaseRequestObject()与CreateFeatureRequestObject()一一映射。URO中的curProcessSequenceId决定FRO的maxProcessSequenceURO的numRequests决定FRO的m_numRequests。这种映射让 App 的“一帧多请求”如 Preview ZSL Video能被 Feature2 精确拆解为多个FRO每个FRO独立走自己的状态机。URO是 Feature2 与上层的契约FRO是 Feature2 与 CamX 的契约Chi-cdk是这两者之间的翻译官。6.3 设计取舍的终极思考为什么 Feature2 不直接取代 CamXFeature2 的存在本质上是对“调度权归属”的一次妥协式创新。CamX 的优势在于对硬件 pipeline 的极致优化与稳定劣势在于静态拓扑无法适应复杂用例Feature2 的优势在于 runtime 动态调度与灵活组合劣势在于无法直接操作硬件寄存器。高通选择让 Feature2 架在 CamX 之上而非重写 CamX是经过深思熟虑的工程权衡稳定性优先CamX 经过数代手机验证替换风险极高。Feature2 作为上层框架即使出错影响范围可控。渐进式演进OEM 可以逐步将旧 XML pipeline 迁移为 Feature2 Graph无需一次性重写所有模块。分工明确CamX 专注“如何高效执行单个 pipeline”Feature2 专注“何时、何地、以何种组合执行 pipeline”。这种分层也带来了调试复杂度的上升——当你看到RTBayer2YUV链路异常时问题可能在Chi-cdk的 descriptor 解析、FeatureGraph的 link 状态传播、CHITargetBufferManager的 refCount 错误、CamX Session的 buffer 绑定甚至camxoverrides.xml的 pipeline ID 拼写错误。从那以后我每次遇到InputResourcePending卡死都强制走一遍WalkAllExtSinkLinks()的日志逐行核对pExtSinkLinkData-linkState的变化再顺藤摸瓜查requestTable的 size 和内容。Feature2 的强大恰恰藏在它层层抽象的缝隙里——只有亲手拨开m_linkData、requestTable、refCount这三重迷雾你才算真正握住了高通 camera 的调度权。希望帮到你。本文还有配套的精品资源点击获取