设备枚举没有统一接口,只能「按接口名试开」。 而且「打开成功」跟「能用」是两件事——gdigrab 就是典型。
第八章讲的都是「网络上的一条流」,这一章换成「插在机器上的设备」。 9.1 先把「本机到底有哪些设备接口」摸清楚。
一、设备类输入只有三种平台之分
设备接口不是一个统一标准,而是「一种操作系统一套」。 把八个常见名字列在一起,一眼就能看出自己这台机器有哪几套:
名字 哪个平台 管什么
dshow Windows 摄像头与话筒(DirectShow) gdigrab Windows 抓桌面(GDI) vfwcap Windows 老式 VfW 采集接口 v4l2 Linux 摄像头(Video4Linux2) alsa Linux 话筒(ALSA) x11grab Linux 抓桌面(X11) avfoundation macOS 摄像头、话筒、屏幕
本机是 Windows,所以只有前三个在,后五个全是「没有」。 这不是配置问题,是编译时就决定了——换平台只能换一套二进制。
二、没有「列出全部设备」这个接口
很多人会去找 av.devices_available,然后发现它不存在。
想做的事 以为能这么写 实际情况
列出所有设备 av.devices_available 这个属性不存在
列出所有格式 av.formats_available 存在,414 个
列出摄像头 同上 得按接口试开才知道
所以「枚举设备」真实的做法是两步:
先用 formats_available 看「支持哪种设备接口」,
再对每种接口试着打开,能打开且能解出帧的才是真设备。
这也解释了为什么 ffmpeg -devices 列出来的是一串接口名,
而不是一串「你的摄像头叫什么」——它也不知道。
三、试开三种接口,结果完全不一样
同一个「试着打开」的动作,三种接口给出三种结局。
接口 参数 开完再解 流宽高
dshow video=nonexist OSError — gdigrab desktop OSError 0x0 vfwcap 0 能解码 640x480
dshow 直接 OSError——沙箱里没有摄像头,也没有话筒。
gdigrab 打开是成功的,可一解码就 OSError,宽高还是 0。
vfwcap 反而真给了画面,这是本章所有实验能做下去的关键。
中间那一行最值得记住:打开成功跟能用是两件事。
gdigrab 的 open 不报错,decode 才炸——
只看返回值,你会以为它好了。
四、vfwcap 索引 0 能出什么
vfwcap 是 Windows 上最老的采集接口,参数直接写设备序号。
写 0 就是「第一个设备」,写 1 就是第二个。
项 值
编码 rawvideo 分辨率 640 x 480 像素格式 yuyv422 帧率 30000/1001 打开耗时 1799 ms 抓 120 帧耗时 6812 ms
rawvideo 加 yuyv422 意思是「设备只吐原始像素,一点没压」。
所以一帧 640×480 就是 614400 字节,120 帧将近 74 MB。
这也是为什么「采集」这件事必须自己编码——设备不会帮你压。
30000/1001 就是常说的 29.97 fps,不是整 30。
这个「差一点」的帧率来自 NTSC 时代的遗留,
写代码时用 Fraction(30000, 1001),别硬写 30。
五、命令行枚举设备的正确姿势
本机没有 ffmpeg.exe,但命令要记住——换台机器就要用。
ffmpeg -list_devices true -f dshow -i dummy
细节 说明
列表打在哪 stderr,不是 stdout 为什么退出 列完就退,不等输入 返回码 非 0,别当成失败
列表打在 stderr 上这一条最容易踩:写了 > devices.txt,
打开文件是空的,因为输出被重定向到了错误流。
正确写法是 2> devices.txt,或者干脆 2>&1。
Linux 那边对应的是 v4l2-ctl --list-devices,
macOS 是 ffmpeg -f avfoundation -list_devices true -i ""。
注意 macOS 那条要写一对空引号,不写会报参数错误。
六、动手 Demo:翻接口清单,再逐个试开
下面这段先列出本机支持哪些设备接口,再对三种接口各试一次「打开 + 解码」。 最后把 vfwcap 的流参数读出来,确认它真能出画面。
import av
names = list(av.formats_available)
print(u"格式总数 %d" % len(names))
for k in ("dshow", "gdigrab", "vfwcap", "v4l2", "x11grab"):
print(u" %-12s %s" % (k, "有" if k in names else "没有"))
print(u"devices_available 存在吗:%s" % hasattr(av, "devices_available"))
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 (("dshow", "video=nonexist"),
("gdigrab", "desktop"),
("vfwcap", "0")):
print(u"%-10s %-16s -> %s" % (fmt, arg, probe(fmt, arg)))
c = av.open("0", format="vfwcap")
st = c.streams[0]
print(u"编码 %s %dx%d %s %s"
% (st.codec_context.name, st.codec_context.width,
st.codec_context.height, st.codec_context.pix_fmt,
st.average_rate))
c.close()
实跑输出:
--------------------------------------------------------------------
9.1 FFmpeg 枚举设备
--------------------------------------------------------------------
**① 本机的「设备类输入」只有三个**
名字 哪个平台 本机有没有
--------------------------------------------
dshow Windows 有
gdigrab Windows 有
vfwcap Windows 有
x11grab Linux 没有
kmsgrab Linux 没有
avfoundation macOS 没有
alsa Linux 没有
v4l2 Linux 没有
**八个名字里只有前三个在——它们是 Windows 的:**
**`dshow` 管摄像头与话筒,`gdigrab` 抓桌面,`vfwcap` 是老式 VfW 采集接口。**
**Linux 的 `x11grab`/`v4l2`/`alsa` 与 macOS 的 `avfoundation` 全都没有。**
**② 枚举设备没有统一接口,只能「试开」**
想做的事 有人这么以为 实际情况
------------------------------------------------------------------
列出所有设备 `av.devices_available` 这个属性不存在
列出所有格式 `av.formats_available` 存在,414 个
列出摄像头 同上 得按平台试开才知道
**PyAV 19 之前有过 `devices_available`,现在没了。**
**所以「有哪些设备」这件事,只能靠 `formats_available` 判断**
**「支持哪种设备接口」,再对每种接口试开。**
**③ 三种接口试开,结果完全不同**
接口 参数 开完再解 流宽高
-------------------------------------------------------
dshow video=nonexist OSError —
gdigrab desktop OSError 0x0
vfwcap 0 能解码 640x480
**`dshow` 直接 `OSError`,因为沙箱里没有摄像头,也没有话筒。**
**`gdigrab` 打开是成功的,可一解码就 `OSError`——**
**宽高还是 0,因为沙箱里没有可抓的桌面会话。**
**`vfwcap` 反而给了真画面,这是这一章能做实验的关键。**
**这一行最值得记住:打开成功跟能用是两件事。**
**`gdigrab` 就是典型——open 不报错,decode 才炸。**
**④ vfwcap 索引 0 真能出画面**
项 值
--------------------------------
编码 rawvideo
分辨率 640 x 480
像素格式 yuyv422
帧率 30000/1001
打开耗时 1799 ms
抓 120 帧耗时 6812 ms
**编码是 `rawvideo`,像素格式 `yuyv422`,帧率 30000/1001——**
**这是「摄像头吐原始帧」的标准样子,没经过任何压缩。**
**所以后面每一帧都要自己编码,CPU 开销全在自己这边。**
**⑤ 命令行枚举设备的正确姿势(本机没有 ffmpeg.exe,只列命令)**
`ffmpeg -list_devices true -f dshow -i dummy`
**列表是打在 stderr 上的,不是 stdout——所以 `2>&1` 或者重定向别写错。**
**而且它列完就退出,返回码非 0,别把「非 0」当成失败。**
**Linux 那边对应的是 `v4l2-ctl --list-devices`,macOS 是**
**`ffmpeg -f avfoundation -list_devices true -i ""`。**
三种接口三种结局:dshow 直接 OSError,gdigrab 解码时 OSError,
vfwcap 真解出 640x480——「打开成功」这句话在这里两次被推翻。
devices_available 打印的是 False:PyAV 现在没有这个属性,
所以「枚举设备」只能靠自己试开。
30 秒实验
- 把
vfwcap的0改成1:看是不是还有第二个设备。 - 给
gdigrab加上video_size=640x480:看宽高能不能变成 640x480。 - 把
dshow的参数改成video=dummy:报错一模一样,说明它根本没查设备名。
常见误区
- 以为有
av.devices_available。PyAV 现在没有,只能试开。 - 把「打开成功」当成「设备可用」。gdigrab 就是这么骗人的。
- 把
-list_devices的输出重定向到 stdout。列表在 stderr 上。
自测三问(点开对答案)
1. 本机为什么没有 `v4l2`? —— 因为它是 Linux 接口,Windows 版 FFmpeg 编译时就没包含进去。 2. `gdigrab` 的「打开成功」为什么不可信? —— open 只是建立了上下文,diecode 阶段才真正去读桌面,那时才报 OSError。 3. `30000/1001` 是多少帧?为什么不是 30? —— 约 29.97 fps,NTSC 遗留的帧率,写代码用 Fraction(30000, 1001)。- ☐ 9.1 完,下一节 9.2 FFmpeg 采集本地话筒与摄像头数据