2. FFmpeg基础回顾
好,咱们进入第二章。上一章我们把滤镜的基本概念捋了一遍,这一章要聊的,是FFmpeg滤镜体系里最核心、也最容易让人懵圈的东西——滤镜图语法。
说白了,就是怎么用一串字符串,把多个滤镜串起来、并联起来,组成一个处理流水线。我当年刚接触这个的时候,看着那一堆分号逗号,心里直犯嘀咕:这玩意儿到底是啥?后来在项目里被坑过几次,才真正搞明白。今天咱们就把这事彻底说透。
2.1 滤镜链:最简单的串联方式
先看最基础的情况。你只有一个滤镜,比如做缩放:
ffmpeg -i input.mp4 -vf "scale=1280:720" output.mp4
这没什么好说的。但如果你要先缩放,再转成灰度呢?
这时候就需要滤镜链了。多个滤镜用逗号 , 连接,前一个的输出自动成为后一个的输入。
ffmpeg -i input.mp4 -vf "scale=1280:720,format=gray" output.mp4
注意这个逗号。它的作用就是串联——数据从左往右流。scale处理完,交给format。
再复杂一点,三个滤镜串联:
ffmpeg -i input.mp4 -vf "scale=1280:720,format=gray,eq=brightness=0.1" output.mp4
顺序是:缩放 → 转灰度 → 调亮度。每个滤镜只处理上一级的结果。
2.2 滤镜图:分号带来的并联世界
单条链子好理解。但实际场景里,你经常需要同时处理多个输入,或者把一个输出分给多个滤镜。这时候逗号就不够用了。
分号 ; 登场。它的作用是分隔不同的滤镜链,或者说,分隔不同的处理分支。
看个经典例子——画中画(PIP):
ffmpeg -i main.mp4 -i overlay.mp4 -filter_complex \
"[0:v]scale=1280:720[main]; \
[1:v]scale=320:240[overlay]; \
[main][overlay]overlay=10:10[out]" \
-map "[out]" output.mp4
这里有三条链,用分号隔开:
- 第一条:处理主视频,缩放后存为标签
[main] - 第二条:处理叠加视频,缩放后存为标签
[overlay] - 第三条:把两个标签喂给 overlay 滤镜,合成最终结果
分号的作用,就是让这些链并行存在,互不干扰。每条链可以有自己的输入源、自己的处理逻辑。
,(逗号)= 串联,数据流在同一链内传递;(分号)= 并联,分隔不同的链或分支
2.3 标签系统:给滤镜链起名字
刚才的例子里出现了 [main]、[overlay]、[out] 这样的东西。这就是标签(label)。
标签的作用是给某个处理阶段的输出命名,方便后面引用。没有标签,后面的滤镜就不知道从哪取数据。
标签的语法很简单:[名字]。名字随便起,但建议有意义。
看这个例子:
ffmpeg -i input.mp4 -filter_complex \
"[0:v]split[base][enhanced]; \
[base]eq=brightness=0.0[base_out]; \
[enhanced]eq=brightness=0.2,unsharp=5:5:1.0[enhanced_out]; \
[base_out][enhanced_out]hstack[out]" \
-map "[out]" output.mp4
这里用到了 split 滤镜,它把一路视频分成两路。两路分别做不同的处理,最后用 hstack 并排显示。
标签就是管道上的阀门,你可以在任意位置接出来,也可以往任意位置送进去。
[my filter],结果FFmpeg直接报错。老老实实用下划线或驼峰:[my_filter] 或 [myFilter]。
2.4 输入输出索引:0:v 是什么意思?
刚才的例子里有 [0:v] 和 [1:v]。这是FFmpeg引用输入流的方式。
0表示第一个输入文件1表示第二个输入文件v表示视频流(video)a表示音频流(audio)
所以 [0:v] 就是「第一个输入文件的视频流」。
如果你有多个视频流,还可以用 [0:v:0]、[0:v:1] 这样更精确的写法。不过日常用 [0:v] 就够了。
| 写法 | 含义 |
|---|---|
[0:v] |
第1个输入文件的视频流 |
[1:a] |
第2个输入文件的音频流 |
[0:v:0] |
第1个输入文件的第1个视频流 |
[0:v:1] |
第1个输入文件的第2个视频流 |
2.5 滤镜图的分区:把复杂逻辑拆开看
当滤镜图变得很复杂时,我习惯把它分区理解。每个分区完成一个独立的功能。
举个例子,一个常见的视频处理流程:
ffmpeg -i input.mp4 -filter_complex \
"[0:v]split=3[bg][fg1][fg2]; \
[bg]boxblur=10:5[bg_blur]; \
[fg1]crop=iw/2:ih:0:0,scale=640:480[fg1_scaled]; \
[fg2]crop=iw/2:ih:iw/2:0,scale=640:480[fg2_scaled]; \
[bg_blur][fg1_scaled]overlay=0:0[tmp]; \
[tmp][fg2_scaled]overlay=640:0[out]" \
-map "[out]" output.mp4
这个图可以分成三个区:
- 输入分区:
split把原视频分成三路 - 处理分区:一路做模糊背景,两路分别裁剪并缩放
- 合成分区:用两次 overlay 把两路前景叠到背景上
分区的好处是:出了问题,你能快速定位是哪一段的锅。我在项目里调试复杂滤镜图时,经常先跑通每个分区,再连起来。
2.6 核心知识图谱
下面这张图,把滤镜图语法的核心关系画出来了。建议你多看两眼,后面写复杂滤镜图的时候,脑子里有这个结构就不会乱。
2.7 实战:从简单到复杂的写法演进
咱们用同一个需求,看看写法怎么一步步变复杂。
需求:把视频缩放成1280x720,加上文字水印「Hello」,再调亮一点。
版本1:一条链搞定
ffmpeg -i input.mp4 -vf "scale=1280:720,drawtext=text='Hello':fontsize=48,eq=brightness=0.1" output.mp4
三个滤镜用逗号串起来,简单直接。但有个问题:如果我想让文字水印在缩放之前加上去,效果会不一样(文字会被缩放影响)。
版本2:用标签控制顺序
ffmpeg -i input.mp4 -filter_complex \
"[0:v]drawtext=text='Hello':fontsize=48[texted]; \
[texted]scale=1280:720,eq=brightness=0.1[out]" \
-map "[out]" output.mp4
先加文字,再缩放。文字不会被缩放影响,保持清晰。
版本3:多输入场景
ffmpeg -i bg.mp4 -i logo.png -filter_complex \
"[0:v]scale=1280:720[bg]; \
[1:v]scale=200:100[logo]; \
[bg][logo]overlay=W-w-10:10[out]" \
-map "[out]" output.mp4
两个输入,一个背景视频,一个Logo图片。各自处理完再合成。
2.8 常见错误与避坑
最后聊几个我实际踩过的坑,你遇到了能少走弯路。
分号是链之间的分隔符,不是滤镜之间的。写
scale=1280:720;format=gray 会报错,因为分号不能代替逗号。
同一个滤镜图里,标签名必须唯一。我试过两个链都输出
[out],结果后面的覆盖了前面的,合成时拿到的数据不对。
用了
-filter_complex 之后,输出必须用 -map "[标签名]" 指定。我刚开始经常忘了写,结果FFmpeg报「Output file does not contain any stream」。