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

多宫格靠 xstack 一次拼完,不用叠八次 overlay。 三件事要记:inputs 写路数、layout 写坐标、fill 补空白。

📝 本节和前面几节的关系

7.9 是「一主一小」,这一节把主画面也切成格子。 坐标的写法跟 overlay 一脉相承,都是「左上角在哪」。

一、为什么不用叠 overlay

九宫格=三行三列。最直觉的做法是叠八次 overlay,但那样每叠一层 都要重算整幅画布,九层下来不只慢,反复重采样还会让画面变糊。

xstack 一次收多路输入,按 layout 一次算完,只重采样一次。 同理还有 hstack(横排)与 vstack(竖排),都是它的特例。

ffmpeg -i a.mp4 -i b.mp4 -i c.mp4 -i d.mp4 -filter_complex " [0:v][1:v][2:v][3:v]xstack=inputs=4:layout=0_0|w0_0|0_h0|w0_h0" out.mp4

四个 [N:v] 就是四路输入,方括号的顺序就是格子顺序。 layout 里四条坐标用竖线隔开,一条对一格。

二、三条参数

xstack 的常用参数只有三条:

参数 写什么 不写会怎样


inputs 输入路数,4 或 9 默认 2 路,接 4 路报 returned 22 layout 每格左上角坐标,竖线隔开 默认横排 0_0|w0_0 fill 空格子补什么色 不补,空白处是 YUV 全 0 的绿

inputs 是最容易漏的一条:不写默认 2 路,你接 4 路它就报错。 报的是 Invalid argument returned 22,看不出是路数问题,得凭经验认。

layout 不写也有默认值 0_0|w0_0,两路横排。 两路时不写没区别,四路九路时少写这一句,格子就会按默认值歪着排。

三、layout 里的变量

layout 能用几个内置变量拼坐标:

变量 含义


w0 / w1 / … 第 0 路、第 1 路…的宽 h0 / h1 / … 第 0 路、第 1 路…的高

所以「第二列」写 w0_0,「第三列」写 w0+w1_0, 「第三行」写 0_h0+h1。

注意 w0 里的 0 是「第 0 路输入」的编号,不是「第 1 格」。 等大时随便用哪个都一样,不等大时就只能认准那一路。

四、输出多大

输出尺寸就是「格宽 × 列数」与「格高 × 行数」,不留额外边距:

排法 每格尺寸 输出尺寸


四宫格 2×2 80×45 160×90 九宫格 3×3 160×90 480×270

四宫格拼完跟原片一样大(每格缩一半),九宫格拼完是三倍大。 所以九宫格一般要把每格缩到原片的九分之一左右,输出才不至于爆掉。

想要格子之间有缝、有边框,layout 里把坐标错开就行: 第二列写 w0+4_0,第三列写 w0+w1+8_0,缝就出来了,只是画布也要跟着算大。

五、动手 Demo:两种排法的尺寸、inputs 不写会怎样

下面这段代码先把四宫格与九宫格各拼一张,量输出尺寸; 再试 inputs 不写时会怎样,最后验证 hstack 与 xstack 等价。

import av

L4 = u"0_0|w0_0|0_h0|w0_h0"
L9 = (u"0_0|w0_0|w0+w1_0|0_h0|w0_h0|w0+w1_h0"
      u"|0_h0+h1|w0_h0+h1|w0+w1_h0+h1")

def grid(n, layout, size, fill=u"black"):
    g = av.filter.Graph()
    srcs = [g.add(u"color", u"c=red:s=%s:r=25" % size) for _ in range(n)]
    xs = g.add(u"xstack", u"inputs=%d:layout=%s:fill=%s" % (n, layout, fill))
    sk = g.add(u"buffersink")
    for i, p in enumerate(srcs):
        p.link_to(xs, 0, i)
    xs.link_to(sk)
    g.configure()
    o = sk.pull()
    return o.width, o.height

print(u"--- 四宫格与九宫格各多大")
print(u"  2x2 每格 80x45   -> %dx%d" % grid(4, L4, u"80x45"))
print(u"  3x3 每格 160x90  -> %dx%d" % grid(9, L9, u"160x90"))

print(u"--- inputs 不写会怎样")
for n, lay in ((2, u"0_0|w0_0"), (4, L4)):
    try:
        g = av.filter.Graph()
        srcs = [g.add(u"color", u"c=red:s=80x45:r=25") for _ in range(n)]
        xs = g.add(u"xstack", u"layout=%s" % lay)
        sk = g.add(u"buffersink")
        for i, p in enumerate(srcs):
            p.link_to(xs, 0, i)
        xs.link_to(sk)
        g.configure()
        o = sk.pull()
        print(u"  %d 路不写 inputs -> %dx%d" % (n, o.width, o.height))
    except Exception as ex:
        msg = str(ex)
        if u"returned 22" in msg:
            msg = u"Invalid argument returned 22"
        print(u"  %d 路不写 inputs -> %s %s" % (n, type(ex).__name__, msg))

print(u"--- hstack 两路与 xstack 等价")
g = av.filter.Graph()
a = g.add(u"color", u"c=red:s=80x45:r=25")
b = g.add(u"color", u"c=blue:s=80x45:r=25")
hs = g.add(u"hstack", u"inputs=2")
sk = g.add(u"buffersink")
a.link_to(hs, 0, 0)
b.link_to(hs, 0, 1)
hs.link_to(sk)
g.configure()
o = sk.pull()
print(u"  hstack=2 -> %dx%d" % (o.width, o.height))
print(u"  xstack=2 -> %dx%d" % grid(2, u"0_0|w0_0", u"80x45"))

实跑输出:

--------------------------------------------------------------------
7.10.1 九宫格简介
--------------------------------------------------------------------

**① 九宫格不是叠八次 overlay**

**把九段画面排成三行三列,最直觉的做法是叠八次 overlay。**
**但那样每一层都要重算整幅画布,九层下来又慢又容易糊。**

**ffmpeg 给了专门的滤镜:`xstack`(横向为主的堆叠)与 `hstack`、`vstack`。**
**它们一次收多路输入,按 `layout` 把每路摆在画布的哪一格,一次算完。**

**② 三条参数管全部**

  参数       写什么                           不写会怎样
  --------------------------------------------------------------------------------
  inputs     输入路数,4 或 9                 默认 2 路,接 4 路报 returned 22
  layout     每格的左上角坐标,用竖线隔开     默认横排 0_0|w0_0
  fill       空格子补什么色                   不补,空白处是 YUV 全 0 的绿

**`layout` 里能用三个内置变量:`w0` 是第 0 路的宽、`w1` 是第 1 路的宽、**
**`h0`、`h1` 同理管高。所以「第二列」写 `w0_0`,「第三列」写 `w0+w1_0`。**

**`layout` 不写的默认值是 `0_0|w0_0`,也就是两路横着并排——**
**两路时不写它跟写了没区别,四路、九路时少写这一句,坐标就全靠默认值歪着排。**

**③ 四宫格与九宫格各多大**

  排法           每格尺寸     输出尺寸
  ----------------------------------------
  四宫格 2×2     80×45        160×90
  九宫格 3×3     160×90       480×270

**输出尺寸就是「格宽 × 列数」与「格高 × 行数」,没有额外留边。**
**想加边距得自己在 layout 里留,或者拼完再接一次 pad。**

**④ 三个堆叠滤镜的分工**

  滤镜       排法         典型用途
  --------------------------------------------------
  hstack     横着一排     前后对比、左右分屏
  vstack     竖着一摞     上下分屏、字幕区预留
  xstack     任意格子     四宫格、九宫格、监控墙

**`hstack` 与 `vstack` 其实是 `xstack` 的特例,写起来更短:**
**`hstack=inputs=2` 等价于 `xstack=inputs=2:layout=0_0|w0_0`。**
**格子一多就用 xstack,不用纠结。**

前两行就是本节那张尺寸表:2×2 的 80×45 拼成 160×90,3×3 的 160×90 拼成 480×270。

中间两行是 inputs 的现场:2 路不写照样通,4 路不写报 returned 22。 这个错很难看出来是路数问题,因为错误信息里根本不提 inputs。

最后两行 hstack=2 与 xstack=2 都出 160×90, 证实了「hstack 只是 xstack 的横排特例」这句话。

30 秒实验

  1. 改格子数:把 inputs=4 改成 inputs=2,layout 只留两条,看输出多宽。
  2. 加缝:把第二列坐标改成 w0+4_0,看缝隙有没有出现。
  3. 去掉 fill:九宫格全等大时把 fill 删掉,看画面有没有变化。

常见误区

  1. 忘了写 inputs。默认 2 路,接 4 路直接报 returned 22。
  2. 以为 w0 指第 1 格。它指的是第 0 路输入的宽。
  3. 九宫格按原片尺寸拼。输出会是原片的 3 倍宽高,编码扛不住。
自测三问(点开对答案) 1. 九宫格用哪个滤镜拼? —— `xstack`,`hstack` 与 `vstack` 是它的横排、竖排特例。 2. `inputs` 不写默认几路? —— 2 路。接 4 路就会报 `returned 22`。 3. 三行三列每格 160×90,输出多大? —— 480×270,宽高各三倍。

相关笔记