🎬 第九章 AVFilter过滤器层理论及案例实战
📄 一句话概括

任何一张滤镜图都是三段:一个头、若干中间、一个尾。 头尾不是滤镜,是端点——少了任何一头都配不通。

📝 本节和前面几节的关系

9.1.3 讲了两种形状,这一节把形状画成图。 图画对了,代码就是照抄;图画错了,代码怎么写都跑不通。

一、永远是三段

头、中、尾,这一段是固定的。 段 节点 作用


头 buffer 把帧喂进图里 中 若干滤镜 真正干活 尾 buffersink 把帧从图里取出来

中间那一段可以是零个滤镜:头直接连尾,图照样配得通,出来的帧跟进去的一模一样。 这也是判断「问题出在滤镜还是出在图」的好办法。 头尾都是「端点」,不做任何处理。 它们只负责与外界交接:一头收你的帧,一头把帧还给你。

二、少了 sink 会怎样

直接 configure,报 ArgumentError。 缺什么 报什么 消息说什么


只有源与滤镜 ArgumentError Invalid argument

这个消息什么都没说。 它既没说是少了 sink,也没说是哪一行错的。 所以查这类问题不能等它告诉你,得自己按「头、尾、中间」三段过一遍:有源吗、有汇吗、 连线连全了吗。

三、少了 buffer 会怎样

只有 sink、没有源,报的还是一样的话。 缺什么 报什么


只有汇 ArgumentError 只有头没有尾 ArgumentError

两头缺哪一头,报的都一样,这就是它难查的地方。 唯一可靠的办法是「一次只改一处」:先搭一条头直连尾的空图跑通,再一段一段往中间加。 这跟第六章「判协议在不在」是同一个套路:报错不可信,就自己造一个对照, 用「能不能跑通」当判据。

四、configure 能不能省

能省,但有代价。实测不调 configure 直接 push/pull 也能出帧。 做法 能不能出帧 代价


显式 configure 能 时间基是你给的 不调 能 时间基是猜的,会报警告

PyAV 在背后替你配了一次,猜的时间基是 1/1000,同时报一句 DeprecationWarning。 猜错时间基的后果比报错严重得多:程序跑得好好的,只是 pts 全歪, 最后表现出来是「时长不对」「音画不同步」这种隔了很远才发现的毛病。 所以 configure 该显式调,而且要给它正确的 time_base。 这一行是整个搭图流程里最便宜的一份保险。

五、一张图的完整样子

把三段串起来,就是这个形状。 顺序 动作 代码


一 建图 Graph() 二 加头 add_buffer(...) 三 加中间 add(name, args) 四 加尾 add("buffersink") 五 连线 link_to 六 配好 configure()

六步的顺序可以微调,但有两处不能反:configure 必须在所有连线之后,push 必须在 pull 之前。 照这六步写,图就不会配错。 剩下的问题只可能是参数写错或下标写反。

六、动手 Demo:滤镜图的形状是固定的

先搭一条只有头尾的空图,再故意缺一头看报什么,最后试不调 configure。

import av
import av.filter
from fractions import Fraction

# 只有头尾,中间空着
g = av.filter.Graph()
s = g.add_buffer(width=160, height=120, format="yuv420p",
                  time_base=Fraction(1, 25))
k = g.add("buffersink")
s.link_to(k)
g.configure()
s.push(make_frame(0))
print("空图出帧宽", k.pull().width)

# 缺 sink
g2 = av.filter.Graph()
g2.add_buffer(width=160, height=120, format="yuv420p",
               time_base=Fraction(1, 25))
try:
    g2.configure()
except Exception as e:
    print("缺 sink 报", type(e).__name__)

# 故意不 configure
g3 = av.filter.Graph()
s3 = g3.add_buffer(width=160, height=120, format="yuv420p")
sc3 = g3.add("scale", "80:60")
k3 = g3.add("buffersink")
s3.link_to(sc3); sc3.link_to(k3)
s3.push(make_frame(0))
print("没 configure 也出帧,宽", k3.pull().width)

实跑输出:

--------------------------------------------------------------------
滤镜图的形状是固定的
--------------------------------------------------------------------

**① 永远是三段**

  段     节点           作用
  ------------------------------------------
  头     buffer         把帧喂进图里
  中     若干滤镜       真正干活
  尾     buffersink     把帧从图里取出来

**头尾都不是「滤镜」,是「端点」。** 它们不做任何处理,只负责与外界交接。
**少了任何一个,图都配不通。**

**② 少了 sink 会怎样**

没有 sink 直接 configure,报 **ArgumentError**。

**③ 少了 buffer 会怎样**

只有 sink、没有源,报 **ArgumentError**。

**两次报的都是 ArgumentError,消息只有一句「Invalid argument」。**
**看不出是缺了哪一头**,所以这类报错要自己按「头尾中」三段去查。

**④ configure 能不能省**

**不调 configure 直接 push/pull,能出帧,宽 80。** 说明 PyAV 在背后替你配了一次。

**省事但有代价**:隐式配置时用的时间基是猜的(会报一句 DeprecationWarning,按 1/1000 猜)。
**时间基猜错,后面 pts 就全错。**

**所以 configure 该显式调**,而且要给它正确的 time_base。**这一行几乎是最便宜的一份保险。**

空图(头直连尾)能跑通,说明问题只在中间那段,不在图本身。 不调 configure 也能出帧,但时间基是猜的,会报 DeprecationWarning。

30 秒实验

  1. 把空图那段的 s.link_to(k) 注释掉,看报什么。
  2. 给 add_buffer 传 time_base=Fraction(1, 1000),看输出 pts 的变化。
  3. 在中间插两个 scale(一个 80:60、一个 40:30),看最终宽度是 40 还是 30。

常见误区

  1. 以为 configure 可选。 不调也能跑,但时间基是猜的。
  2. 以为 ArgumentError 能指出缺哪头。 两头缺哪头都报同一句。
  3. 以为中间必须有滤镜。 头直连尾也是合法的一张图。
自测三问(点开对答案) 1. 滤镜图固定有哪三段? —— buffer 头、若干滤镜、buffersink 尾。 2. 不调 configure 会出什么事? —— 能跑,但时间基按 1/1000 猜,pts 会全歪,且报 DeprecationWarning。 3. 为什么「头直连尾的空图」有用? —— 它能跑通就说明图的结构没问题,可以把排查范围缩到中间那段。

相关笔记