4、帧率与延迟的关系
帧率和延迟,这两个概念在ISP Pipeline里就像一对冤家。你想想看,我们做图像处理,既想要画面流畅(高帧率),又想要反应快(低延迟)。但现实往往是——你追一个,另一个就跑了。
我刚开始做ISP架构那会儿,总觉得帧率上去了,延迟自然就下来了。后来被现实狠狠教育了一顿。今天咱们就把这笔账算清楚。
4.1 帧率计算公式
先看最基础的东西。帧率(FPS)怎么算?说白了就是单位时间内能处理多少帧图像。
FPS = 1 / T_frame
其中 T_frame = T_sensor + T_isp + T_transfer + T_overhead
这里T_frame就是一帧的总耗时。我习惯把它拆成四段:
- T_sensor:传感器曝光+读出时间。这个通常占大头。
- T_isp:ISP Pipeline处理时间。包括RAW域、RGB域、YUV域所有模块。
- T_transfer:数据搬运时间。从传感器到ISP,再从ISP到内存。
- T_overhead:调度开销、帧同步等待、中断响应等。
举个例子。假设一个1080P@60fps的系统:
| 阶段 | 耗时 | 占比 |
|---|---|---|
| 传感器曝光+读出 | 12 ms | 72% |
| ISP处理 | 3 ms | 18% |
| 数据搬运 | 1 ms | 6% |
| 调度开销 | 0.67 ms | 4% |
| 总帧周期 | 16.67 ms | 100% |
你看,传感器占了将近四分之三。所以很多时候,瓶颈不在ISP,而在前端。
4.2 帧率与延迟的权衡
这里有个容易搞混的地方。很多人以为帧率翻倍,延迟就减半。其实不是这么回事。
延迟(Latency)指的是从光线进入传感器,到图像数据输出,这一整条链路的时间。它跟帧率有关系,但不是简单的反比。
我画个图你就明白了:
看到没?两条曲线都随着帧率升高而下降,但下降速度越来越慢。而且Pipeline越深,延迟就越高——哪怕帧率一样。
为什么会这样?因为延迟不光取决于帧周期,还取决于Pipeline里缓存了多少帧。我管这个叫「帧驻留量」。
系统延迟 ≈ (Pipeline深度 + 1) × 帧周期 + 传感器曝光时间
举个例子:Pipeline深度=4帧,帧周期=16.67ms(60fps),传感器曝光=10ms
延迟 ≈ (4+1) × 16.67 + 10 = 93.35 ms
所以你看,就算帧率到了120fps(帧周期8.33ms),如果Pipeline深度还是4帧,延迟也有 (5×8.33)+10 ≈ 51.7ms。比60fps时只降了不到一半。
4.3 高帧率场景下的调度挑战
高帧率(比如120fps以上)带来的问题,远不止算力不够这么简单。我踩过的坑,一个一个说。
挑战一:帧间隔时间被压缩
60fps时,你有16.67ms来处理一帧。120fps时,只剩8.33ms。240fps呢?4.17ms。
这意味着什么?你的ISP Pipeline必须在更短的时间内完成所有模块。如果某个模块超时了,整个帧就丢了。
挑战二:DDR带宽争抢
帧率翻倍,数据量也翻倍。DDR带宽就这么大,你抢我抢大家抢。我见过一个项目,高帧率模式下ISP和显示控制器在DDR端口上打架,结果帧率反而掉下来了。
挑战三:中断风暴
每帧都要产生中断。60fps时每秒60次中断,120fps就是120次。CPU处理中断也是要时间的。如果中断处理函数写得不够轻量,CPU就被拖死了。
| 帧率 | 帧周期 | 每秒中断数 | 中断处理开销(假设10μs/次) |
|---|---|---|---|
| 30 fps | 33.33 ms | 30 | 0.3 ms/s (0.03%) |
| 60 fps | 16.67 ms | 60 | 0.6 ms/s (0.06%) |
| 120 fps | 8.33 ms | 120 | 1.2 ms/s (0.12%) |
| 240 fps | 4.17 ms | 240 | 2.4 ms/s (0.24%) |
| 480 fps | 2.08 ms | 480 | 4.8 ms/s (0.48%) |
看起来占比不高?但别忘了,中断处理期间CPU是关中断的。如果其他高优先级任务被堵住了,系统响应就会出问题。
挑战四:帧同步与帧丢失
高帧率下,帧同步信号(VSYNC)来得更快。如果你的ISP Pipeline还没处理完上一帧,下一帧就来了——要么丢帧,要么覆盖。这两种情况都会导致画面撕裂或卡顿。
4.4 低延迟模式设计思路
好,前面说了这么多问题,那怎么解决?我总结了几种低延迟模式的设计思路,都是实战中验证过的。
思路一:减少Pipeline深度
这是最直接的办法。把Pipeline从4帧压缩到2帧,延迟直接减半。但代价是并行度降低,吞吐量可能下降。
我习惯的做法是:在低延迟模式下,把一些非关键的模块(比如某些统计计算、后处理美化)跳过或简化。只保留核心的RAW域处理和色彩重建。
思路二:零拷贝与流水线直通
传统做法是:传感器→DDR→ISP→DDR→输出。每一进一出DDR,延迟就多一截。
低延迟模式应该走直通路径:传感器→ISP→输出,中间不经过DDR。这叫「零拷贝流水线」。
// 传统模式(高延迟)
sensor -> DMA_to_DDR -> ISP_load_from_DDR -> ISP_process -> ISP_store_to_DDR -> DMA_to_display
// 低延迟模式(零拷贝)
sensor -> ISP_linebuffer -> ISP_process -> output_fifo -> display
思路三:行级流水代替帧级流水
帧级流水是以帧为单位调度。行级流水是以行为单位。什么意思呢?传感器出一行数据,ISP就开始处理这一行,不用等整帧都出来。
这样做的好处是:延迟从「帧周期×Pipeline深度」降到了「行周期×Pipeline深度」。对于1080P图像,一帧有1080行,延迟能降低三个数量级。
思路四:动态帧率调节
不是所有场景都需要高帧率。静止场景下,15fps就够了。运动场景才需要60fps甚至更高。
动态帧率调节的思路是:根据场景运动量,自动调整帧率。运动多的时候提帧率,运动少的时候降帧率。这样既保证了流畅度,又控制了延迟和功耗。
我常用的判断依据是:
- 帧间差异(SAD/MAD)超过阈值 → 运动剧烈 → 提高帧率
- 帧间差异低于阈值 → 静止/缓慢 → 降低帧率
- 用户交互事件(触摸、按键)→ 临时提高帧率保证响应
思路五:优先级抢占调度
在ISP Pipeline里,不同模块的实时性要求不一样。比如自动曝光(AE)需要快速响应光照变化,而色彩校正(CCM)可以慢半拍。
低延迟模式下,给高实时性模块更高的优先级。当帧间隔时间紧张时,优先保证这些模块的执行,低优先级模块可以延迟到下一帧处理。
- 评估系统允许的最大延迟(比如10ms)
- 计算当前Pipeline的理论延迟
- 如果超标,优先减少Pipeline深度
- 其次考虑零拷贝直通路径
- 如果还不够,上行车级流水
- 最后用动态帧率调节和优先级抢占做精细控制
嗯,说到这儿,帧率与延迟的关系基本讲透了。记住一句话:高帧率不等于低延迟,低延迟需要专门的设计。下次做ISP架构设计的时候,先问问自己——我到底要的是高吞吐,还是快响应?这两个目标,得分开对待。