设备这一层没有新 API,它只做了一件事:把设备伪装成一个格式。 所以会开文件,就会开摄像头;差别只有格式名与设备名两个字符串。
第十一章在讲画面怎么换尺寸、换颜色,这一章回到更前面的一步:画面从哪来。 这也是全书第一次让程序去碰硬件,碰不到的地方会老实标出来。
一、设备被伪装成格式
采集一条链,与读文件那条链是同一个五步。 步 做什么 读文件那条链里是什么
一 打开设备 打开文件 二 读包 读包 三 解码 解码 四 处理 处理 五 关闭 关闭
除了第一步,其余四步一个字都不用改。
这就是 AVDevice 的全部设计意图——把设备伪装成一个格式。
为什么要这么绕:文件那条链已经跑了几十年,demux、decode、filter、encode 全都在上面长好了。
设备要是另起一套,等于把整条链重写一遍。
伪装成格式的好处是「一个入口通吃」:同一个 av.open,给它文件名就开文件,
给它设备名就开摄像头,给它 URL 就开网络流。
代价也是真的:文件格式名(mp4、flv)是 FFmpeg 自己定的,
设备格式名却由操作系统决定(dshow、v4l2、avfoundation)。统一到这一层,就统一不下去了。
二、所以它没有自己的 API
读这一节容易带着错误的期待:以为要学一批 avdevice_ 开头的函数。
你可能担心的 实际上
要不要注册设备 不用,自动注册 要不要另一套读包函数 不用,还是 demux 要不要另写解码 不用,还是 decode
老教材里常有一句 avdevice_register_all(),新版已经不需要了。
看到它不用慌,知道它是历史遗留就行。
这一层真正新增的只有「选项」这一类东西:video_size、framerate、list_devices 这些名字,
只有在设备格式下才有意义,文件格式根本不认。
所以这一章的学习重点要换一个方向:不是背新函数,
而是搞清楚「哪些参数因设备而异」,以及「报错怎么读」。
三、本机有哪几条路
本机格式表里有 414 个名字,与设备有关的只有十来个。 格式名 在表里 能读 能写
dshow 在 True False gdigrab 在 True False v4l2 不在 — — avfoundation 不在 — —
这台 Windows 机器上只有 dshow 与 gdigrab,Linux 的 v4l2 与苹果的 avfoundation 都不在表里。
而且两个都只能读、不能写——设备本来就是输入源。
能写的设备格式(decklink 那种采集卡输出)本机没编进来。
自查方法就两步:先用 av.formats_available 这个集合看名字在不在,
再用 av.ContainerFormat(名字) 读 is_input 与 is_output 看方向。
这里有个坑:av.formats 在 av 18 里已经没有了,
写成 av.formats.values() 会直接 AttributeError。
要用的是 av.formats_available,它是个 set,只能判成员,没有 values。
四、真开一次,三种结果
在不在表里是一回事,开不开得起来是另一回事。 设备名 结果
desktop(gdigrab) 开成功 video=0(dshow) OSError audio=麦克风(dshow) OSError
注意这三种结果的差别,排查时就靠它分辨方向。 报什么 说明什么 往哪里查
ValueError 格式没编进来 换平台或重编库 OSError 路在、设备没拿到 查插没插、占没占
所以报 OSError 反而是好消息:说明 dshow 这条路真的编进来了, 只是没设备、或者被别的程序占着(摄像头最常见的占用者是会议软件)。 这条沙箱的实情是:没有摄像头、没有麦克风,但桌面抓得到。 下文讲摄像头时,就用桌面这条路代跑。
五、设备名是操作系统的东西
这是全课最不统一的一张表。 平台 格式名 设备名长什么样
Windows dshow video=名字/audio=名字 Windows gdigrab desktop/title=窗口标题 Linux v4l2 /dev/video0 macOS avfoundation 0/0:0
设备名不是 URL,它没有统一的命名空间。FFmpeg 只能把整串原样交给后端,
后端再拿它去问操作系统。
Linux 给的是设备文件路径,因为那儿的设备就是文件;
Windows 给的是「类型=名字」,因为那儿的设备要靠枚举名字;
macOS 给的是序号,因为那儿的采集设备按索引排列。
所以跨平台代码必须分支,标准写法是一个小函数,
按 platform.system() 返回「格式名 + 设备名」这两个字符串,其余代码一个字都不改。
这一节的边界要说明白:沙箱只有 Windows。
Linux 与 macOS 那两行是查规格写的,不是这台机器跑出来的。
六、动手 Demo:设备这一层,先看本机支持哪几条路
先数格式表、查四个设备格式在不在,再真开三个设备看报什么错。
import av
# 1) 本机格式表里有没有设备格式
tab = av.formats_available
print(len(tab))
for n in ("dshow", "gdigrab", "v4l2", "avfoundation"):
if n in tab:
f = av.ContainerFormat(n)
print(n, f.is_input, f.is_output)
else:
print(n, "不在")
# 2) 真开三个设备,看报什么错
for dev, fmt in (("desktop", "gdigrab"), ("video=0", "dshow"),
("audio=麦克风", "dshow")):
try:
av.open(dev, format=fmt).close()
print(dev, "开成功")
except Exception as e:
print(dev, type(e).__name__)
实跑输出:
--------------------------------------------------------------------
设备这一层,先看本机支持哪几条路
--------------------------------------------------------------------
**① 五个步骤,与文件那条链几乎一样**
步 做什么 文件那条链里是什么
------------------------------------------
一 打开设备 打开文件
二 读包 读包
三 解码 解码
四 处理 处理
五 关闭 关闭
**除了第一步,其余四步一个字都不用改。**
**这就是 AVDevice 的全部设计意图**——**把「设备」伪装成「格式」。**
**② 所以它没有自己的 API**
你担心的 实际上
--------------------------------------------
要不要注册设备 不用,自动注册
要不要另一套读函数 不用,还是 demux
要不要另写解码 不用,还是 decode
**老教材里有一句 `avdevice_register_all()`**,新版已经不需要了。
**看到它不用慌,知道它是历史遗留就行。**
**③ 本机有哪几条路**
**本机格式表里一共 414 个名字,与设备有关的只有下面这几个。**
名字 在表里 能读 能写
----------------------------------------------
dshow 在 True False
gdigrab 在 True False
v4l2 不在 — —
avfoundation 不在 — —
**这台 Windows 机器上只有 dshow 与 gdigrab**,Linux 与苹果的都不在。
**而且两个都只能读不能写**——设备本来就是输入源。
**④ 真开一次,三种结果**
设备名 结果
----------------------------------------
desktop(gdigrab) **开成功**
video=0(dshow) OSError
audio=麦克风(dshow) OSError
**同一个 dshow,摄像头失败、麦克风也失败;而 gdigrab 抓桌面成功了。**
**所以「这台机器能不能采」只能一条一条试,看报错类型。**
**⑤ 设备名怎么写**
平台 格式名 设备名长什么样
---------------------------------------------------------------
Windows dshow video=摄像头名/audio=麦克风名
Windows gdigrab desktop 或 title=窗口标题
Linux v4l2 /dev/video0
macOS avfoundation 0 或 0:0
**设备名是「操作系统的东西」,不是 FFmpeg 的**,所以各平台长得完全不一样。
**跨平台代码里这一段必须按平台分支写。**
414 个格式名里,与设备有关的只有十来个;本机在的只有 dshow 与 gdigrab。 三个设备只有 desktop 开成功,另两个报 OSError——路在、设备没拿到。
30 秒实验
- 把
video=0换成video=Integrated Camera,看错误类型变不变(提示:还是 OSError)。 - 打印
av.formats_available里含dshow、grab、v4l的所有名字。 - 读
av.ContainerFormat("gdigrab").long_name与extensions,观察设备格式的字段有多空。
常见误区
- 以为设备要单独注册一遍。 新版自动注册,
avdevice_register_all()是遗留。 - 以为设备要另一套读包与解码函数。 还是 demux 加 decode,一行都不变。
- 报错就说「库没编进来」。 先看是 ValueError 还是 OSError,方向完全不同。
自测三问(点开对答案)
1. AVDevice 这一层的设计意图是什么? —— 把设备伪装成一个格式,好复用文件那条五步链。 2. 报 OSError 说明什么? —— 路通了、但设备没拿到——没插、被占用、或者名字写错。 3. 设备名为什么各平台差这么远? —— 设备归操作系统管,FFmpeg 只包一层,包不住「它叫什么」。- ☐ (本节完,下一节 12.2 AVDevice 的 API 方式采集本地摄像头获取的数据)