第三章:Camera特性与Stream的映射关系
不同特性(HDR、夜景、人像)对Stream配置的影响
好,咱们直接进入正题。这一章聊的是Camera特性怎么跟Stream配置挂钩。说白了,就是你在相机App里点一下“HDR”或者“夜景”模式,底层HAL层到底发生了什么变化。
我刚开始做HAL开发时,总觉得这些特性就是上层传几个参数下来,底层改改ISP寄存器就完事了。后来踩了坑才发现——Stream的配置才是真正的“地基”。地基没打好,上层再怎么调算法都是白搭。
3.1 HDR模式:多帧合成的“流”设计
HDR,高动态范围。它的核心思想很简单:拍多张不同曝光的照片,合成一张亮部和暗部都清晰的图。但问题来了——这“多张”怎么通过Stream来表达?
我个人习惯把HDR的Stream配置分成两类:单流多帧和多流多帧。
3.1.1 单流多帧模式
这种模式下,App只请求一个输出Stream(比如YUV或JPEG),但底层HAL会内部触发多次Capture。每次Capture的曝光参数不同,最后在HAL内部完成合成。
配置要点:
- Stream的width/height固定为最终输出尺寸
- format通常选择YUV_420_888(便于算法处理)
- usageFlags需要包含GRALLOC_USAGE_HW_CAMERA_WRITE和GRALLOC_USAGE_HW_VIDEO_ENCODER(如果涉及编码)
关键点:单流模式下,HAL需要自己管理内部buffer队列。我建议至少分配3个buffer用于多帧合成,否则容易出现“生产者等消费者”的死锁。
3.1.2 多流多帧模式
这种模式更“正统”。App会创建多个Stream,比如一个用于预览,一个用于HDR合成。每个Stream可以独立配置不同的曝光参数。
我在项目中遇到过一种情况:某平台的多流HDR模式下,预览流和HDR流共用同一个ISP管线,导致预览帧率掉到15fps。后来怎么解决的?给预览流单独分配了一条轻量级管线,才把帧率拉回来。
配置示例(伪代码):
// HDR多流配置
camera_stream_t previewStream;
previewStream.stream_type = CAMERA_STREAM_OUTPUT;
previewStream.width = 1920;
previewStream.height = 1080;
previewStream.format = HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED;
previewStream.usage = GRALLOC_USAGE_HW_TEXTURE;
camera_stream_t hdrStream;
hdrStream.stream_type = CAMERA_STREAM_OUTPUT;
hdrStream.width = 1920;
hdrStream.height = 1080;
hdrStream.format = HAL_PIXEL_FORMAT_YCbCr_420_888;
hdrStream.usage = GRALLOC_USAGE_HW_CAMERA_WRITE | GRALLOC_USAGE_SW_READ_OFTEN;
注意:多流模式下,不同Stream的width/height必须一致(至少在同一组配置中)。否则底层ISP可能无法同时处理不同分辨率的HDR合成。
3.2 夜景模式:长曝光与多帧降噪的“流”博弈
夜景模式,说白了就是“用时间换画质”。它通常涉及长曝光(比如1/4秒甚至更久)和多帧降噪。这对Stream配置的影响主要在两个方面:帧率控制和buffer深度。
3.2.1 帧率控制
夜景模式下,预览流通常需要降低帧率。为什么?因为长曝光时,如果预览还保持30fps,那每一帧的曝光时间就会被压缩,夜景效果出不来。
我建议的做法是:在configure_streams时,将预览Stream的max_fps设置为15fps或更低。同时,给夜景合成流单独设置一个更大的buffer count(比如8个),用于多帧降噪。
配置示例:
// 夜景模式Stream配置
camera_stream_t previewStream;
previewStream.max_buffers = 4; // 预览buffer数
previewStream.usage = GRALLOC_USAGE_HW_TEXTURE;
camera_stream_t nightStream;
nightStream.max_buffers = 8; // 夜景合成需要更多buffer
nightStream.usage = GRALLOC_USAGE_HW_CAMERA_WRITE | GRALLOC_USAGE_SW_READ_OFTEN;
小技巧:夜景模式下,我习惯把预览流的format改成HAL_PIXEL_FORMAT_YCbCr_420_888,而不是IMPLEMENTATION_DEFINED。这样HAL可以直接在YUV域做降噪,省去一次颜色空间转换的开销。
3.2.2 buffer深度与内存管理
夜景模式需要多帧合成,buffer数量直接决定了降噪效果。但buffer多了,内存压力也大。我记得有一次在低端平台上做夜景,8个1080p的YUV buffer直接吃掉了200多MB内存,导致系统OOM。
怎么破?两个思路:
- 动态buffer分配:只在夜景模式开启时才分配大buffer池,退出时释放
- 压缩格式:使用NV12或NV21等半平面格式,比全平面YUV节省30%内存
3.3 人像模式:深度信息的“流”通道
人像模式的核心是“背景虚化”。这需要深度信息,而深度信息通常来自双摄或ToF传感器。在Stream层面,这意味着需要额外的深度流或辅助流。
3.3.1 双摄人像的Stream拓扑
典型的双摄人像模式需要至少3个Stream:
- 主摄预览流(YUV)
- 副摄预览流(YUV,用于视差计算)
- 输出流(JPEG或YUV,带虚化效果)
配置时要注意:主副摄的Stream尺寸必须匹配,否则深度算法会报错。我见过一个案例,主摄配了4032x3024,副摄配了1920x1080,结果深度图全是噪点——因为分辨率不匹配导致视差计算失败。
配置示例:
// 人像模式Stream配置(双摄)
camera_stream_t mainPreview;
mainPreview.width = 1920;
mainPreview.height = 1080;
mainPreview.physical_camera_id = "0"; // 主摄
camera_stream_t auxPreview;
auxPreview.width = 1920;
auxPreview.height = 1080;
auxPreview.physical_camera_id = "1"; // 副摄
camera_stream_t outputStream;
outputStream.width = 1920;
outputStream.height = 1080;
outputStream.format = HAL_PIXEL_FORMAT_BLOB; // JPEG输出
3.3.2 深度流的特殊处理
如果使用ToF传感器,深度流通常是单独的Stream,format为HAL_PIXEL_FORMAT_Y16(16位深度数据)。这个Stream不需要显示,所以usage可以设为GRALLOC_USAGE_SW_READ_OFTEN。
避坑指南:我曾经在配置深度流时忘了设置data_space为HAL_DATASPACE_DEPTH,结果上层一直拿不到有效的深度数据。检查了半天才发现是data_space没配对。嗯,这个坑大家注意一下。
3.4 特性切换时的Stream重配置
用户从普通模式切换到HDR、夜景或人像时,HAL需要重新配置Stream。这个过程叫“stream reconfiguration”。
我建议的做法是:在特性切换前,先调用configure_streams释放旧Stream,再创建新Stream。不要试图在原有Stream上“打补丁”——底层硬件管线通常不支持动态修改参数。
切换流程:
- App下发特性切换命令
- HAL停止所有正在进行的Capture
- 调用configure_streams释放旧Stream
- 根据新特性创建对应的Stream配置
- 重新启动Capture
注意:Stream重配置期间,预览画面会黑屏几百毫秒。这是正常的,但用户体验不好。我一般会在HAL层做一个“最后一帧冻结”的处理——在重配置期间,持续输出最后一帧预览画面,避免黑屏。
3.5 总结:一张表看懂映射关系
| Camera特性 | Stream数量 | 关键配置项 | 常见坑点 |
|---|---|---|---|
| HDR(单流) | 1个输出流 | format=YUV_420_888, buffer≥3 | 内部buffer死锁 |
| HDR(多流) | 2个输出流 | 预览流+合成流,尺寸一致 | ISP管线竞争导致帧率下降 |
| 夜景 | 1~2个输出流 | max_fps≤15, buffer≥8 | 内存占用过高 |
| 人像(双摄) | 3个输出流 | 主副摄尺寸匹配,物理ID正确 | 深度流data_space未配置 |
| 人像(ToF) | 2个输出流+1个深度流 | 深度流format=Y16, usage=SW_READ | 深度数据对齐问题 |
好了,这一章的内容就这些。说白了,不同Camera特性对Stream配置的影响,核心就是“多帧、多流、多buffer”这三个维度。你只要把这三个维度想清楚,配置就不会出大问题。
下一章我们会聊Stream配置的调试与性能优化,到时候我会分享一些实际项目中的调优案例,包括怎么用dumpsys和systrace定位Stream配置问题。咱们下章见。