ARTICLE DETAIL

资讯详情

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

Ubuntu下USB摄像头参数查看与调试:v4l2-ctl与带宽计算

Ubuntu下USB摄像头参数查看与调试:v4l2-ctl与带宽计算 插上 USB 摄像头ls /dev/video*也能看到节点可一设 1920x1080 画面就卡成幻灯片或者标签上明明写着 30fps抓出来的流只有 7fps。这类问题十有八九不是代码写错了而是没先弄清楚这只摄像头到底对外声明了哪些参数。在 ubuntu 下看 USB 摄像头核心就四件事找到正确的设备节点、读出它在各个分辨率下支持的压缩格式、算出这些组合背后的带宽账、再用实际抓帧验证一遍。这篇内容面向所有需要在 Linux 上接摄像头的人——做机器视觉的、做视频采集的、在开发板上把 USB 摄像头转成 RTSP 流往外推的都会用到。哪怕你只是想在 OpenCV 里把画面调到 720p看完也能明白为什么cap.set()返回 True 但画面没变。所有命令都在 Ubuntu 20.04 / 22.04 / 24.04 上验证过内核 5.15 和 6.8 都适用。1. 设备节点不是一个摄像头而是一个功能节点1.1 为什么一个摄像头会占掉两个甚至三个 /dev/video很多人第一次看--list-devices会愣住明明只插了一只摄像头却冒出来/dev/video0和/dev/video1。这不是驱动抽风而是 UVCUSB Video Class规范里一个物理设备可以注册多个 video 节点最常见的是两到三个采集节点capture真正出图的那个v4l2-ctl --list-formats-ext能看到一堆分辨率的通常是 video0。元数据节点metadata capture用于传输每帧附带的曝光、时间戳等附加信息它不产生图像列格式时只会返回 Type: Metadata Capture。部分设备还会注册第二个采集节点用作某些厂商私有的第二路输出或者用于 H.264 硬编码通道。判断哪个是采集节点最省事的办法不是猜编号而是看能力v4l2-ctl --list-devices输出大概长这样HD Webcam: HD Webcam (usb-0000:00:14.0-3): /dev/video0 /dev/video1拿到编号之后逐个验证v4l2-ctl -d /dev/video0 --info v4l2-ctl -d /dev/video1 --info采集节点的Device Caps里会写Video Capture元数据节点则写Metadata Capture。这一步千万别跳过我在一个项目里就因为想当然地把 video1 当成采集口白白花了一个下午排查为什么读不到帧。顺手记一下/sys/class/video4linux/这个目录每个节点下面都有name和index两个文件cat /sys/class/video4linux/video0/name cat /sys/class/video4linux/video0/indexindex这个属性的意义是在同一个物理设备内部这个节点是第几个。采集节点通常是 0元数据节点是 1。这条信息后面写 udev 规则时会派上大用场。1.2 用 lsusb、dmesg 把摄像头的身份证翻出来知道节点编号只是第一步你还得确认它是从哪个 USB 口上来的、VID/PID 是多少。这两条命令配合着用lsusb dmesg | grep -i -E uvc|usb.*camera | tail -20lsusb给的是 VID:PID 和厂商字符串dmesg给的是内核识别过程。如果摄像头是 UVC 标准设备插上时会看到类似这样的日志uvcvideo: Found UVC 1.00 device HD Webcam (046d:0825) uvcvideo 1-3:1.0: UVC device initialized1-3就是 USB 拓扑路径总线 1端口 3。这条路径是硬件位置不会因为重启而变比/dev/video0这种编号稳定得多。真正会漂的是节点号两台摄像头同时插上时谁先枚举完谁就是 video0跟插在哪个口没关系。想看得更细一点用lsusb -tlsusb -t输出里会显示每个设备挂在哪个 hub 下、协商到的速率480M 表示 USB 2.0 High Speed5000M 表示 USB 3.0。这一点在后面算带宽的时候是关键证据。也可以直接读 sysfscat /sys/bus/usb/devices/1-3/speed如果这里返回 480而摄像头本身标称是 USB 3.0那基本可以确定是线材不行、插在了 USB 2.0 口上或者中间串了个 USB 2.0 hub。带宽会直接砍到十分之一后面所有帧率问题都是从这里来的。1.3 udevadm 与固定符号链接别让 /dev/video0 换号生产环境里最忌讳代码里写死/dev/video0。解决方法是查属性、写规则、做符号链接。先看节点身上带了哪些属性udevadm info -q property -n /dev/video0 udevadm info -a -n /dev/video0 | head -50-q property输出的是已经解析好的键值对比如ID_VENDOR_ID、ID_MODEL_ID、ID_SERIAL_SHORT、ID_PATH。-a输出的是属性链能看到父设备的ATTRS{idVendor}、ATTRS{idProduct}这些才是写规则时能用的匹配条件。然后写一条规则文件放到/etc/udev/rules.d/90-usb-camera.rulesSUBSYSTEMvideo4linux, KERNELvideo[0-9]*, \ ATTRS{idVendor}046d, ATTRS{idProduct}0825, \ ATTR{index}0, \ SYMLINKcam-front, MODE0660, GROUPvideo这条规则里最容易被忽略的是ATTR{index}0。少了它采集节点和元数据节点会一起被匹配上udev 会给两个节点抢同一个符号链接名最后哪个赢是随机的。我第一次写规则时就没加结果/dev/cam-front时不时指向元数据节点表现就是程序跑一半读不到帧。改完规则sudo udevadm control --reload-rules sudo udevadm trigger ls -l /dev/cam-front之后代码里统一用/dev/cam-front插拔顺序换了也不受影响。2. v4l2-ctl 读能力参数的正确姿势2.1 装 v4l-utils先摸清家底v4l2-ctl来自v4l-utils包Ubuntu 上直接装sudo apt update sudo apt install -y v4l-utils v4l2-ctl --version装完第一条命令永远是--list-devices先把系统里所有 video 节点和它们的物理归属列出来。如果你在一台机器上跑多路摄像头这一步能帮你快速建立节点号 ↔ 物理设备的映射表记在便签上都值得。接下来是--info它回答的是这个节点能干什么v4l2-ctl -d /dev/video0 --info输出里的Driver name如果是uvcvideo说明是标准 UVC 摄像头如果是rkisp、sun6i-video这类那就是 SoC 的 MIPI CSI 通道。Bus info会给出 USB 拓扑路径和前面dmesg看到的能对上。Capabilities里的Video Capture、Streaming、Extended Pix Format三个标志基本是标配Device Caps才是这个具体节点的实际能力集。顺便把所有控制项也拉一份出来调曝光、亮度、对焦的时候用得上v4l2-ctl -d /dev/video0 --list-ctrls v4l2-ctl -d /dev/video0 --list-ctrls-menus--list-ctrls给的是数值型控制会显示当前值、最小值、最大值、步长。--list-ctrls-menus给的是枚举型控制比如自动曝光模式有哪些可选、白平衡预设有哪些。这两个命令在调参阶段比图形界面快得多。2.2 --list-formats-ext 的三层结构格式、分辨率、帧率这是整篇文章最重要的一条命令v4l2-ctl -d /dev/video0 --list-formats-ext它的输出是严格的三层嵌套结构看懂了这个结构摄像头的能力就全在纸面上了ioctl: VIDIOC_ENUM_FMT Type: Video Capture [0]: MJPG (Motion-JPEG, compressed) Size: Discrete 1920x1080 Interval: Discrete 0.033s (30.000 fps) Interval: Discrete 0.040s (25.000 fps) Size: Discrete 1280x720 Interval: Discrete 0.033s (30.000 fps) Interval: Discrete 0.017s (60.000 fps) Size: Stepwise 640x480 - 1280x720 with step 160/90 [1]: YUYV (YUYV 4:2:2) Size: Discrete 640x480 Interval: Discrete 0.033s (30.000 fps) Size: Discrete 1280x720 Interval: Discrete 0.100s (10.000 fps)第一层是像素格式方括号里的四个字符是 FourCC 码后面括号里是描述。compressed表示这是压缩格式摄像头内部已经压过了没有这个标记的就是原始格式raw。第二层是分辨率。这里有个细节要特别注意分辨率有两种声明方式。Discrete表示离散枚举只能取列出来的这些值不能插值Stepwise表示连续可调格式是最小值 - 最大值步长 宽/高。上面例子里640x480 - 1280x720 with step 160/90意思就是宽度可以从 640 按 160 递增到 1280高度从 480 按 90 递增到 720。遇到 Stepwise你能设的是一大片组合但能不能真的跑起来还得看带宽。第三层是帧率。同样是Discrete或Stepwise单位是秒间隔和 fps 两种写法并存。这里列出来的是这个格式 这个分辨率组合下驱动允许申请的帧率集合。2.3 MJPG 和 YUYV 差在哪位深、带宽与画质取舍为什么同一只摄像头MJPG 能给到 1080p30YUYV 只给到 720p10答案就是位深。YUYV 是 4:2:2 采样的原始格式每个像素平均占 16 bit也就是 2 字节。注意这里有个常见误解4:2:2 是亮度每像素一个采样、色度每两个像素一个采样摊到每个像素是 16 bit不是 24 bit。RGB24 那种 3 字节/像素的格式在 UVC 摄像头上很少见。MJPG 是每帧独立压缩的 JPEG。压缩率取决于画面内容和质量设置实测 1080p 的 MJPEG 码流通常在 20~40 Mbps 之间折算下来 2.5~5 MB/s。而 1080p 的 YUYV 是 1920×1080×2×30 ≈ 124 MB/s。两者差了将近三十倍。这个差距直接决定了 USB 2.0 上什么能跑什么不能跑。USB 2.0 高带宽等时端点每个 microframe125 微秒最多传 3 个 1024 字节的包也就是 3072 字节换算上限 ≈ 24.576 MB/s ≈ 196.6 Mbps。拿这个上限去卡各种组合格式分辨率帧率原始码率USB 2.0 能否承载YUYV640x4803018.4 MB/s可以接近上限YUYV1280x7201027.6 MB/s超了通常只能到 7.5fpsYUYV1920x108030124 MB/s完全不可能MJPG1920x108030约 3~5 MB/s轻松所以当你看到一只 USB 2.0 摄像头在--list-formats-ext里 MJPG 支持 1080p30、YUYV 只支持 640x480这不是厂商偷懒是物理约束。理解了这一层选型时就不会提我要用 YUYV 跑 1080p画质好这种需求了——除非你把摄像头插到 USB 3.0 口上。画质上的取舍也值得说一句MJPEG 是帧内压缩会引入块效应尤其在暗部和高频细节上明显。做图像测量、边缘检测这类对像素值敏感的任务能上 YUYV 就上 YUYV做人眼观看的监控画面、视频通话MJPEG 完全够用还能省下 USB 带宽给第二路摄像头。2.4 用 get/set 三件套验证真实生效值列表里写了不等于设得上。验证要三步走# 1. 看当前生效值 v4l2-ctl -d /dev/video0 --get-fmt-video # 2. 尝试设置 v4l2-ctl -d /dev/video0 --set-fmt-videowidth1280,height720,pixelformatMJPG # 3. 再看一次确认是否被驱动改写 v4l2-ctl -d /dev/video0 --get-fmt-video--get-fmt-video的输出有几个字段值得逐一看Width/Height当前实际生效的分辨率。Pixel Format当前像素格式的 FourCC。Field扫描方式USB 摄像头基本都是None。Bytes per Line每行占多少字节。YUYV 下这个值通常是宽度的两倍MJPG 下没有意义。Size Image单帧缓冲区大小。写采集程序分配 buffer 时按这个来比你自己算靠谱。Colorspace色彩空间通常是sRGB或Rec. 709。--set-fmt-video有一个非常重要的特性它不保证成功也不保证按你要的值生效。V4L2 的VIDIOC_S_FMT语义是驱动返回它能给你的最接近的组合。如果你要1280x800而设备只有1280x720和1600x1200驱动通常会回退到1280x720然后返回成功。所以第 3 步的复查是必须的不能只看返回值。帧率是单独管的v4l2-ctl -d /dev/video0 --get-parm v4l2-ctl -d /dev/video0 --set-parm15--get-parm返回的Frames per second是当前设定的目标帧率。注意这是目标不是实测。UVC 摄像头控制帧率的方式比较间接——驱动会去挑一个端点的 alt setting让带宽刚好装得下你要的帧率。所以设了 30 实际跑 10多半是带宽不够被降了档这一点在第 3 节详细说。另外还有两个专门查尺寸和帧率的辅助命令写脚本做能力探测时很好用v4l2-ctl -d /dev/video0 --list-framesizesMJPG v4l2-ctl -d /dev/video0 --list-frameintervalswidth1280,height720,pixelformatMJPG3. USB 层的参数端点、alt setting 与带宽账3.1 lsusb -v 里的 VideoStreaming 描述符与 bAlternateSettingv4l2-ctl看到的是 V4L2 层的抽象再往下一层是 USB 描述符。这一层的信息决定了为什么我申请的组合会失败。命令是sudo lsusb -v -d 046d:0825 2/dev/null | less阅读权限不够时加sudo某些发行版上还可能需要先确认usbfs挂载正常。要关注的是VideoStreaming Interface Descriptor那几段。它长这样简化Interface Descriptor: bInterfaceClass 14 Video bInterfaceSubClass 2 Video Streaming bNumEndpoints 1 ... VideoStreaming Interface Descriptor: bNumFormats 2 Endpoint Descriptor: bEndpointAddress 0x81 EP 1 IN bmAttributes 1 Transfer Type Isochronous wMaxPacketSize 0x1400 1x 3072 bytes bInterval 1 Interface Descriptor: bInterfaceNumber 1 bAlternateSetting 8 ...几个关键字段的含义bInterfaceClass 14 / bInterfaceSubClass 2确认这是标准 UVC 视频流接口。wMaxPacketSize 0x1400等时端点每包最大 3072 字节高带宽模式等于 3×1024。bInterval轮询间隔1 表示每个 microframe 都传。bAlternateSetting这是 UVC 最关键的设计。同一个 Streaming 接口有多个 alternate settingalt 0 带宽为 0没有端点用来停流alt 1 到 alt N 各自声明一组不同的带宽。驱动在VIDIOC_S_FMT时会去遍历这些 alt setting挑一个刚好能装下你请求的帧尺寸和帧率的。也就是说你在 V4L2 层申请的每一个格式分辨率帧率组合最终都对应到某一个具体的 alt setting。如果没有任何 alt setting 能装下这个组合就会返回EINVAL或者被驱动降到更低的档位。这就是 2.4 节里设了值不生效的根本原因。3.2 算一笔带宽账解释标称 30fps 实测只有 10fps有了上面的描述符带宽账就能算得明明白白单端点有效带宽 wMaxPacketSize × 每 microframe 包数 ÷ 125 μs上面那个例子是 3072 字节 / 125 μs 24.576 MB/s ≈ 196.6 Mbps。再算你要的组合需要多少YUYV: 宽 × 高 × 2 字节 × 帧率 MJPG: 宽 × 高 × 平均压缩比实测值通常 1/20 ~ 1/50× 帧率拿 640x480 YUYV30 举例640×480×2×30 18,432,000 B/s 18.43 MB/s小于 24.576 MB/s能跑。拿 1280x720 YUYV30 举例1280×720×2×30 55.3 MB/s超了两倍多。驱动会怎么办它会去找一个更低的帧率让乘积落进 24.576 MB/s 以内24.576×10⁶ ÷ (1280×720×2) ≈ 13.3 fps。而 UVC 摄像头通常只提供若干离散帧率30、20、15、10、7.5所以最后能给你的是 10 fps。这刚好解释了列表里那个1280x720 10.000 fps是怎么来的。这个计算方法我在好几个项目里用来做前期评估比插上试试快得多。拿到摄像头规格书或lsusb -v的描述符十分钟就能判断出它在 USB 2.0 上能跑什么、在 USB 3.0 上能跑什么不用一个个试。顺便提一下 USB 3.0 的量级理论 5 Gbps实际等时传输大概能拿到 350~400 MB/s。1080p YUYV30 需要 124 MB/s没问题1080p YUYV60 需要 249 MB/s勉强4K YUYV 就别想了。3.3 压缩格式到底谁在压摄像头硬件还是主机这个问题经常被搞混。--list-formats-ext里的compressed标记指的是摄像头输出给主机的数据已经是压缩过的。MJPEG 帧从 USB 线上走下来的时候就是一堆 JPEG 字节流主机收到之后要自己解码。对 CPU 的影响很大1080p30 的 MJPEG 软解在普通 x86 上大概占 15%~30% 的一个核在嵌入式板上就吃力了比如常见的那类带 NPU 的板卡跑 MJPEG 软解 1080p30 可能直接吃掉一两个大核。如果只是把流往外推比如转成 RTSP其实不需要解码再编码可以让 ffmpeg 直接把 MJPEG 重新封装进容器或者转成 H.264 硬编。前提是你得先知道摄像头输出的是 MJPG这又回到第 2 节。还有一类格式值得单独提H.264。部分 USB 摄像头支持直接输出 H.264 码流FourCC 通常是H264这种格式下摄像头内部已经做了硬编码主机几乎零开销是推流场景最理想的选择。但 UVC 规范对 H.264 的支持是后期才加的老设备基本没有检查方法还是在--list-formats-ext里看有没有H264那一项。3.4 看实际占用了多少带宽理论算完了想知道实际有没有跑满、有没有别的设备在抢带宽有两个地方可以看。第一个是lsusb -t它会显示设备协商到的速率lsusb -t/: Bus 02.Port 1: Dev 1, Classroot_hub, Driverxhci_hcd/6p, 10000M |__ Port 3: Dev 5, If 0, ClassVideo, Driveruvcvideo, 480M这里的480M就是警钟。哪怕摄像头是 USB 3.0 的只要挂在 480M 上前面的带宽账就得按 24.576 MB/s 重算。第二个是 USB 总线的实时占用需要 root 权限读 debugfssudo mount -t debugfs none /sys/kernel/debug # 如果还没挂载 sudo cat /sys/kernel/debug/usb/devices输出里会有一条S: Product...接着是端点行形如E: Ad81(I) Atr01(Isoc) MxPS3072 Ivl125usMxPS3072就是当前这个端点实际申请到的最大包大小。如果流在跑这个值会反映出实际的带宽预留。对比一下你算出来的理论值就知道有没有浪费。4. 用抓帧和图形工具做交叉验证4.1 ffmpeg 与 ffprobe抓一帧、打一串v4l2-ctl是声称支持什么ffmpeg 是实际能跑出什么。两者对不上问题一定在某个环节。先列出 ffmpeg 眼中的可用格式ffmpeg -f v4l2 -list_formats all -i /dev/video0输出会分成Raw和Compressed两组分别对应 YUYV 这类原始格式和 MJPG 这类压缩格式每个格式后面跟着支持的分辨率列表。这条命令的好处是它和v4l2-ctl是两套独立实现互相印证。抓一帧存成图片ffmpeg -f v4l2 -input_format mjpeg -video_size 1280x720 -framerate 30 \ -i /dev/video0 -frames:v 1 frame.jpg这里-input_format必须显式指定。不指定的话ffmpeg 会自己挑一个默认最优在不同设备上行为不一致可能给你挑到 YUYV 然后直接因为带宽不足失败。录 10 秒看看实际帧率ffmpeg -f v4l2 -input_format mjpeg -video_size 1920x1080 -framerate 30 \ -i /dev/video0 -c:v libx264 -preset ultrafast -t 10 out.mp4跑完之后看 ffmpeg 结尾的统计行fps那一列就是实测帧率。如果输入是 30 而实测只有 15说明驱动实际给的是 15回去用v4l2-ctl --get-parm复查。ffprobe用来看设备当前的输出参数ffprobe -f v4l2 -input_format mjpeg -video_size 1280x720 -i /dev/video0它会阻塞等设备就绪然后把分辨率、像素格式、帧率、色彩空间全部打出来CtrlC 退出即可。这个输出格式比v4l2-ctl更接近一个播放器看到的世界做兼容性验证时很有价值。4.2 guvcview 与 qv4l2把参数变成可点的按钮命令行熟练之后图形工具依然有用——排查某个特定分辨率到底出不出图的时候点一下比敲命令快。guvcview是 GTK 的 UVC 专用工具sudo apt install -y guvcview guvcview -d /dev/video0它会把所有支持的格式、分辨率、帧率做成下拉框选完立刻出画面还能顺手调曝光、白平衡。更贴心的是它有一个格式面板会把每个格式对应的实际 fps 显示出来实测值和标称值不一致时会很明显。qv4l2是 V4L2 官方的 Qt 测试工具功能更偏底层sudo apt install -y qv4l2 qv4l2 -d /dev/video0它能直接列出所有 control、所有格式、所有帧率还能手动下发VIDIOC_S_FMT和VIDIOC_S_PARM验证某个组合到底成不成立。调试那种列表里有但设不上的边缘组合用 qv4l2 比写临时脚本快。4.3 OpenCV 与 GStreamer 拿到的参数为什么和 v4l2-ctl 不一样这是被问得最多的一个问题。现象是v4l2-ctl --list-formats-ext明明写着支持 1080p30OpenCV 里设完却只有 640x480或者帧率对不上。原因有三个按出现频率排第一OpenCV 的 set 是异步的而且不报错。cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1920)返回 True 只代表命令发出去了不代表生效了。正确做法是设完立刻回读import cv2 cap cv2.VideoCapture(0, cv2.CAP_V4L2) # 先定格式再定分辨率顺序很重要 cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*MJPG)) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1920) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080) cap.set(cv2.CAP_PROP_FPS, 30) print(实际得到:, int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)), cap.get(cv2.CAP_PROP_FPS))注意CAP_PROP_FOURCC要先于分辨率设置。因为切格式会导致驱动重新枚举尺寸顺序反了之前设的分辨率可能被重置。第二OpenCV 默认走的是自己那套 V4L2 后端在部分版本上默认尝试 YUYV。一旦格式是 YUYV1080p 就必然失败然后就静默回退到默认分辨率。显式指定 FOURCC 是唯一的解法。第三GStreamer 和 OpenCV 看到的设备可能根本不是同一个节点。GStreamer 可以用gst-device-monitor-1.0枚举gst-device-monitor-1.0 Video它会列出每个设备的名字、节点路径、以及一份 caps 表这份 caps 表是 GStreamer 自己探测出来的格式和v4l2-ctl完全不同但内容一致。对比两边的输出能快速定位是不是选错了节点。用 GStreamer 直接拉流测试gst-launch-1.0 v4l2src device/dev/video0 \ ! image/jpeg,width1280,height720,framerate30/1 \ ! jpegdec ! autovideosink这里的 caps 写法image/jpeg对应 MJPGvideo/x-raw,formatYUY2对应 YUYV。如果 caps 写得设备不支持v4l2src会协商失败并打印详细的协商过程这个过程本身就是最好的调试信息。4.4 一个批量导出能力清单的脚本做项目交付的时候我习惯把所有摄像头的完整能力导成一份文本附在部署文档里。脚本很简单#!/bin/bash OUTcamera-report-$(date %Y%m%d-%H%M).txt { echo 生成时间: $(date) echo echo lsusb 设备列表 lsusb echo echo USB 拓扑与协商速率 lsusb -t for dev in /dev/video*; do [ -e $dev ] || continue echo echo ########## $dev ########## echo --- sysfs name/index --- cat /sys/class/video4linux/$(basename $dev)/name 2/dev/null cat /sys/class/video4linux/$(basename $dev)/index 2/dev/null echo --- v4l2 info --- v4l2-ctl -d $dev --info 21 echo --- 支持的格式/分辨率/帧率 --- v4l2-ctl -d $dev --list-formats-ext 21 echo --- 当前生效格式 --- v4l2-ctl -d $dev --get-fmt-video 21 echo --- 当前帧率设定 --- v4l2-ctl -d $dev --get-parm 21 done } | tee $OUT echo 报告已生成: $OUT这份报告拿到手任何一台新机器上的摄像头问题都能在几分钟内定位。我在嵌入式项目里也用它——板子上跑之前先导出报告和开发机上的一对比就知道是驱动差异还是硬件差异。5. 那些真正会卡住人的坑5.1 节点存在但打不开权限与占用/dev/video0的默认权限通常是crw-rw---- root video。当前用户不在video组里就会报Permission denied。groups # 看看有没有 video sudo usermod -aG video $USER加完组必须重新登录或者newgrp video因为组信息在登录时就被写进进程凭证里了光改文件不生效。这一点坑过很多人改完就测试还是报权限错其实只是没重登。另一个更隐蔽的问题是设备被占用。V4L2 的采集设备默认是独占的一个进程打开着第二个进程打开会返回EBUSY。sudo lsof /dev/video0 sudo fuser -v /dev/video0经常发现是之前跑挂的后台 ffmpeg 或者一个忘了关的 Python 进程还占着。用fuser -k /dev/video0干掉即可但注意别把正在跑的业务进程误杀。5.2 分辨率列出来了却设置失败这是最让人困惑的一类问题列表里明明有就是设不上。常见原因有四种。离散 vs 连续的误解。列表里写Discrete就是只能取那几个值你设一个中间值驱动只会回退。判断方法看--set-fmt-video之后的--get-fmt-video返回值。格式与分辨率不匹配。你要设1280x720 YUYV但那个分辨率只在 MJPG 下存在。很多人只看了自己关心的那一项忘了分辨率是从属于格式的。正确做法是先定格式再在--list-framesizesMJPG的结果里挑分辨率。带宽冲突。组合在列表里但驱动需要同时满足多个约束比如同时开着第二路挑不出合适的 alt setting就会失败。这种时候先停掉其他摄像头再试。驱动 quirk。某些摄像头型号在内核里有专门的 quirk 处理比如强制某个格式、禁用某个分辨率。可以查看modinfo uvcvideo | grep -i quirk cat /sys/module/uvcvideo/parameters/quirks dmesg | grep -i quirk如果发现是 quirk 导致的可以尝试用uvcvideo.quirks0x...内核参数覆盖但这个操作需要清楚在做什么乱改会引入更多问题。5.3 多摄像头同插帧率集体掉档这是带宽竞争最直观的表现。两个 1080p MJPEG 摄像头挂同一个 USB 控制器下本来各自 30fps一起开之后就变成各 15fps。原因是它们共享同一个 USB 控制器的总带宽。排查步骤lsusb -t看两个摄像头是不是挂在同一个 root hub 下。如果是把其中一个换到另一个物理 USB 控制器上主板后面板通常分成几组前置面板往往和某组后置共用。换完之后再跑lsusb -t确认拓扑变了。用v4l2-ctl -d /dev/videoX --stream-mmap --stream-count300分别测两路的实际帧率。--stream-mmap --stream-countN这个用法特别适合快速压测它直接在内核里做内存映射采集不走用户态拷贝跑完之后会打印实际达到的帧率。v4l2-ctl -d /dev/video0 --stream-mmap --stream-count300 --stream-to/dev/null输出末尾的fps就是实测值。比写 OpenCV 脚本快得多。还有一个经验如果确实要在一个控制器上跑多路优先用 MJPEG 或者 H.264别用 YUYV。YUYV 一路就能吃满 USB 2.0根本没给第二路留空间。5.4 虚拟机、WSL 和远程桌面下看到的参数会残缺在虚拟机里跑 UbuntuUSB 摄像头是透传进来的看到的--list-formats-ext可能只有寥寥几项。这不是摄像头的问题是虚拟 USB 控制器的能力限制——它可能不支持高带宽等时端点或者干脆把等时传输转换成了块传输。判断方法在宿主机和虚拟机里分别跑一遍--list-formats-ext对比结果。如果虚拟机里少了很多项那就是虚拟化层的问题。解决办法通常是换 USB 控制器版本比如 VMware 里从 USB 2.0 换成 USB 3.0 控制器、或者改用物理机。WSL2 的情况更特殊默认根本不带uvcvideo驱动需要自己编译内核模块并做 USB 转发而且官方对等时传输的支持一直不完整。真要做视频采集别在 WSL 里折腾直接用物理机或者双系统。远程桌面VNC、RDP下也要注意如果你是通过远程桌面看图形工具的输出guvcview可能因为拿不到显示设备而报错但命令行工具一切正常。这种情况用--stream-to或 ffmpeg 抓帧到文件比开图形界面靠谱。5.5 推流前的参数核对清单最后说一下把 USB 摄像头转成 RTSP 流这个典型场景。很多人卡在流推上去了但画面卡顿、花屏、掉帧其实都是推流前的参数没核对。在动手写推流脚本之前先确认这几项核对项命令期望结果节点是采集口--infoDevice Caps 含 Video Capture目标格式可用--list-formats-ext目标 FourCC 存在目标分辨率可用--list-framesizesFMT分辨率在列表中目标帧率可用--list-frameintervals...帧率在列表中USB 速率够用lsusb -t按第 3.2 节算出的需求匹配实测帧率达标--stream-mmap --stream-count300实测 fps 接近目标单帧能解出ffmpeg -frames:v 1生成正常图片这套核对流程我在几个板卡项目上固定下来之后推流跑不起来这类问题的排查时间从半天缩短到十几分钟。尤其第 6 项--stream-mmap的实测帧率是最有说服力的证据比任何配置文件的声明都可靠。推流本身如果用的是 ffmpeg输入参数一定要显式写全不要依赖默认值ffmpeg -f v4l2 -input_format mjpeg -video_size 1280x720 -framerate 30 \ -i /dev/video0 \ -c:v libx264 -preset ultrafast -tune zerolatency -g 30 \ -f rtsp -rtsp_transport tcp rtsp://目标地址/流名-input_format、-video_size、-framerate三个参数都要写死和前面核对出来的能力严格对应。少写任何一个ffmpeg 都可能自己挑一个组合然后因为带宽不足静默降质。-g 30把 GOP 设成和帧率一致是为了让关键帧每秒来一次缓解推流初期的花屏。我个人在实操中的体会是USB 摄像头的所有玄学问题九成都能量化到带宽和格式这两件事上。养成先跑--list-formats-ext、再算一遍带宽、最后用--stream-mmap实测确认的习惯比反复重装驱动、换线、换口管用得多。
返回列表