-vf 是给这条路加工序,-filter_complex 是重新画一张路网。 本机实测:多路合成靠 movie 补位才建得起来。
前两节各讲了一半,这一节把两者的区别收成一张表,再拿三次实测验证。 其中「两路怎么接」这条,是后面画中画、九宫格的地基。
一、一张表看全区别
先给结论,再逐条验:
对比项 简单滤镜 -vf 复杂滤镜
输入路数 只能 1 任意 输出路数 只能 1 任意 能不能分叉 不能 能 命令行位置 跟在 -i 后面 可放任意位置 要不要 -map 不用 多半要
五行里最要紧的是前两行——路数。 后面三行都是这条约束带来的副作用。
二、实测一:单链只有一个出口
在代码里,链的末端必须接一个 buffersink 才能把帧取出来。 一条单链只有一个末端,所以只能拉到一个 sink——这是硬约束。
写法 滤镜数 末端尺寸
scale,crop 2 60×40
两环串起来,仍然是一路进一路出,所以它依然是简单滤镜。 滤镜的个数不影响「简单」还是「复杂」——影响的是分不分叉。
三、实测二:两路怎么接才通
想接第二路输入时,本机只认一种组合:
建图写法 结果
两个 add_buffer 接 overlay returned 22 buffer + movie 接 overlay 成功
两个「内存缓冲」当输入时,PyAV 的对接层建不起来,报的是一句干巴巴的 22。 换成一路 buffer 加一路 movie,立刻就通了。
还有一个细节:接第二路必须显式给槽位号。
link_to(ov, 0, 1) 里的最后那个 1,意思是「接到 overlay 的第 1 号入口」。
不给槽位号、按顺序连,一样是 returned 22。
这条限制在第六章没露过面,因为那边全是单输入链。
四、实测三:三路、四路都行
同样的办法可以往上堆:一路 buffer,后面全用 movie 补。
滤镜 输入路数 输出尺寸
hstack 2 320×90 vstack 2 160×180 xstack 4 320×180 xstack 9 480×270
四宫格的尺寸是两倍宽、两倍高,正好四个 160×90 拼成 320×180。 九宫格是三倍宽、三倍高,480×270。
这个能力第七章后面要大用——画中画、九宫格、多路拼接全靠它。 所以本节这两条实测,是整章的技术地基。
五、动手 Demo:两路、四路、九路,一路一路加上去
下面这段代码按「一路 buffer + 其余 movie」的办法,从两路试到九路。 每一步都量输出尺寸,看是不是真的按倍数长。
import av
from fractions import Fraction
TB = Fraction(1, 25)
SRC = u"s7.mp4"
ESC = SRC.replace(u"\\", u"/").replace(u":", u"\\:")
def build(name, args, extra, w=160, h=90):
g = av.filter.Graph()
b = g.add_buffer(width=w, height=h, format=u"yuv420p", time_base=TB)
f = g.add(name, args)
sk = g.add(u"buffersink")
b.link_to(f, 0, 0)
for i in range(extra):
g.add(u"movie", ESC + u":loop=0").link_to(f, 0, i + 1)
f.link_to(sk)
g.configure()
return g, b, sk
LAY9 = u"0_0|w0_0|w0+w1_0|0_h0|w0_h0|w0+w1_h0|0_h0+h1|w0_h0+h1|w0+w1_h0+h1"
for tag, name, args, extra in (
(u"hstack", u"hstack", u"", 1),
(u"vstack", u"vstack", u"", 1),
(u"xstack 4", u"xstack", u"inputs=4:layout=0_0|w0_0|0_h0|w0_h0", 3),
(u"xstack 9", u"xstack", u"inputs=9:layout=" + LAY9, 8)):
g, b, sk = build(name, args, extra)
c0 = av.open(SRC)
arr = next(c0.decode(video=0)).to_ndarray()
c0.close()
vf = av.VideoFrame.from_ndarray(arr, format=u"yuv420p")
vf.pts = 0
vf.time_base = TB
b.push(vf)
o = sk.pull().to_ndarray(format=u"rgb24")
print(u"%-10s %d 路 -> %d×%d" % (tag, extra + 1, o.shape[1], o.shape[0]))
# 对照:两路全用内存缓冲
try:
g = av.filter.Graph()
a = g.add_buffer(width=160, height=90, format=u"yuv420p", time_base=TB)
c = g.add_buffer(width=160, height=90, format=u"yuv420p", time_base=TB)
ov = g.add(u"overlay", u"10:10")
sk = g.add(u"buffersink")
a.link_to(ov)
c.link_to(ov)
ov.link_to(sk)
g.configure()
print(u"两个内存缓冲 -> 成功")
except Exception as e:
print(u"两个内存缓冲 -> %s" % str(e)[:40])
实跑输出:
--------------------------------------------------------------------
7.1.3 简单滤镜和复杂滤镜的区别
--------------------------------------------------------------------
**① 这一节把上两节的话收成一张对照表,然后实测三件事。**
对比项 简单滤镜 -vf 复杂滤镜 -filter_complex
-------------------------------------------------------------
输入路数 只能 1 任意
输出路数 只能 1 任意
能不能分叉 不能 能
命令行位置 跟在 -i 后面 可放在任意位置
要不要 -map 不用 多半要
**② 实测一:简单滤镜只有一个出口**
**PyAV 里给 buffersink 拉帧,一条单链只能拉到一个 sink,这是硬约束。**
**想拉两路,就得在链里插一个 split——一插,它就不再是简单滤镜了。**
写法 滤镜数 末端尺寸
--------------------------------------
scale,crop 2 60×40
**两环串起来仍然是一路进一路出,所以它依然是简单滤镜。**
**滤镜的个数不影响「简单」还是「复杂」——影响的是分不分叉。**
**③ 实测二:多输入在没有 movie 补位时根本建不起来**
建图写法 结果
----------------------------------------------
两个 add_buffer 接 overlay returned 22
buffer + movie 接 overlay 成功
**两个「内存缓冲」当输入时,PyAV 的对接层建不起来,报的是一句干巴巴的 22。**
**换成一路 buffer 加一路 movie,就能跑——这是本机实测出来的唯一可行组合。**
**④ 实测三:三路合成也能跑**
滤镜 输入路数 输出尺寸
------------------------------------
hstack 2 320×90
xstack 4 320×180
**四宫格出来了:宽是两倍、高是两倍,正好四个 160×90 拼成 320×180。**
**这个能力第七章后面要大用——画中画、九宫格全靠它。**
**⑤ 一句话记住区别**
**`-vf` 是「给这条路加几道工序」,`-filter_complex` 是「重新画一张路网」。**
**要改的是工序,就用前者;要改的是路网,就用后者。**
四行尺寸全部按倍数长:两路是 2 倍宽或 2 倍高,四路是 2×2,九路是 3×3。 最后一行是反面对照,同样的两路,换成两个内存缓冲就建不起来。
movie 在这里的角色是「无输入的源」:它自己去读文件,不需要别人喂。 所以一路 buffer 加 N 个 movie,就凑出了 N+1 路。
代价是每一次 movie 都真的打开一次文件。九宫格就是九次打开, 在实际工程里对 IO 有压力——能用 split 复制的场合,优先用 split。
30 秒实验
- 加一路:把
extra从 1 改成 2,看 hstack 建得起来还是报错。 - 换个源:把 movie 换成
color=red:s=160x90:d=1,看能不能当第二路。 - 打乱顺序:把两次 link 的顺序对调,看那句 22 是不是又回来了。
常见误区
- 以为两路都要喂帧。movie 自己读文件,不用管它。
- 忘了给槽位号。按顺序 link 会得到
returned 22,报错完全看不出原因。 - 用九宫格做实时处理。九次开文件,延迟会明显上去。
自测三问(点开对答案)
1. 为什么两个内存缓冲接不起来? —— PyAV 与 libavfilter 的对接层在这一组合上建图失败,只报 returned 22。 2. movie 补位要显式给什么? —— 槽位号,即 `link_to(目标, 0, 第几号入口)`。 3. 九宫格为什么是 480×270? —— 九个 160×90 拼成 3×3,宽高各三倍。- ☐ (本节完,下一节 7.1.4 流和滤镜的结合使用)