ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Ubuntu USB摄像头参数查询:V4L2格式、带宽与设备节点

Ubuntu USB摄像头参数查询:V4L2格式、带宽与设备节点 摄像头这东西平时不接也就算了一接上 Ubuntu 各种幺蛾子就来了明明是 1080p 的摄像头跑起来只有 640x480好不容易把分辨率拉上去帧率掉到个位数再或者插上去ls /dev/video*出来一堆节点根本分不清哪个才是真正出图像的。我前后在 RK3588、x86 工控机、笔记本上折腾过十几款 USB 摄像头从几十块的杂牌到工业级模组都有踩的坑基本都集中在同一件事上——没有先把 USB 摄像头的参数摸清楚就直接写代码。这篇文章就讲一件事在 ubuntu 上把 USB 摄像头的参数查明白包括分辨率、帧率、设备节点、压缩格式以及这些参数背后的 USB 带宽约束。不夸张地说会看这几个参数能省掉你后面 80% 的调试时间。内容面向刚上手嵌入式视觉、做 RTSP 推流、跑超分辨率重建这类活儿的朋友也适合只是想确认自己摄像头到底是不是真 1080p的普通用户。全程命令行不需要写一行代码工具就三个包。1. 先把思路理顺查参数到底在查什么1.1 三个层次别混在一起看很多人查摄像头参数的方式是打开一个上位机软件看分辨率下拉框里有什么选项就以为摄像头就这些能力。这个认知在 Linux 上会吃大亏因为摄像头的能力是分层的每一层都有各自的限制。第一层是物理层也就是 USB 总线和摄像头的 USB 描述符。这一层决定的是这个设备插在 USB 2.0 口还是 3.0 口、供电够不够、理论上能传多少数据。这一层的参数你用lsusb、dmesg、lsusb -t看。第二层是驱动层也就是内核里的uvcvideo驱动向外暴露的 V4L2 接口。这一层是摄像头固件自己上报的能力表包括支持哪些分辨率、哪些帧率、哪些像素格式。这一层用v4l2-ctl --list-formats-ext看也是这篇文章的核心。第三层是应用层就是你实际拿到的图。ffmpeg、OpenCV、GStreamer 拿来用的都是这一层它受前两层约束。很多人卡在第三层调不出来其实问题在第二层甚至第一层。举个例子摄像头标称支持 1920x108030fps但 t你插在 USB 2.0 口上用 YUYV 格式去要 1080p结果只能拿到 5fps。这不是驱动坏了是带宽物理上就不够。所以查参数的正确顺序是从下往上查先确认 USB 链路再确认 V4L2 能力表最后才去抓帧验证。提示不要迷信摄像头包装盒上印的参数。很多产品标的是最高支持 1080p但没告诉你 1080p 只在 MJPG 格式下成立YUYV 格式下最高只到 640x480。这两件事在 Linux 下是完全不同的两条能力记录。1.2 为什么这些参数直接决定你后面的选型如果你只是拍个照参数不重要。但只要涉及持续采集、推流、AI 推理参数就直接决定架构了。举个很典型的场景现在很多人拿 RK3588 做 USB 摄像头转 RTSP 流。这个活儿看起来很直白读摄像头编码推流。但实际做的时候你要先回答几个问题摄像头输出的是原始 YUV 还是已经压缩好的 MJPG如果是 MJPG那 RK3588 上就不需要再做 JPEG 解码可以直接封进 RTP如果是 YUYV那 1080p 的数据量会让 USB 2.0 直接跪。再比如做超分辨率重建你得知道输入到底是 360p 还是 720p重建倍率怎么定这些全都依赖于第一步的参数确认。换句话说查看参数不是了解一下而是设计输入。参数没查清楚就动手后面大概率要推倒重来。2. 环境准备三个包搞定全部工具2.1 装什么为什么装这些Ubuntu 上查 V4L2 参数的官方工具是v4l-utils里面有几个命令最常用的是v4l2-ctl。Ubuntu 20.04 之后的版本默认不一定装所以第一步先补上sudo apt update sudo apt install -y v4l-utils ffmpeg usbutils这三个包各自负责什么值得说清楚v4l-utils提供v4l2-ctl是查询和控制 V4L2 设备的主力工具查格式、查能力、设置分辨率、抓原始帧全靠它。ffmpeg用来做实际抓帧验证。v4l2-ctl抓出来的是裸数据得靠 ffmpeg 转成能看的图片才能确认参数是不是真的生效。usbutils提供lsusb用来查 USB 链路信息判断设备挂在哪个总线上、跑在什么速度。有个细节v4l2-ctl的版本会影响输出详略。老版本1.16 之前对某些扩展属性的输出会缺字段建议用 Ubuntu 22.04 及以上或者手动装新版本。我之前在一台 Ubuntu 18.04 的老机器上--list-formats-ext不显示帧率间隔排查半天以为是驱动问题换系统后一切正常。所以遇到输出少了字段先怀疑工具版本再怀疑驱动。装完之后可以用v4l2-ctl --version确认一下一般 1.22 以上就没什么问题了。2.2 用 dmesg 和 lsusb 确认硬件被识别插上摄像头第一件事是确认内核认出来了。分两步dmesg | grep -i uvc | tail -20 lsusbdmesg里如果能看到类似uvcvideo: Found UVC 1.00 device xxx的行说明uvcvideo驱动已经接管了这个摄像头。注意这里的 UVC 版本号有意义UVC 1.0/1.1 和 UVC 1.5 在带宽协商和格式支持上有差别UVC 1.5 才支持 H.264 这类压缩格式。lsusb输出的是 USB 设备列表长这样Bus 002 Device 003: ID 1bcf:2c99 Sunplus Innovation Technology Inc. HD Camera这里的1bcf:2c99就是idVendor:idProduct后面配置 udev 固定设备节点的时候会用到先记下来。接下来看更关键的一步查这个设备跑在什么速度上lsusb -t输出是树状结构类似/: Bus 02.Port 1: Dev 1, Classroot_hub, Driverxhci_hcd/4p, 5000M |__ Port 2: Dev 3, If 0, ClassVideo, Driveruvcvideo, 5000M /: Bus 01.Port 1: Dev 1, Classroot_hub, Driverxhci_hcd/12p, 480M注意看设备那一行末尾的5000M或者480M。这就是这个摄像头实际协商到的链路速度。如果一台标称 USB 3.0 的摄像头在这里显示 480M那它实际就跑在 USB 2.0 上后面所有带宽计算都要按 USB 2.0 来算。为什么会这样最常见的原因有三个线材是 USB 2.0 的很多摄像头原装线就是接口是 USB 2.0 的或者线太长信号衰减导致降速协商。这三种情况我都遇到过尤其第一种换根线立竿见影。注意lsusb -t显示的 480M / 5000M 是链路协商速率不是可用带宽。实际可用带宽要打很大折扣下一节会算。3. 设备节点定位别再用硬编码 /dev/video03.1 /dev/video0 到底代表什么Linux 下 V4L2 设备统一挂在/dev/videoN下N 从 0 开始。但这里有个非常容易踩的坑一个物理摄像头往往会注册不止一个 video 节点。拿一个普通的 UVC 摄像头举例插上去之后ls /dev/video*可能出来/dev/video0 /dev/video1/dev/video0是真正的视频采集节点/dev/video1通常是 metadata 节点用来传每帧的时间戳和曝光信息。如果你用 OpenCV 打开/dev/video1会拿到一个永远读不出图或者报错的设备很容易误判成摄像头坏了。更麻烦的是节点编号不是固定的。你先插 A 摄像头它是 video0 和 video1拔掉再插 BB 也可能占 video0同时插两个编号顺序还和 USB 拓扑、插入顺序有关。所以任何写死/dev/video0的代码在换了插法之后都会失效。3.2 用 v4l2-ctl --list-devices 做分组正确的查法是让工具帮你按物理设备分组v4l2-ctl --list-devices输出类似HD Camera (usb-xhci-hcd.1-1): /dev/video0 /dev/video1 Integrated Camera (usb-0000:00:14.0-5): /dev/video2这个输出把同一个物理设备的所有节点归在一组你一眼就能看出哪个摄像头对应哪几个/dev/videoN。括号里的字符串是设备在 USB 拓扑中的路径也是最稳定的标识比节点编号靠谱得多。然后你要判断组里哪个节点是真采集节点。用v4l2-ctl -d /dev/video0 --info输出里有一行Device Caps如果是采集节点会看到Device Caps : 0x84a00001 Video Capture Streaming Extended Pix Format如果只有Metadata Capture而没有Video Capture那这个节点不能出图。这一步能帮你快速排除掉 metadata 节点。3.3 用 udev 把设备节点固定下来如果你要写程序或者做服务每次启动都去猜节点号肯定不行。稳妥的做法是用 udev 规则根据 VID/PID 或者序列号建一个固定名字的软链接比如/dev/cam0。先拿信息udevadm info -a -n /dev/video0 | grep -E idVendor|idProduct|serial | head -5拿到idVendor、idProduct如果有serial更好序列号唯一能区分同型号的多个摄像头。然后写规则文件sudo vim /etc/udev/rules.d/99-usb-cam.rules内容SUBSYSTEMvideo4linux, ATTRS{idVendor}1bcf, ATTRS{idProduct}2c99, ATTR{index}0, SYMLINKcam0这里的关键是ATTR{index}0。同一个物理摄像头的多个 video 节点各自有个index属性采集节点的index通常是 0metadata 节点是 1。加上这个条件软链接才会指向真正的采集节点而不是随手指到一个 metadata 节点上。写完重载sudo udevadm control --reload-rules sudo udevadm trigger ls -l /dev/cam0如果软链接建出来了以后代码里就用/dev/cam0插拔顺序怎么变都不影响。这个技巧在工控机上特别有用因为工控机的 USB 口数量有限经常要换着插。提示如果同一型号插了两个摄像头光靠 VID/PID 区分不了必须用serial或者 USB 物理端口号KERNELS属性。我一般会优先用物理端口号因为序列号不是所有摄像头都提供。4. 分辨率、帧率和压缩格式怎么查4.1 --list-formats-ext 才是完整能力表这是全文最重要的一条命令v4l2-ctl -d /dev/video0 --list-formats-ext注意必须带-ext。不加-ext的--list-formats只列出像素格式名称不列分辨率和帧率信息量差一个数量级。典型输出长这样ioctl: VIDIOC_ENUM_FMT Type: Video Capture [0]: MJPG (Motion-JPEG, compressed) Size: Discrete 1920x1080 Interval: Discrete 0.033s (30.000 fps) Size: Discrete 1280x720 Interval: Discrete 0.033s (30.000 fps) Size: Discrete 640x480 Interval: Discrete 0.033s (30.000 fps) [1]: YUYV (YUYV 4:2:2) Size: Discrete 640x480 Interval: Discrete 0.033s (30.000 fps) Size: Discrete 640x480 Interval: Discrete 0.067s (15.000 fps)怎么读这张表几个要点格式是分组的第一层。每个[n]: FOURCC就是一个像素格式它下面挂的分辨率只在这个格式下有效。这是最容易被忽略的一点——同样的 1920x1080MJPG 下能跑到 30fpsYUYV 下可能压根不列出来。Size 后面是分辨率Discrete表示离散可选值不是连续范围。如果显示的是Stepwise那才是连续区间。Interval 是帧间隔单位秒括号里是换算出来的 fps。0.033s就是 30fps0.067s是 15fps0.1s是 10fps。同一个分辨率可能对应多个帧率像上面 640x480 在 YUYV 下就有 30fps 和 15fps 两档。从上面这个例子就能看出问题这个摄像头 1080p 只在 MJPG 下可用YUYV 最高就是 640x480。如果你用 OpenCV 默认去打开OpenCV 在 Linux 下默认会选 YUYV那你就永远拿不到 1080p。这不是摄像头的问题是格式选错了。4.2 常见的四种像素格式各自意味着什么表里出现的 FOURCC 就是像素格式的四字符代码。USB 摄像头上最常见的几种我把它们的含义和取舍整理成一张表FOURCC含义每像素字节数特点适用场景YUYVYUV 4:2:2 未压缩2画质无损CPU 零解码开销但带宽占用大低分辨率、高帧率、对延迟极敏感MJPGMotion-JPEG 压缩视质量而定约 0.1-0.2带宽小得多需要 CPU 解码高分辨率、USB 2.0 下唯一选择H264H.264 压缩更低需要摄像头支持 UVC 1.5可直接封流推 RTSP、低带宽传输NV12YUV 4:2:0 半平面1.5直接对接硬件编码器省一次转换RK3588 等带 VPU 的平台YUYV 之所以叫 4:2:2是因为它每两个水平相邻的像素共享一组色度采样亮度每个像素都保留。所以平均下来是每像素 2 字节。NV12 是 4:2:0色度再降一半平均 1.5 字节每像素。这几个格式在 RK3588 这类平台上做推流时选择逻辑完全不一样。如果摄像头能直接出 H264那基本是白捡的便宜直接封进 RTP 推出去RK3588 的 CPU 几乎不干活如果只能出 MJPG那就得先解 JPEGRK3588 有硬件 JPEG 解码可以分担如果是 YUYV那就得做 YUV 到 YUV420 的转换再交给硬件编码器多一道内存拷贝。4.3 抓一帧看看参数是不是真的生效光看能力表还不够因为能力表是自报家门不一定真实。稳妥的做法是抓一帧真正验证。用v4l2-ctl抓原始数据v4l2-ctl -d /dev/video0 --set-fmt-videowidth1920,height1080,pixelformatMJPG --stream-mmap --stream-count1 --stream-toframe.mjpg几个参数的含义--set-fmt-video设置要用的格式如果这个格式不支持命令会直接报错这也是一种验证方式。--stream-mmap用内存映射方式取流效率最高。--stream-count1只抓一帧。--stream-toframe.mjpg写到文件。注意这里的扩展名要和格式对应MJPG 存成.mjpgYUYV 存成.raw。抓完检查文件大小ls -l frame.mjpg一帧 1080p 的 MJPG 通常在 100KB 到 400KB 之间取决于画面复杂度。如果只有几 KB那大概率是抓空了或者格式没设上。更直观的方式是用 ffmpeg 直接抓成能看的图片ffmpeg -f v4l2 -input_format mjpeg -video_size 1920x1080 -i /dev/video0 -frames:v 1 out.jpg注意-input_format mjpeg这个参数必须加不然 ffmpeg 会按默认格式去协商很可能拿到的不是你想要的。抓出来打开看一眼如果画面正常、分辨率对得上那这条链路才算验证通过。想看实际跑多少帧率可以抓 100 帧然后看耗时time v4l2-ctl -d /dev/video0 --set-fmt-videowidth1920,height1080,pixelformatMJPG --stream-mmap --stream-count100 --stream-to/dev/null用总耗时除以帧数就能算出实际帧率。如果算出来比能力表标的低不少那多半就是带宽不够下一节专门讲这个。5. USB 带宽计算分辨率上不去的根本原因5.1 手算一遍为什么 1080p YUYV 在 USB 2.0 上跑不动这部分是全文最能解释为什么的地方。很多人遇到分辨率或帧率上不去第一反应是驱动问题、摄像头问题其实绝大多数时候是带宽算不过来。先算 YUYV 格式的 1080p30fps 需要多少带宽一帧像素数1920 × 1080 2,073,600 像素YUYV 每像素 2 字节2,073,600 × 2 4,147,200 字节/帧每秒 30 帧4,147,200 × 30 124,416,000 字节/秒换算成 MB/s124,416,000 / 1024 / 1024 ≈ 118.6 MB/s换算成 Mbps124,416,000 × 8 / 1,000,000 ≈ 995 Mbps也就是说YUYV 1080p30fps 需要将近1 Gbps 的稳定带宽。再看看 USB 2.0 能给多少。USB 2.0 高速模式理论速率 480 Mbps但这是原始信号速率扣掉协议开销、握手、事务间隔之后实际可用于等时传输的上限大概在24-30 MB/s 之间也就是 200-240 Mbps 左右。这还是理想情况。995 Mbps 的需求 vs 240 Mbps 的供给差了四倍多。所以 YUYV 1080p30 在 USB 2.0 上物理上就不可能实现跟驱动好不好没关系。按这个上限倒推USB 2.0 上 YUYV 格式能跑的分辨率大概是分辨率每帧字节数30fps 需求USB 2.0 能否支撑640x480614,40018.4 MB/s可以接近上限800x600960,00028.8 MB/s勉强实际常用 15fps1280x7201,843,20055.3 MB/s不行只能到 10fps 左右1920x10804,147,200118.6 MB/s完全不行这张表就能解释为什么那么多人在 USB 2.0 下只能拿到 640x480。不是摄像头不行是 USB 2.0 就这个水平。5.2 MJPG 为什么能救场MJPG 是压缩格式带宽占用取决于压缩比。同一款摄像头1080p 的一帧 MJPG 通常在 100KB 到 300KB 之间取中间值 200KB每帧 200KB 204,800 字节30fps204,800 × 30 6,144,000 字节/秒 ≈ 5.9 MB/s ≈ 47 Mbps47 MbpsUSB 2.0 完全吃得下还能留出很大余量。这就是为什么几乎所有 USB 2.0 摄像头的 1080p 都只挂在 MJPG 下面。代价是 CPU 要做 JPEG 解码。1080p30 的 MJPG 解码在普通 x86 上占一两个核在 RK3588 上如果有硬件 JPEG 解码单元开销就很小。所以选型时的逻辑很清晰如果你的平台 CPU 富余、又必须上高分辨率就用 MJPG如果对延迟极致敏感、分辨率要求不高就用 YUYV。还有一个中间选项是 MJPEG 的压缩比可调。有些摄像头允许通过 UVC 扩展单元设置 JPEG 质量质量调低带宽更小但画质下降这个要具体看摄像头是否支持。5.3 USB 3.0 下能放开到什么程度USB 3.0 超级速度理论 5 Gbps实际可用带宽大概在3.2-3.6 Gbps换算成字节大概 400-450 MB/s。这时候 YUYV 1080p30 需要的 118.6 MB/s 就完全没问题了甚至可以上 1080p60 或者 4K30 的 YUYV。但实际上很多标称 USB 3.0 的摄像头在 1080p 下依然只提供 MJPG因为厂商为了兼容性UVC 描述符里就把高分辨率绑在 MJPG 上。这时候你拿lsusb -t看到 5000M 也没用格式表里还是没有 YUYV 1080p。所以顺序一定是先确认链路速度再看格式表最后算带宽够不够。任何一步跳过去都可能白忙。5.4 迁移到 RK3588 推流场景的选型逻辑顺着这个思路说一下现在很多人做的 USB 摄像头转 RTSP 流。假设平台是 RK3588摄像头是普通 USB 2.0 的 1080p 模组链路是 480M。第一步查格式表大概率看到 1080p 只支持 MJPG。那么方案就是从/dev/cam0以 MJPG 格式读 1080p30交给 RK3588 的硬件 JPEG 解码器rkmpp里的mpp_jpegd解成 NV12NV12 直接喂给硬件 H.264 编码器mpp_enc编码后的码流用 RTSP 服务器推出去这条链路的关键在于每一步的格式都能对上中间不需要 CPU 做多余的色彩空间转换。如果一开始没查格式选了 YUYV那 1080p 压根拿不到或者硬用 MJPG 但用软件解码CPU 会被吃满30fps 都跑不满。再往上一层如果你的应用是超分辨率重建输入分辨率直接决定模型选型。360p 输入做 4 倍重建得到 1440p720p 输入做 2 倍也是 1440p两套模型的计算量差很多。而这些输入分辨率全都要从--list-formats-ext的结果里确认不能拍脑袋定。6. 常见问题速查与避坑经验6.1 故障速查表把我在实际项目里遇到过的问题整理成表格遇到类似症状可以直接对照排查现象可能原因排查方式解决方式ls /dev/video* 没有任何节点驱动未加载或供电不足dmesg 看有无 uvcvideo 报错换 USB 口、换线、检查供电有节点但打不开打开的是 metadata 节点v4l2-ctl --info 看 Device Caps换到有 Video Capture 的节点只能拿到 640x480应用默认选 YUYV高分辨率只在 MJPG--list-formats-ext 对比格式分组显式指定 MJPG 格式帧率远低于标称USB 带宽不足或链路降速lsusb -t 看 480M 还是 5000M换 3.0 口和 3.0 线或降到 MJPG图像花屏、条纹等时传输丢包线材质量差dmesg 看有无 uvcvideo 丢帧日志换屏蔽更好的短 USB 线换插口后节点号变了节点编号不固定v4l2-ctl --list-devices 看分组用 udev 规则固定软链接Device or resource busy节点被其他进程占用lsof /dev/video0杀掉占用进程或换节点权限不足 Permission denied当前用户不在 video 组id 命令查看所属组sudo usermod -aG video $USER 后重登这张表里出现频率最高的是只能拿到 640x480和帧率远低于标称这两条而它们背后是同一个原因没看格式表里的带宽约束。6.2 几条踩坑记录第一条线材比想象中重要得多。我曾经用一根三米长的 USB 延长线接 1080p 摄像头lsusb -t显示 480M格式表里 1080p 只有 MJPG结果推流半小时必掉线。换成原装 80 厘米的线之后链路协商到 5000MYUYV 1080p 都能出。所以遇到莫名其妙的不稳定先换线再排查软件这个顺序能省很多时间。第二条别忽略供电。USB 3.0 口标称 900mA2.0 口 500mA但那是单口上限实际主板上多个口共享。有些摄像头功耗偏高插在已经挂了几个外设的 hub 上就会掉。症状是插上后 dmesg 频繁报设备重连。解决方式是直插主板后置口或者用带独立供电的 hub。第三条关于帧率验证。不要相信格式表上标的 30fps一定要用time命令实测。我遇到过一款摄像头格式表标 1080p30实测只能跑到 22fps 左右因为它的固件在 MJPG 编码上速度不够。这种虚标在便宜模组上并不少见。实测的方式就是前面写的那条命令抓 100 帧算平均耗时。第四条多摄像头同时工作时要重新算带宽。一台机器上插两个 1080p MJPG 摄像头每个占 47 Mbps加起来 94 MbpsUSB 2.0 还是够的但如果两个都是 YUYV 720p单个就 55 MB/s加起来 110 MB/s直接超了。而且两个摄像头如果挂在同一个 USB 控制器下面它们共享这个控制器的总带宽。可以用lsusb -t看它们是否在同一个 root hub 下必要时分开到不同的控制器上。第五条关于内核日志。dmesg -w可以实时跟日志插拔摄像头的时候盯着看UVC 驱动的报错信息很有价值比如urb status -71这种通常是传输错误bandwidth not available就是带宽不够。养成接摄像头先开dmesg -w的习惯很多问题在第一秒就暴露了。第六讲一个容易被忽略的点v4l2-ctl设置的格式只对当前会话有效进程退出后就恢复默认了。如果你想改摄像头的持久默认值得靠应用自己在打开时设置或者用 udev 配合脚本。所以写代码的时候永远要在打开设备后显式调用一次格式设置不要指望上次设过就还在。最后再补一个查温度的小技巧。有些 USB 摄像头尤其是带 ISP 的模组长时间工作会发热降频帧率会慢慢掉。可以定期lsusb -v看描述符有没有变化或者干脆在摄像头外壳上贴个温度贴纸超过 60 度就要考虑加散热了。这个不是软件问题但很多越跑越慢的玄学问题根源就在这儿。我个人的习惯是每次拿到一个新摄像头先跑一套固定动作lsusb -t看链路速度v4l2-ctl --list-devices定位节点v4l2-ctl --list-formats-ext抄下能力表ffmpeg抓一帧验证最后time实测帧率。这五步下来大概两分钟但能避免后面几小时的瞎调。摄像头参数这东西看着琐碎实际上是整个视觉链路的地基地基没夯实上面盖什么都会歪。
返回列表