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 ms72%
ISP处理3 ms18%
数据搬运1 ms6%
调度开销0.67 ms4%
总帧周期16.67 ms100%

你看,传感器占了将近四分之三。所以很多时候,瓶颈不在ISP,而在前端。

我的经验: 做帧率预算时,别只盯着ISP的算力。先看看传感器能不能扛得住。我曾经在一个项目里,ISP明明能跑120fps,结果传感器只能出60fps——白折腾。

4.2 帧率与延迟的权衡

这里有个容易搞混的地方。很多人以为帧率翻倍,延迟就减半。其实不是这么回事。

延迟(Latency)指的是从光线进入传感器,到图像数据输出,这一整条链路的时间。它跟帧率有关系,但不是简单的反比。

我画个图你就明白了:

帧率 vs 延迟:不同Pipeline深度下的关系 帧率 (FPS) 0 30 60 90 120 0 33ms 66ms 100ms 延迟 浅Pipeline(3帧) 深Pipeline(6帧) 60fps→20ms 60fps→50ms 关键观察: 帧率从30→60fps时, 延迟下降明显;再往上就平缓了

看到没?两条曲线都随着帧率升高而下降,但下降速度越来越慢。而且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 fps33.33 ms300.3 ms/s (0.03%)
60 fps16.67 ms600.6 ms/s (0.06%)
120 fps8.33 ms1201.2 ms/s (0.12%)
240 fps4.17 ms2402.4 ms/s (0.24%)
480 fps2.08 ms4804.8 ms/s (0.48%)

看起来占比不高?但别忘了,中断处理期间CPU是关中断的。如果其他高优先级任务被堵住了,系统响应就会出问题。

我曾经踩过的坑: 在一个480fps的工业检测项目中,中断处理函数里做了太多事情——状态查询、统计更新、甚至还有日志打印。结果CPU 30%的时间都在处理中断。后来我把中断处理拆成上半部和下半部,上半部只做最必要的寄存器操作,下半部用tasklet处理。CPU占用率降到了3%以下。

挑战四:帧同步与帧丢失

高帧率下,帧同步信号(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行,延迟能降低三个数量级。

我的建议: 行级流水对硬件设计要求高,需要足够的line buffer。但如果你做的是实时性要求极高的应用(比如自动驾驶、工业视觉),这个投入是值得的。我在一个ADAS项目里用行级流水,把延迟从33ms降到了不到2ms。

思路四:动态帧率调节

不是所有场景都需要高帧率。静止场景下,15fps就够了。运动场景才需要60fps甚至更高。

动态帧率调节的思路是:根据场景运动量,自动调整帧率。运动多的时候提帧率,运动少的时候降帧率。这样既保证了流畅度,又控制了延迟和功耗。

我常用的判断依据是:

  • 帧间差异(SAD/MAD)超过阈值 → 运动剧烈 → 提高帧率
  • 帧间差异低于阈值 → 静止/缓慢 → 降低帧率
  • 用户交互事件(触摸、按键)→ 临时提高帧率保证响应

思路五:优先级抢占调度

在ISP Pipeline里,不同模块的实时性要求不一样。比如自动曝光(AE)需要快速响应光照变化,而色彩校正(CCM)可以慢半拍。

低延迟模式下,给高实时性模块更高的优先级。当帧间隔时间紧张时,优先保证这些模块的执行,低优先级模块可以延迟到下一帧处理。

低延迟模式设计清单:
  1. 评估系统允许的最大延迟(比如10ms)
  2. 计算当前Pipeline的理论延迟
  3. 如果超标,优先减少Pipeline深度
  4. 其次考虑零拷贝直通路径
  5. 如果还不够,上行车级流水
  6. 最后用动态帧率调节和优先级抢占做精细控制

嗯,说到这儿,帧率与延迟的关系基本讲透了。记住一句话:高帧率不等于低延迟,低延迟需要专门的设计。下次做ISP架构设计的时候,先问问自己——我到底要的是高吞吐,还是快响应?这两个目标,得分开对待。