任何一张滤镜图都是三段:一个头、若干中间、一个尾。 头尾不是滤镜,是端点——少了任何一头都配不通。
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 秒实验
- 把空图那段的
s.link_to(k)注释掉,看报什么。 - 给
add_buffer传time_base=Fraction(1, 1000),看输出 pts 的变化。 - 在中间插两个
scale(一个 80:60、一个 40:30),看最终宽度是 40 还是 30。
常见误区
- 以为 configure 可选。 不调也能跑,但时间基是猜的。
- 以为 ArgumentError 能指出缺哪头。 两头缺哪头都报同一句。
- 以为中间必须有滤镜。 头直连尾也是合法的一张图。
自测三问(点开对答案)
1. 滤镜图固定有哪三段? —— buffer 头、若干滤镜、buffersink 尾。 2. 不调 configure 会出什么事? —— 能跑,但时间基按 1/1000 猜,pts 会全歪,且报 DeprecationWarning。 3. 为什么「头直连尾的空图」有用? —— 它能跑通就说明图的结构没问题,可以把排查范围缩到中间那段。- ☐ (本节完,下一节 9.1.5 滤镜图、滤镜链、滤镜之间的关系)