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

判据只有一个:几进几出。 一进一出是简单滤镜,多进或多出是复杂滤镜——跟滤镜名字没关系。

📝 本节和前面几节的关系

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 秒实验

  1. 把 overlay 的两个 link_to 下标对调,看输出像素差在哪。
  2. 用 g3.pull() 跑 split 那张图,抄下它报的原话。
  3. 数一数 g.add("scale") 的输入口与输出口各有几个(提示:inputs/outputs)。

常见误区

  1. 按滤镜名字判类型。 判据是几进几出,不是名字长短。
  2. 把 link_to 的两个下标当成同一件事。 前面是源的输出口,后面是目标的输入口。
  3. 多个 sink 还想用 g.pull()。 它只能自动拉一个。
自测三问(点开对答案) 1. 简单滤镜与复杂滤镜的唯一判据是什么? —— 几进几出。一进一出是简单,多进或多出是复杂。 2. `link_to(ov, 0, 1)` 里那个 1 是什么意思? —— 接到目标的第 1 个输入口,也就是 overlay 的上层。 3. 为什么复杂滤镜不能像 `-vf` 那样自动插进去? —— 它的输入输出对不上,工具判断不出哪一路该接哪一路。

相关笔记