Pipeline设计:Camera Pipeline核心流程、Stream配置与Buffer流转、Request与Result机制

大家好,我是你们的讲师。今天我们来聊聊Pipeline设计。说实话,这是整个Camera HAL里最核心的部分,也是我当年踩坑最多的地方。你想想看,从用户按下快门到最终拿到照片,中间到底发生了什么?Pipeline就是答案。

一、Camera Pipeline核心流程

先画个简单的图在脑子里:App发请求 → Framework拆解 → HAL处理 → Sensor曝光 → ISP处理 → 返回结果。就这么一条线,但细节多到让人头皮发麻。

我个人习惯把Pipeline分成三个阶段:

  1. 配置阶段:App告诉系统“我要拍照了,给我准备3个Stream,一个预览、一个拍照、一个分析”。
  2. 请求阶段:App不断下发Capture Request,HAL收到后开始调度。
  3. 处理阶段: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在循环流转。流程大概是:

  1. Framework从BufferQueue里取出一个空闲Buffer,塞进Request里发给HAL。
  2. HAL拿到Request,调度Sensor曝光,ISP处理,把数据填到Buffer里。
  3. 填完后,HAL把Buffer通过Result回调还给Framework。
  4. 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性能差不了