之前那篇 FFmpeg 入门里我主要讲了转码、切割和加水印。但这半年我陆陆续续用 FFmpeg 处理了更多需求:给视频加片头、画中画、抽帧做缩略图、甚至把音频频谱可视化。这些全靠 FFmpeg 的滤镜链(filtergraph)。这篇文章把我常用的几个滤镜和踩过的坑集中记录下来。
用滤镜之前先说一个原则:滤镜会重新编码,速度比 -c copy 慢很多。如果只是切头切尾或者改封装格式,别用滤镜。但如果你要缩放、旋转、加文字、叠加视频,那滤镜是你的唯一选择。
一、滤镜的基本写法
FFmpeg 的滤镜用 -vf(视频滤镜)或 -af(音频滤镜)参数。多个滤镜用逗号分隔,串联执行。
最简单的例子:把视频缩小一半
ffmpeg -i input.mp4 -vf "scale=iw/2:ih/2" output.mp4
iw 和 ih 是内置变量,分别代表输入宽度和高度。
如果要同时缩放并旋转 90 度:
ffmpeg -i input.mp4 -vf "scale=640:480,transpose=1" output.mp4
滤镜顺序很重要:先缩放再旋转比先旋转再缩放的计算量大不一样。
二、安装(复用之前的就行)
FFmpeg 的安装在第一篇博客里已经详细写过。只要 FFmpeg 编译时带 --enable-libx264 和滤镜支持(默认都带),就可以直接用。
验证滤镜是否可用:
ffmpeg -filters | grep "scale"
能看到 scale、crop、overlay 等就 OK。
三、快速测试:用滤镜做第一个裁剪
ffmpeg -i input.mp4 -vf "crop=640:360:100:50" cropped.mp4
这个命令从原视频的 (100,50) 坐标处,裁出一个 640x360 的区域。如果裁剪区域超出原视频边界,FFmpeg 会报错 Invalid too big。
四、常用视频滤镜参数(我工作中经常用)
4.1 缩放(scale)
# 固定宽高,强制拉伸(会变形)
-vf "scale=1280:720"
# 按宽度缩放,高度自动等比
-vf "scale=1280:-1"
# 高度自动,但确保宽度是偶数(避免编码报错)
-vf "scale=1280:trunc(ow/a/2)*2"
# 限制最大宽高,等比缩放放入
-vf "scale='min(1280,iw)':'min(720,ih)'"
踩坑:-2 和 -1 的区别。-1 让 FFmpeg 自动计算比例,但结果可能是奇数,某些编码器(如 libx264)要求宽高是偶数。用 trunc(ow/a/2)*2 可以强制偶数。
4.2 裁剪(crop)
# 语法:crop=宽:高:X:Y
-vf "crop=960:540:200:100"
# 居中裁剪到 1280x720
-vf "crop=1280:720:(iw-1280)/2:(ih-720)/2"
4.3 旋转(transpose / rotate)
# transpose=0 顺时针90度,=1 逆时针90度,=2 翻转,=3 逆时针90度+翻转
-vf "transpose=1"
# 任意角度旋转,需要 fillcolor 填充背景
-vf "rotate=45*PI/180:fillcolor=black"
踩坑:rotate 滤镜性能很差,转大视频会很慢。能转 90 度的倍数尽量用 transpose。
4.4 画中画(overlay)
把另一个视频/图片叠加到主视频上:
ffmpeg -i main.mp4 -i logo.png -filter_complex "overlay=10:10" output.mp4
注意:overlay 通常用在 -filter_complex 而不是 -vf,因为涉及两个输入。
想让画中画动起来(比如从左上角移动到右下角):
ffmpeg -i main.mp4 -i sub.mp4 -filter_complex "overlay=x='if(lte(t,2),10,10+(t-2)*50)':y=10" output.mp4
4.5 文本水印(drawtext)
需要编译时带 --enable-libfreetype。大多数完整版都支持。
ffmpeg -i input.mp4 -vf "drawtext=text='Hello World':x=100:y=100:fontsize=30:fontcolor=white" output.mp4
更实用的:显示当前时间戳
drawtext=text='%{pts\:hms}':x=10:y=10:fontsize=20
踩坑:drawtext 里的特殊字符需要转义,比如 % 要写成 \%。路径里的冒号要用 \: 转义。
五、实战:几个我每天都在用的滤镜命令
- 批量把 4K 视频缩放成 1080p(保持比例)
for f in *.mp4; do
ffmpeg -i "$f" -vf "scale=1920:-2" -c:a copy "${f%.mp4}_1080p.mp4"
done
音频直接复制 -c:a copy 加速处理。
2. 去掉视频前后的黑边(自动裁剪)
ffmpeg -i input.mp4 -vf "crop=iw:ih,blackdetect=d=0.2" -f null -
这个不会直接输出,而是检测黑边位置。更直接的做法是用 cropdetect 自动参数:
ffmpeg -i input.mp4 -vf "cropdetect=limit=30" -f null - 2>&1 | grep crop
输出类似 crop=640:360:0:40,再拿去裁剪。
3. 把两张视频左右拼接(水平排列)
ffmpeg -i left.mp4 -i right.mp4 -filter_complex "hstack" output.mp4
上下拼接用 vstack。两个视频的高度必须一致(对于 hstack),否则会报错。可以先缩放对齐:
ffmpeg -i left.mp4 -i right.mp4 -filter_complex "[0:v]scale=640:360[a];[1:v]scale=640:360[b];[a][b]hstack" output.mp4
- 给视频加上一个渐变淡入淡出
ffmpeg -i input.mp4 -vf "fade=t=in:st=0:d=1,fade=t=out:st=30:d=2" output.mp4
- 从视频中提取一帧作为封面(精确到第 10 秒)
ffmpeg -i input.mp4 -ss 10 -vframes 1 -vf "scale=1280:-2" cover.jpg
-vframes 1 表示只取一帧。-ss 放在 -i 前面速度更快但不精确,放在后面精确但慢。我一般放在前面做大致定位,再调整。
6. 制作视频的 GIF 动图(带调色板优化)
# 第一步:生成调色板
ffmpeg -i input.mp4 -vf "fps=10,scale=320:-1:flags=lanczos,palettegen" palette.png
# 第二步:用调色板生成 GIF
ffmpeg -i input.mp4 -i palette.png -filter_complex "fps=10,scale=320:-1[x];[x][1:v]paletteuse" output.gif
不用调色板直接转 GIF 会非常糊。这两步是标准做法。
六、问题汇总(全都是我的血泪史)
-
Filter scale has unconnected output
原因:滤镜链写错了,比如多个滤镜之间没有用逗号或分号关联。
解决:检查 -vf 或 -filter_complex 的语法,每个滤镜输出要连接到下一个的输入。 -
画中画 overlay 时,小视频黑边严重
场景:小视频分辨率跟主视频不一样,叠加上去周围是黑色。
解决:在 overlay 之前先 scale 小视频,或者设置透明背景。更好的办法:
ffmpeg -i main.mp4 -i sub.mp4 -filter_complex "[1:v]scale=iw/2:ih/2,format=rgba,colorchannelmixer=aa=0.8[sub];[0:v][sub]overlay=10:10" output.mp4
- drawtext 中文乱码或显示方框
原因:系统缺中文字体,或者没指定字体文件。
解决:下载一个中文字体(如 NotoSansCJK-Regular.ttc),然后用 fontfile 参数:
-vf "drawtext=text='你好':fontfile=/path/to/font.ttc:fontsize=30:fontcolor=white"
-
滤镜链太长,报 Error: Too many packets 或内存爆炸
原因:复杂的滤镜链(比如同时缩放、叠加、旋转、加水印)会消耗大量内存。
解决:拆成两步。第一步先缩放和裁剪,输出中间文件;第二步再加水印。牺牲一点时间换稳定性。 -
frame size not set 错误
场景:用 overlay 时没有指定主视频的分辨率。
解决:在滤镜链里先用 scale 强制设置主视频尺寸,或者用 setsar 设置 SAR。 -
旋转视频后,播放器显示的方向还是原来
原因:旋转只改了像素数据,没有改变 metadata 中的旋转标记(比如手机录像的 rotate tag)。
解决:加 -metadata:s:v rotate=0 清除旋转标记:
ffmpeg -i input.mp4 -vf "transpose=1" -metadata:s:v rotate=0 output.mp4
- 用 hstack 拼接时,两个视频的帧率不同步
原因:左右视频的帧率、时长不一样。
解决:用 fps 过滤器统一帧率,再用 setpts 对齐时间:
ffmpeg -i left.mp4 -i right.mp4 -filter_complex "[0:v]fps=25,setpts=PTS-STARTPTS[left];[1:v]fps=25,setpts=PTS-STARTPTS[right];[left][right]hstack" output.mp4
七、一点关于滤镜的建议
1、复杂的滤镜链(超过三个滤镜)先在 -t 10 截一小段视频上测试,通过后再跑全量。不然等一小时发现失败很崩溃。
2、使用 -filter_complex 时,一定要理解 [0:v]、[1:a] 这些标签的含义。0:v 代表第一个输入的视频流,1:a 代表第二个输入的音频流。
3、如果只是想快速查看滤镜效果,可以用 ffplay 实时预览:
ffplay -i input.mp4 -vf "crop=640:360"
4、保存常用的滤镜组合为脚本。我有一个 ~/bin/ffmpeg-pip.sh,专门做画中画,省得每次敲 20 个参数。
写这篇的时候我又翻车了一次:想给视频加一个半透明的水印,结果 overlay 之后水印完全不透明,查了半天才发现 PNG 的透明通道丢了,要加 format=rgba。记在这里,下次自己也能找到。
滤镜是 FFmpeg 最强的部分,也是文档最晦涩的部分。多用 ffmpeg -h filter=scale 查看具体参数的帮助,比上网搜快多了。