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_OFTENHW_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 避坑指南

最后,我总结几个常见的坑,都是我曾经踩过的:

  1. Stream 类型搞反:把 INPUT 配成了 OUTPUT,或者反过来。configure_streams 会直接报错。
  2. 格式不匹配:比如 Sensor 只输出 YUV,但你配了 RAW。HAL 在 configure_streams 里会拒绝。
  3. usage 冲突:同时加了 SW_READ_OFTEN 和 HW_VIDEO_ENCODER,导致 buffer 分配失败。
  4. 分辨率超出 Sensor 能力:配了一个 Sensor 不支持的尺寸,HAL 直接返回 -EINVAL。
  5. 忘记加 HW_CAMERA_WRITE:OUTPUT 流不加这个,HAL 写入 buffer 时可能会崩溃。

好了,关于 camera3_stream_t 的核心内容就这些。记住一句话:Stream 配置是 Camera HAL 的基石,配错了,后面全是白搭。下一章咱们聊聊 camera3_callback_ops_t,看看框架是怎么从 HAL 拿数据的。