简介:本资源是一份面向高通平台Camera驱动开发工程师与Android底层视觉系统研发人员的技术解析文档,聚焦Chi-CDK Feature2框架的架构设计与运行机制,解决Camera HAL层多pipeline协同调度、缓冲区高效管理及实时图像处理链路构建等核心问题。文档以PDF格式呈现,共1个文件,大小约1000KB,内容涵盖FeatureGraph与FeatureNode建模逻辑、CHITargetBufferManager缓冲区分配策略、Request/Execute/Result三阶段处理流程、RealTime/RTBay2YUV/RTMFSRYUV实时数据流组件作用、Camx XML pipeline定义方式、FeatureInternalLink跨节点连接机制及Link状态机管理等关键模块,并辅以FeatureGraph简易模型图示与RTBayer2YUV执行时序详解。目前已有8061人学习下载,适合具备Camera HAL基础、正深入高通QCS平台影像子系统开发的中高级工程师系统掌握Feature2框架设计思想与落地实践要点。
1. Feature2 不是“另一个 pipeline 封装”:它用图遍历替代了 XML 静态调度,让多 stage 协同真正可编程
你写完一个RTBayer2YUV的 FeatureNode,编译进feature2.rt.so,再改一行 XML 就能跑通——这感觉很顺?错。那只是表象。真正让你的 MFSR 多帧融合不卡顿、ZSL 预览不丢帧、RealTime Display 能同时吐出 Raw + Meta + YUV 三路 buffer 的底层机制,根本不是 Camx 的 DRQ 队列在干活,而是 Feature2 在内存里动态构建了一张有向无环图(DAG),并用深度优先遍历(DFS)+ 状态机驱动的方式,把“谁该等谁”“谁该先申请 buffer”“谁该后释放资源”全部从编译期绑定,搬到了运行时按需决策。这不是语法糖,这是调度模型的代际升级:Camx 的 pipeline 是“铁轨”,固定拓扑、静态依赖;Feature2 的 FeatureGraph 是“交通管制中心”,它不预设路径,只根据每个 SinkPort 是否被上层消费(即ExtSinkLink->linkState == OutputPending),实时反向推导出哪些上游 Node 必须启动、哪些 input buffer 必须提前就位、哪些 output port 实际上根本没人要(直接设为Disabled)。所以当你看到WalkAllExtSinkLinks()从 Sink 开始往回扫、ProcessInputDepsForUpstreamFeatureRequest()把 requestIndex 塞进上行 Node 的requestTable、CheckAllOutputLinksReadyToProcessRequest()判定 ready 状态——你看到的不是一堆冗余函数调用,而是一个基于数据流驱动的轻量级 DAG 调度引擎在裸机上跑。搞不懂这个,你永远在 patch XML、硬塞 buffer count、对着InputResourcePending日志抓狂;搞懂了,你才能把MFSR的maxProcessSequence设为 4 而不是拍脑袋写 8,才能让Virtual InternalLink真正撑起 ZSL 的跨帧 buffer 复用,而不是靠堆内存硬扛。
2. FeatureGraph:不是容器,是带状态的 DAG 调度器,其核心是 m_linkData + DFS 遍历协议
FeatureGraph 表面看是个管理FeatureNode的容器,但它的本质,是 Feature2 框架的运行时调度中枢。它不持有 pipeline 实例,不执行图像处理,但它精确控制着每一个FeatureNode何时OnProcessRequest()、何时HandleInputResourcePending()、何时ReleaseTargetBuffer()。这种控制力,源于两个不可拆分的设计:全局唯一的m_linkData数据结构,和一套严格定义的 DFS 遍历协议。没有m_linkData,FeatureGraph就是空壳;没有 DFS 协议,m_linkData只是一堆死数据。它们共同构成 Feature2 区别于 Camx 静态 pipeline 的第一道分水岭。
2.1 m_linkData:Link 状态的唯一真相源,所有 ready 判定都查它
m_linkData是ChiFeature2Graph类中一个std::vector<ChiFeature2GraphLinkData>类型的成员变量,它不是缓存,不是副本,而是整个 FeatureGraph 中所有 Link 状态的唯一、权威、可变的真相源(Single Source of Truth)。每一个ExtSrcLink、ExtSinkLink、InternalLink的实例,其linkState字段(NotVisited/Disabled/OutputPending)都直接映射到m_linkData中对应索引的元素。这意味着:
WalkAllExtSinkLinks()扫描到某个ExtSinkLink并将其linkState设为OutputPending,修改的就是m_linkData[i].linkState;CheckAllOutputLinksReadyToProcessRequest(pSrcGraphNode)判断一个 Node 是否 ready,就是遍历该 Node 所有outputLinkIndex,去m_linkData里查每个对应 link 的linkState是否为OutputPending;ProcessDependency()从上行 Node 获取 dependency buffer,其触发条件InputResourcePending的判定,同样依赖m_linkData中 upstream link 的状态是否已满足。
提示:
m_linkData的生命周期与FeatureGraph绑定,其 size 在Create()时由ChiFeature2Descriptor中解析出的 Link 总数决定。它不增长、不收缩,所有 Link 操作都是原地状态变更。这是 Feature2 高性能的关键——无锁、无拷贝、无动态分配,只有原子状态切换。
// ChiFeature2Graph.cpp 关键片段:m_linkData 的状态更新入口 void ChiFeature2Graph::SetLinkState( UINT32 linkIndex, ChiFeature2GraphLinkState newState) { if (linkIndex < m_linkData.size()) { // 直接修改真相源,无中间层 m_linkData[linkIndex].linkState = newState; // 后续所有 ready 判定、dependency 查询都基于此值 } }这段代码背后是 Feature2 的设计哲学:状态必须集中、必须唯一、必须廉价访问。m_linkData就是那个“廉价”的载体。它让FeatureGraph的调度逻辑可以完全脱离 Camx 的DRQ队列状态,自成闭环。
2.2 DFS 遍历协议:从 Sink 入口反向推导执行路径,而非正向驱动
Camx 的DRQ是正向驱动:Node 准备好 input,就入ready队列;dependency 满足,就DispatchReadyNodes()。Feature2 的WalkAllExtSinkLinks()则是严格的、单向的、从出口(Sink)反向追溯入口(Source)的 DFS 协议。它不关心“谁准备好了”,只关心“谁被需要了”。
其执行流程是确定性的:
- 入口扫描:
WalkAllExtSinkLinks()遍历所有ExtSinkLink(即上层 APP 显式消费的输出端口,如 Preview、Metadata、Raw); - 状态标记:对每个被上层实际请求的 SinkPort(通过
portId匹配m_extSinkPortIdToChiStreamMap),将其linkState设为OutputPending; - 反向递归:对每个
OutputPending的ExtSinkLink,调用WalkBackFromLink(),找到其pSrcGraphNode(上行 FeatureNode),然后:- 若该 Node 的所有 output link当前都是
Disabled,则将其所有 input link也设为Disabled(剪枝,避免无效计算); - 若该 Node 的所有 output link中,至少有一个是
OutputPending,且其余非Disabled,则判定该 Node 为ready,为其创建FeatureRequestObject(FRO),并进入ProcessRequest(); - 递归继续向上游
pSrcGraphNode的 input link 执行WalkBackFromLink(),直到抵达图的起点(如 Sensor Input)。
- 若该 Node 的所有 output link当前都是
这个协议的威力在于:它天然支持稀疏执行。例如,APP 只请求 Preview 流,不请求 Metadata 流,那么所有与 Metadata 相关的ExtSinkLink保持Disabled,其上游所有InternalLink和ExtSrcLink也会被WalkBackFromLink()一路Disabled,相关 Node 根本不会被创建 FRO,更不会申请任何 buffer 或提交任何 request。这比 Camx 在DRQ中挂起一堆deferredNode 等待 metadata dependency 要高效得多。
2.3 FeatureNode 的“ready”状态:一个动态的、基于 output link 的布尔表达式
在 Camx 中,“Node ready” 意味着其 input buffer 已到位、fence 已 signalled。在 Feature2 中,“FeatureNode ready” 是一个纯逻辑判断,其公式为:
Node.ready == ( (所有 output link 的 linkState != Disabled) && (至少一个 output link 的 linkState == OutputPending) )注意,这里不检查 input link!ready状态只由该 Node 的 output 是否被下游消费(OutputPending)决定,input 的 availability 是ProcessRequest()进入InputResourcePending状态后,由ProcessDependency()去解决的问题。这是一个关键的设计解耦:
ready解决“要不要启动这个 Node”;InputResourcePending解决“启动后,input 从哪来”。
这使得 Feature2 能优雅处理RealTime这类特殊 Node:它的 input 是 sensor 的实时流,理论上永不“pending”,所以它的ready状态一旦满足(比如RTDisplay的OutputPending被置位),它就会立刻进入Executing,无需等待任何 input dependency。而Bayer2YUV这类 Node,其ready状态满足后,会先进入InputResourcePending,等待RealTime的 output buffer 到位。
// ChiFeature2Graph.cpp: CheckAllOutputLinksReadyToProcessRequest 的核心逻辑 BOOL ChiFeature2Graph::CheckAllOutputLinksReadyToProcessRequest( ChiFeature2GraphNode* pGraphNode) { BOOL isReady = FALSE; UINT32 numOutputLinks = pGraphNode->outputLinkIndexMap.size(); if (0 == numOutputLinks) { // 无 output link 的 Node(如纯 sink),视为 ready isReady = TRUE; } else { UINT32 outputPendingCount = 0; for (UINT32 i = 0; i < numOutputLinks; i++) { UINT32 linkIndex = pGraphNode->outputLinkIndexMap[i]; if (linkIndex < m_linkData.size()) { ChiFeature2GraphLinkState state = m_linkData[linkIndex].linkState; if (ChiFeature2GraphLinkState::OutputPending == state) { outputPendingCount++; } else if (ChiFeature2GraphLinkState::Disabled != state) { // 存在非 Disabled 也非 OutputPending 的状态(如 NotVisited),说明图未完全 walk,不 ready return FALSE; } } } // 至少一个 OutputPending,且无 NotVisited,则 ready isReady = (outputPendingCount > 0); } return isReady; }这段代码清晰地展示了ready的判定逻辑:它只读m_linkData,只看outputLinkIndexMap,不碰任何 buffer manager 或 fence。isReady是一个纯粹的、瞬时的、基于当前图状态的布尔值,是 Feature2 调度引擎的“心跳信号”。
2.4 Link 的三种类型:ExtSrc/ExtSink/Internal,定义了数据流的边界与所有权
FeatureGraph中的 Link 不是同质的,ExtSrcLink、ExtSinkLink、InternalLink这三种类型,划定了数据流的逻辑边界和buffer 所有权转移点,是理解 Feature2 buffer 生命周期的钥匙。
ExtSinkLink(外部接收链路):代表 FeatureGraph 的输出边界。它的pSinkGraphNode是上层 APP(或 Chi-CDK wrapper),pSrcGraphNode是图内最后一个 FeatureNode(如Bayer2YUV)。当 APP 调用configure_streams()请求 Preview 流时,ChiFeature2Graph::WalkAllExtSinkLinks()就是通过匹配ExtSinkLink的portId来定位并激活这条链路。ExtSinkLink的OutputPending状态,是整个 DFS 遍历的唯一触发源。ExtSrcLink(外部提供链路):代表 FeatureGraph 的输入边界。它的pSrcGraphNode是上层 APP(或 Chi-CDK wrapper),pSinkGraphNode是图内第一个 FeatureNode(如RealTime)。它通常用于将 APP 预先分配的 buffer(如 sensor raw buffer)注入图中。ExtSrcLink的linkState一般不参与ready判定,它更多是SetupTargetBuffer()时的配置依据。InternalLink(内部连接链路):代表 FeatureGraph内部的数据流动。它的pSrcGraphNode和pSinkGraphNode都是图内的FeatureNode(如RealTime->Bayer2YUV)。InternalLink的linkState是ready判定的核心,也是ProcessDependency()查找上游 output buffer 的路径。InternalLink的存在,使得FeatureGraph可以将多个物理 pipeline(Stage)无缝编织成一个逻辑处理单元,Bayer2YUV的 input 不再是 Camx 的某个固定 port,而是RealTime的某个OutputPendingport。
这三种 Link 共同构成了 Feature2 的“数据契约”:ExtSinkLink定义了“我要给谁”,ExtSrcLink定义了“我从谁拿”,InternalLink定义了“我在内部怎么传”。m_linkData就是这份契约的数字化账本。
3. CHITargetBufferManager:不是 malloc/free 封装,是基于 refCount 的三级队列 buffer 生命周期控制器
CHITargetBufferManager常被误认为是ImageBufferManager的简单包装。大错特错。它是一个精密的、基于引用计数(refCount)和三级队列(ImportList/ProducerList/ConsumerList)的 buffer 生命周期控制器。它的核心目标,不是“分配 buffer”,而是“精确追踪每一个 buffer 在 FeatureGraph 数据流中的位置、状态和所有权”,确保RealTime输出的 YUV buffer 能被Bayer2YUV正确识别为 input,且在Bayer2YUV处理完毕后,能被RTDisplay消费,最后在无人使用时被安全释放。refCount是它的灵魂,三级队列是它的骨架,SetupTargetBuffer()和UpdateTarget()是它的脉搏。
3.1 三级队列:Import/Producer/Consumer,定义 buffer 的三个生命阶段
CHITargetBufferManager内部维护三个DoublyLinkedNode链表:m_importList、m_producerList、m_consumerList。每个DoublyLinkedNode封装一个CHIBufferInfo(含hBufferhandle、pBufferpointer、size等),并有一个refCount字段。这三个队列并非并列,而是代表 buffer 的线性演进阶段:
ImportList(导入队列):存放上层 APP 主动传入的 external buffer。当 APP 调用ImportExternalTargetBuffer()时,CHITargetBufferManager会为该 buffer 创建一个DoublyLinkedNode,将hBuffer和pBuffer保存在pData中,并将该 node 挂入m_importList。此时refCount为 0。ImportList是 buffer 的“入境口岸”,只进不出,除非被SetupTargetBuffer()“征用”。ProducerList(生产者队列):存放已被 FeatureNode 申请并准备作为 output 使用的 buffer。当FeatureNode(如RealTime)执行SetupTargetBuffer()时,若该 buffer 是 external,则从m_importList中摘下对应 node,将其refCount设为 1,并挂入m_producerList;若该 buffer 是 internal(由ChiBufferManager在 Camx 中申请),则新建 node,申请 buffer,refCount=1,挂入m_producerList。ProducerList是 buffer 的“上岗预备队”,refCount=1表示它已被一个 FeatureNode 认领为 output。ConsumerList(消费者队列):存放已被下游 FeatureNode 作为 input 消费的 buffer。当FeatureNode(如Bayer2YUV)的ProcessResult()回调收到OnBufferResult()时,它会调用CHITargetBufferManager::UpdateTarget()。该函数会从m_producerList中摘下该 buffer 对应的 node,将其refCount--(变为 0),并挂入m_consumerList。同时,如果该 buffer 将被用作下一个FeatureNode(如RTDisplay)的 input,则UpdateTarget()会再次refCount++(变为 1),并返回TargetBufferInfo给下游。ConsumerList是 buffer 的“流转中转站”,refCount的增减在此发生。
提示:
refCount的语义非常明确:refCount == 0表示该 buffer 当前没有被任何 FeatureNode 作为 input 或 output 持有;refCount >= 1表示它正在 FeatureGraph 的数据流中“服役”。m_producerList和m_consumerList的 node 可以互相迁移,但m_importList的 node 只能单向流入m_producerList。
3.2 SetupTargetBuffer:一次调用,完成 buffer 绑定、refCount 初始化、队列迁移三件事
SetupTargetBuffer()是CHITargetBufferManager的核心入口函数,它不是一个简单的“设置 buffer info”,而是一次完整的 buffer 生命周期初始化仪式。其逻辑高度统一,无论处理 external 还是 internal buffer:
// CHITargetBufferManager.cpp 伪代码 CDKResult CHITargetBufferManager::SetupTargetBuffer( const CHITargetBufferInfo* pTargetBufferInfo, CHITargetBufferHandle* pTargetBufferHandle) { CDKResult result = CDKResultSuccess; DoublyLinkedNode* pNode = nullptr; if (pTargetBufferInfo->bufferType == CHI_BUFFER_TYPE_EXTERNAL) { // Step 1: 从 ImportList 找到对应的 node pNode = FindNodeInImportList(pTargetBufferInfo->hBuffer); if (nullptr == pNode) { // 失败:APP 传入的 buffer 未被 ImportExternalTargetBuffer() return CDKResultFailure; } // Step 2: 从 ImportList 摘下 RemoveNodeFromImportList(pNode); } else { // Step 1: 为 internal buffer 创建新 node pNode = CreateNewNode(); // Step 2: 调用 ChiBufferManager 在 Camx 中申请 buffer result = m_pChiBufferManager->AllocateBuffer(..., &pNode->pBuffer); if (CDKResultSuccess != result) { FreeNode(pNode); return result; } } // Step 3: 统一初始化 refCount = 1 pNode->refCount = 1; // Step 4: 挂入 ProducerList,表示该 buffer 已被认领为 output AddNodeToProducerList(pNode); // Step 5: 填充返回的 handle *pTargetBufferHandle = pNode->handle; return CDKResultSuccess; }这段伪代码揭示了SetupTargetBuffer()的原子性:它要么完整成功(buffer 进入ProducerList,refCount=1),要么完全失败(node 被清理)。这保证了FeatureNode在Sequence(0)阶段拿到的TargetBufferInfo,其背后的 buffer 一定是处于一个确定的、可预测的状态(ProducerList中,refCount=1),绝不会出现“buffer 已分配但未挂队列”或“refCount 错乱”的情况。这是 Feature2 buffer 管理稳定性的基石。
3.3 UpdateTarget:refCount 的舞蹈,决定 buffer 是流转还是终结
UpdateTarget()是CHITargetBufferManager的另一核心,它负责处理 buffer 在 FeatureGraph 中的流转与终结。当FeatureNode的OnBufferResult()被回调,意味着它已经完成了对该 buffer 的处理,并准备将其交给下游或释放。UpdateTarget()的逻辑就是围绕refCount的加减法展开:
// CHITargetBufferManager.cpp 伪代码 CDKResult CHITargetBufferManager::UpdateTarget( CHITargetBufferHandle targetBufferHandle, CHITargetBufferUsage usage, CHITargetBufferHandle* pNextTargetBufferHandle) { CDKResult result = CDKResultSuccess; DoublyLinkedNode* pNode = FindNodeByHandle(targetBufferHandle); if (nullptr == pNode) { return CDKResultFailure; } // Step 1: 从 ProducerList 摘下(因为它是上游 Node 的 output) RemoveNodeFromProducerList(pNode); // Step 2: refCount--,因为它不再是上游的 output pNode->refCount--; // Step 3: 挂入 ConsumerList AddNodeToConsumerList(pNode); // Step 4: 如果下游需要此 buffer 作为 input,则 refCount++ if (CHI_TARGET_BUFFER_USAGE_CONSUMER == usage) { pNode->refCount++; *pNextTargetBufferHandle = pNode->handle; } else { *pNextTargetBufferHandle = CHI_INVALID_HANDLE; } // Step 5: 检查是否可以释放 if (0 == pNode->refCount) { // Buffer 已无任何 FeatureNode 持有,可以释放 if (CHI_BUFFER_TYPE_INTERNAL == pNode->bufferType) { m_pChiBufferManager->FreeBuffer(pNode->pBuffer); } FreeNode(pNode); // 释放 node 本身 } return result; }UpdateTarget()的精妙在于Step 4和Step 5的配合:refCount++是为了将 buffer “借给”下游 Node,refCount--是为了确认上游 Node 已完成使用。只有当refCount归零,才触发真正的释放。这完美支持了RTBayer2YUV这样的场景:RealTime的 output buffer(YUV)被Bayer2YUV消费(refCount从 1 变 2),Bayer2YUV处理完后,又将它作为自己的 output 交给RTDisplay(refCount从 2 变 1),最后RTDisplay消费完毕,refCount归零,buffer 才被释放。refCount就像一个精准的计数器,记录着 buffer 在 FeatureGraph 这条流水线上被多少个工位(FeatureNode)同时持有。
3.4 避坑:CHITargetBufferManager 的五大血泪经验
Feature2 的 buffer 管理看似自动,实则暗藏玄机。以下是某开发者在调试RTMFSRYUV时踩过的五个典型坑,每一条都对应一个真实的崩溃或内存泄漏:
现象:
RTMFSRYUV的MFSR多帧融合过程中,OnBufferResult()回调频繁,但Preview流画面卡死,log 中大量InputResourcePending。
原因:MFSR的FeatureNode在ProcessRequest()的Sequence(-1)阶段,错误地将refCount为 0 的 buffer(已从ConsumerList释放)再次SetupTargetBuffer()。这导致ProducerList中出现了一个指向已释放内存的 dangling node。后续UpdateTarget()访问该 node 时崩溃。
解决:在SetupTargetBuffer()前,务必检查pTargetBufferInfo->hBuffer是否有效,且该 buffer 的refCount必须为 0(即它必须来自ImportList或刚被释放的 internal buffer)。MFSR的多帧 buffer 复用逻辑,必须严格管理每个 frame 的 buffer handle 生命周期。现象:
RealTimeDisplay_OutRaw_OutMeta_OutRDI_InMeta_InMeta_OutYUV_OutyuvmetadataPreview的复合 FeatureGraph 中,Metadata流正常,但Raw流始终黑屏,OnBufferResult()从未被调用。
原因:ExtSinkLink的portId配置错误。RealTime的Rawoutput port ID 被错误地映射到了Metadata的ExtSinkLink上,导致WalkAllExtSinkLinks()只将Metadata的 link 设为OutputPending,Raw的 link 保持Disabled,整个Raw分支被 DFS 剪枝。
解决:仔细核对ChiFeature2Descriptor中ExtSinkLinkDesc的portId,必须与CHI_STREAM_PORT_ID枚举值完全一致。使用ChiFeature2Graph::DumpGraph()打印出所有ExtSinkLink的portId和linkState,是排查此类问题的最快方法。现象:
RTBayer2YUV的ProcessResult()中,ProcessBufferCallback(yuv)成功,但ProcessReleaseInputDependencyMessage()失败,log 显示Invalid buffer handle。
原因:Bayer2YUV的OnProcessRequest()在Sequence(0)阶段,调用SetupTargetBuffer()为yuvoutput 申请 buffer 后,又错误地调用了ReleaseTargetBuffer()。这导致该 buffer 的refCount被减为 0 并被释放,但Bayer2YUV的ProcessResult()仍试图用这个已释放的 handle 去UpdateTarget()。
解决:ReleaseTargetBuffer()只应在FeatureNode明确放弃对某个 buffer 的 ownership 时调用(如 error path),绝不能在Sequence(0)的正常流程中调用。Sequence(0)的职责是SubmitRequest(),buffer 的 release 由UpdateTarget()在ProcessResult()后自动完成。现象:
configure_streams()后,FeatureGraph创建成功,但ExecuteProcessRequest()第一次调用就 crash,堆栈指向CHITargetBufferManager::Create()中的m_pChiBufferManager->GetBufferManager()。
原因:CHITargetBufferManager::Create()被调用时,m_pChiBufferManager指针尚未被正确初始化。ChiFeature2Graph::CreateFeatureGraph()的执行顺序中,CreateTargetBufferManagers()发生在CreatePipeline()之前,而CreatePipeline()才会调用ChiSession::GetBufferManager()来初始化m_pChiBufferManager。
解决:将CreateTargetBufferManagers()的调用时机,从CreateFeatureGraph()的早期,推迟到CreatePipeline()之后、SelectFeatureGraphforRequest()之前。确保m_pChiBufferManager在CHITargetBufferManager需要它时,已经是有效的。现象:
RTMFSRYUV的maxProcessSequence设为 4,但实际只跑了 2 帧就停止,URO的curProcessSequenceId停在 1。
原因:URO(UsecaseRequestObject)和FRO(FeatureRequestObject)的ProcessSequenceId同步失败。RTMFSRYUV的StageDescriptor中定义了 4 个StageName(BlendLoop[0]到BlendLoop[3]),但FRO的m_numRequests被错误地初始化为 1(而非 4),导致FRO只为BlendLoop[0]创建了BatchOutputRequestInfo,后续BlendLoop[1]的 request 无法被调度。
解决:FRO的m_numRequests必须严格等于URO的maxProcessSequence。在ChiFeature2RequestObject::Create()中,m_numRequests应直接取自URO->maxProcessSequence,而不是硬编码或从requestTable.size()推导(requestTable只反映当前帧的 active request 数)。
4. ProcessRequest 状态机:从 Initialized 到 OutputResourcePending,一个 request 的完整生命周期
FeatureRequestObject(FRO)的状态机,是 Feature2 执行模型的时间轴。它不像传统线程状态机那样关注“运行/阻塞/就绪”,而是精确刻画一个 request 在 FeatureGraph 数据流中所处的空间位置和资源状态。Initialized、ReadyToExecute、InputResourcePending、Executing、OutputResourcePending这五个状态,不是随意命名,而是对request与buffer、dependency、Camx pipeline之间关系的精准建模。理解这个状态机,是读懂ProcessRequest()日志、定位ProcessDependency()卡死、优化maxProcessSequence的前提。
4.1 状态转换图:一条由事件驱动的单向路径
FRO 的状态转换是一条强约束的单向路径,由特定的事件(Event)驱动,而非定时器或轮询。其核心逻辑是:request一旦开始,就必须沿着这条路径走到终点(OutputResourcePending),中间任何一步失败,都会导致整个request被 abort,状态机重置。路径如下:
Initialized ↓ (OnPrepareRequest() success) ReadyToExecute ↓ (No dependency OR dependency satisfied) Executing ↓ (SubmitRequestToSession() success) OutputResourcePending ↓ (ReleaseTargetBuffer() success) [End]而InputResourcePending是一个可选的、临时的、等待型分支,它只在ReadyToExecute状态下,检测到GetDependency()返回FALSE(即上游 output buffer 尚未就位)时,才会进入。一旦上游ProcessResult()触发ProcessDependency(),且 dependency 满足,状态立即跳回Executing。
这个设计的精妙之处在于:它将复杂的异步依赖(ProcessDependency())封装在一个状态内,对外暴露的是一个简洁的、可预测的执行流。ChiFeature2Base::ProcessRequest()的主循环,本质上就是在GetCurRequestState()和CanRequestContinue()的引导下,沿着这条路径“推”request 前进。
4.2 Sequence(-1) 与 Sequence(0):两阶段提交,解耦 dependency 设置与 buffer 提交
ProcessRequest()的两阶段(Sequence(-1)和Sequence(0))设计,是 Feature2 对 CamxDRQ模型的继承与超越。Sequence(-1)是“规划阶段”,Sequence(0)是“执行阶段”。
Sequence(-1)(PopulateDependency):此阶段的目标是收集所有必要的 input dependency 信息。它不申请任何 buffer,不提交任何 request,只做两件事:- 调用
GetDependency(),遍历所有InternalLink,查询上游FeatureNode的output port是否已OutputPending。如果已OutputPending,则记录其TargetBufferInfo;如果未OutputPending,则返回FALSE,驱动状态机进入InputResourcePending。 - 调用
OnPrepareRequest(),为即将进行的Sequence(0)做准备,例如填充SessionData中的pipelineId、stageId等元数据。
Sequence(-1)的成功,意味着request的所有 input 来源都已明确,FeatureNode已准备好进入执行。- 调用
Sequence(0)(SubmitRequestToSession):此阶段的目标是将 request 物理提交给 Camx。它执行Sequence(-1)规划好的所有操作:- 调用
SetBufferInfo(),为 Camx pipeline 的每个input port和output port设置TargetBufferInfo。这会触发CHITargetBufferManager::SetupTargetBuffer(),将 buffer 挂入ProducerList。 - 调用
SubmitRequestToSession(),将填充好的CamxRequest提交给 Camx 的DRQ。至此,FeatureNode的工作移交给了 Camx。
- 调用
Sequence(-1)和Sequence(0)的分离,使得 Feature2 可以在Sequence(-1)中进行复杂的、可能失败的 dependency 查询(如跨FeatureGraph的 virtual link),而不会影响Sequence(0)的原子性提交。这是一种典型的“两阶段提交(2PC)”思想在图像处理框架中的应用。
4.3 InputResourcePending:不是 bug,是 Feature2 的“等待艺术”
InputResourcePending状态常被误解为一种错误或性能瓶颈。恰恰相反,它是 Feature2最精巧的设计之一,是其实现RealTime、ZSL、MFSR等高级用例的基础设施。
RealTime的基石:RealTimeNode 的InputResourcePending状态几乎总是FALSE,因为它的 input 是 sensor 的实时流,Camx 会保证其fence在每一帧到来时 signalled。因此RealTime的request会瞬间从ReadyToExecute跳到Executing,实现真正的零延迟。ZSL的关键:ZSL(Zero Shutter Lag)要求 preview 和 capture 共享同一组 buffer。ZSL的FeatureNode会设置virtual InternalLink,其InputResourcePending状态依赖于RealTime的output port。当用户按下快门,ZSL的request进入InputResourcePending,它并不等待一个新帧,而是等待RealTime刚刚输出的、尚未被 preview 消费的那一帧 buffer。InputResourcePending在这里,是ZSL实现“所见即所得”捕获的精确同步点。MFSR的保障:MFSR的BlendLoop阶段,其InputResourcePending状态依赖于前一帧BlendLoop的output port。InputResourcePending确保了 `
本文还有配套的精品资源,点击获取