第2章:CamX架构深度解析:Pipeline、Session、Node的概念与生命周期管理,以及Usecase的配置流程
各位同学,欢迎来到第二章。上一章我们聊了CamX的整体架构,今天咱们深入核心——Pipeline、Session、Node这三个概念。说实话,这三个东西是CamX的骨架,搞不懂它们,后面写代码会非常痛苦。我在刚接触CamX时,就被这些术语绕晕过,后来花了整整一周才理清楚。
2.1 核心概念:Pipeline、Session、Node
先打个比方。你想想看,拍照就像一条流水线。Node就是流水线上的工人,每个工人干一件事:有的负责对焦,有的负责测光,有的负责降噪。Pipeline就是这条流水线的图纸,规定了工人按什么顺序干活。Session呢?就是一次具体的生产任务——比如“拍一张照片”或者“录一段视频”。
嗯,这个比喻虽然简单,但很贴切。咱们来逐一拆解。
2.1.1 Node(节点)
Node是CamX中最小的执行单元。每个Node负责一个具体的算法或硬件操作。比如:
- IFE Node:处理图像前端数据
- IPE Node:做图像后处理,比如降噪、锐化
- Stats Node:统计信息采集,比如AWB、AF的统计数据
每个Node都有自己的输入端口和输出端口。数据从输入进来,经过处理,再从输出出去。我在项目中遇到过一个问题:某个Node的输入端口配置错了,导致数据流中断,整条Pipeline都卡住了。排查了半天才发现是端口ID没对上。
重要提示:Node的端口配置必须和XML中定义的完全一致,否则数据流会断掉。这是新手最容易踩的坑。
2.1.2 Pipeline(管道)
Pipeline就是Node的有向无环图(DAG)。它定义了Node之间的连接关系和执行顺序。CamX中,Pipeline是静态的——一旦创建,Node的拓扑结构就不能改了。
为什么会这样?因为硬件管线是固定的。比如ISP的硬件流水线,IFE后面必须接IPE,你不能把顺序反过来。所以Pipeline在设计时就已经定死了。
我记得有一次,我想在运行时动态插入一个Node,结果发现根本行不通。后来查文档才知道,Pipeline是编译时确定的。如果你需要不同的处理流程,得定义不同的Pipeline。
2.1.3 Session(会话)
Session是动态的。它代表一次完整的相机使用过程。比如你打开相机App,开始预览,这就是一个Session。你切换前后摄像头,旧的Session结束,新的Session开始。
Session的生命周期管理非常关键。我建议你记住这几个状态:
| 状态 | 说明 |
|---|---|
| Created | Session刚创建,还没准备好 |
| Initialized | 资源已分配,Pipeline已加载 |
| Active | 正在处理数据流(预览/拍照/录像) |
| Destroyed | 资源已释放,Session结束 |
警告:Session销毁时,一定要确保所有Node都停止工作了。我曾经遇到过Session销毁后,某个Node还在后台跑,结果导致内存泄漏。后来加了同步机制才解决。
2.2 生命周期管理
咱们来聊聊生命周期。说白了,就是“什么时候创建,什么时候销毁”。
2.2.1 Node的生命周期
Node的生命周期由Pipeline管理。当Pipeline被加载时,所有Node会被创建并初始化。当Pipeline被卸载时,所有Node会被销毁。
每个Node有以下几个关键回调:
- Create:分配Node内部资源
- Initialize:初始化硬件或算法库
- ProcessRequest:处理一帧数据(核心函数)
- Flush:清空所有待处理请求
- Destroy:释放资源
嗯,这里要注意:ProcessRequest是会被多线程调用的。如果你的Node内部有共享数据,一定要加锁。我刚开始写Node时,忘了加锁,结果多帧并发处理时数据全乱了。
2.2.2 Session的生命周期
Session的生命周期更复杂一些。它由Usecase控制。一个典型的流程是这样的:
- App调用open(),创建Session
- Session加载对应的Pipeline
- App调用configureStreams(),配置数据流
- Session创建Node并初始化
- App发送Request,Session开始处理
- App调用close(),Session销毁所有资源
你想想看,这个流程是不是很像Android Camera HAL3的流程?没错,CamX就是按照HAL3的规范设计的。Session对应着HAL3中的CameraDevice,Pipeline对应着CameraDevice中的处理管线。
个人经验:调试Session生命周期问题时,我建议在关键回调里加日志。比如Create、Destroy、ProcessRequest这几个函数,打印出当前Session的ID和状态。这样能快速定位问题。
2.3 Usecase的配置流程
Usecase是CamX中一个非常重要的概念。它定义了“用户想干什么”。比如:
- Preview:预览场景
- Snapshot:拍照场景
- Video:录像场景
- ZSL:零延时拍照场景
每个Usecase都对应一套完整的配置。配置流程大致如下:
2.3.1 选择Usecase
App通过CameraCharacteristics或者请求参数,告诉CamX它想要什么。比如App说“我要拍照”,CamX就会选择Snapshot Usecase。
2.3.2 加载Pipeline
Usecase会从XML配置中加载对应的Pipeline。这个XML文件定义了:
- 需要哪些Node
- Node之间的连接关系
- 每个Node的初始参数
举个例子,Snapshot Usecase的Pipeline可能包含:
<Pipeline name="SnapshotPipeline">
<Node name="IFE" type="HW" />
<Node name="IPE" type="HW" />
<Node name="JPEG" type="SW" />
<Link source="IFE" sink="IPE" />
<Link source="IPE" sink="JPEG" />
</Pipeline>
你看,这个Pipeline定义了三个Node:IFE、IPE、JPEG。IFE处理完数据后传给IPE,IPE处理完再传给JPEG做编码。这就是一条完整的拍照流水线。
2.3.3 配置Stream
Usecase会根据App的需求,配置输出Stream。比如预览需要YUV流,拍照需要JPEG流。每个Stream都有分辨率、格式、帧率等参数。
我记得有一次,App要求输出4K分辨率的预览流,但Pipeline只支持到1080p。结果配置失败了。后来我查了硬件规格,发现是IPE Node的带宽限制。所以配置Stream时,一定要检查Node的能力。
2.3.4 启动Session
所有配置完成后,Usecase会启动Session。Session开始接收Request,处理数据,输出结果。
整个流程可以用一句话概括:Usecase决定做什么,Pipeline决定怎么做,Session负责执行。
核心要点:Usecase的配置流程是CamX的精华。你只要掌握了这个流程,就能理解CamX是如何从App的请求一步步变成实际的数据处理的。
2.4 避坑指南
最后,分享几个我在项目中踩过的坑:
- Node的初始化顺序:Pipeline中的Node是按拓扑顺序初始化的。如果你在Node A的初始化中依赖Node B,那就要确保Node B先初始化。否则会崩溃。
- Session的并发访问:多个线程同时访问同一个Session,一定要加锁。我见过最离谱的bug是,一个线程在销毁Session,另一个线程还在发送Request,结果直接段错误。
- Usecase切换:从Preview切换到Snapshot时,要确保旧的Session完全销毁了,再创建新的。否则资源会冲突。
嗯,今天就讲到这里。下一章我们会深入Node的实现细节,教你怎么写一个自己的Node。到时候我会分享更多实战经验。
记住,理解Pipeline、Session、Node这三个概念,是掌握CamX的第一步。别急,慢慢来。