Pipeline设计:Camera Pipeline核心流程、Stream配置与Buffer流转、Request与Result机制
大家好,我是你们的讲师。今天我们来聊聊Pipeline设计。说实话,这是整个Camera HAL里最核心的部分,也是我当年踩坑最多的地方。你想想看,从用户按下快门到最终拿到照片,中间到底发生了什么?Pipeline就是答案。
一、Camera Pipeline核心流程
先画个简单的图在脑子里:App发请求 → Framework拆解 → HAL处理 → Sensor曝光 → ISP处理 → 返回结果。就这么一条线,但细节多到让人头皮发麻。
我个人习惯把Pipeline分成三个阶段:
- 配置阶段:App告诉系统“我要拍照了,给我准备3个Stream,一个预览、一个拍照、一个分析”。
- 请求阶段:App不断下发Capture Request,HAL收到后开始调度。
- 处理阶段:Sensor曝光、ISP处理、Buffer填数据,最后通过Result回调返回给Framework。
我在项目中遇到过一个问题:某个厂商的ISP处理延迟特别高,导致预览卡顿。后来发现是Pipeline里某个节点没有做流水线并行,Sensor在等ISP,ISP又在等DMA。说白了,就是没把Pipeline真正“流”起来。
核心要点:Pipeline的本质是“流水线”,不是“单线程”。每个阶段都应该独立运行,用Buffer队列解耦。
二、Stream配置:你准备好了吗?
Stream配置是Pipeline的起点。App通过CameraDevice.createCaptureSession()告诉HAL:我需要哪些Stream,每个Stream的格式、尺寸、用途是什么。
常见的Stream类型:
| Stream类型 | 典型用途 | 常见格式 |
|---|---|---|
| PREVIEW | 取景器显示 | YUV_420_888, NV21 |
| STILL_CAPTURE | 拍照 | JPEG, RAW10 |
| VIDEO_RECORD | 视频录制 | YUV_420_888, HEVC |
| ANALYSIS | 人脸检测、AR | YUV_420_888 |
嗯,这里要注意:Stream配置一旦提交,就不能改了。除非你重新创建Session。所以App端通常会一次性把所有可能用到的Stream都配好,哪怕有些暂时不用。
我曾经在一个项目里看到App只配了预览Stream,结果用户一点拍照,系统不得不重新配置Session,导致黑屏了将近1秒。这就是典型的Stream配置没规划好。
我的建议:如果App同时需要预览和拍照,最好一次性配两个Stream。别偷懒,否则用户体验会教你做人。
三、Buffer流转:数据是怎么跑的?
Buffer流转是Pipeline的血脉。每个Stream背后都有一组Buffer在循环流转。流程大概是:
- Framework从BufferQueue里取出一个空闲Buffer,塞进Request里发给HAL。
- HAL拿到Request,调度Sensor曝光,ISP处理,把数据填到Buffer里。
- 填完后,HAL把Buffer通过Result回调还给Framework。
- Framework把Buffer送给Display或Encoder,用完后再放回BufferQueue。
你看,这就是一个闭环。但问题往往出在“谁等谁”上。
举个例子:如果ISP处理速度跟不上Sensor的帧率,Buffer就会堆积。Sensor拍完一帧,发现没Buffer可用,只能丢帧。这就是为什么你有时候看到预览画面卡一下——不是手机性能差,是Pipeline里某个环节堵了。
避坑指南:我曾经调试过一个案子,预览帧率始终上不去。查了两天,最后发现是BufferQueue的深度设成了1。Sensor拍完一帧,必须等ISP处理完才能拍下一帧。改成3之后,问题立刻解决。Buffer深度,真的很重要。
四、Request与Result机制:一问一答的艺术
Request和Result是Camera HAL里最优雅的设计之一。App发一个Request,HAL返回一个Result。但这里有个关键点:Request和Result不是一一对应的。
什么意思?
App可以连续发多个Request,HAL可以并行处理。比如App发了3个Request,HAL可能先处理第2个,再处理第1个,最后处理第3个。Result返回的顺序和Request下发的顺序不一定相同。
为什么会这样?因为不同的Request可能涉及不同的Stream。比如Request A要拍照(JPEG),Request B要预览(YUV)。JPEG处理慢,YUV处理快,那YUV的Result肯定先回来。
代码里怎么体现?看这个简化示例:
// App端:下发Request
CaptureRequest request = builder.build();
session.capture(request, callback, handler);
// HAL端:处理Request
void processRequest(CaptureRequest request) {
// 1. 解析Request中的设置(AE、AF、AWB等)
// 2. 调度Sensor曝光
// 3. ISP处理
// 4. 填充Buffer
// 5. 构造Result并回调
notifyShutter(request);
processCaptureResult(result);
}
你想想看,如果App发了10个Request,HAL必须保证每个Request都有对应的Result。而且Result里要包含完整的metadata,比如曝光时间、ISO、AWB值等。这些信息对上层做3A算法至关重要。
重要提醒:Result里的metadata必须和实际拍摄参数一致。我见过一个案例,Result里写的ISO是100,但实际Sensor用的是400。结果App根据ISO 100做了后处理,照片过曝得一塌糊涂。这种坑,千万别踩。
五、实战经验总结
最后,我把自己在Pipeline设计上的一些经验分享给大家:
- Buffer数量要够:预览至少3个Buffer,拍照至少2个,视频至少4个。少了容易丢帧,多了浪费内存。
- Pipeline要并行:Sensor、ISP、DMA尽量独立运行,用队列解耦。别让一个环节拖慢整个流程。
- Request要合理:别一次性发太多Request,HAL处理不过来会丢帧。一般保持2-3个Request在Pipeline里就够了。
- Result要准确:metadata必须真实反映硬件状态。造假的结果,迟早会出问题。
嗯,Pipeline设计就讲到这里。说白了,它就是Camera HAL的骨架。骨架搭好了,后面的优化才有基础。下一章我们会深入聊聊Buffer管理,那才是真正考验功底的地方。
记住一句话:Pipeline设计得好,Camera性能差不了。