🎬 第十二章 AVDevice设备读写理论及案例实战
📄 一句话概括

设备这一层没有新 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 秒实验

  1. 把 video=0 换成 video=Integrated Camera,看错误类型变不变(提示:还是 OSError)。
  2. 打印 av.formats_available 里含 dshow、grab、v4l 的所有名字。
  3. 读 av.ContainerFormat("gdigrab").long_name 与 extensions,观察设备格式的字段有多空。

常见误区

  1. 以为设备要单独注册一遍。 新版自动注册,avdevice_register_all() 是遗留。
  2. 以为设备要另一套读包与解码函数。 还是 demux 加 decode,一行都不变。
  3. 报错就说「库没编进来」。 先看是 ValueError 还是 OSError,方向完全不同。
自测三问(点开对答案) 1. AVDevice 这一层的设计意图是什么? —— 把设备伪装成一个格式,好复用文件那条五步链。 2. 报 OSError 说明什么? —— 路通了、但设备没拿到——没插、被占用、或者名字写错。 3. 设备名为什么各平台差这么远? —— 设备归操作系统管,FFmpeg 只包一层,包不住「它叫什么」。

相关笔记