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的典型处理流程:
- HAL3完成Request处理后,回调
process_capture_result() - Framework解析Result中的元数据,更新相机状态
- 图像Buffer被送到对应的Stream消费者(如SurfaceView、MediaCodec)
- 如果Result中有错误标志(如
ERROR_REQUEST),Framework会重试或报错
3.5 四者如何协同工作?
好了,四个概念都讲完了。咱们把它们串起来,看看一次完整的拍照流程是什么样的:
- 配置阶段:Framework调用
configure_streams(),告诉HAL3需要哪些Stream(预览流、拍照流)。HAL3根据硬件能力,分配Pipeline资源。 - 提交Request:Framework创建CaptureRequest,指定使用哪条Pipeline、输出到哪些Stream、以及AE/AWB/AF参数。
- 硬件执行:HAL3驱动Sensor曝光、ISP处理、JPEG编码。这个过程是流水线式的——Sensor在曝光第N帧时,ISP在处理第N-1帧,编码器在压缩第N-2帧。
- 返回Result:每完成一帧,HAL3就回调一个Result。Framework拿到Result后,更新预览画面或保存照片。
说白了,这就是一个「配置→请求→执行→反馈」的循环。你提交的Request越多,这个循环就跑得越快。但要注意,Pipeline的吞吐量是有限的,Request提交得太快,反而会导致Buffer溢出。
调试小技巧:在HAL3的日志里搜索process_capture_request和process_capture_result,可以清晰地看到每个Request的生命周期。如果发现某个Request长时间没有对应的Result,那大概率是Pipeline卡住了。
最后说一句:搞懂这四个概念,你就掌握了HAL3的「语法」。剩下的「词汇」(各种Metadata、Buffer格式、硬件能力)都可以慢慢积累。我在做第一个HAL3移植项目时,光是理解Pipeline和Stream的关系就花了两周。但一旦通了,后面的工作就顺风顺水了。
下一章,咱们会深入configure_streams()的实现细节,看看怎么配置Stream才能让硬件跑满性能。到时候我会分享一个我在MTK平台上踩过的坑——保证让你印象深刻。