工业相机设置 ROI 后外部触发慢一帧(N−1):定位 RKISP 帧完成边界

这次的 N−1 并不是在所有外部触发场景下都会出现。无 ROI、保持全幅采集时,测试基本正常;设置或清除 ROI、重新配置采集链路后,才比较容易复现:外部触发第 N 次,后端拿到的却是第 N−1 张图。

初期排查过浏览器缓存、MJPEG 预览、GStreamer 缓存队列,也怀疑过 IMX296 的曝光脉冲是否晚一次触发生效。清理队列、重启采集链路、修改缓存响应头都能影响表面现象,但没有稳定消除 N−1。

后续通过逐层对齐帧序号确认:ROI 变化是主要复现条件,但问题并不是简单的裁剪坐标错误。真正发生帧身份错位的位置,是 ROI 变化之后 RKISP 提交图像缓冲区的时机。

系统背景与问题现象

硬件是 RK3576(泰山派内核环境)加 Sony IMX296,使用 Rockchip 的 RKISP V39。IMX296 工作在 Fast Trigger Mode,外部控制器通过 XTRIG 输入触发拍照。

根据 Sony《IMX296LQR-C Datasheet》第 59、64 页,IMX296 使用全局快门;在 Fast Trigger Mode 下,曝光会在 XTRIG 下降沿立即开始,而且该模式只支持 Master mode,也就是由传感器提供图像读出的主同步时序。外部 XTRIG 低电平脉冲同时决定本次曝光时间。

实际数据路径是:

XTRIG
  → IMX296
  → RKCIF
  → RKISP V39
  → V4L2 buffer
  → GStreamer appsink
  → 业务、预览和存图

这几层可以简单理解为:RKCIF 负责接收传感器送来的原始图像,RKISP 负责图像处理并判断一帧何时结束,V4L2 buffer 是 Linux 内核交给应用的一块帧内存,GStreamer 再从这里取图。只要 RKISP 过早或过晚地宣布 buffer 完成,上面的业务层都会跟着拿错。

我们希望它严格满足“一枪一帧”:第 N 次 XTRIG 触发后,业务拿到第 N 张图。全幅采集时基本如此;但 ROI 设置或恢复全幅、链路重启以后,第 N 次触发可能拿到第 N−1 张图。

这里有个很重要的区别:如果 frame N 只是晚 50ms 到达,那是延迟;如果当前 trigger 拿到上一枪的图,那是帧身份错位。两者看起来都叫“慢一帧”,修法完全不同。

先排除曝光参数生效时序

早期排查时,通过交替修改曝光让画面一亮一暗。观察结果的亮暗相位落后一次触发,因此一度认为 IMX296 Fast Trigger 的曝光脉冲要到下一帧才生效。

后续实验说明,这个判断并不成立。

问题在于,亮暗交替只能说明最终看到的画面相位落后,却分不清下面两种情况:

情况 A:曝光参数确实晚一枪生效
情况 B:曝光已经生效,但后面交付的是上一张 buffer

后面我们不再用相机参数给帧做标记,而是在完成一次 ROI 设置或清除之后,固定曝光、增益、ROI(只读取图像中的指定区域)和 XTRIG 脉宽,只改变外部场景,例如切换光源或遮挡。与此同时,把外部触发编号、CIF/ISP 事件、V4L2 缓冲区序号、GStreamer 接收顺序、业务层采集序号和最终图片内容串起来看。

实验结果是:在 ROI 变化后的异常状态中,相邻两次触发完全不修改曝光和增益,N−1 仍然存在;而修改曝光或增益后的第一张图,又可以正确体现新参数。作为对照,无 ROI 的全幅采集没有稳定复现同样问题。

所以不能再把“曝光脉冲下一帧生效”当作最终根因。真正的问题是,内核晚了一次触发才把上一张图交给应用。

定位 RKISP 的帧完成时机

继续检查内核侧事件后,问题范围收敛到 RKISP V39 的帧完成时机。

通常情况下,ISP 会在一帧处理结束时产生 frame-end,也就是“本帧结束”信号,驱动随后把对应的 V4L2 图像缓冲区交给应用。

在这次 ROI 变化后出现异常的 Fast Trigger 状态中,常规 frame-end 可能要等下一次 XTRIG 到来、流水线再次向前推进时才出现。如果驱动一直等它,当前这张图就会被压到下一枪才交付。这里说的是本次平台和复现条件,并不表示 IMX296 Fast Trigger 在无 ROI 时一定会慢一帧。

第 N 次触发
  → 当前图像进入 ISP
  → 当前图像缓冲区没有及时完成

第 N+1 次触发
  → 常规帧结束信号到达
  → 第 N 次触发的图像才交给应用

这可以解释 ROI 后出现的 N−1:不是 GStreamer 取错,也不是前端显示错,而是图像在进入应用之前就已经晚了一次触发。

为了更早知道当前帧处理到了哪里,RKISP 还可以使用“输出行计数”。它类似一个进度条:ISP 每输出一行,计数就向前走;计数达到指定值时,可以产生 line IRQ,也就是行中断,通知驱动执行处理。

在这次 1080 行输出的实验里,1079 是能够产生所需行中断的最后一个计数位置,1080 是硬件计数终值。这两个数字不是 ROI 坐标,也不是什么神秘的传感器参数,它们只是我们用来选择“何时把这一帧交给应用”的两个相邻时间点。

为了不再等待下一次触发,我们尝试在计数到 1079、收到行中断时,提前把当前图像缓冲区标记为完成。修改后,整帧 N−1 消失,当前触发可以拿到当前图像。

与此同时出现了另一个现象:图像主体属于当前帧,但底部大约 8 行仍然是上一帧的内容。

这说明计数到 1079 只能证明 ISP 已经处理到接近最后一行,并不能证明 DMA 已把尾部数据全部写回内存。DMA 可以简单理解为硬件直接向帧内存搬运图像数据。此时立刻把图像交给应用,还是早了一点。

接着把阈值改成硬件终值 1080。底部旧行确实没了,说明像素数据已经完整写入;但 1080 不产生所需的行中断,驱动只能重新等待常规 frame-end,整帧 N−1 随即恢复。

这组对照将问题范围进一步限定在图像缓冲区的完成边界:

设置 结果
等常规帧结束信号 图像完整,但下一枪才发布,出现 N−1
计数到 1079 后立即交付 当前枪及时发布,但尾部约 8 行还没写完
等到计数终值 1080 尾部完整,但没有行中断,N−1 恢复
1079 行中断后再等尾部写完 当前枪及时发布,尾部也完整

浏览器缓存、JPEG、单纯的 ROI 裁剪坐标错误和曝光相位都无法解释这组现象。ROI 负责触发问题,而 1079/1080 对照说明:“RKISP 可以提交当前帧”和“DMA 已经写完全部像素”并不是同一个时刻。

最终修复:提交前等待尾部写入完成

最终方案仍然使用 1079 行中断,因为它能保证当前图像不会拖到下一枪;区别是在把图像交给应用之前,再给尾部数据留出写入时间。

对等待时间进行了逐档测试:

  • 10ms:底部仍有旧行;
  • 20ms:底部仍有旧行;
  • 30ms:本轮测试可以通过,但接近临界值;
  • 50ms:多种场景下稳定通过。

最终产品值采用 50ms。它会增加固定的提交延迟,但图像身份正确,也不会再混入上一帧的尾部。

50ms 只是 RK3576、RKISP V39、IMX296 和当前实现组合上的实测安全值。换分辨率、换 ISP 版本或换平台,都应该重新测,不能照抄。

长期方案应当寻找 ISP/DMA 的真实完成状态或硬件完成中断,用硬件完成条件代替固定等待。当前的 50ms 是经过实测的工程方案。

可复用的排查方法

遇到类似的“慢一帧”问题,可以先做两件事。

第一,不要先急着清队列或丢弃第一帧,而是给每一层增加可对齐的身份:外部触发编号、ISP 和 V4L2 缓冲区序号、GStreamer 接收顺序、业务层采集序号,以及最终图像的内容标记。找到第一个从 N 变成 N−1 的位置,后续排查范围会明显缩小。

第二,将相机参数与帧内容标记分开。曝光亮暗交替适合判断参数是否生效,但不能单独判断图像属于哪一次触发。验证帧身份时,应固定相机参数,只改变外部场景。

最终验收也不能只看某一次测试通过。无 ROI 的全幅采集要作为正常基线;然后分别验证设置 ROI、清除 ROI、ROI 后的第一枪和连续多枪,以及曝光或增益修改后的第一枪,同时检查 N−1、漏帧、重复帧和底部旧行。

这次 N−1 主要由 ROI 变化和链路重建触发,但不能因此只检查 ROI 参数。排查的关键是找到 ROI 之后第一次发生帧身份错位的层,并确认内核在什么时刻把图像标记为完成。

Comments

No comments yet. Why don’t you start the discussion?

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注