第四节: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 总结一下

今天聊的内容,说白了就是三件事:

  1. 分辨率:根据用途选,预览用1080p,拍照用最大,视频看帧率。
  2. 格式:优先选Sensor原生支持的,避免不必要的格式转换。
  3. 匹配:永远做降级处理,但要让App知道实际配置。

我个人习惯在HAL层加一个调试接口,可以实时打印当前Stream配置。这样出问题时,一眼就能看出是分辨率不对还是格式不匹配。嗯,这个习惯帮我省了不少排查时间。

下一节,咱们聊聊多Camera场景下的Stream同步问题。那个更刺激,多个Sensor同时出流,配置复杂度直接翻倍。到时候见!