4. camera3_stream_t 结构体:Stream 类型、格式与 usage 标志位
好,咱们今天来聊聊 camera3_stream_t。这个结构体,说白了就是 HAL 层和框架之间传递图像数据的「快递单」。你想想看,每一帧图像从传感器出来,要经过 ISP、算法处理,最后送到应用层,中间怎么流转?全靠这个结构体来定义。
我个人习惯,拿到一个新的 Camera HAL 项目,第一件事就是先看各个 Stream 是怎么配置的。因为这里一旦配错,后面全是坑。
4.1 Stream 类型:INPUT 与 OUTPUT
先看最基础的东西——stream_type。它只有两个值:
- CAMERA3_STREAM_OUTPUT(输出流)
- CAMERA3_STREAM_INPUT(输入流)
OUTPUT 很好理解,就是 HAL 往框架送数据。比如预览、拍照、录像,这些都是 OUTPUT。你打开相机 App,看到的画面就是从 OUTPUT 流里来的。
INPUT 呢?它是个反向通道。HAL 从框架接收数据,而不是往外送。这玩意儿什么时候用?我记得最早接触 INPUT 流是在做 Reprocessing(重处理)功能的时候。比如你拍了一张 RAW 图,想再跑一遍算法降噪,那就需要把这张 RAW 图作为 INPUT 送回去。
关键点:一个 Stream 不能同时是 INPUT 和 OUTPUT。但一个 Camera 会话里可以同时存在多个 Stream,比如一个 OUTPUT 用于预览,另一个 INPUT 用于重处理。
嗯,这里要注意:INPUT 流不是所有平台都支持。我在项目中遇到过某款 SoC,文档里写着支持 INPUT,结果调了三天发现底层驱动根本没实现。所以做方案选型时,一定要先确认硬件能力。
4.2 Stream 格式与分辨率
接下来是格式和分辨率,这两个是 Stream 配置里最容易出问题的地方。
4.2.1 常见格式
| 格式 | 说明 | 典型用途 |
|---|---|---|
| HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED | 厂商自定义格式 | 预览、录像(性能最优) |
| HAL_PIXEL_FORMAT_YCbCr_420_888 | 标准 YUV420 格式 | 第三方算法、ImageReader |
| HAL_PIXEL_FORMAT_BLOB | JPEG 编码数据 | 拍照输出 |
| HAL_PIXEL_FORMAT_RAW16 | RAW 数据 | 专业摄影、DNG 输出 |
这里我想重点说一下 IMPLEMENTATION_DEFINED。这个格式,说白了就是「我不管,你 HAL 自己看着办」。框架把这个格式丢给 HAL,HAL 可以选自己最擅长的内部格式。比如某厂商的 ISP 对 NV12 处理最快,那 HAL 就可以把 IMPLEMENTATION_DEFINED 映射成 NV12。
我的建议:预览和录像流,优先用 IMPLEMENTATION_DEFINED。这样 HAL 能选最优路径,性能最好。只有当你需要拿到具体像素数据做处理时,才用 YUV 或 RAW。
4.2.2 分辨率匹配
分辨率这块,有个常见的坑:Stream 的分辨率必须和 Sensor 的输出分辨率对齐。不是说你随便写个 1920x1080 就能跑。
我曾经在一个项目里,为了省带宽,把预览流设成了 1280x720,但 Sensor 只支持 1920x1080 和 640x480。结果呢?HAL 在 configure_streams() 里直接返回了 -EINVAL。折腾了半天才发现是分辨率不匹配。
所以,配置分辨率前,一定要先查 Sensor 支持的输出尺寸列表。一般通过 static_camera_metadata 里的 ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS 来获取。
4.3 usage 标志位
最后是 usage 标志位。这个字段是 Android 特有的,它告诉 HAL 和 Gralloc:这块 buffer 会被谁用、怎么用。
常见的 usage 标志:
- GRALLOC_USAGE_SW_READ_OFTEN:CPU 会频繁读取。比如你用 ImageReader 拿 YUV 数据做算法。
- GRALLOC_USAGE_SW_WRITE_OFTEN:CPU 会频繁写入。比如你从网络传一张图进来。
- GRALLOC_USAGE_HW_TEXTURE:GPU 会作为纹理使用。比如用在 SurfaceView 或 GLSurfaceView 里。
- GRALLOC_USAGE_HW_VIDEO_ENCODER:视频编码器会用。录像流必须带这个标志。
- GRALLOC_USAGE_HW_CAMERA_WRITE:Camera HAL 会写入。所有 OUTPUT 流都应该带这个。
注意:usage 标志位不是随便组合的。比如 SW_READ_OFTEN 和 HW_VIDEO_ENCODER 同时出现,可能会导致 buffer 分配失败。因为有些硬件不支持 CPU 和编码器同时访问同一块内存。
我个人的经验是:usage 标志能少就少。只加你真正需要的。比如预览流,一般只需要 GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_CAMERA_WRITE。录像流再加一个 GRALLOC_USAGE_HW_VIDEO_ENCODER。别画蛇添足。
4.4 实战:一个典型的 Stream 配置
咱们来看一个实际例子。假设你要配置三个 Stream:
// 预览流:1080p,IMPLEMENTATION_DEFINED
stream_preview.stream_type = CAMERA3_STREAM_OUTPUT;
stream_preview.width = 1920;
stream_preview.height = 1080;
stream_preview.format = HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED;
stream_preview.usage = GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_CAMERA_WRITE;
// 拍照流:JPEG 输出
stream_jpeg.stream_type = CAMERA3_STREAM_OUTPUT;
stream_jpeg.width = 4032;
stream_jpeg.height = 3024;
stream_jpeg.format = HAL_PIXEL_FORMAT_BLOB;
stream_jpeg.usage = GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_HW_CAMERA_WRITE;
// 重处理输入流:YUV 格式
stream_input.stream_type = CAMERA3_STREAM_INPUT;
stream_input.width = 1920;
stream_input.height = 1080;
stream_input.format = HAL_PIXEL_FORMAT_YCbCr_420_888;
stream_input.usage = GRALLOC_USAGE_SW_WRITE_OFTEN;
你看,每个 Stream 的 type、format、resolution、usage 都是配套的。预览流追求性能,所以用 IMPLEMENTATION_DEFINED 和 HW_TEXTURE。拍照流要输出 JPEG,所以用 BLOB 格式,并且允许 CPU 读取。输入流是给重处理用的,所以 CPU 写入。
嗯,这里有个细节:BLOB 格式的 usage 为什么加 SW_READ_OFTEN? 因为 JPEG 数据最终要交给应用层,应用层是通过 CPU 来读取 buffer 的。如果不加这个标志,Gralloc 可能会分配一块 GPU 专用的内存,CPU 读不了,那就出大问题了。
4.5 避坑指南
最后,我总结几个常见的坑,都是我曾经踩过的:
- Stream 类型搞反:把 INPUT 配成了 OUTPUT,或者反过来。configure_streams 会直接报错。
- 格式不匹配:比如 Sensor 只输出 YUV,但你配了 RAW。HAL 在 configure_streams 里会拒绝。
- usage 冲突:同时加了 SW_READ_OFTEN 和 HW_VIDEO_ENCODER,导致 buffer 分配失败。
- 分辨率超出 Sensor 能力:配了一个 Sensor 不支持的尺寸,HAL 直接返回 -EINVAL。
- 忘记加 HW_CAMERA_WRITE:OUTPUT 流不加这个,HAL 写入 buffer 时可能会崩溃。
好了,关于 camera3_stream_t 的核心内容就这些。记住一句话:Stream 配置是 Camera HAL 的基石,配错了,后面全是白搭。下一章咱们聊聊 camera3_callback_ops_t,看看框架是怎么从 HAL 拿数据的。