4、初始化Stream:configure_streams的输入输出参数详解

好,咱们接着往下走。上一章我们聊了open()之后拿到了camera device,接下来要干什么?嗯,得告诉底层:「我要拍照了,你准备好Stream等着。」这个动作就是configure_streams

说白了,configure_streams就是HAL层和Framework之间的一次「握手」。Framework说:「我要这些格式、这些分辨率的Stream。」HAL层看看自己能不能搞定,然后回一句:「行,我帮你配好。」或者:「不行,你得改改。」

我个人习惯把这一步叫做「Stream的初始化谈判」。为什么是谈判?因为输入参数和输出参数之间,往往不是完全一致的。HAL层可能会根据硬件能力做一些调整。

4.1 函数原型回顾

先看一眼函数长什么样:

int configure_streams(
    const struct camera3_device *device,
    camera3_stream_configuration_t *stream_list
);

注意看,第二个参数是个指针。这意味着什么?Framework传进来,HAL层可以改它。嗯,这里就是谈判的入口。

4.2 输入参数详解

输入参数藏在camera3_stream_configuration_t这个结构体里。我把它拆开来讲。

成员 类型 说明
num_streams uint32_t Stream的数量,通常1~3个
streams camera3_stream_t ** 指向Stream指针数组的指针
operation_mode uint32_t 操作模式,比如NORMAL、ZSL等

num_streams:这个值一般不大。预览+拍照,两个Stream。如果加上录像,三个。我在项目中遇到过Framework一口气传进来5个Stream的情况,当时我就知道——嗯,这手机硬件肯定扛不住。

streams:这是一个二级指针。每个camera3_stream_t描述一个Stream的详细信息。包括宽、高、格式、用途等等。Framework会先填好这些信息,然后交给HAL层审核。

operation_mode:这个字段很关键。它告诉HAL层:「我现在要干什么。」比如:

  • CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE:普通模式,预览+拍照
  • CAMERA3_STREAM_CONFIGURATION_ZSL_MODE:零延时模式,拍照时不能有黑屏
  • CAMERA3_STREAM_CONFIGURATION_CONSTRAINED_HIGH_SPEED_MODE:高速模式,比如慢动作

为什么要有这个?你想想看,不同模式下,HAL层对Buffer的管理策略完全不同。ZSL模式下,我得提前准备好几个Buffer轮转,不能等用户按下快门才去抓图。

4.3 每个Stream的输入参数

每个Stream由camera3_stream_t描述。Framework会填好以下字段:

字段 方向 说明
stream_type 输入 CAMERA3_STREAM_OUTPUT / INPUT / BIDIRECTIONAL
width 输入 期望的宽度,比如1920
height 输入 期望的高度,比如1080
format 输入 像素格式,HAL_PIXEL_FORMAT_xxx
usage 输入 使用标记,比如GRALLOC_USAGE_HW_TEXTURE
max_buffers 输出 HAL层告诉Framework需要多少个Buffer

stream_type:大部分情况下是OUTPUT。拍照和预览都是输出Stream。但如果你要做RAW回显,可能会用到BIDIRECTIONAL。我建议初学者先别碰这个,容易搞混。

width/height:Framework会根据应用层设置的分辨率来填。比如你打开相机App,选了4:3比例,那这里可能就是3264x2448。但HAL层不一定支持这个分辨率。怎么办?HAL层可以改。

format:常见的有:

  • HAL_PIXEL_FORMAT_YCbCr_420_888:YUV格式,预览常用
  • HAL_PIXEL_FORMAT_BLOB:JPEG压缩数据,拍照用
  • HAL_PIXEL_FORMAT_RAW16:RAW数据,专业模式用

这里有个坑。Framework可能会传一个HAL层不支持的格式。比如它想要NV12,但你的ISP只支持NV21。这时候HAL层必须拒绝,或者协商成支持的格式。

注意:format字段在输入时由Framework填写,但HAL层可以修改。不过修改后必须保证Framework能接受。我曾经遇到一个案子,HAL层把YUV改成了NV21,结果Framework那边解码器不认,直接黑屏。

4.4 输出参数详解

输出参数其实就藏在同一个结构体里。HAL层通过修改camera3_stream_t的某些字段来告诉Framework:「我帮你调整了一下。」

主要修改的字段有:

  • width/height:如果硬件不支持原始分辨率,HAL层会改成最接近的。比如Framework要1920x1088,但硬件只支持1920x1080,那就改成1080。
  • format:如果格式不兼容,HAL层可以改成硬件原生支持的格式。
  • usage:HAL层可以添加额外的usage标记。比如Framework只写了GRALLOC_USAGE_HW_TEXTURE,但HAL层需要CPU访问,就会加上GRALLOC_USAGE_SW_READ_OFTEN。
  • max_buffers:这个很重要。HAL层告诉Framework:「这个Stream,我至少需要N个Buffer才能正常工作。」Framework会根据这个值去分配Buffer池。

嗯,这里有个细节。max_buffers是HAL层输出的,但Framework不一定完全听你的。它可能会多分配几个,也可能少分配。不过一般来说,Framework会尊重这个值。

经验之谈:max_buffers不要设太大。设大了浪费内存,设小了可能丢帧。我一般预览Stream设3~4个,拍照Stream设2个。如果是ZSL模式,预览Stream可能需要5~6个。

4.5 返回值与错误处理

configure_streams的返回值很简单:

  • 0:成功。Stream配置完成。
  • -EINVAL:参数无效。比如num_streams为0,或者某个Stream的格式完全不可接受。
  • -ENODEV:设备异常。比如camera sensor掉电了。
  • -EUSERS:资源不足。比如内存不够分配Buffer。

我遇到过最头疼的是-EINVAL。Framework传了一个奇怪的组合,比如两个Stream都是INPUT类型。这明显不合理。HAL层直接返回-EINVAL,Framework那边就会报错,然后App闪退。

所以,我建议在configure_streams里做充分的校验。不要等到后面process_capture_request时才发现问题,那时候就晚了。

4.6 一个典型的配置流程

咱们走一遍实际流程,你感受一下:

  1. Framework调用configure_streams,传入2个Stream:预览(1920x1080, YUV)和拍照(3264x2448, BLOB)。
  2. HAL层检查硬件能力。发现预览分辨率支持,但拍照分辨率不支持3264x2448,只支持3264x2448的近似值——3264x2448本身。嗯,这个例子有点巧,实际上很多sensor支持这个分辨率。
  3. HAL层检查format。BLOB格式没问题,因为JPEG编码器在ISP里。
  4. HAL层计算Buffer需求。预览需要4个Buffer,拍照需要2个Buffer。写入max_buffers。
  5. HAL层返回0,表示成功。

如果HAL层发现某个Stream完全无法支持,比如Framework要一个4K 60fps的预览Stream,但硬件只能跑4K 30fps。这时候怎么办?

嗯,我一般会返回-EINVAL,并在log里打印具体原因。Framework收到错误后,会尝试降低分辨率重新配置。

4.7 避坑指南

最后,分享几个我踩过的坑:

  • 不要修改num_streams:HAL层不能增减Stream的数量。Framework传进来几个就是几个。如果你想增加,得通过其他方式协商。
  • usage标记要谨慎:添加usage标记前,先确认Gralloc分配器是否支持。我遇到过在某个平台上加了GRALLOC_USAGE_PROTECTED,结果分配Buffer一直失败。
  • 注意Stream的顺序:Framework可能依赖Stream在数组中的顺序。比如第一个Stream是预览,第二个是拍照。HAL层不要随意调换顺序。
  • operation_mode不能改:这个字段是Framework定的,HAL层只能读,不能写。如果你发现模式不支持,直接返回错误。

好了,关于configure_streams的输入输出参数,咱们就聊到这里。下一章,我会带你看看HAL层内部是怎么处理这些Stream的——包括Buffer的分配、格式转换、以及如何跟ISP打交道。到时候见。