拿到一块 Orin NX 16GB 开发套件,刷好 JetPack、接上 DP 显示器、插好键鼠,第一件想干的事多半是接摄像头。但等你真正把手头的 MIPI CSI 摄像头接上去,或者打算一个 CSI 接口挂两个 sensor 的时候,各种问题就来了:图像花屏、stream-on 直接报错、两个摄像头的数据串在一起。这些问题十有八九都和 Virtual Channel 有关。
Nvidia Jetson 上的 Virtual Channel Driver 是摄像头驱动栈里最容易被忽略、却又最影响多目方案落地的一块。它解决的是一个很朴素的问题:怎么让多路 sensor 数据在同一个物理 CSI 接口上有条不紊地进到 SoC,并且还能被精确对应到各自的视频节点上。这篇就来把它的架构从头到尾拆一遍,从 MIPI 协议层的语义,到 Jetson 设备树里的配置,再到驱动内部的调用链,最后拉一个双摄像头的实例。凡是做机器人、车载、工业检测多目视觉的工程师,这篇应该能帮你少踩不少坑。
1. 先从一块 Orin NX 开发板说起:为什么需要虚拟通道
1.1 从开箱到摄像头:先把基础环境捋清楚
Orin NX 16GB 开发套件和普通的 x86 迷你主机长得差不多,但显示输出习惯很不一样。开发套件板上带了 DP 接口,想接 HDMI 显示器要么用 DP 转 HDMI 线,要么走 USB-C 口的 DP Alt Mode,不是随便拿根 HDMI 线就能点亮。键鼠更简单,后面板 USB 口插上就能用,无线键鼠的接收器建议插在靠近天线的 USB 口,避免信号不稳。系统层面,用 SDK Manager 刷 JetPack 5.1.2 或更高版本,默认就带了完整的摄像头驱动栈,不需要额外装驱动。
开机之后先确认系统版本和内核模块状态。cat /etc/nv_tegra_release看一下 L4T 版本号,lsmod | grep -E "tegra_camera|vi4|csi"确认摄像头相关内核模块有没有加载。如果这两个命令的输出都是空白,那问题往往在刷机那一步,系统没刷完整,驱动栈压根没起来。
基础环境没问题之后,接摄像头才真正开始。UVC 摄像头(就是普通 USB 免驱摄像头)在 Jetson 上走的是 USB host 控制器,天然没有 CSI 虚拟通道这回事,插上就能用。但一旦你开始用 MIPI CSI 摄像头,事情就不一样了。
1.2 多摄像头接入的几种姿势:为什么不能全指望物理端口
做视觉方案的人,最终几乎都会走向多目。双目前视、四目环视、六路工业检测,摄像头数量一多,第一道坎就是 SoC 的物理 CSI 接口够不够用。
Jetson 的 CSI 控制器提供了若干 lane(以 AGX Orin 为例,整个 CSI 引脚的 lane 数比早期型号多不少,Orin NX 开发套件上也有一组专用的摄像头连接器),每组 lane 可以接到某个 CSI 端口上。一个 4-lane sensor 占掉一组 lane,一个 2-lane sensor 占掉一组 lane,如果每个 sensor 都独占一组 lane,那端口数量很快就会耗尽。
虚拟通道解决的就是这个资源利用率的问题。它允许你在同一组物理 lane 上挂多个 sensor,这些 sensor 的数据在协议层用 Virtual Channel ID 区分,SoC 的 CSI 接收端拿到数据后,根据 VC 号把不同的图像数据分流到不同的处理通道和视频节点。
1.3 谁最需要搞懂 Virtual Channel Driver
这个问题在不同人群眼里价值完全不同:
- 只接单个摄像头做 demo 的入门玩家,基本不需要碰 VC,默认 VC0 走天下就行了。
- 做多目机器人、自动驾驶感知、工业视觉的工程师,如果不懂 VC 配置,就会一直被摄像头数据串流、枚举失败这类问题卡住,而且很难排查。
- 做 sensor 驱动移植、BSP 定制的人,设备树里那几行
vc-id配置就是整个方案的命脉,配置错了图就出不来。 - 用 libcamera 或 GStreamer 的
nvarguscamerasrc做应用的开发者,虽然不直接改驱动,但理解 VC 之后,才能明白sensor-id这个参数背后到底发生什么。
所以这篇博文面向的核心人群是第二类和第三类:做多目方案、需要自己配设备树的嵌入式视觉工程师。
2. MIPI CSI-2 协议层面的虚拟通道机制
2.1 CSI-2 的数据包结构与 VC 字段
Virtual Channel 不是 NVIDIA 发明的东西,它是 MIPI CSI-2 标准里定义的基础机制。CSI-2 物理层用 D-PHY 做差分信号传输,协议层把数据切成一个个 packet,包括 Short Packet 和 Long Packet。
Long Packet 的结构是:32-bit Packet Header、不定长的 Payload、16-bit CRC(Packet Footer)。32-bit Header 里最关键的两个字段是 Data Identifier(8 bit)和 Word Count(16 bit),外加一个 ECC(8 bit)。Data Identifier 的 8 bit 由两部分组成:高 6 位是 Data Type(DT),也就是数据类型,比如 RAW10、RAW12、YUV422、帧起始、帧结束等;低 2 位就是 Virtual Channel ID,取值 0~3,最多支持 4 个虚拟通道。
接收端(也就是 Jetson 的 CSI receiver)拿到一个 packet 之后,第一件事就是解析 Data Identifier 里的 VC 字段,根据 VC 值把数据放进对应的接收通道。这个过程是硬件层面的标签交换,不涉及系统调度。
2.2 虚拟通道与 Lane 的区别:最常见的概念混淆点
很多朋友把“虚拟通道”和“物理 lane”搞混,其实两者是不同层级的东西。
物理 lane 是 D-PHY 上的一对差分线,类比成高速公路上的车道,决定了物理上能同时跑多少数据。lane 越多,同一时刻能传的比特就越多。虚拟通道 VC 是跑在 lane 之上的逻辑通道,类比成高速公路上的车道标线,它不增加道路宽度,只规定每条车流走哪条标线。多个 VC 共享同一组 lane 的总带宽,VC 的数量再多,带宽上限还是由 lane 数量和 data rate 决定。
所以配置的时候要分清楚两件事:
bus-width或 lane 数:对应物理带宽,决定能传多大分辨率、多高帧率。vc-id:对应逻辑区分,决定同一组 lane 上挂的几个 sensor 谁是谁。
举个例子,一路 1080P60 RAW10 的带宽需求大概是 1920 × 1080 × 10 bit × 60 fps ≈ 1.24 Gbps,加上协议开销大约 1.3 Gbps 出头。如果是一组 2-lane CSI,单 lane 速率 1.5 Gbps,总带宽 3 Gbps,那么一路 1080P60 RAW10 跑在 VC0 上之后,还有余量给 VC1 上再挂一路 720P60 或其他小分辨率数据流。
2.3 虚拟通道的时序与同步问题
VC 解决了数据区分的问题,但没有解决同步问题。多个 sensor 挂在同一组 lane 上,如果各跑各的帧率、各出各的时钟,虽然协议层面的数据不会串,但图像帧的时间戳会很乱,甚至会造成 buffer 错位。
解决这个问题的常见做法有两个:
- 用 sensor 的硬件同步功能。很多 sensor 支持 master/slave mode,通过 FSYNC 信号线把帧同步时钟从一个主 sensor 分发到从 sensor,所有 sensor 在同一时刻曝光和读出。
- 用外部硬件触发信号,统一触发所有 sensor 采集。
在 Jetson 上做多目,即使 VC 配置正确,我也建议优先考虑硬件同步。软件上虽然可以通过 ioctl 来对齐时间戳,但那是事后补偿,精度远不如硬件同步。这一点在后面实操章节还会再提。
3. Jetson 平台软件栈与 Virtual Channel Driver 的定位
3.1 从 V4L2 到 Media Controller 再到 Tegra Camera Framework
Jetson 上的摄像头驱动栈是很典型的分层结构,从下往上大致是:
sensor 硬件 (MIPI CSI-2) → sensor subdev 驱动 (如 imx219.c) → CSI receiver 驱动 (csi.c) → VI / tegra-capture 驱动 (vi4.c 等) → Media Controller 拓扑 → /dev/video0、/dev/video1 等 video device → 用户态 V4L2 API / GStreamer / libcamera这套结构里,Virtual Channel 的配置分散在两层:设备树里通过vc-id告诉驱动某个通道用哪个 VC;驱动初始化时把 VC 配置到 CSI receiver 的寄存器里;数据流通过时,CSI receiver 根据硬件包头里的 VC 值把数据分流到不同的 DMA 通道。
3.2 Flash Descriptor 与 Sensor 地址管理
Jetson 的摄像头栈有一个和普通 Linux 平台很不一样的地方:Flash Descriptor。简单说,JetPack 默认的 rtcpu 模式下,sensor 的初始化配置并不仅仅由内核驱动自己处理,一部分参数会通过设备树里的 flash descriptor 节点,打包传给运行在 Camera Control Processor(rtCPU)上的 ISP firmware。
所以你会看到设备树里除了标准的compatible、reg、mode0等节点之外,还有类似tegra-camera-rtcpu-module、flash-descriptor这样的内容。Flash descriptor 里面会包含 sensor 的寄存器初始化序列、曝光/增益参数范围、还有底层 sensor 时序配置。Virtual Channel 信息也会通过这个通道传递到 rtCPU,ISP firmware 才知道某个 capture channel 对应的 VC 是几。
这对做应用层的人来说可能无感,但对做驱动移植的人来说很重要:改完 sensor 之后,不只是改内核驱动,还要保证 flash descriptor 里的底层配置和内核设备树保持一致,否则会出现“驱动 probe 成功了,但图像数据完全不对”的诡异问题。
3.3 驱动模块划分:谁负责什么
tegra-camera-platform:平台驱动,负责解析设备树里摄像头相关节点,构建 media controller 的拓扑骨架。没有它,整个摄像头栈起不来。vi/tegra-capture:video device 驱动,注册 /dev/videoX 节点,负责和用户态 V4L2 API 对接。每个 capture channel 对应一个 video node。csi驱动:CSI receiver,负责物理 lane 的配置、虚拟通道的映射、以及把数据从 D-PHY 搬到内存。- sensor subdev 驱动:对应具体 sensor 型号,负责 I2C 寄存器读写、sensor 曝光增益控制、以及把 sensor 的 VC 寄存器配置好。
- rtCPU 上的 ISP firmware:跑图像信号处理,把 RAW 数据变成用户能用的图像格式,同时负责部分时序控制。
4. 设备树里的虚拟通道配置解析
4.1 先看一份基础设备树结构
要理解 Virtual Channel 在 Jetson 上的配置,最直接的办法是打开一份实际的设备树。以典型的 Orin NX / AGX Orin 设备树为例,摄像头相关节点一般分布在两个位置:一个是 I2C 总线上的 sensor 节点,一个是 CSI/VI 相关的 capture 节点。
sensor 节点的典型结构大致这样:
i2c@3180000 { imx219_cam0: imx219@10 { compatible = "sony,imx219"; reg = <0x10>; vc-id = <0>; reset-gpios = <&gpio ...>; // sensor 的 mode 配置,每个 mode 对应一组分辨率/帧率 mode0 { tegra_sinterface = "serial_a"; vc_id = <0>; clk-hz = <24000000>; active_w = <1920>; active_h = <1080>; pixel_t = <TEGRA_PIXEL_FORMAT_10P>; ... }; }; };而 CSI/VI 一侧的 capture 通道配置会出现在tegra-capture-vi或类似节点里:
tegra-capture-vi { num-channels = <2>; ports { port@0 { reg = <0>; endpoint { vc-id = <0>; port-index = <0>; bus-width = <2>; }; }; port@1 { reg = <1>; endpoint { vc-id = <1>; port-index = <0>; bus-width = <2>; }; }; }; };这里port@0和port@1是两个不同的 capture 通道,但它们都指向port-index = <0>,也就是同一个 CSI 物理端口。区别在于vc-id:一个用 VC0,一个用 VC1。这正好就是虚拟通道模式的核心写法。
4.2 单端口多 sensor 的配置写法:关键属性逐一说明
做双摄共线方案时,我一般会按下面的清单核对配置:
tegra_sinterface:标识 sensor 接在哪个 CSI 物理接口上。两个共享 lane 的 sensor 必须指向同一个接口名。常见的有serial_a、serial_b等,具体要看 SoC 和开发板的接口布局。vc_id(或vc-id):虚拟通道号,取值范围 0~3。同一组 lane 上的多个 sensor 必须配置不同的值。port-index:CSI 端口的索引,和tegra_sinterface对应,多个共享 lane 的 sensor 这里要一致。bus-width:使用的 lane 数量。比如 2-lane 传感器就写 2。这里要注意,bus-width是每个 sensor 占多少 lane,如果两个 sensor 共享 lane,它们各自还是写自己 sensor 输出的 lane 数。
一个典型双摄配置中,两个 imx219 sensor 都接serial_a,也就是同一组 lane 上,但一个配vc_id = <0>,另一个配vc_id = <1>。这样 SoC 从同一个物理接口收到数据后,靠 VC 字段就能区分哪个包来自 cam0、哪个包来自 cam1。
4.3 一个容易忽略的坑:vc-id 与 CSI 端口映射
我在实际项目里踩过一个大坑:sensor 的vc_id和 capture 通道的vc-id不一致。sensor 节点里写的是 VC1,但 capture 通道 endpoint 里写的是 VC0,结果图像能出但内容不对,或者干脆 stream-on 直接报错。更隐蔽的情况是tegra_sinterface和port-index不一致,sensor 明明物理上接在serial_b,capture 通道却写在port-index = <0>上,数据永远对不上。
原则很简单:sensor 节点里声明的接口、VC、lane 数,必须和 capture 通道 endpoint 里的声明完全对齐,两端缺一不可。把设备树比作一张接线表,sensor 节点是插头侧标注,capture 节点是插座侧标注,两侧对不上,电就传不过去。
4.4 双摄同时输出时的带宽预算
配设备树的时候顺手算一笔带宽账,能避免很多后续问题。以两个 1080P60 RAW10 sensor 共享一组 2-lane CSI 为例:
单路带宽需求:1920 × 1080 × 10 × 60 = 1,244,160,000 bit/s ≈ 1.16 Gbps(纯数据),算上 MIPI packet header/footer 和 ECC/CRC 开销,大约 1.25 Gbps。两路就是 2.5 Gbps。
一组 2-lane CSI,如果单 lane data rate 是 1.5 Gbps,总带宽 3 Gbps,看起来勉强够用,但余量只有 0.5 Gbps。一旦 sensor 实际输出的 pixel clock 偏高或者 overhead 偏大,就会出现掉帧。这时候要么降低帧率到 30fps,要么把 CSI 接到 4-lane 口上,或者改用 RAW8 格式减少单像素位数。这些取舍要在配置阶段决定,等上了真机再调就要费不少精力了。
5. 驱动内部实现与数据流打通
5.1 从 open 到 stream-on 的调用链
理解了设备树之后,再看驱动实现就清楚多了。用户态open("/dev/video0")之后,ioctl 调用链大致是这样的:
应用层发起VIDIOC_STREAMON→tegra-capture驱动收到s_stream(1)→ 先查这个 video node 对应的 capture channel 配置(包括 vc-id、port-index、bus-width)→ 调用 CSI 驱动配置物理层 → 调用 sensor subdev 的s_stream(1)→ sensor 驱动通过 I2C 写寄存器,让 sensor 开始输出 MIPI 数据。
这里值得注意的一点是,sensor 的 VC 寄存器也需要驱动去写好。以 IMX219 为例,寄存器 0x0114 的低两位就是 Virtual Channel ID 选择位,写 0 表示 VC0,写 1 表示 VC1。如果设备树配了vc_id = <1>,但 sensor 驱动初始化时没有写这个寄存器,sensor 实际还是输出 VC0 的数据包,那内核层面的分流就会错位。
5.2 VC 数据如何被 ISP 分流到不同 video node
数据从 sensor 出来后,以 packet 的形式流过 D-PHY 差分线,进入 CSI receiver 硬件块。CSI receiver 内部有多个接收通道(virtual channel context),每个通道预先绑定了 VC 号和 DMA 目标。硬件解析到 packet 头里的 VC 字段后,把 payload 数据按 VC 值写入对应的 FIFO,再由 DMA 引擎搬运到内存中的 capture buffer。
这就是为什么tegra-capture可以同时注册多个 video node,每个 node 对应一个 VC。对用户态应用来说,打开/dev/video0和/dev/video1就是两个完全独立的视频流设备,各自拿到各自的数据。如果没有 VC 机制,多个 sensor 的数据会混在同一个 FIFO 里,无法区分。
5.3 用户态如何区分不同虚拟通道
用户态区分虚拟通道的方式很简单:看 video device 节点。v4l2-ctl --list-devices会列出所有 video node 和对应的名字,通常能看出哪个是 capture 通道。更严谨的做法是用 media controller 导出的拓扑:
media-ctl -d /dev/media0 -p这条命令会打出整个媒体拓扑图,sensor subdev、CSI 实体、VI 实体之间的连接关系一目了然。每个端口上标记的 channel index 或 pad 号,配合设备树里的vc-id,就能确认某个 video node 对应的是哪个 sensor、哪个 VC。
实际开发中,我建议把media-ctl -p的输出保存成文件,和设备树配置对照着看。只要拓扑里连接的实体和配置一致,驱动层面基本没问题。
6. 实操:Orin NX 上跑通双摄像头虚拟通道模式
6.1 准备:确认系统与摄像头硬件
这一步先把基础条件备齐:
- 确保 Orin NX 已刷好 JetPack 5.1.2 或兼容版本,系统能正常启动。
- 准备两块 IMX219 sensor 模块,或者任何支持 VC 配置的 MIPI CSI sensor。
- 确认 sensor 模块的 I2C 地址不冲突,或者在设备树里分别指定不同地址。
- 准备一个可以同时接入两个 sensor 到同一 CSI 接口的转接板。注意信号线顺序和供电,别把差分线接反。
6.2 修改设备树启用 VC 模式
拿到一份可用的基础设备树后,先在干净环境里单独验证每个 sensor 都能正常工作,再合到一起配 VC。这个习惯帮我省了大量排查时间。单独正常的 sensor 合并后出问题,大概率是 VC 配置或共享 lane 的时序问题;单独就不正常的 sensor,先解决硬件和基础驱动问题。
双摄 VC 模式的设备树修改,按上一章的结构来:sensor 节点里写vc_id = <0>和vc_id = <1>,tegra_sinterface保持一致;CSI/VI 的 capture 通道分别创建port@0和port@1,各自配好vc-id和port-index。
修改完成后编译 DTB,把生成的 dtb 覆盖到 boot 分区,重启系统。启动后先看dmesg | grep camera的输出,确认两个 sensor 都成功 probe。
6.3 验证与抓图
设备起来后,按照下面的顺序做验证:
# 查看 media 拓扑,确认两个 sensor 都在 media-ctl -d /dev/media0 -p # 查看 video 设备 v4l2-ctl --list-devices # 抓取第一路图像 v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080,pixelformat=NV12 --stream-mmap --stream-count=1 --stream-to=frame0.yuv # 抓取第二路图像 v4l2-ctl -d /dev/video1 --set-fmt-video=width=1920,height=1080,pixelformat=NV12 --stream-mmap --stream-count=1 --stream-to=frame1.yuv用 GStreamer 也能验证,会更直观一些:
gst-launch-1.0 nvarguscamerasrc sensor-id=0 ! 'video/x-raw(memory:NVMM),width=1920,height=1080' ! fakesink gst-launch-1.0 nvarguscamerasrc sensor-id=1 ! 'video/x-raw(memory:NVMM),width=1920,height=1080' ! fakesink这里sensor-id对应设备树里 capture channel 的索引。如果两路都能正常出流,说明虚拟通道配置成功。如果其中一路报错或图像异常,进入下一章的排查流程。
6.4 实测效果与参数对照
在环境稳定的情况下,双 IMX219 1080P30 在 VC 模式下可以稳定并行输出两路视频流。下面是实测的一组参数对照:
| 参数 | 单摄模式 | 双摄 VC 模式(共享 2-lane) |
|---|---|---|
| sensor 数量 | 1 | 2 |
| 分辨率/帧率 | 1920×1080@60 | 1920×1080@30 ×2 |
| VC ID | VC0 | cam0=VC0,cam1=VC1 |
| 物理 lane | 2-lane | 2-lane 共享 |
| /dev/videoX | /dev/video0 | /dev/video0、/dev/video1 |
| 单路带宽需求 | 约 1.25 Gbps | 约 1.25 Gbps ×2 = 2.5 Gbps |
| 总带宽余量 | 约 1.75 Gbps | 约 0.5 Gbps |
如果改成双摄 1080P60,带宽余量就非常紧张了,实测中会出现偶发丢帧。这时候要么提高 lane rate,要么改用 4-lane 物理接口,要么降低帧率。这个曲线很能说明问题:VC 解决的是“逻辑区分”,不解决“物理带宽”,两者必须一起规划。
7. 常见问题与排查技巧实录
7.1 VC ID 冲突导致花屏或无法 stream-on
现象:两路都能 open,但图像花屏、颜色错乱,或者第二路 stream-on 时报错。
排查思路:先在设备树里确认两个 sensor 的vc_id确实不同,然后确认 sensor 驱动真的把 VC 寄存器写进去了。比如 IMX219 的 0x0114 寄存器,低两位要和设备树对齐。可以用i2cget/i2cset直接读写 sensor 寄存器验证,也可以加打印看驱动初始化流程。
这个问题的隐蔽之处在于:有些 sensor 默认输出 VC0,如果你只改了设备树的vc-id而没改 sensor 寄存器的值,驱动层会以为收到的数据是 VC1,但实际包头里写的还是 VC0,数据就会跑到错误的通道里。
7.2 sensor 枚举失败:/dev/videoX 里缺节点
现象:/dev/video0存在,/dev/video1不存在,或者两个都没有。
排查思路:先看内核日志。dmesg | grep -i imx219(换成你的 sensor 型号)看有没有 probe 失败;dmesg | grep -i csi和dmesg | grep -i tegra-camera看平台驱动有没有正确解析设备树。
最容易犯的错是设备树里加了 sensor 节点,但忘了加对应的 capture 通道节点,或者 capture 通道的num-channels数值比实际通道数小。num-channels要和ports里定义的 port 数量一致,否则后面的通道压根不会被注册。
7.3 带宽不足导致的掉帧与丢包
现象:两路视频流都能起来,但运行一段时间后某一路开始掉帧、卡顿,或者v4l2-ctl抓图时提示 buffer timeout。
排查思路:先算带宽账。如果总需求已经超过物理 lane 的承载能力,再调软件都没有用。我一般按“总通路带宽 × 0.8 = 可用带宽”来估算,MIPI 协议开销大约占 5%~10%。超出就降帧率、降分辨率、换格式,或者改成独立 lane 方案。
另外检查一下设备树里bus-width是不是写大了。传感器实际输出 2-lane,你写 4-lane,驱动会去配置 4 条 lane 的接收,但其中两条根本没信号,也会导致异常。
7.4 问题排查速查表
| 现象 | 可能原因 | 排查方式 | 解决办法 |
|---|---|---|---|
| 两路图像花屏或串流 | 两端 vc-id 不一致 | 检查设备树 + sensor 寄存器 | 对齐 vc-id 配置 |
| 一路无图像 | capture 通道缺失 | 检查 num-channels 和 ports | 补齐 capture 节点 |
| 运行中掉帧 | 带宽超限 | 计算总带宽占用 | 降帧率/降分辨率/换 4-lane |
| stream-on 报错 | lane 配置错误 | 检查 bus-width 与实际接线 | 用实际 lane 数 |
| sensor probe 失败 | I2C 地址冲突 | i2cdetect 检测总线 | 改 I2C 地址或加 mux |
| 图像时间戳乱跳 | 缺少硬件同步 | 观察多路时间戳 | 加 FSYNC 硬件同步 |
8. 最后再分享两个小技巧
第一个技巧是:调 VC 模式前,先用单 sensor、单通道把每个摄像头单独跑通。这听起来很基础,但真的能帮你隔离问题。我见过太多人一上来就改双摄配置,结果两个 sensor 都配错了,排查的时候根本不知道问题出在共性还是个性。逐个验证,再合并联调,成功率会高很多。
第二个技巧是:务必保留一套只改了一行的增量配置。比如先在单摄 VC0 正常的基础上,只加一个 VC1 的 sensor 节点,其他全不变。如果这一行改动导致了整个系统崩溃,定位起来会非常快。这种“最小变更验证”的思路,在嵌入式底层调试里比什么都好用。
Virtual Channel Driver 本身看起来只是设备树里几行配置,但背后的机制贯穿了 MIPI 协议、CSI 硬件、平台驱动、sensor 固件多个层次。把这套架构吃透了,多目视觉方案的摄像头选型、设备树配置、问题定位都会顺很多。希望这篇能帮你在 Jetson 上少烧几个调试之夜。