🎬 第七章 FFmpeg命令行实现音视频特效及复杂滤镜应用
📄 一句话概括

滤镜是最小单位,链是逗号串起来的几个滤镜,图是分号隔开的一堆链。 三层包含,一层比一层大。

📝 本节和前面几节的关系

上一节说「复杂滤镜是一张网」,这一节把这张网拆成三个层级。 概念不难,难的是把概念对应到写法上。

一、三层包含,一层比一层大

三个词,三种粒度:

中文 英文 怎么分隔 例子


滤镜 filter 一个名字 scale 滤镜链 filterchain 逗号 scale,hflip 滤镜图 filtergraph 分号 A;B

三个层级是包含关系:图里有若干条链,链里有若干个滤镜。 理解这一点,读别人写的长命令就不会晕——先按分号切,再按逗号切。

二、一条链,逐环看尺寸

把四个滤镜串成一条链,看每一环交出什么尺寸:

第几环 参数 输出尺寸


0 源 — 160×90 1 scale 320:180 320×180 2 crop 200:121:40:20 200×120 3 hflip (无参数) 200×120 4 pad 240:160:20:20:black 240×160

每一环只看上一环交过来的东西,不管前面发生过什么。 这就是链的本质:四次独立的变换,串在一起。

第二环那行值得停一下:crop 要的是 200×121,出来却是 200×120。 因为 yuv420p 的宽高必须是偶数,121 被就近取成了 120。

这件事不报错、也不警告,只是默默改数。 查「输出尺寸对不上」的时候,先怀疑这里,别急着怀疑命令写错了。

三、图就是链加标签

把上面那条链和另一条链并排放,用标签连起来,就成了图:

split=2[a][b]; [a]scale=320:180,crop=200:121:40:20[v1]; [b]hflip[v2]

按分号切,得到三条链:split=2[a][b]、[a]scale=...、[b]hflip。 三条链之间用 [a]、[b] 两个标签交接,整体才是图。

标签就是「管子」的名字。 一条链的出口起个名字,另一条链的入口喊这个名字,数据就从那儿流过去。

所以写图的步骤是固定的三步:先想清楚要几条链, 每条链里放哪几个滤镜、按什么顺序,最后用标签把它们接起来。

四、从命令行读回三层结构

拿一条稍长的命令练手:

-vf "scale=80:45,hflip,drawbox=x=10:y=10:w=20:h=10"

这一整串是一条链,三个滤镜,没有分号——所以它还只是链,不是图。 -vf 的参数位置上永远只能放一条链,这是它名字里那个 v 的由来。

换成 -filter_complex 之后,位置放的就是图了: 可以一条链,也可以十条链,全看你要画多复杂。

五、动手 Demo:四环链逐级量尺寸,抓出取偶那一环

下面这段代码把四环链逐级跑一遍,每一步都量一次真实尺寸。 重点看第二环:参数写的 121,出来的是 120。

import av
from fractions import Fraction

TB = Fraction(1, 25)
CHAIN = [(u"scale", u"320:180"), (u"crop", u"200:121:40:20"),
         (u"hflip", u""), (u"pad", u"240:160:20:20:black")]

c0 = av.open(u"s7.mp4")
base = next(c0.decode(video=0)).to_ndarray()
c0.close()

for n in range(len(CHAIN) + 1):
    g = av.filter.Graph()
    prev = g.add_buffer(width=160, height=90,
                         format=u"yuv420p", time_base=TB)
    for name, args in CHAIN[:n]:
        f = g.add(name, args) if args else g.add(name)
        prev.link_to(f)
        prev = f
    sk = g.add(u"buffersink")
    prev.link_to(sk)
    g.configure()
    vf = av.VideoFrame.from_ndarray(base, format=u"yuv420p")
    vf.pts = 0
    vf.time_base = TB
    prev.push(vf)
    o = sk.pull().to_ndarray(format=u"rgb24")
    tag = u"源" if n == 0 else CHAIN[n - 1][0]
    print(u"%-8s %d×%d" % (tag, o.shape[1], o.shape[0]))

实跑输出:

--------------------------------------------------------------------
7.1.2 滤镜图、滤镜链、滤镜的关系
--------------------------------------------------------------------

**① 这三个词是层层包含的:滤镜 < 链 < 图。**

  中文       英文            怎么分隔     例子
  -------------------------------------------------------
  滤镜       filter          一个名字     scale
  滤镜链     filterchain     逗号         scale,hflip
  滤镜图     filtergraph     分号         A;B

**② 一条四环的链,逐环看尺寸怎么变**

  第几环      参数                    输出尺寸
  ------------------------------------------------
  0 源        —                       160×90
  1 scale     320:180                 320×180
  2 crop      200:121:40:20           200×120
  3 hflip     (无参数)              200×120
  4 pad       240:160:20:20:black     240×160

**每一环只看上一环交过来的东西,不管前面发生过什么。**
**这就是链的本质:四个滤镜,四次独立的变换,串在一起。**

**③ 中间那两环有个细节值得停一下**

**crop 要的是 200×121,实际输出是 200×120。**
**因为 yuv420p 的宽高必须是偶数,121 被就近取成了 120——**
**这件事不报错、也不警告,只是默默改数。查画面尺寸对不上时,先怀疑这里。**

**④ 图就是「几条链 + 几个连接点」**

**把刚才那条链和另一条链并排放,用标签连起来,就是图:**

`split=2[a][b]; [a]scale=320:180,crop=200:120:40:20[v1]; [b]hflip[v2]`

**这里有三条链:split 那条、[a] 开头那条、[b] 开头那条。**
**三条链之间用标签 [a]、[b] 交接,整体才是图。**

**⑤ 关系可以一句话说完**

  层级       数量关系
  -----------------------------------
  滤镜图     由若干条链组成
  滤镜链     由若干个滤镜组成
  滤镜       最小单位,只做一件事

**所以「写一个滤镜图」的活儿,拆开就是:先想清楚要几条链,**
**每条链里放哪几个滤镜、按什么顺序,最后用标签把它们接起来。**

每一轮都重新建图,只挂前 n 个滤镜——所以每行都是「走到这一环」的真实尺寸。 这个方法比算一遍公式可靠,尤其是遇到取偶、取整这类隐藏规则时。

crop 那一行的 121 变成 120,是 yuv420p 带来的: 色度平面是两两合并的,奇数高最后一行没有配对,只能砍掉。 换成 rgb24 就不会砍——同一句 crop,换个像素格式结果就不同。

30 秒实验

  1. 换格式:把 format=u"yuv420p" 换成 rgb24,看 crop 还砍不砍。
  2. 加一环:在 pad 后面再加 scale=120:80,看尺寸回到哪里。
  3. 改奇数:把 crop 写成 200:119,看输出是 118 还是 120。

常见误区

  1. 把滤镜链叫成滤镜图。只有一条链时它就是链,没有分号就不成图。
  2. 以为标签可以随便起。同名标签会被当成同一根管子,数据流就串了。
  3. 以为 crop 写什么就出什么。yuv420p 下宽高会被取偶,不报错。
自测三问(点开对答案) 1. 滤镜链用什么分隔,滤镜图用什么分隔? —— 链用逗号,图用分号。 2. crop 要 200×121 为什么得到 200×120? —— yuv420p 要求宽高为偶数,121 被就近取成 120。 3. 标签的作用是什么? —— 给管子起名字,让一条链的出口能接到另一条链的入口。

相关笔记