scale 的参数不多,坑集中在三处:奇偶、整除、参数顺序。 force_divisible_by 是往上取整,不是往下压。
前面七节把 scale 的每个功能都跑了一遍,这一节把参数收成一张表。 顺便补两个没讲过的:force_divisible_by 与 eval。
一、参数全表
常用的就这七个:
参数 含义 常用值
w / h 输出宽高,可写表达式 80、-1、-2、iw/2 flags 缩放算法 bilinear、bicubic、lanczos force_original_aspect_ratio 等比还是硬拉 disable、decrease、increase force_divisible_by 强制宽高能被 N 整除 2、4、8、16 interl 隔行扫描处理 0、1 eval 表达式何时求值 init、frame sws_flags 与 flags 同义,可全局用 —
interl 只在处理隔行素材时才要管,逐行素材保持默认 0 就行。
设成 1 会让缩放按场处理,速度慢一倍,但隔行素材不设会抖。
二、force_divisible_by 是往上取整
这个参数强制输出宽高能被 N 整除。方向容易搞反:
写法 输出
640:480:ratio=decrease 640×360 同上:force_divisible_by=4 640×360 同上:force_divisible_by=16 640×368
第二行跟第一行一模一样:360 本来就能被 4 整除,不用动。 第三行从 360 变成 368,是往上取到 16 的倍数。
注意方向:是就近往大整,不是往下压。 368 比 360 大,但仍在 480 的框内,所以不违反 decrease。
什么时候需要它?做 H.264 硬编码、某些老播放器、 还有把画面切成宏块做并行处理的场合,都要求宽高是 16 的倍数。
三、eval 决定表达式何时求值
拿带帧号 n 的表达式试一下,四种写法各喂三帧:
写法 三帧的输出宽
80+n0 returned 22 80+n0:eval=init returned 22 80+n0:eval=frame 80 / 80 / 80 80+n4:eval=frame 84 / 88 / 92
前两行都报错:默认就是 init,那一刻还没有「帧」这个概念,n 无从取起。
第三行写了 frame,每帧都重算,宽度稳定在 80。
第四行把 n 乘了 4,于是宽度逐帧 84、88、92 地长上去。
这直接证明了「每帧重算」是什么意思。
但真实转码里几乎不会用 frame:H.264 的序列头里写死了宽高, 尺寸一变,编码器就接不下去。它多半用来做分析、抽帧,不是拿来出片的。
四、参数顺序的坑
滤镜参数用冒号分隔,位置参数(w 与 h)必须写在最前面:
写法 本机结果
flags=lanczos:80:45 returned 22 80:45:flags=lanczos 80×45 w=80:h=45:flags=lanczos 80×45
因为前面一旦出现过 名字=值,解析器就认定后面全是这种配对格式。
裸的 80 与 45 既没有名字也没有等号,只能当成无效参数丢掉。
所以两种写法都成立:全位置写法、或全命名写法。 混着写就出事——这是 FFmpeg 所有滤镜的通病,不止 scale。
五、这一节收个尾
scale 看着简单,其实一半的坑都在「尺寸必须是偶数」和「比例算不整」上。 记住三条就够应付九成场合:
要什么 怎么写
等比缩放 另一边写 -2 塞进固定框 decrease 加 pad 要锐利 flags=lanczos
剩下那一成,回来翻这张参数表。 真正需要死记的只有「宽在前、高在后」这一条。
六、动手 Demo:整除、求值时机、参数顺序,三处一次验完
下面这段代码验三件事:force_divisible_by 的方向、eval 的差别、参数顺序的坑。 第二件会用带帧号的表达式,看每一帧的宽度是不是真的在变。
import av
from fractions import Fraction
TB = Fraction(1, 25)
c0 = av.open(u"s7.mp4")
frames = []
for i, fr in enumerate(c0.decode(video=0)):
frames.append(fr.to_ndarray())
if i >= 2:
break
c0.close()
def sizes(expr, n=1):
g = av.filter.Graph()
p = g.add_buffer(width=160, height=90, format=u"yuv420p", time_base=TB)
sc = g.add(u"scale", expr)
sk = g.add(u"buffersink")
p.link_to(sc)
sc.link_to(sk)
g.configure()
out = []
for i in range(n):
vf = av.VideoFrame.from_ndarray(frames[i], format=u"yuv420p")
vf.pts = i
vf.time_base = TB
p.push(vf)
while True:
try:
o = sk.pull().to_ndarray(format=u"rgb24")
out.append((o.shape[1], o.shape[0]))
except Exception:
break
return out
print(u"--- force_divisible_by")
for n in (u"", u":force_divisible_by=4", u":force_divisible_by=16"):
e = u"640:480:force_original_aspect_ratio=decrease" + n
print(u"%-22s -> 高 %d" % (n or u"不给", sizes(e)[0][1]))
print(u"--- eval")
for e in (u"iw*0+80+n*0:ih*0+90", u"iw*0+80+n*0:ih*0+90:eval=init",
u"iw*0+80+n*0:ih*0+90:eval=frame",
u"iw*0+80+n*4:ih*0+90:eval=frame"):
try:
print(u"%-40s -> %s"
% (e, [w for w, h in sizes(e, 3)]))
except Exception as ex:
print(u"%-40s -> %s" % (e, str(ex)[:30]))
print(u"--- 参数顺序")
for e in (u"flags=lanczos:80:45", u"80:45:flags=lanczos",
u"w=80:h=45:flags=lanczos"):
try:
print(u"%-24s -> %d×%d" % ((e,) + sizes(e)[0]))
except Exception as ex:
print(u"%-24s -> %s" % (e, str(ex)[:30]))
实跑输出:
--------------------------------------------------------------------
7.2.8 scale 参数说明
--------------------------------------------------------------------
**① scale 的全部参数,常用的就这几个**
参数 含义 常用值
-----------------------------------------------------------------------------------------
w / h 输出宽高,可写表达式 80、-1、-2、iw/2
flags 缩放算法 bilinear、bicubic、lanczos
force_original_aspect_ratio 等比还是硬拉 disable、decrease、increase
force_divisible_by 强制宽高能被 N 整除 2、4、8
interl 隔行扫描处理 0、1
eval 表达式何时求值 init、frame
sws_flags 与 flags 同义,全局用 —
**② force_divisible_by 实测**
写法 输出
----------------------------------------------------------------------------------
640:480:force_original_aspect_ratio=decrease 640×360
640:480:force_original_aspect_ratio=decrease:force_divisible_by=4 640×360
640:480:force_original_aspect_ratio=decrease:force_divisible_by=16 640×368
**第二行跟第一行一模一样:360 本来就能被 4 整除,不用动。**
**第三行从 360 变成 368,是往上取到 16 的倍数——注意方向,是就近往大整,不是往下压。**
**做 H.264 硬编码或某些老播放器时,宽高要能被 16 整除,就靠这个参数。**
**③ eval 决定表达式什么时候求值,实测差别很直接**
**拿带帧号 `n` 的表达式试一下,四种写法各喂三帧:**
写法 三帧的输出宽
--------------------------------------
80+n*0 returned 22
80+n*0:eval=init returned 22
80+n*0:eval=frame 80 / 80 / 80
80+n*4:eval=frame 84 / 88 / 92
**前两行都报错:默认就是 init,那一刻还没有「帧」这个概念,`n` 无从取起。**
**第三行写了 frame,每帧都重算,所以宽度稳定在 80。**
**第四行把 `n` 乘了 4,于是宽度逐帧 84、88、92 地长上去。**
**但真实转码里几乎不会用 frame:H.264 的序列头里写死了宽高,**
**尺寸一变,编码器就接不下去。所以它多半用来做分析、抽帧,不是拿来出片的。**
**④ 一个参数顺序的坑**
**滤镜参数用冒号分隔,位置参数(w 与 h)必须写在最前面。**
**本机实测 `scale=flags=lanczos:80:45` 直接报 `returned 22`,**
**而 `80:45:flags=lanczos` 与 `w=80:h=45:flags=lanczos` 都能跑。**
**因为前面一旦出现过 `名字=值`,解析器就认定后面全是这种配对格式,**
**裸的 80 与 45 既没有名字也没有等号,只能当成无效参数丢掉。**
**⑤ 这一节收个尾**
**scale 看着简单,其实一半的坑都在「尺寸必须是偶数」和「比例算不整」上。**
**记住三条就够用:等比用 -2、塞框用 decrease 加 pad、要锐利用 lanczos。**
第一段三行:不给与给 4 的高都是 360(360 已能被 4 整除),给 16 变成 368。 这也顺带证明了方向:是往上取到 16 的倍数,不是往下压。
第二段四行是 eval 的四种写法,第三、四行给出 [80, 80, 80] 与 [84, 88, 92]。
这两个列表就是「每帧重算」的直接证据。
第三段三行里第一行报错、后两行通过。 所以写 scale 时要么全位置、要么全命名,混着写就踩这个坑。
30 秒实验
- 量高度:把第一段改成打印高度,看 360 与 368 的差。
- 换整除:试
force_divisible_by=32,看还往不往上取。 - 去掉 eval:把第四行的
eval=frame删掉,看报的错是不是一样。
常见误区
- 以为 force_divisible_by 是往下压。它往上取,360 变 368。
- 以为 eval=init 也能用 n。init 时还没有帧,
n直接报错。 - 混写位置参数与命名参数。解析器会丢掉裸数字,报一句笼统的 22。
自测三问(点开对答案)
1. force_divisible_by=16 时 360 变成多少? —— 368,往上取到 16 的倍数。 2. 为什么 `80+n*0` 不写 eval 会报错? —— 默认 eval=init,求值时还没有帧号 `n`。 3. `flags=lanczos:80:45` 为什么失败? —— 出现过命名参数后,解析器不再接受裸的位置参数。- ☐ 7.2 完,下一节 7.3.1 视频倍速