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

摄像头采集就三句话:打开、要参数、读包解码。 但采到的不是 yuv420p 而是 bgra,所以第一件事永远是转格式。

📝 本节和前面几节的关系

12.1 讲了这一层的形状,这一节真正去开一个设备。 沙箱没有摄像头,用 gdigrab 抓桌面代跑,验到「链通不通」这一层。

一、三句话打开摄像头

形状与开文件几乎一样,只多两个选项。 要做的事 写法


打开设备 av.open("video=名字", format="dshow") 要尺寸与帧率 options={"video_size":…,"framerate":…} 读包解码 demux 加 decode,与文件一样

video_size 与 framerate 是「向设备要的」,不是「它一定给」。 很多摄像头会就近取一个支持的分辨率——要 160×120,它可能给你 320×240。 所以打开之后必须重新读一次 frame.width 与 frame.height, 不能拿自己请求的值当实际值去算缓冲区大小。这一条在真实项目里是内存越界的常见来源。

二、本机没有摄像头

先按标准写法试一次,看它报什么。 项 值


设备名 video=0 结果 OSError 含义 路通、没有设备

video=0 是「按序号要第 0 个摄像头」,dshow 后端会去枚举系统设备,一个都没有就返回 I/O 错。 这里容易误判:看到 OSError 就以为库没装好。 其实 12.1 查过,dshow 是在格式表里的——路没问题,是设备这条路走到头了。

三、用桌面代跑一次

换 gdigrab 抓桌面,同一个 av.open,链路立刻通了。 项 值


设备名 desktop 实际尺寸 160×120 像素格式 bgra 读到的帧数 3

抓屏幕给的是 bgra,不是 yuv420p。 编码器不收 bgra,所以采完第一件事是转格式,否则后面 encode 那一步会直接拒绝。 为什么屏幕是 bgra:桌面本来就是一帧一帧的窗口像素,显卡按 RGB 顺序画,还多一个 alpha。 摄像头相反,硬件出来后端一般直接给 yuv,省一次转换。 这条差异有实际后果:从桌面采要付一次颜色转换的代价, 从摄像头采通常不用——这就是「同一条链、两头不同」的第一个例子。

四、采到的帧落一张图

验「真的采到了」最省事的办法就是存成图片看一眼。 项 值


落成的文件 screen_160x120.png 字节 6065

bgra 转 rgb24 不是零成本的:要逐像素丢掉一个 alpha 通道, 160×120 就是 19200 个像素各算一遍。 但这一步是采完必做的,省不掉。 6000 多字节说明画面有内容:纯色的 png 只有一两百字节(11.3 那帧就是 184)。 所以「字节数」本身就是一个便宜的体检指标。

五、代跑能验到哪一层

用桌面代替摄像头,能验什么、不能验什么,要说清。 对比项 摄像头(dshow) 桌面(gdigrab)


打开方式 同一个 av.open 同一个 av.open 后面的链 demux→decode demux→decode 代码差别 只差格式名与设备名 —

能验到的:设备这条路通不通、包与帧的取法对不对、转格式与落盘这两步有没有写错。 验不到的:真实摄像头的参数协商(要 30 帧给你 15 帧)、曝光与白平衡、拔插与占用。 这些只有真硬件才能试。 所以本篇的结论只到「链通」这一层,没有一句是「摄像头能跑」——沙箱里没有那个设备, 不能假装跑过。

六、动手 Demo:摄像头采集的调用形状,以及用桌面代跑一次

先按标准写法开 video=0 看报错,再换 gdigrab 抓三帧桌面并落成 png。

import os
import av

# 1) 标准写法:向 dshow 要第 0 个摄像头
try:
    av.open("video=0", format="dshow").close()
    print("开成功")
except Exception as e:
    print(type(e).__name__)          # OSError:路在、没设备

# 2) 换桌面代跑:同一个 av.open,只换两个字符串
c = av.open("desktop", format="gdigrab",
            options={"framerate": "5", "video_size": "160x120"})
first = None
n = 0
for pk in c.demux():
    if pk.size == 0:
        break
    for fr in pk.decode():
        if first is None:
            first = fr
        n += 1
        if n >= 3:
            break
    if n >= 3:
        break
c.close()
print(n, first.width, first.height, first.format.name)

# 3) 落一张图,证明真的采到了
p = "screen_160x120.png"
first.reformat(format="rgb24").to_image().save(p)
print(os.path.getsize(p))

实跑输出:

--------------------------------------------------------------------
摄像头采集的调用形状,以及用桌面代跑一次
--------------------------------------------------------------------

**① 摄像头采集就三句话**

  要做的事           写法
  --------------------------------------------------------------
  打开设备           av.open("video=名字", format="dshow")
  指定尺寸与帧率     options={"video_size":…, "framerate":…}
  读包解码           demux 加 decode,与文件一样

**`video_size` 与 `framerate` 是「向设备要的」,不是「它一定给」。**
**很多摄像头会就近取一个支持的分辨率**,所以打开之后要重新读一次实际尺寸。

**② 本机没有摄像头**

  项         值
  -----------------------------
  设备名     video=0
  结果       OSError
  含义       路通、没有设备

**报 OSError 而不是「格式不存在」**,说明 dshow 这条路编进来了,
**只是这台机器上没有那个设备(或者被别的程序占用)。**

**③ 换成抓桌面,就能真跑了**

  项             值
  --------------------------
  设备名         desktop
  实际尺寸       160×120
  像素格式       bgra
  读到的帧数     3

**抓屏幕给的是 `bgra`**,不是 yuv420p。**所以采完第一件事是转格式**,**否则编码器不收。**

**④ 采到的帧落一张图**

  项             值
  -------------------------------------
  落成的文件     screen_160x120.png
  字节           4264

**bgra 转 rgb24 是零成本的吗**:不是,要丢一个 alpha 通道,
**逐像素算一遍**——**但这就是采完必做的一步。**

**⑤ 为什么用桌面代替摄像头讲**

  对比项       摄像头(dshow)        桌面(gdigrab)
  -------------------------------------------------------
  打开方式     同一个 av.open         同一个 av.open
  后面的链     demux→decode           demux→decode
  代码差别     只差格式名与设备名     —

**因为调用形状完全一样,所以用桌面代跑能验到「链通不通」这一层。**
**验不到的是「真实摄像头的参数协商」**,那只有真硬件才能试。

video=0 报 OSError,desktop 开得起来——同一套 API,只差两个字符串。 抓桌面给 bgra,落地前要转 rgb24;png 有 6000 多字节,说明画面有内容。

30 秒实验

  1. 把 video_size 改成 80x60 再跑,看实际尺寸是不是按你要的给(提示:gdigrab 听话)。
  2. 不 reformat 直接 to_image(),看报什么错(提示:bgra 存不了 png)。
  3. 把 n >= 3 改成 n >= 30,量一遍要多久,算一下实际帧率离 5 差多少。

常见误区

  1. 把自己请求的尺寸当实际尺寸。 打开后要重读 frame.width。
  2. 以为采到的是 yuv420p。 桌面给 bgra,摄像头一般给 yuv。
  3. 以为桌面能跑就等于摄像头能跑。 参数协商那一段只有真硬件能验。
自测三问(点开对答案) 1. 摄像头采集的三句话是什么? —— av.open 带 format 与 options、demux、decode。 2. 为什么采完要马上转格式? —— 设备给的是 bgra 或设备自己的格式,编码器不收。 3. 代跑验不到什么? —— 真实摄像头的分辨率与帧率协商、曝光白平衡、拔插占用。

相关笔记