Linux 上一行 -f v4l2 -i /dev/video0 就开干,参数只有五个。 其中最值钱的是 -input_format mjpeg——它能把带宽砍到几分之一。
9.5.1 把摄像头接进了虚拟机,这一节真去采它。 顺便把三平台的写法摆在一起对照。
一、Linux 上的一条命令
采五分钟存成文件,就这么长:
ffmpeg -f v4l2 -i /dev/video0 -c:v libx264 -t 5 out.mp4
参数 作用
-f v4l2 告诉它这是 Linux 摄像头接口 -i /dev/video0 第一个视频节点 -video_size 1280x720 要多大,不写用设备默认 -framerate 30 要多少帧,不写用设备默认 -input_format mjpeg 让设备直接吐 MJPEG
-t 5 是「录五秒就停」,不写会一直录到按 Ctrl+C。
这个参数在采集里特别有用——设备是没有「结束」这个概念的。
二、为什么一定要 -input_format mjpeg
USB 2.0 的理论带宽只有 480 Mbps,实际能用的约 60 MB/s。 而 720p 的裸 YUYV 一秒就要这么多:
格式 一帧字节 一秒(30 帧)
YUYV 422 1843200 52.7 MB RGB 24 2764800 79.1 MB H.264 4 Mbps — 0.5 MB
52.7 MB 对 60 MB/s,一个摄像头就吃掉近九成带宽。 再加一个摄像头,直接不够——这就是「插两个就都卡」的原因。
-input_format mjpeg 是让设备内部先压一道。
MJPEG 就是「每一帧都是一张 JPEG」,压缩率大概十分之一,
带宽立刻降到 5 MB/s 量级,两个摄像头也够了。
代价是 CPU:解码 MJPEG 要花力气。 所以这是「用 CPU 换带宽」——跟 9.2 里「设备吐原始、自己压」正好相反。
三、三个平台的写法对照
换平台只改前两个参数,后面那一长串可以照抄。
平台 命令
Linux ffmpeg -f v4l2 -i /dev/video0 …
Windows ffmpeg -f dshow -i video="名字" …
macOS ffmpeg -f avfoundation -i "0:0" …
差别只在「接口名 + 设备名的写法」: Linux 用路径,Windows 用设备名,macOS 用「视频序号:音频序号」。
macOS 的 0:0 特别容易写错——第一个 0 是视频设备序号,
第二个 0 是音频设备序号,不想采音频要写 0:none。
四、实测:本机四种结果对应四种原因
同一套试开代码,在 Windows 上得到四种完全不同的结果。
接口 结果 原因
v4l2 ValueError 接口没编进去 dshow OSError 接口在,但没设备 vfwcap 能解码 接口在,也有设备 gdigrab OSError 接口在,没有桌面会话
四种结果对应四种完全不同的排查方向:
ValueError 只能换二进制,OSError 要考虑设备与权限,
「能解码」才是真正可用的。
采集中最费时间的判断就是这个—— 先分清「没有通路」「没有设备」「有设备但没权限」三件事, 再谈参数怎么调。
五、动手 Demo:四种结果,四种原因
下面这段对四个接口各试一次,把异常类型与宽高都打出来。 然后算一遍 720p 裸 YUYV 一秒要多少带宽。
import av
def probe(fmt, arg):
c = None
try:
c = av.open(arg, format=fmt)
st = c.streams[0]
for _f in c.decode(st):
return u"能解码 %dx%d" % (st.codec_context.width,
st.codec_context.height)
return u"解不出帧"
except Exception as e:
return type(e).__name__
finally:
if c is not None:
c.close()
for fmt, arg in (("v4l2", "/dev/video0"),
("dshow", "video=nonexist"),
("vfwcap", "0"),
("gdigrab", "desktop")):
print(u"%-10s -> %s" % (fmt, probe(fmt, arg)))
W, H, FPS = 1280, 720, 30
for name, bpp in (("YUYV 422", 2), ("RGB 24", 3)):
one = W * H * bpp
print(u"%s 一帧 %d 字节 一秒 %.1f MB"
% (name, one, one * FPS / 1048576.0))
实跑输出:
--------------------------------------------------------------------
9.5.2 FFmpeg 采集 USB 摄像头获取的数据
--------------------------------------------------------------------
**① Linux 上的一条命令**
`ffmpeg -f v4l2 -i /dev/video0 -c:v libx264 -t 5 out.mp4`
参数 作用
--------------------------------------------------------------
-f v4l2 告诉 FFmpeg 这是 Linux 摄像头接口
-i /dev/video0 第一个视频节点
-video_size 1280x720 要多大,不给就用设备的默认值
-framerate 30 要多少帧,不给就用设备的默认值
-input_format mjpeg 让设备直接吐 MJPEG,省带宽
**`-input_format mjpeg` 是这一节最值钱的一个参数。**
**USB 2.0 的带宽只有 480 Mbps,一路 720p 的裸 YUYV 就要占掉大半;**
**换 MJPEG 是设备内部先压一道,带宽立刻降到几分之一。**
**② 实测:本机没有 v4l2,只有 Windows 那两个**
接口 参数 结果 宽高
---------------------------------------------------------
v4l2 /dev/video0 ValueError —
dshow video=nonexist OSError —
vfwcap 0 能解码 640x480
gdigrab desktop OSError 0x0
**`v4l2` 报 `ValueError`(没编),`dshow` 报 `OSError`(没设备),**
**`vfwcap` 能出画面,`gdigrab` 开着但解不出。**
**四种结果对应四种完全不同的原因——这就是采集中最费时间的判断。**
**③ 同一件事在 Windows 上怎么写**
平台 命令
-------------------------------------------------------
Linux `ffmpeg -f v4l2 -i /dev/video0 …`
Windows `ffmpeg -f dshow -i video="摄像头名" …`
macOS `ffmpeg -f avfoundation -i "0:0" …`
**三者的差别只在「接口名 + 设备名的写法」,后面编码、封装完全一样。**
**所以换平台时只需要改前两个参数,中间那一长串可以照抄。**
**④ 采到的原始帧有多大**
格式 一帧字节 一秒(30 帧)
-----------------------------------------------
YUYV 422 1843200 52.7 MB
RGB 24 2764800 79.1 MB
H.264 4 Mbps — 0.5 MB
**720p 的裸 YUYV 一秒要 53 MB,USB 2.0 的 60 MB/s 理论带宽被吃掉近九成。**
**这就是为什么一定要用 `-input_format mjpeg`——**
**先让设备压成几百 KB,再交给 FFmpeg 解码,带宽一下就够了。**
ValueError 与 OSError 是两种完全不同的事:
前者是「没这条通路」,后者是「通路有、设备没有」。
720p 的 YUYV 一秒 52.7 MB,对 USB 2.0 的 60 MB/s 来说太满了——
这就是 -input_format mjpeg 存在的理由。
30 秒实验
- 在
-input_format mjpeg下再算一次带宽:约十分之一,两个摄像头也够。 - 把
-video_size改成 320x240:算一下带宽降到了多少。 - 把
-framerate从 30 降到 15:帧率减半,带宽也减半。
常见误区
- 采 720p 裸 YUYV 还插两个摄像头。带宽直接不够,两个都卡。
- 把
ValueError当成「设备没插」。它是接口没编进去。 - macOS 写
-i 0。少一个冒号,会被当成文件路径。
自测三问(点开对答案)
1. 720p 的 YUYV 一帧多少字节? —— 1843200 字节,等于 1280 乘 720 乘 2。 2. 为什么建议加 `-input_format mjpeg`? —— 让设备先压一道,带宽降到约十分之一,代价是多花 CPU 解码。 3. `ValueError` 与 `OSError` 分别说明什么? —— 前者是接口没编进去,后者是接口在但设备(或权限)没有。- ☐ 9.5.2 完,下一节 9.6.1 Windows 系统中 FFmpeg 录屏