4.15. 帧缓冲区

摄像头传感器初始化完成后,会按其帧率持续输出帧画面——无论应用程序是否准备好接收,每个帧周期都会产生一个新帧。每一帧都需要在 RAM 中有落脚之处,否则就会丢失。帧缓冲区池就是这些帧在离开 DMA 之后、被用户代码处理之前所存放的地方,而摄像头在该池中保留的帧缓冲区数量则决定了 DMA 与应用程序如何共享它们。这一选择通过 framebuffers() 进行设置,共有四种模式可用,由缓冲区数量来选择。

4.15.1. 单缓冲(数量 = 1)

RAM 中只有一个帧缓冲区。DMA 向其中填充数据;应用程序从中读取数据;在应用程序释放该缓冲区之前,下一次对 snapshot() 的调用无法开始,因为两者使用的是同一个缓冲区。

摄像头与应用程序以锁步方式运行。DMA 必须等待应用程序完成,应用程序也必须等待 DMA 完成,这意味着可达到的帧率最多只有传感器帧率的一半——传感器输出的每隔一帧到来时缓冲区正忙,于是被丢弃。

这种模式占用的 RAM 最小,吞吐量也最慢。仅在 RAM 过于紧张、无法分配第二个缓冲区时才使用它。

4.15.2. 双缓冲(数量 = 2)

RAM 中有两个帧缓冲区:一个由 DMA 填充的后台缓冲区,以及一个供应用程序读取的前台缓冲区。当应用程序处理完前台缓冲区后,两者的角色互换,DMA 开始填充刚释放出来的缓冲区,而应用程序则读取刚刚填充完的那个缓冲区。

只要应用程序在不到一个摄像头帧周期的时间内处理完每一帧,应用程序就能看到传感器的完整帧率——当应用程序再次调用 snapshot() 时,DMA 的下一帧已经在后台缓冲区中等候了。然而,一旦处理时间超过一个帧周期,帧率就会减半:在应用程序处理一帧的时间里,摄像头会产生两帧,而这两帧中只有第二帧会被交付。

超过那个临界点之后,帧率会随处理时间平滑地下降。每当 DMA 在应用程序仍在处理前台缓冲区时完成了一个新的后台缓冲区帧,新帧就会就地覆盖之前的采集结果,而不是被丢弃。应用程序在下一次 snapshot() 时总能获得摄像头产生的最新一帧,而可达到的应用程序帧率则变成其处理时间的倒数。

4.15.3. 三缓冲(数量 = 3)

RAM 中有三个帧缓冲区:两个供 DMA 循环使用的后台缓冲区,以及一个应用程序当前正在处理的前台缓冲区。这是 OpenMV Cam 在有足够空余 RAM 时默认选择的模式,当 RAM 不足时会自动回退到双缓冲或单缓冲。

第三个缓冲区将摄像头帧率与应用程序帧率完全解耦。DMA 总是有一个缓冲区可写入;应用程序总是有一个缓冲区可读取;在每次 snapshot() 时,最新就绪的后台缓冲区成为新的前台缓冲区,而之前的前台缓冲区则被释放给 DMA。应用程序的帧率与它实际处理每一帧所花的时间相匹配——不会出现双缓冲在处理时间刚刚超过一个帧周期时所陷入的那种 1/2 的台阶式下降。

4.15.4. 视频 FIFO(数量 = 4 或更多)

RAM 中有四个或更多帧缓冲区,排列成一个连续采集帧的环形结构。摄像头交付的每一帧都会排入 FIFO,而 snapshot() 返回的是队列中最旧的帧,而非最新的一帧。应用程序按采集顺序逐帧处理这些已采集的帧,并各自拥有实际可用于处理的时间。

当每一帧都很重要且预期会出现短暂的处理停顿时,这种模式是正确的选择:例如将视频写入 SD 卡,而其存储栈在擦除期间可能会阻塞数十毫秒;通过 USB 流式传输到某个偶尔停止读取的主机;或者缓冲一段快速事件的短暂突发,以便在代码中进行检查。

有两种策略用于处理 FIFO 在应用程序排空之前就已填满的情况。

  • 丢弃旧帧(默认)。 当 FIFO 填满时,除当前活动的帧之外,所有排队的帧都会被丢弃,这样下一次 snapshot() 返回的是较新的帧而非陈旧的帧。DMA 在整个过程中持续采集,因此在停顿之后应用程序总能看到最新的数据。当目标是保持采集流的实时性时——例如视频录制、实时流传输——这就是正确的策略。

  • 溢出时停止采集。CSI 构造函数传入 fflush=False,当 FIFO 填满时 DMA 就会停止向其填充,从而保持排队的帧完好无损。snapshot() 会继续按采集顺序返回帧,直到应用程序将它们排空,之后 DMA 才恢复采集。当目标是保留短暂突发中的每一帧时——例如采集快速运动以便事后在代码中逐帧检查——这就是正确的策略。

完整的 API 请参阅 csi.CSI.framebuffers()

4.15.5. 触发模式

上述始终运行的各种模式的一种替代方案是触发式采集,在这种方式下,只有当 snapshot() 请求时传感器才会输出一帧。摄像头在两次快照之间处于空闲状态,并在应用程序每次调用时开始一次全新的曝光。

代价是吞吐量:一次触发采集无法与上一次采集重叠,因此可达到的最大帧率是传感器正常帧率的一半。其好处在于曝光时机。快照精确地控制曝光的开始时刻,当曝光必须与外部事件对齐时——例如频闪闪光、传送带位置传感器、GPIO 线上的脉冲——这正是应用程序所需要的,而不是在应用程序准备读取时让曝光恰好落在自由运行的传感器滚动帧的任意位置。

触发模式因传感器而异。在支持的传感器上,通过调用 csi0.ioctl(csi.IOCTL_SET_TRIGGERED_MODE, True) 来启用它,并通过传入 False 来禁用它。