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层必须拒绝,或者协商成支持的格式。
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会尊重这个值。
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 一个典型的配置流程
咱们走一遍实际流程,你感受一下:
- Framework调用
configure_streams,传入2个Stream:预览(1920x1080, YUV)和拍照(3264x2448, BLOB)。 - HAL层检查硬件能力。发现预览分辨率支持,但拍照分辨率不支持3264x2448,只支持3264x2448的近似值——3264x2448本身。嗯,这个例子有点巧,实际上很多sensor支持这个分辨率。
- HAL层检查format。BLOB格式没问题,因为JPEG编码器在ISP里。
- HAL层计算Buffer需求。预览需要4个Buffer,拍照需要2个Buffer。写入max_buffers。
- 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打交道。到时候见。