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

pad 的四个数:总宽、总高、左边距、上边距,最后还能补一个颜色。 它只能把画布变大,而且高度必须是偶数,奇数值会被往下取。

📝 本节和前面几节的关系

7.2.6 已经用过 pad 补黑边,这一节把它当一个独立滤镜讲透。 7.8 的倒影第一步就是 pad,所以这一节是它的前置。

一、四个数加一个颜色

完整写法:

ffmpeg -i s7.mp4 -vf pad=200:120:20:15:black out.mp4

前两个是画布总尺寸,后两个是画面在画布里的左上角坐标,最后一个是补边的颜色。 注意:x、y 说的是「画面放哪」,不是「补多少」。

写 20:15 表示画面从 (20, 15) 开始放,于是左边补 20、上边补 15, 右边补 200-160-20=20、下边补 120-90-15=15。两边对称是巧合,不是规则。

二、第五个参数能写什么颜色

四种写法都试一遍,看左上角那个像素:

写法 左上角像素


pad=200:120:20:15:red [253, 0, 0] pad=200:120:20:15:0x336699 [49, 100, 151] pad=200:120:20:15:white [255, 255, 255] pad=200:120:20:15:black [0, 0, 0]

四个颜色都照写照出,说明颜色名与十六进制都认。 red 出来是 253 不是 255,还是那个 YUV 范围压缩的零头,7.4.6 讲过。

补边颜色能写 [email protected] 这种带透明度的,但输出得是 yuva420p 才有 alpha, 否则透明度被丢掉,还是实心黑。

三、高度必须是偶数

五个总高度试一遍:

写的总高 实际输出高


130 130 131 130 134 134 135 134 136 136

131 与 135 都被压成了 130 与 134,其余原样。 因为 yuv420p 的纵向色度是减半存的,高度必须是偶数,奇数值被往下取。

这个「往下取」很容易把后面的计算带偏: 你按 135 算了倒影的位置,实际画布只有 134,最后一行就被切掉了。

所以定画布时就要按偶数写,别指望它帮你凑整。 7.8 的倒影用的是 134 而不是 135,就是为了避开这一次取整。

四、pad 与 scale 的分工

两个都能改尺寸,但改的不是同一样东西:

滤镜 改什么 方向


scale 画面本身的大小 可大可小 pad 画面外面的画布 只能变大

pad 不允许把画布改小:目标尺寸比输入小直接报错。 要缩小画面用 scale,要加边框用 pad,两个经常一起出现。

标准写法是「先 scale 到目标比例,再 pad 到目标尺寸」, 7.2.5 的 decrease 加 pad 就是这一套,用来做等比缩放加黑边。

五、和 overlay 的关系

一句话分清:

pad 是「把自己的画布变大」,overlay 是「把别人的画面贴上来」。 只想加黑边、加留白、加边框,用 pad 一步到位; 要把另一段画面放进来,才需要 overlay。

两者的坐标语义一样:都是「左上角在哪」,都从 0 开始数,都对齐到偶数。 学会一个,另一个的坐标为也不会写错。

7.8 的倒影是两者合起来用的:先用 pad 把画布加高,再用 overlay 把翻过来的画面贴到底部。 所以这一节先讲透,下一节才不至于卡住。

六、动手 Demo:四种补色、五档高度、一次报错

下面这段代码先试四种补边颜色,再试五档总高度,最后试把画布改小。 取左上角那个像素,就能看出补的是什么色。

import av
from fractions import Fraction

TB = Fraction(1, 25)
c0 = av.open(u"s7.mp4")
base = next(c0.decode(video=0)).to_ndarray(format=u"yuv420p")
c0.close()

def pad(args):
    g = av.filter.Graph()
    b = g.add_buffer(width=160, height=90, format=u"yuv420p", time_base=TB)
    f = g.add(u"pad", args)
    sk = g.add(u"buffersink")
    b.link_to(f)
    f.link_to(sk)
    g.configure()
    vf = av.VideoFrame.from_ndarray(base, format=u"yuv420p")
    vf.pts = 0
    vf.time_base = TB
    b.push(vf)
    return sk.pull().to_ndarray(format=u"rgb24")

print(u"--- 补边颜色")
for c in (u"red", u"0x336699", u"white", u"black"):
    o = pad(u"200:120:20:15:%s" % c)
    print(u"%-10s -> 左上 %s" % (c, o[0, 0].tolist()))

print(u"--- 总高度取偶")
for hh in (130, 131, 134, 135, 136):
    o = pad(u"160:%d:0:0:black" % hh)
    print(u"写 %d -> 实得 %d" % (hh, o.shape[0]))

print(u"--- 改小会不会报错")
try:
    pad(u"120:80:0:0:black")
except Exception as ex:
    print(u"120:80 -> %s" % type(ex).__name__)

实跑输出:

--------------------------------------------------------------------
7.7 视频填充 pad 滤镜
--------------------------------------------------------------------

**① pad 的四个数:总宽、总高、左边距、上边距**

`ffmpeg -i s7.mp4 -vf pad=200:120:20:15:black out.mp4`

**前两个是画布总尺寸,后两个是画面在画布里的左上角坐标,最后一个是补边的颜色。**
**注意:x、y 说的是「画面放哪」,不是「补多少」。**

**② 第五个参数能写什么颜色**

  写法                           左上角像素
  --------------------------------------------------
  pad=200:120:20:15:red          [253, 0, 0]
  pad=200:120:20:15:0x336699     [49, 100, 151]
  pad=200:120:20:15:white        [255, 255, 255]
  pad=200:120:20:15:black        [0, 0, 0]

**四个颜色都照写照出,说明颜色名与十六进制都认。**
**`red` 出来是 253 不是 255,还是那个 YUV 范围压缩的零头,7.4.6 讲过。**

**③ 高度必须是偶数**

  写的总高     实际输出高
  ---------------------------
  130          130
  131          130
  134          134
  135          134
  136          136

**131 与 135 都被压成了 130 与 134,其余原样。**
**因为 yuv420p 的纵向色度是减半存的,高度必须是偶数,奇数值被往下取。**

**这个「往下取」很容易把后面的计算带偏:**
**你按 135 算了倒影的位置,实际画布只有 134,最后一行就被切掉了。**

**④ pad 与 scale 的分工**

  滤镜      改什么             方向
  -----------------------------------------
  scale     画面本身的大小     可大可小
  pad       画面外面的画布     只能变大

**pad 不允许把画布改小:目标尺寸比输入小直接报错。**
**要缩小画面用 scale,要加边框用 pad,两个经常一起出现。**

**⑤ 和 overlay 的关系**

**pad 是「把自己的画布变大」,overlay 是「把别人的画面贴上来」。**
**只想加黑边、加留白、加边框,用 pad 一步到位;**
**要把另一段画面放进来,才需要 overlay。**

**两者的坐标语义一样:都是「左上角在哪」,都从 0 开始数,都对齐到偶数。**

第一段四行跟本节那张表一一对上,红色是 253、十六进制是 [49, 100, 151]。

第二段五行里,131 与 135 被压成了 130 与 134, 其余三行原样——这就是「高度必须偶数」的直接证据。

最后一行是改小的情况,报的是 ArgumentError, 跟 7.4.2 里 crop 超尺寸报的是同一族错误(都源于 returned 22)。

30 秒实验

  1. 改坐标:把 20:15 改成 0:0,看左上角是不是变成画面内容。
  2. 量补边:写 pad=200:120:20:15:red,数一下左边到底有几列红。
  3. 试透明:写 [email protected] 并把 pix_fmt 换成 yuva420p,看 alpha 有没有留下。

常见误区

  1. 以为 x、y 是补多少。它们是画面左上角的位置。
  2. 写奇数总高。会被往下取到偶数,位置全偏一行。
  3. 想用 pad 缩小画布。它只能变大,缩小要用 scale。
自测三问(点开对答案) 1. `pad=200:120:20:15` 右边补了多少? —— 20 列。`200 - 160 - 20 = 20`,左右碰巧相等。 2. 总高写 135 实际得到多少? —— 134。yuv420p 要求高度是偶数,奇数被往下取。 3. pad 能把画布改小吗? —— 不能,目标尺寸比输入小直接报 `returned 22`。

相关笔记