3、Camera HAL3核心概念:Pipeline、Stream、Request、Result模型详解

好,咱们今天来啃一块硬骨头——HAL3的四大核心概念。说实话,我刚接触HAL3的时候,也被Pipeline、Stream、Request、Result这几个东西绕得晕头转向。但后来我发现,只要搞懂了它们之间的关系,整个相机框架就像一张清晰的地图摆在你面前。

我个人习惯把这四个概念比作一个「流水线工厂」:Pipeline是生产线,Stream是传送带,Request是生产订单,Result是成品检验单。你想想看,是不是一下子就通透了?

3.1 Pipeline:相机硬件抽象流水线

Pipeline,说白了就是一条从传感器到输出图像的完整处理链路。在HAL3里,每个Camera设备可以有多条Pipeline,但通常我们只关心主Pipeline。

我在项目中遇到过一个问题:某款芯片的ISP(图像信号处理器)支持同时处理两路输入,但HAL3的Pipeline设计只暴露了一条。结果就是,双摄虚化功能死活调不通。后来我追了三天代码才发现,是Pipeline的配置没对齐硬件能力。

Pipeline的核心特征:

  • 每个Pipeline对应一个物理或逻辑相机设备
  • Pipeline内部包含多个Stage(阶段):Sensor、ISP、JPEG Encoder等
  • Pipeline的配置在configure_streams()时确定

嗯,这里要注意:Pipeline不是你想开几条就开几条的。硬件资源有限,比如某款中低端芯片,同时开两条Pipeline就会导致帧率掉到15fps以下。我建议你在做移植前,先跟硬件团队确认好Pipeline的并发能力。

3.2 Stream:数据流的通道

Stream是连接Pipeline和Buffer的桥梁。每个Stream定义了一种输出格式、分辨率和用途。比如预览流、拍照流、视频流,它们各自占用一条Stream。

你可能会问:「为什么不能把所有数据都塞到一条Stream里?」

原因很简单——不同用途对图像质量的要求不一样。预览流需要低延迟,拍照流需要高分辨率,视频流需要固定帧率。把它们分开,HAL才能针对性地做优化。

Stream类型 典型分辨率 格式 用途
预览流 1920x1080 NV12/YUV420 取景器显示
拍照流 4000x3000 JPEG/YUV420 最终照片
视频流 3840x2160 NV12 视频编码
深度流 640x480 DEPTH16 3D感知

我曾经踩过一个坑:在配置Stream时,把预览流和拍照流的分辨率设成了完全一样。结果拍照时发现,预览画面突然卡顿了一下。后来才明白,HAL内部会为每条Stream分配独立的Buffer队列,如果两条Stream竞争同一个硬件资源(比如ISP),就会互相影响。

我的建议:预览流和拍照流的分辨率最好保持整数倍关系。比如预览用1080p,拍照用4K,这样ISP在做缩放时效率最高。别问我怎么知道的——调了两个月性能优化总结出来的。

3.3 Request:一次完整的拍摄请求

Request是HAL3里最灵活的概念。它描述了一次完整的拍摄任务:用哪条Pipeline、输出到哪些Stream、使用什么参数

一个典型的Request长这样:

// 伪代码示例
CameraMetadata settings;
settings.set(CONTROL_AE_MODE, AE_MODE_ON);
settings.set(CONTROL_AE_TARGET_FPS_RANGE, {30, 30});
settings.set(JPEG_QUALITY, 95);

CaptureRequest request;
request.pipelineId = 0;
request.streams = {previewStream, jpegStream};
request.settings = settings;
request.bufferCount = 3;  // 环形缓冲区深度

你看,Request把「拍什么」和「怎么拍」都封装在一起了。这样做的好处是——你可以同时提交多个Request,HAL会按顺序处理。比如你先提交一个预览Request,再提交一个拍照Request,HAL会在完成预览后立即执行拍照。

我记得有一次调试连拍功能,发现每拍一张照片都要重新配置Stream。后来看了HAL3的文档才明白:Request可以复用已有的Stream配置,只需要改变settings里的参数就行。这样一来,连拍的效率提升了3倍。

注意:Request的提交顺序和完成顺序不一定一致。HAL内部有重排序机制,比如高优先级的Request(比如拍照)可能会插队到预览Request前面。如果你依赖顺序,记得在settings里设置REQUEST_PRIORITY字段。

3.4 Result:拍摄结果的完整反馈

Result是Request的「回执」。它包含了三样东西:图像Buffer、元数据(Metadata)、状态信息

元数据里有什么?我列几个关键的:

  • SENSOR_TIMESTAMP:曝光开始的时间戳
  • STATISTICS_FACE_RECTANGLES:人脸检测结果
  • CONTROL_AE_STATE:自动曝光是否收敛
  • LENS_FOCUS_DISTANCE:实际对焦距离

你想想看,如果没有Result,你拍完一张照片,连对没对上焦都不知道,那还怎么调试?

我曾经在调试夜景模式时,发现拍出来的照片总是偏暗。查了半天,最后在Result里看到CONTROL_AE_STATE一直是AE_STATE_SEARCHING——原来曝光根本没收敛!后来调整了AE的收敛时间,问题就解决了。

Result的典型处理流程:

  1. HAL3完成Request处理后,回调process_capture_result()
  2. Framework解析Result中的元数据,更新相机状态
  3. 图像Buffer被送到对应的Stream消费者(如SurfaceView、MediaCodec)
  4. 如果Result中有错误标志(如ERROR_REQUEST),Framework会重试或报错

3.5 四者如何协同工作?

好了,四个概念都讲完了。咱们把它们串起来,看看一次完整的拍照流程是什么样的:

  1. 配置阶段:Framework调用configure_streams(),告诉HAL3需要哪些Stream(预览流、拍照流)。HAL3根据硬件能力,分配Pipeline资源。
  2. 提交Request:Framework创建CaptureRequest,指定使用哪条Pipeline、输出到哪些Stream、以及AE/AWB/AF参数。
  3. 硬件执行:HAL3驱动Sensor曝光、ISP处理、JPEG编码。这个过程是流水线式的——Sensor在曝光第N帧时,ISP在处理第N-1帧,编码器在压缩第N-2帧。
  4. 返回Result:每完成一帧,HAL3就回调一个Result。Framework拿到Result后,更新预览画面或保存照片。

说白了,这就是一个「配置→请求→执行→反馈」的循环。你提交的Request越多,这个循环就跑得越快。但要注意,Pipeline的吞吐量是有限的,Request提交得太快,反而会导致Buffer溢出。

调试小技巧:在HAL3的日志里搜索process_capture_requestprocess_capture_result,可以清晰地看到每个Request的生命周期。如果发现某个Request长时间没有对应的Result,那大概率是Pipeline卡住了。

最后说一句:搞懂这四个概念,你就掌握了HAL3的「语法」。剩下的「词汇」(各种Metadata、Buffer格式、硬件能力)都可以慢慢积累。我在做第一个HAL3移植项目时,光是理解Pipeline和Stream的关系就花了两周。但一旦通了,后面的工作就顺风顺水了。

下一章,咱们会深入configure_streams()的实现细节,看看怎么配置Stream才能让硬件跑满性能。到时候我会分享一个我在MTK平台上踩过的坑——保证让你印象深刻。