判据只有一个:几进几出。 一进一出是简单滤镜,多进或多出是复杂滤镜——跟滤镜名字没关系。
9.1.2 讲了链是什么,这一节讲链的两种形状。
分不清这两种,命令行里就会在 -vf 与 -filter_complex 之间选错。
一、一句话判据
看输入几路、输出几路,别管它叫什么名字。 类型 几进几出 命令行怎么写
简单 1 进 1 出 -vf 复杂 多进或多出 -filter_complex
同一个滤镜永远属于同一类:scale 永远一进一出,overlay 永远两进一出,
split 永远一进两出。这是滤镜本身的形状,不会变。
所以判类型不用查文档,想清楚「它得吃几路、吐几路」就够了。
二、简单:一进一出
绝大多数滤镜都是这一类。 滤镜 进 出 改了什么
scale 160×120 80×60 尺寸 hflip 160×120 160×120 左右翻 format yuv420p rgb24 存法
尺寸没变的那两个,出帧还是 160×120;缩放过的那个变成 80×60。
一进一出,进出的「一」是数量,不是尺寸。
这类滤镜最省事:转码时你说一句 -vf scale=80:60,工具自己会在解码与编码之间搭好图,
不用你写一行代码。
三、复杂之一:两进一出
典型代表是 overlay,把一张图叠到另一张上。 输入口 接什么 说明
第 0 个 底图 被叠的那张 第 1 个 上层图 叠上去的那张
link_to(ov, 0, 0) 后面那两个数字是关键:第一个是「源的第几个输出」,
第二个是「目标的第几个输入」。
写反了不报错,画面却会叠错。 这是复杂滤镜最常见的坑——两个输入都是「一路画面」,
接口类型一样,所以编译器不管。
底图是第 0 个、上层是第 1 个,记不住就记「先垫后盖」:先连的是底,后连的是盖上去的。
四、复杂之二:一进两出
典型代表是 split,把一路画面复制成两路。 口 数量 用途
输入 1 个 原始那一路 输出 2 个 两路一模一样的副本
实测一帧进去、两路各出一帧,两路尺寸都是 160×120。
两个 sink 的时候不能再用 g.pull(),它报「只能自动拉取单个 sink」。
要改成在 sink 节点上 pull:k1.pull()、k2.pull()。
这条报错信息是这批 API 里少见的清楚话,看到它直接改写法就行。
五、为什么这个区分重要
因为两种滤镜的「谁来搭图」完全不同。 类型 图谁搭 你要写什么
简单 工具自动插 一句参数 复杂 你自己搭 整张图,含连线
简单滤镜能自动插,是因为它一进一出、位置唯一:解码器后面、编码器前面,中间就一个空位。 复杂滤镜的输入输出对不上,工具不知道该把哪一路接到哪一路,所以必须你亲手写清整张图。 这就是「一句话能搞定」与「写二十行」的分界,也是本章后半段要练的东西。
六、动手 Demo:简单滤镜与复杂滤镜,差在几进几出
三个滤镜各搭一次图:hflip(一进一出)、overlay(两进一出)、split(一进两出)。
import av
import av.filter
from fractions import Fraction
def buf(g, name):
return g.add_buffer(width=160, height=120,
format="yuv420p",
time_base=Fraction(1, 25),
name=name)
# 一进一出
g = av.filter.Graph()
s = buf(g, "in")
f = g.add("hflip")
k = g.add("buffersink")
s.link_to(f); f.link_to(k)
g.configure()
s.push(make_frame(0))
print(k.pull().width)
# 两进一出:下标指明「源的第几路输出」与「目标的第几路输入」
g2 = av.filter.Graph()
a = buf(g2, "bg")
b = buf(g2, "fg")
ov = g2.add("overlay")
k2 = g2.add("buffersink")
a.link_to(ov, 0, 0) # 底图接第 0 个输入
b.link_to(ov, 0, 1) # 上层接第 1 个输入
ov.link_to(k2)
g2.configure()
a.push(make_frame(0)); b.push(make_frame(0))
print(k2.pull().width)
# 一进两出:两个 sink,各自 pull
g3 = av.filter.Graph()
c = buf(g3, "in")
sp = g3.add("split")
k3a = g3.add("buffersink")
k3b = g3.add("buffersink")
c.link_to(sp)
sp.link_to(k3a, 0, 0)
sp.link_to(k3b, 1, 0)
g3.configure()
c.push(make_frame(0))
print(k3a.pull().width, k3b.pull().width)
实跑输出:
--------------------------------------------------------------------
简单滤镜与复杂滤镜,差在几进几出
--------------------------------------------------------------------
**① 一句话区分**
类型 几进几出 命令行怎么写
-------------------------------------------
简单 1 进 1 出 -vf
复杂 多进或多出 -filter_complex
**「几进几出」是唯一判据**,跟滤镜名字没关系。
同一个 scale 永远是简单滤镜,同一个 overlay 永远是复杂滤镜。
**② 简单:一进一出**
进 **160×120**,出 **160×120**,格式 yuv420p。**尺寸没变,只是左右翻了。**
**③ 复杂之一:两进一出(overlay 叠图)**
两路各进一帧,出一帧 **160×120**。
**注意 `link_to(ov, 0, 0)` 后面那两个数字**:第一个是「源的第几个输出」,
第二个是「目标的第几个输入」。**写反了不报错,画面却会叠错。**
**④ 复杂之二:一进两出(split 分流)**
一帧进去,两路各出一帧(160、160)。
**两个 sink 的时候不能再用 `g.pull()`**,它报「只能自动拉取单个 sink」。
**要改成在 sink 节点上 `pull`。**
**⑤ 这个区分为什么重要**
**简单滤镜可以「自动插进去」**:转码时你说一句 `-vf scale=80:60`,
工具自己会在解码与编码之间搭好图。
**复杂滤镜不行**,因为它的输入输出对不上,工具不知道该把哪路接到哪路。
**所以必须你亲手写清整张图。**
link_to(节点, 第几路输出, 第几路输入) 那两个下标,写反不报错、画面错。
两个 sink 时 g.pull() 会报错,要改成在 sink 节点上 pull。
30 秒实验
- 把 overlay 的两个
link_to下标对调,看输出像素差在哪。 - 用
g3.pull()跑 split 那张图,抄下它报的原话。 - 数一数
g.add("scale")的输入口与输出口各有几个(提示:inputs/outputs)。
常见误区
- 按滤镜名字判类型。 判据是几进几出,不是名字长短。
- 把
link_to的两个下标当成同一件事。 前面是源的输出口,后面是目标的输入口。 - 多个 sink 还想用
g.pull()。 它只能自动拉一个。
自测三问(点开对答案)
1. 简单滤镜与复杂滤镜的唯一判据是什么? —— 几进几出。一进一出是简单,多进或多出是复杂。 2. `link_to(ov, 0, 1)` 里那个 1 是什么意思? —— 接到目标的第 1 个输入口,也就是 overlay 的上层。 3. 为什么复杂滤镜不能像 `-vf` 那样自动插进去? —— 它的输入输出对不上,工具判断不出哪一路该接哪一路。- ☐ (本节完,下一节 9.1.4 FFmpeg滤镜流程图)