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

要做「新画布贴视频」,顺序是 nullsrc 造布、crop 定尺寸、overlay 贴上去。 overlay 只往已有画面上贴,它不会帮你扩画布。

📝 本节和前面几节的关系

前面学的 crop 是「从画面里取一块」,这一节要反过来「往画面上加一块」。 这是画中画、九宫格的地基。

一、先造画布,再贴

命令是两段:一段造布,一段贴图。

ffmpeg -f lavfi -i nullsrc=s=320x240:d=1 -i s7.mp4

-filter_complex "[0:v]crop=200:120:60:60[bg];[bg][1:v]overlay=20:20"

nullsrc 造一块什么都没有的画布,crop 把它的尺寸定成想要的大小, overlay 再把真视频贴上去。三步一条链。

[bg] 是给中间结果起的标签,方括号里的名字随便起, 但前后必须一致——这是 filter_complex 的连接方式。

二、沙箱跑一遍这条链

三个环节各做什么,输出多大:

环节 做了什么 尺寸


nullsrc 造空画布 320×240 crop 抠出中间一块 200×120 overlay 贴到 (20, 20) 200×120

最终画布 200×120,左上角是 [0, 135, 0],贴图区域内是 [1, 1, 1]。 左上角那三个数里绿通道有 135,看着像一层深绿,不是纯黑。

nullsrc 的底本来就不是标准黑,7.4.4 一节专门讲这个坑。 要真黑底,把 nullsrc 换成 color=c=black 就行。

三、为什么非要绕这一圈

因为 overlay 只能把图贴到「已有画面」上,它不会帮你扩画布。 视频只有 160×90,想贴到 200×120 的画布中间,就得先有人把 200×120 那块布造出来。

nullsrc 干的就是这件事:不读任何文件,凭空给一帧。 它不占磁盘、不解码,成本几乎为零,所以被当成「默认画布」。

那 crop 在这里干什么?把 320×240 裁成 200×120。 为什么不直接写 nullsrc=s=200x120?可以直接写,crop 这一环是多余的。

教材这一节的写法串了三环,实际能用更短的写法: nullsrc=s=200x120:r=25 一句话就把前两环做完了。 串三环的意义在于:crop 还能顺手改取景位置,一步到位。

四、沙箱的限制要说清楚

想做两个输入时,PyAV 这边有个坑:

要两个输入时 结果


两个 add_buffer 接 overlay returned 22 一路 buffer 加一路 movie 能跑

所以上面那段用的是 movie 去读文件补第二路,不是两个内存缓冲。 movie 的参数是文件路径,Windows 路径要把反斜杠换成正斜杠、冒号转义。

命令行里没有这个限制,-i a.mp4 -i b.mp4 就是两路输入,随便接。 这个差别只影响「用 Python 建图」的写法,不影响命令行的效果。

五、动手 Demo:nullsrc 造布、crop 定尺寸、overlay 贴图

下面这段代码把三环串起来跑一次,打印输出尺寸与两个取样点。 第二路用 movie 读文件补上,这是 PyAV 里唯一能跑通的双输入写法。

import av

ESC = u"s7.mp4".replace(u"\\", u"/").replace(u":", u"\\:")
g = av.filter.Graph()
ns = g.add(u"nullsrc", u"size=320x240:rate=25")
cr = g.add(u"crop", u"200:120:60:60")
mv = g.add(u"movie", ESC)
ov = g.add(u"overlay", u"20:20")
sk = g.add(u"buffersink")
ns.link_to(cr)
cr.link_to(ov, 0, 0)
mv.link_to(ov, 0, 1)
ov.link_to(sk)
g.configure()
o = sk.pull()
a = o.to_ndarray(format=u"rgb24")
print(u"输出 %d×%d" % (o.width, o.height))
print(u"左上角 %s" % a[0, 0].tolist())
print(u"贴图内 %s" % a[30, 30].tolist())

实跑输出:

--------------------------------------------------------------------
7.4.3 复杂滤镜 nullsrc、crop、overlay 结合使用
--------------------------------------------------------------------

**① 想做「新画布贴视频」,第一步不是贴,是造一块空画布**

`ffmpeg -f lavfi -i nullsrc=s=320x240:d=1 -i s7.mp4`
`-filter_complex "[0:v]crop=200:120:60:60[bg];[bg][1:v]overlay=20:20"`

**`nullsrc` 造一块什么都没有的画布,`crop` 把它的尺寸定成想要的大小,**
**`overlay` 再把真视频贴上去。三步一条链。**

**② 沙箱里跑一遍这条链**

  环节        做了什么          尺寸
  -----------------------------------------
  nullsrc     造空画布          320×240
  crop        抠出中间一块      200×120
  overlay     贴到 (20, 20)     200×120

**最终画布 200×120,画布底色(左上角)是 [0, 135, 0],贴图区域里是 [1, 1, 1]。**
**左上角那三个数里绿通道有 135,看着像一层深绿,不是纯黑。**
**nullsrc 的底本来就不是标准黑,后面 7.4.4 一节专门讲这个坑。**

**③ 为什么非要绕这一圈**

**因为 overlay 只能把图贴到「已有画面」上,它不会帮你扩画布。**
**视频只有 160×90,想贴到 200×120 的画布中间,就得先有人把 200×120 那块布造出来。**
**nullsrc 干的就是这件事:不读任何文件,凭空给一帧。**

**④ 沙箱的一个限制要说清楚**

  要两个输入时                   结果
  ----------------------------------------------
  两个 add_buffer 接 overlay     returned 22
  一路 buffer 加一路 movie       能跑

**所以上面那段用的是 `movie` 去读文件补第二路,不是两个内存缓冲。**
**命令行里没有这个限制,`-i a.mp4 -i b.mp4` 就是两路输入,随便接。**

三行输出:200×120、左上角 [0, 135, 0]、贴图内 [1, 1, 1]。 中间那一行就是 nullsrc 的底色,绿通道 135 说明它不是纯黑。

ov 的两个输入槽:槽 0 是底(nullsrc 那条链路),槽 1 是要贴的图。 link_to(ov, 0, 0) 与 link_to(ov, 0, 1) 最后那个数字就是槽号。

贴图内取样点给的是 (30, 30),落在贴图区域里—— overlay 从 (20, 20) 开始贴,所以 30 已经进去 10 个像素了。

30 秒实验

  1. 换位置:把 overlay=20:20 改成 0:0,看左上角变成什么颜色。
  2. 换尺寸:把 crop 改成 200:120:0:0,看画布内容变不变。
  3. 换底色:把 nullsrc 换成 color=c=black:s=320x240:r=25,看左上角是不是 [0, 0, 0]。

常见误区

  1. 以为 overlay 能扩画布。它只在已有画面上画,画布大小得先定好。
  2. 两个 add_buffer 接 overlay。PyAV 里直接 returned 22,必须用 movie 补位。
  3. movie 路径不转义。Windows 路径的冒号要写成 \:,否则解析失败。
自测三问(点开对答案) 1. 为什么需要 nullsrc? —— overlay 只能往已有画面上贴,想贴到更大的画布上就得先造出那块布。 2. `[bg]` 是什么? —— 给中间结果起的标签,供后面用 `[bg]` 引用,是 filter_complex 的连接方式。 3. PyAV 里为什么要用 movie 补第二路? —— 两个 add_buffer 同时接一个双输入滤镜会报 22,一路 buffer 加一路 movie 才行。

相关笔记