第四节:Stream分辨率与格式选择——如何根据Sensor能力选择最优分辨率与PixelFormat
各位做Camera HAL的兄弟,咱们今天聊个实战中绕不开的话题:Stream配置。
说白了,就是App要一个流,Sensor能出什么流,中间怎么匹配。我见过太多开发者在分辨率与格式选择上翻车,要么性能拉胯,要么兼容性爆炸。今天我把这些年踩过的坑、总结的经验,一次性讲清楚。
4.1 先搞清楚Sensor的“底牌”
拿到一颗Sensor,第一件事不是写代码,而是看它的能力表。我个人习惯先问三个问题:
- Sensor最大输出分辨率是多少?
- 支持哪些PixelFormat?
- 不同分辨率下,帧率能跑到多少?
举个例子,IMX586最大支持48M(8000x6000),但如果你要跑60fps,它只能降到1080p。这就是Sensor的“底牌”——不是所有分辨率都能跑满帧率。
核心原则:Stream配置的本质,是在App需求、Sensor能力、系统性能三者之间找平衡点。
4.2 分辨率选择:不是越大越好
很多App开发者喜欢直接要最大分辨率,觉得“清晰”。但实际项目中,我遇到过好几次这样的场景:
App要了4K预览流,Sensor确实支持4K,但ISP处理不过来,导致预览掉帧到15fps。用户一看,画面卡成PPT,体验极差。
我的建议是:
- 预览流:选1080p或720p,保证30fps以上。人眼对流畅度更敏感。
- 拍照流:用Sensor最大分辨率,但注意拍照时预览流可以降级。
- 视频流:根据目标帧率选分辨率。比如要60fps,1080p通常比4K更稳。
小技巧:我习惯在HAL层维护一个“分辨率-帧率”映射表。App请求时,直接查表返回最优组合,省去实时计算的开销。
4.3 PixelFormat选择:YUV还是RAW?这是个问题
PixelFormat的选择,直接影响图像质量和处理效率。常见的格式有:
| 格式 | 典型用途 | 特点 |
|---|---|---|
| NV12/NV21 | 预览、视频编码 | YUV420格式,兼容性好,编码器直接吃 |
| YV12 | 老平台兼容 | 平面格式,内存占用稍大 |
| RAW10/RAW16 | 拍照、算法处理 | 保留Sensor原始数据,后期处理空间大 |
| JPEG | 拍照输出 | 压缩格式,直接存盘 |
嗯,这里要注意:不是所有Sensor都支持所有格式。比如有些老Sensor只出RAW,需要ISP转成YUV。如果你直接要NV12,HAL层就得做格式转换,性能损耗不小。
我曾经在一个项目里,App坚持要NV21,但Sensor只出RAW10。结果HAL层每帧都要做RAW到NV21的转换,CPU占用飙到80%,发热严重。后来我建议App改用YV12,转换开销降了一半。
避坑指南:我曾经因为没检查Sensor的format支持列表,直接硬编码NV12,结果在某个平台上预览全黑。查了两天才发现,那颗Sensor只支持NV21。所以,永远不要假设Sensor支持什么格式,一定要通过驱动或datasheet确认。
4.4 实战:如何做最优匹配
好了,理论说完了,咱们看代码。下面是我在HAL层常用的匹配逻辑:
// 伪代码:Stream配置匹配
status_t configureStreams(...) {
// 1. 获取Sensor能力
SensorCapabilities caps = getSensorCapabilities(sensorId);
// 2. 遍历App请求的流
for (auto &stream : streams) {
// 3. 检查分辨率是否支持
if (!caps.supportsResolution(stream.width, stream.height)) {
// 降级到最接近的支持分辨率
stream.width = findClosestSupportedWidth(stream.width);
stream.height = findClosestSupportedHeight(stream.height);
}
// 4. 检查PixelFormat是否支持
if (!caps.supportsFormat(stream.format)) {
// 选择最接近的格式
stream.format = findBestFormat(stream.format, caps);
}
// 5. 检查帧率是否可达
int maxFps = caps.getMaxFps(stream.width, stream.height);
if (stream.fps > maxFps) {
// 降低帧率或分辨率
stream.fps = maxFps;
}
}
// 6. 配置Sensor
return configureSensor(streams);
}
这段逻辑看起来简单,但实际项目中要考虑的细节很多。比如:
- 降级策略:分辨率降级时,保持宽高比。否则画面会拉伸。
- 格式优先级:如果Sensor支持NV12和NV21,优先选NV12,因为编码器对NV12支持更好。
- 帧率妥协:如果App要60fps但Sensor只能跑30fps,我建议直接拒绝请求,而不是偷偷降帧率。否则App端会以为自己在跑60fps,实际只有30fps,导致时间戳计算错误。
核心观点:Stream配置不是简单的“能跑就行”,而是要保证整个Pipeline的稳定性和效率。你想想看,一个错误的配置可能导致预览卡顿、拍照延迟、编码丢帧,甚至系统崩溃。
4.5 总结一下
今天聊的内容,说白了就是三件事:
- 分辨率:根据用途选,预览用1080p,拍照用最大,视频看帧率。
- 格式:优先选Sensor原生支持的,避免不必要的格式转换。
- 匹配:永远做降级处理,但要让App知道实际配置。
我个人习惯在HAL层加一个调试接口,可以实时打印当前Stream配置。这样出问题时,一眼就能看出是分辨率不对还是格式不匹配。嗯,这个习惯帮我省了不少排查时间。
下一节,咱们聊聊多Camera场景下的Stream同步问题。那个更刺激,多个Sensor同时出流,配置复杂度直接翻倍。到时候见!