本机六种 overlay 建图写法全部失败,报的都是含义含糊的 returned 22。 命令行那一侧照旧能写能用,公式也已经用两条独立实现验过。
上一节把公式讲清楚了,这一节讲「怎么把两路接起来」。 接法在命令行里很成熟,在 PyAV 这一层却有坑。
一、六种写法,全部失败
建图写法 结果 说明
两个 buffer + pad 号 returned 22 最常见的写法 两个 buffer + pad 名 an integer pad 名不是整数 rgb24 主 + rgba 副 returned 22 换格式也不行 用 template 建 buffer returned 22 换建法也不行 只接一路输入 returned 22 缺一路也报同一个错 换 hstack 双输入 returned 22 别的双输入滤镜一样
连「只接一路」都失败,说明这与参数写法无关—— 是这份 PyAV 与 libavfilter 之间的对接层,压根没把多输入接通。
returned 22 是 EINVAL,意思是「参数无效」。
它不告诉你是哪个参数无效,所以这类错误只能靠「换写法逐个排除」来定位。
二、换成手算,结果仍然对得上
把水印按公式贴上去,再让 Pillow 独立合成一次:
算法 平均误差 最大误差
手算 alpha 混合 0.00 0
两条独立实现的结果完全一致。 这说明「公式没算错」这件事已经验过了,剩下的只是「怎么把两路接起来」。
把「原理」与「接口」分开验,是本课一直在用的办法: 接口跑不通的时候,至少原理那一半是确定的,不会两头都糊涂。
三、命令行怎么写
ffmpeg -i in.mp4 -i logo.png -filter_complex "[0:v][1:v]overlay=10:10" out.mp4
[0:v] 是第一个输入的视频流,[1:v] 是第二个。
顺序就是 overlay 的「主画面」与「叠上去的图层」,写反了水印会盖住整个画面。
一定要加 shortest=1。
水印图 loop 起来是无限长,主视频放完它还在出帧,转码就卡在那儿不动了。
shortest=1 让输出跟着短的那一路结束,这是带图片水印的命令里最容易漏的一个参数。
四、动手 Demo:六种建图写法与一次手算
先用六种方式尝试建 overlay 的双输入图,逐个记录报错。 再用 numpy 手算一遍 alpha 混合,与 Pillow 的结果比对。
import av, numpy as np
from fractions import Fraction
from PIL import Image, ImageDraw, ImageFont
W, H = 160, 90
TB = Fraction(1, 25)
def attempt(fn):
try:
fn()
return u"成功"
except Exception as e:
s = str(e)
return u"returned 22" if u"returned 22" in s else s[:20]
def v1():
g = av.filter.Graph()
a = g.add_buffer(width=W, height=H, format=u"yuv420p", time_base=TB)
b = g.add_buffer(width=40, height=20, format=u"yuva420p", time_base=TB)
ov = g.add(u"overlay", u"x=10:y=10")
s = g.add(u"buffersink")
a.link_to(ov, 0)
b.link_to(ov, 1)
ov.link_to(s)
g.configure()
def v2():
g = av.filter.Graph()
a = g.add_buffer(template=av.VideoFrame(W, H, format=u"yuv420p"),
time_base=TB)
b = g.add_buffer(template=av.VideoFrame(40, 20, format=u"yuva420p"),
time_base=TB)
ov = g.add(u"overlay", u"10:10")
s = g.add(u"buffersink")
a.link_to(ov, 0)
b.link_to(ov, 1)
ov.link_to(s)
g.configure()
print(u"pad 号 %s" % attempt(v1))
print(u"template %s" % attempt(v2))
f = ImageFont.truetype(u"C:/Windows/Fonts/msyh.ttc", 24)
im = Image.new(u"RGBA", (56, 31), (0, 0, 0, 0))
ImageDraw.Draw(im).text((4, 4), u"水印", font=f, fill=(255, 255, 255, 255))
bg = np.full((H, W, 3), 100, dtype=np.uint8)
x, y = W - 56 - 8, H - 31 - 8
a8 = np.array(im)[:, :, 3:4].astype(np.float64) / 255.0
fg = np.array(im)[:, :, :3].astype(np.float64)
reg = bg[y:y + 31, x:x + 56].astype(np.float64)
manual = np.clip(fg * a8 + reg * (1 - a8) + 0.5, 0, 255)
comp = np.array(Image.alpha_composite(
Image.fromarray(bg).convert(u"RGBA").crop((x, y, x + 56, y + 31)), im)
.convert(u"RGB"))
d = np.abs(manual.astype(np.int32) - comp.astype(np.int32))
print(u"手算 vs PIL 平均 %.2f 最大 %d" % (d.mean(), d.max()))
实跑输出:
--------------------------------------------------------------------
6.5.2 -filter_complex overlay 添加水印——建图为什么失败
--------------------------------------------------------------------
**① 六种建图写法,全部试一遍**
建图写法 结果 说明
-------------------------------------------------------------------------------------
两个 buffer + pad 号 失败(returned 22) 双输入在这台机器上建不起来
两个 buffer + pad 名 失败(an integer is re) pad 名不是整数,接口不认
rgb24 主 + rgba 副 失败(returned 22) 双输入在这台机器上建不起来
用 template 建 buffer 失败(returned 22) 双输入在这台机器上建不起来
只接一路输入 失败(returned 22) 双输入在这台机器上建不起来
换 hstack 双输入 失败(returned 22) 双输入在这台机器上建不起来
**六种写法全部失败:五种是 `returned 22`,一种栽在 pad 名上。**
**连「只接一路输入」都不行——这说明报错与参数写法无关,**
**而是这份 PyAV 与 libavfilter 之间的对接层,压根没把多输入这条路接通。**
**所以这一节只能改成手算,但命令行那一侧照旧能写、能用。**
**② 换成手算,结果与公式一致**
算法 平均误差 最大误差
---------------------------------------------
手算 alpha 混合 0.00 0
**两条独立实现的结果最大只差 0,说明手算这一版就是 overlay 干的事。**
**③ 命令行怎么写**
**`ffmpeg -i in.mp4 -i logo.png -filter_complex "[0:v][1:v]overlay=10:10" out.mp4`**
**`[0:v]` 是第一个输入的视频流,`[1:v]` 是第二个——**
**顺序就是 overlay 的「主画面」与「叠上去的图层」,写反了水印会盖住整个画面。**
**`overlay` 后面还能跟 `shortest=1`,让它跟着短的那一路结束,**
**否则水印图 loop 起来无限长,主视频放完它还在出帧,转码就卡住了。**
attempt 只用两种结果描述:returned 22 或别的。
把报错分类,比打印一长串更有用——一眼就能看出「是不是同一个原因」。
av.filter.Graph 里 link_to(ov, 0) 的第二个参数是目标 pad 号。
不传的话默认连第一个 pad,两路都往 0 上连,配流必然失败。
Pillow 的 alpha_composite 要求两图尺寸一致,所以先 crop 出对应区域。
直接整图合成再裁,结果一样,只是多算了些像素。
30 秒实验
- 换滤镜:把 overlay 换成
hstack,看双输入是不是一样报错。 - 命令行验证:找一台有 ffmpeg 的机器,跑一遍
[0:v][1:v]overlay。 - 加 shortest:把
shortest=1去掉,观察转码是不是卡住不出结果。
常见误区
- 以为 returned 22 是参数写错。它只是 EINVAL,不指到底是哪个参数。
- 两路都 link_to(pad 0)。第二路把第一路挤掉,配流失败。
- 忘了 shortest=1。水印无限循环,主视频放完后转码卡住。
自测三问(点开对答案)
1. returned 22 是什么错误? —— EINVAL,参数无效。它不指明具体哪一项,只能靠换写法排除。 2. `[0:v][1:v]` 的顺序意味着什么? —— 前者是主画面,后者是叠上去的图层,写反了水印会盖住整个画面。 3. 为什么一定要 shortest=1? —— 水印 loop 后无限长,不加这个,输出不会跟着主视频结束。- ☐ (本节完,下一节 6.6.1 -vf 的 drawtext 添加中文水印)