iw、ih、a、sar、dar 这些变量,让同一条命令适配所有分辨率。 本机实测:ow 读不到,oh 反而读得到。
前两节写的都是固定数字,换一段素材就得改命令。 内置变量就是把这些数字换成「输入画面的实际值」。
一、七个变量,各有各的用处
表达式里能读到的输入信息:
变量 含义
iw / in_w 输入画面宽度 ih / in_h 输入画面高度 a 宽高比,即 iw/ih sar 像素宽高比 dar 显示宽高比 hsub / vsub 色度的水平、垂直取样系数
sar 是「一个像素有多宽」,dar 是「整幅画面看起来多宽」。
大多数素材 sar 是 1(方形像素),这时 dar 就等于 a。
hsub 与 vsub 是色度的取样系数。
4:2:0 时两个都是 2,4:2:2 时是 2 与 1,4:4:4 时都是 1。
二、把变量值算出来
这些变量不能直接打印,但可以让它们变成尺寸,再从尺寸反推。
写法是把宽度设成 iw*0+变量*1000,高度固定 2。
变量 探针写法 输出宽 除一千
iw scale=iw0+iw1000:2 160000 160.000 ih scale=ih0+ih1000:2 90000 90.000 a scale=iw0+a1000:2 1777 1.777 sar scale=iw0+sar1000:2 1000 1.000 hsub scale=iw0+hsub1000:2 2000 2.000
iw 与 ih 乘一千之后再除回去,正好是素材的 160 与 90。
为什么写成乘一千?因为尺寸只能是整数,直接取 1.777 会把小数丢掉。
放大一千倍再除,就等价于保留了三位小数——这个技巧调参时很有用。 想知道某个表达式的值,就把它变成尺寸量出来。
三、按比例写缩放
有了变量,比例缩放就不用自己算了:
写法 输出
iw/2:ih/2 80×45 iw1.5:ih1.5 240×135 iw0.8:ih0.8 128×72
换成 1920×1080 的素材,这三条命令照样成立,只是输出跟着变。 这就是内置变量最大的用处:写一次,适配所有分辨率。
第二行输出 240×135,135 是奇数——
它照样出来了,因为这不是在编码,只是滤镜内部的一帧。
真要出 MP4,还得改成 iw*1.5:trunc(ih*1.5/2)*2。
四、取偶的表达式写法
trunc(iw/2)*2 是最常见的取偶写法:
scale=trunc(iw/2)*2:trunc(ih/2)*2
先除以 2 取整,再乘 2——奇数的最后一位就这么被砍掉了。 160×90 过一遍还是 160×90,因为两个数本来就是偶数。
floor 与 trunc 等价,两个都认。
但千万别照搬 Python 的写法——本机实测四种里只有两种成立:
表达式 本机结果
trunc(iw/2)2 160×90 floor(iw/2)2 160×90 iw//2*2 returned 22 iw-iw%2 returned 22
// 是 Python 的整除符,% 是取余——FFmpeg 的表达式里两个都没有。
它只认四则运算加一批函数名,写法看着像 C,实际语法挺窄。
五、ow 读不到,oh 反而读得到
输出宽高这两个变量,在 scale 里表现不对称:
写法 结果
iw0+ow1000:ih0+2 returned 22 iw0+oh1000:ih0+2 2000×2
第一行报错,第二行成功——这个不对称很说明问题。 因为 scale 先算高、再算宽:算宽的那一刻,高已经定下来了。
所以 oh 读得到(值是前面算出的 2),ow 读不到。
ow 正是「此刻正在算的东西」,自己引用自己,只能报错。
这条规律推得再远一点:表达式里用到某个变量之前,先确认它是不是已经算出来了。
在 pad 里 ow 与 oh 都能读,因为 pad 的总尺寸是你直接给的,不存在先后。
六、动手 Demo:拿尺寸当探针,把七个变量量出来
下面这段代码把七个变量各乘一千,变成尺寸量出来。
最后试 ow 与 oh,看哪一个报错。
import av
from fractions import Fraction
TB = Fraction(1, 25)
c0 = av.open(u"s7.mp4")
base = next(c0.decode(video=0)).to_ndarray()
c0.close()
def probe(expr):
g = av.filter.Graph()
p = g.add_buffer(width=160, height=90, format=u"yuv420p", time_base=TB)
sc = g.add(u"scale", expr + u":ih*0+2")
sk = g.add(u"buffersink")
p.link_to(sc)
sc.link_to(sk)
g.configure()
vf = av.VideoFrame.from_ndarray(base, format=u"yuv420p")
vf.pts = 0
vf.time_base = TB
p.push(vf)
return sk.pull().to_ndarray(format=u"rgb24").shape[1]
for name in (u"iw", u"ih", u"a", u"sar", u"dar", u"hsub", u"vsub"):
try:
n = probe(u"iw*0+" + name + u"*1000")
print(u"%-6s %8d 除一千 %.3f" % (name, n, n / 1000.0))
except Exception as e:
print(u"%-6s %s" % (name, str(e)[:40]))
print(u"--- 输出尺寸这两个变量")
for name in (u"ow", u"oh"):
try:
n = probe(u"iw*0+" + name + u"*1000")
print(u"%-4s 可读,值 %.1f" % (name, n / 1000.0))
except Exception as e:
print(u"%-4s %s" % (name, str(e)[:40]))
print(u"--- trunc 取偶")
for tgt in (u"80:45", u"trunc(iw/2)*2:trunc(ih/2)*2", u"101:trunc(101*90/160)"):
try:
g = av.filter.Graph()
p = g.add_buffer(width=160, height=90,
format=u"yuv420p", time_base=TB)
sc = g.add(u"scale", tgt)
sk = g.add(u"buffersink")
p.link_to(sc)
sc.link_to(sk)
g.configure()
vf = av.VideoFrame.from_ndarray(base, format=u"yuv420p")
vf.pts = 0
vf.time_base = TB
p.push(vf)
o = sk.pull().to_ndarray(format=u"rgb24")
print(u"%-28s -> %d×%d" % (tgt, o.shape[1], o.shape[0]))
except Exception as e:
print(u"%-28s -> %s" % (tgt, str(e)[:30]))
实跑输出:
--------------------------------------------------------------------
7.2.3 使用 FFmpeg 的内置变量进行缩放
--------------------------------------------------------------------
**① scale 的两个参数可以是表达式,表达式里能读到输入画面的信息。**
**这些信息就是内置变量,写死的数字换成了变量,同一条命令就能适配各种素材。**
变量 含义
--------------------------------------------
iw / in_w 输入画面宽度
ih / in_h 输入画面高度
a 宽高比,即 iw/ih
sar 像素宽高比
dar 显示宽高比
hsub / vsub 色度的水平、垂直取样系数
**② 把变量值「算出来」的办法**
**这些变量不能直接打印,但可以让它们变成尺寸,再从输出尺寸反推。**
**写法是把宽度设成 `iw*0+变量*1000`,高度固定 2,输出宽度就是变量的一千倍。**
变量 探针写法 输出宽 除一千
----------------------------------------------------------
iw scale=iw*0+iw*1000:2 160000 160.000
ih scale=ih*0+ih*1000:2 90000 90.000
a scale=iw*0+a*1000:2 1777 1.777
sar scale=iw*0+sar*1000:2 1000 1.000
dar scale=iw*0+dar*1000:2 1777 1.777
hsub scale=iw*0+hsub*1000:2 2000 2.000
vsub scale=iw*0+vsub*1000:2 2000 2.000
**前两行的输出宽是六位数,除一千正好是 160 与 90,跟素材一模一样。**
**`a` 与 `dar` 除一千都是 1.777,就是 16 比 9——160×90 的宽高比正是 16:9。**
**`sar` 是 1.000,说明像素是正方形;`hsub` 与 `vsub` 都是 2,正是 4:2:0 的特征。**
**③ 用变量写「按比例」的缩放**
`scale=iw/2:ih/2` 与 `scale=iw*1.5:ih*1.5`
写法 输出
-----------------------------
iw/2:ih/2 80×45
iw*1.5:ih*1.5 240×135
iw*0.8:ih*0.8 128×72
**换了素材,这些命令照样成立——因为它们读的是输入,不是写死的数。**
**这就是内置变量最大的用处:写一次,适配所有分辨率。**
**④ 取偶的老规矩,用表达式写出来**
`scale=trunc(iw/2)*2:trunc(ih/2)*2`
**`trunc` 是截掉小数,乘 2 再除 2 就把奇数变成偶数。**
**很多人把它简化成 `-2`,效果一样,但 `-2` 只能用在「按比例算」的那一边。**
**要两个方向都自己算时,还是得写这句。**
**⑤ 输出尺寸这两个变量,一个能读、一个不能**
**`ow`(输出宽)与 `oh`(输出高)在 scale 里表现不一样,一个能读、一个不能:**
写法 结果
---------------------------------------
iw*0+ow*1000:ih*0+2 returned 22
iw*0+oh*1000:ih*0+2 2000×2
**第一行报错、第二行成功,这个不对称很说明问题。**
**因为 scale 先算高、再算宽——算宽的那一刻,高已经定下来了,所以 `oh` 读得到。**
**而 `ow` 正是「此刻正在算的东西」,自己引用自己,只能报错。**
**所以真要用输出尺寸,得先想清楚它在什么时候可用:算得早的能用,正在算的不能用。**
前七行全部有值,a 与 dar 都是 1.777,sar 是 1.000。
这一段素材是方形像素、16:9 显示,所以三个数正好对得上。
ow 那行报 returned 22,oh 那行给出 2.0。
因为高度这一轮写死成 2,所以 oh 就是 2;乘一千得 2000,除回去还是 2。
这个数字本身没意义,有意义的是它能出来——它证明了「先算高、再算宽」。
最后三行是取偶的对照:80:45 出 45(不取偶),
trunc 那句原样 90(本来就偶),101 配一个表达式高度出 56。
30 秒实验
- 换素材:换成 16:9 之外的比例,看
a与dar还相等吗。 - 读 ow:把
iw*0+ow*1000换成ih*0+ow*1000,看报错是否一样。 - 除一百:把探针里的 1000 换成 100,看小数位够不够用。
常见误区
- 以为变量能直接打印。它们只在表达式里存在,得转成尺寸才看得见。
- 以为 ow 与 oh 地位相同。算宽的时候 ow 还没定,读它就报错。
- 以为 sar 就是宽高比。sar 是单个像素的宽高比,画面比例看
a或dar。
自测三问(点开对答案)
1. `a` 与 `dar` 什么时候不相等? —— 像素不是方形时。`a` 只看像素个数,`dar` 还乘上 sar。 2. 为什么探针要乘一千? —— 尺寸只能是整数,不放大就丢掉小数部分。 3. `trunc(iw/2)*2` 干的是什么? —— 把奇数宽砍成偶数:先除二取整,再乘二。- ☐ (本节完,下一节 7.2.4 使用 min 或 max 函数进行缩放)