Skip to content

为什么相机生产链坚持单一 Owner,并拒绝静默 Runtime Fallback

相机系统最危险的架构不是某条链路性能差,而是两条链路都“似乎能工作”。当采集、畸变校正、ROS 发布和算法接入分别保留自己的 owner、buffer pool 与恢复逻辑后,系统会进入一种很难证明正确的状态:资源是谁占的、时间戳来自哪里、失败后切到了哪条路,都不再清楚。

所以这次架构改造的核心不是换一个更快的 API,而是把生产链收敛为单一 owner,并让失败明确发生。

旧思路的问题:每个 consumer 都能拿到图

早期系统里,相机数据可以经过 ROS topic、共享内存、兼容 publisher 或独立采集节点到达算法。每条路径单独看都合理,但组合起来会产生几个问题:

  • 多个进程可能争抢同一视频设备;
  • 相同像素被重复转换和复制;
  • 不同路径各自解释曝光、PTS 和 group timestamp;
  • 恢复时无法确认旧进程是否仍持有设备或 buffer;
  • A/B 测量可能把两个版本的进程混在一起。

这类问题往往不会在编译阶段出现,而是在重启、降级或高负载下变成偶发故障。

最终架构:一个 owner,一次采集,多路消费

生产架构被收敛为:

text
V4L2 / native capture owner
  → typed frame bundle
  → trigger / exposure timing matcher
  → hardware fisheye / transform
  → NV12 stereo atlas
  ├─ direct algorithm consumers
  ├─ encoder / SDK consumers
  └─ compatibility ROS adapter

runtime 是唯一相机 owner。Core 不依赖 ROS,负责 FrameHandleBufferLease、时间和布局合同;RK3588 adapter 持有 V4L2、DMA-BUF、RGA、OpenCL 与 MPP 的平台细节;ROS bridge 只做兼容发布,不拥有采集链。

这让“谁负责什么”变得可验证:

  • capture owner 决定 buffer 何时进入和离开驱动;
  • timing matcher 决定哪些帧属于同一组;
  • executor 产出带 layout 的执行结果;
  • consumer 只消费 typed contract;
  • compatibility adapter 可以关闭,不反向定义生产主链。

为什么不做静默 runtime fallback

“OpenCL 失败就自动切 CPU”听起来很稳健,但生产上会制造更隐蔽的问题:

  1. 服务仍然 active,实际性能和延迟已经完全改变;
  2. CPU fallback 可能让温度、帧率和其他实时任务一起退化;
  3. 指标只看到业务仍有输出,却看不到系统已进入非目标架构;
  4. 故障无法复现,因为重启后又可能回到硬件路径。

因此生产主链坚持:平台能力或配置不满足时显式失败。需要回滚时,部署旧版本或切换明确的 legacy mode,而不是在同一个进程里悄悄换执行路径。

Runtime fallback 和版本回滚不是一回事。 前者改变运行语义却可能隐藏故障,后者是可观测、可审计的运维动作。

一次踩坑:把目标能力写成了已完成能力

架构文档第一次整理时,direct consumer 已经存在,很容易顺手写成“ROS BGR 路径已经移除”。但源码检查发现兼容 publisher 仍然会做 NV12 到 BGR、mmap/read 和消息复制。

这不是措辞问题,而是证据边界问题。修正后的文档明确区分:

  • 已实现:typed graph、单一 owner、DMA-BUF fan-out、direct consumer;
  • 兼容路径仍存在:ROS BGR publisher;
  • 待验证:subscriber-aware 关闭策略和新的板端 CPU A/B。

架构说明如果把目标状态写成当前事实,后续性能分析就会从错误基线出发。

队列为什么也是架构合同

共享 buffer 不代表所有 consumer 必须同步阻塞。不同支路采用不同队列语义:

  • source 到 matcher 使用有界队列,满时丢最旧帧;
  • 实时 consumer 使用 Latest(1),保留一帧执行中和一帧最新待处理;
  • 每条支路有独立 worker,慢 consumer 不阻塞整个 graph。

这不是简单的性能参数。队列策略决定系统在过载时是增加延迟、丢旧数据,还是拖死生产者,必须成为显式合同。

停机顺序同样不能靠感觉:

text
Quiesce producer
  → Drain in-flight work
  → Disconnect consumers
  → Stop platform backend

只等 queue empty 就停 backend 会留下竞态,因为 producer 仍可能继续投递;直接 disconnect 也不能强杀正在执行的 callback。

为什么这种设计更容易迁移平台

跨平台复用的不是 V4L2 或 OpenCL 代码,而是 frame、buffer、time、graph 和 consumer contract。迁移到另一颗 SoC 时,应该替换 capture、cache/fence、图像加速、编码和 NPU adapter,而不是复制整套 runtime。

迁移第一步也不是写 backend,而是 capability probe:

  • 能否导出或导入共享 buffer;
  • cache sync 与 fence 怎样表达;
  • 多相机同步由谁保证;
  • pool 深度和 release route 是否可控;
  • 硬件畸变校正与 native tensor 能否接入。

能力不成立就明确失败,重新评估架构;不能先写一个 CPU fallback,把平台差异藏起来。

结论

单一 owner 的价值不是“代码更漂亮”,而是让设备、buffer、时间和失败都只有一份权威解释。拒绝静默 fallback,也不是追求激进,而是避免系统在最需要诊断时继续假装健康。

相机生产链最终要证明的,不只是有图,而是这张图从哪里来、经过什么硬件、由谁持有、何时可复用,以及能力不足时为什么会明确失败。

延伸阅读:RK3588 高性能相机流水线跨进程 DMA-BUF 所有权合同