第三章: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:

  1. 主摄预览流(YUV)
  2. 副摄预览流(YUV,用于视差计算)
  3. 输出流(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上“打补丁”——底层硬件管线通常不支持动态修改参数。

切换流程:

  1. App下发特性切换命令
  2. HAL停止所有正在进行的Capture
  3. 调用configure_streams释放旧Stream
  4. 根据新特性创建对应的Stream配置
  5. 重新启动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配置问题。咱们下章见。