FFmpeg filter graph 入门:从 scale、fps、overlay 到完整滤镜链
前言:为什么很多人会用 FFmpeg 命令,但一到滤镜链就开始发虚
学 FFmpeg 的过程中,很多人会先学会这些事:
- 转码
- 推流
- 抽帧
- 改码率
- 改封装
这时候你会觉得自己已经能“用 FFmpeg 干活了”。
但只要需求再往前走一步,比如想做:
- 缩放
- 裁剪
- 改帧率
- 加水印
- 叠字
- 多路画面拼接
- 预处理后再编码
你几乎一定会撞上一个核心概念:
filter graph。
也是从这里开始,很多人会突然觉得 FFmpeg 变复杂了。
因为之前的命令大多还像“改参数”,而 filter graph 已经明显开始进入:
处理链路编排。
这也是为什么很多人虽然会写一些简单命令,但一旦看到下面这种东西,就会开始发虚:
1 | |
甚至更复杂一点:
1 | |
真正难的地方不是某个滤镜的参数,而是:
- 数据到底在什么时候变成什么样子
- 为什么 filter 操作几乎都发生在 frame 层
- 多个滤镜串起来之后,链路到底怎么流
- simple filter 和 complex filter 差在哪
- 一个系统里的预处理、叠加、缩放、转格式为什么最终都能被统一成一张 graph
所以这篇文章的目标,不是只给你列几个常见滤镜,而是想把 filter graph 讲成一个更完整的工程概念。
重点讲清:
- FFmpeg filter graph 到底是什么
- 它在整条多媒体处理链路里的位置在哪
- 为什么它本质上是一张“frame 处理图”
- scale、fps、overlay 这些滤镜在 graph 里分别扮演什么角色
- 如果让你自己写一个最小滤镜链,应该怎么理解它
而且这篇我会继续按你的要求,重点补:
- 结构图
- 时序图
- FFmpeg CLI 实例
- libavfilter 关键代码骨架
我希望这篇读完之后,你不只是“会抄几条滤镜命令”,而是真的开始把它看成系统里的一个处理层。
一、先给一句最重要的话:filter graph 本质上是一张“原始帧处理流水线图”
如果让我先用一句话概括 FFmpeg 的 filter graph,我会这样说:
filter graph 本质上是一张围绕原始 frame 展开的处理流水线图。
这句话特别重要。
因为它直接告诉你三件事:
- 它是 graph,不是单点命令
- 它处理的核心对象通常是 frame,不是 packet
- 它的作用是把多个处理步骤按图结构串起来
所以你以后看到 filter graph,不要先把它当成“命令行技巧”,而应该先把它理解成:
FFmpeg 里的媒体处理编排层。
二、先看全局:filter graph 在整条链路里到底处于哪一层
先上总图。
flowchart LR
A[输入源] --> B[Demux]
B --> C[Decode]
C --> D[原始 Frame]
D --> E[Filter Graph]
E --> F[处理后 Frame]
F --> G[Encode]
G --> H[Mux / 输出]
这张图最关键的意义是:
filter graph 基本上是夹在 decode 和 encode 之间的一层。
也就是说,在最典型的转码 / 处理链路里:
- 前面先把压缩数据解成原始 frame
- 然后在 frame 层做各种处理
- 最后再把处理后的 frame 送回编码器
这就是为什么我前面一直强调:
filter graph 更像内容处理层,而不是码流处理层。
三、为什么 filter graph 主要处理的是 frame,而不是 packet
这是理解它最关键的第一步。
3.1 packet 更像压缩域数据
packet 通常更接近:
- H.264/H.265 压缩码流片段
- AAC 压缩音频数据
- 容器拆出来的数据包
在这个层面,你更容易做的是:
- 解封装
- 封装
- 码流转发
- bitstream 级别处理
但你很难直接在 packet 层做这些:
- 缩放
- 裁剪
- 加字
- 叠图
- 调整帧率
因为内容还没真正展开。
3.2 frame 更像可加工内容
而 frame 是:
- YUV / RGB 图像帧
- PCM / 音频采样帧
这时候数据已经变成“可操作内容”。
所以像这些动作:
scalefpsoverlaydrawtextcrop
都天然更适合发生在 frame 层。
3.3 一句话记忆
- packet 更偏传输 / 压缩层
- frame 更偏内容处理层
而 filter graph,本质上站在后者这一边。
四、一张图看懂 filter graph 为什么天然适合夹在 decode 和 encode 之间
flowchart TD
A[压缩 packet] --> B[解码]
B --> C[原始 frame]
C --> D1[scale]
D1 --> D2[fps]
D2 --> D3[overlay]
D3 --> E[处理后 frame]
E --> F[编码]
这张图很重要,因为它让你看到:
- 过滤链路更像 frame 上的连续加工
- 它不是“顺手做点小操作”,而是可以成为系统主链路的一部分
所以从工程角度,filter graph 其实是在提供一种能力:
把图像/音频处理步骤可视化地串成一条处理链。
五、什么叫 graph,为什么它不只是“一个个滤镜按顺序排”
很多人刚学时,会把 filter graph 理解成:
不就是 scale 后面接 fps,再后面接 overlay 吗?
这当然是 graph 的一种最简单形式,但它还不够。
因为 graph 的真正含义是:
它不只能线性串联,还能分支、汇合、多输入、多输出。
5.1 最简单的是链式结构
比如:
1 | |
5.2 更复杂的是图结构
比如:
- 一个视频流进来,拆成两路分别处理
- 再和 logo 图片汇合做 overlay
- 再输出给编码器
这时候它就不再是简单“命令列表”,而真的是一张 graph。
5.3 一张结构图看懂 graph 的含义
flowchart LR
A[输入视频] --> B[scale]
C[logo图片] --> D[scale logo]
B --> E[overlay]
D --> E
E --> F[输出视频]
这张图已经明显不是一条单线,而是:
- 两路输入
- 一个汇合点
- 一路输出
这就是“graph”真正比“filter list”更值钱的地方。
六、最常见的三个滤镜,为什么特别值得先搞懂:scale、fps、overlay
如果你想快速建立 filter graph 直觉,我非常建议先抓住这三个。
6.1 scale:处理空间尺寸
scale 本质上是在做:
- 分辨率变换
- 缩小 / 放大
- 为后续编码或显示做适配
它很常见,因为链路里不同阶段常常要求不同尺寸。
6.2 fps:处理时间节奏
fps 本质上是在做:
- 帧率调整
- 补 / 丢帧
- 让输出符合目标帧率要求
它特别适合帮助理解:
filter graph 不只处理空间,还能处理时间上的采样节奏。
6.3 overlay:处理多路图像合成
overlay 本质上是在做:
- 一路主画面
- 叠一路次级图像
比如:
- logo
- 水印
- OSD
- 小窗画中画
它特别适合帮助你理解:
filter graph 不是只能单输入单输出,它可以接多路输入再合成。
七、一张图把 scale / fps / overlay 的角色分开看清楚
flowchart TD
A[原始视频帧] --> B[scale]
B --> C[fps]
C --> D[overlay]
E[logo/图片] --> D
D --> F[处理后帧]
这张图非常适合你后面反复回忆,因为它直接对应三类不同处理:
scale主要改空间尺寸fps主要改时间采样节奏overlay主要改图像组合关系
也就是说,filter graph 不是“一个功能点”,而是在统一承载各种 frame 处理需求。
八、最简单的 CLI 例子:先从一条线性的 filter chain 开始
如果你只是想最直观地感受它,先看一条最简单链:
1 | |
这条命令背后的 graph 可以理解成:
1 | |
这里的 -vf 本质上就是:
- video filter 的简写入口
- 适合单输入、相对简单的线性处理链
这个阶段你可以先把它理解成“simple graph”。
九、再看一个更有画面感的 overlay 例子
比如你想给视频叠一个 logo:
1 | |
这条命令为什么一下子复杂很多?
因为这里已经不是一条简单链,而是一张真正的 graph:
- 主视频一路处理
- logo 一路处理
- 两路汇合 overlay
- 再输出
9.1 对应结构图
flowchart LR
A[input.mp4] --> B[scale 主视频]
C[logo.png] --> D[scale logo]
B --> E[overlay]
D --> E
E --> F[输出 out]
这就是 -filter_complex 的典型使用场景。
十、simple filter 和 complex filter 到底差在哪
这个问题特别常见。
10.1 simple filter 更适合什么
例如:
1 | |
适合:
- 单输入视频流
- 相对线性的滤镜链
- 不需要多路输入输出管理
10.2 complex filter 更适合什么
例如:
- 多输入
- 多输出
- 分支和汇合
- overlay / concat / split 这类图结构
所以你可以粗记:
-vf/-af更像简单链式入口-filter_complex更像真正 graph 编排入口
10.3 一句话记忆
simple filter 更像一条管道,complex filter 更像一张路网。
十一、在 C/C++ 里,filter graph 的基本工程骨架长什么样
如果你不是只用命令行,而是想在代码里接入 libavfilter,最常见的骨架大体是:
- 创建 graph
- 创建 buffer source
- 创建若干 filter 节点
- 创建 buffer sink
- 把节点连起来
- 配置 graph
- 把 frame 推进去,再把处理后 frame 拉出来
这听起来很多,但结构其实很清楚。
十二、一段最小 libavfilter 骨架代码
下面这段代码不是完整生产代码,但足够帮你先建立“图是怎么搭起来的”的感觉。
1 | |
这段代码最值钱的不是 API 名字,而是它体现出:
- 你真的在创建“节点”
- 你真的在“连边”
- 最后真的是一张 graph 被配置好
所以 filter graph 不是比喻,它在实现层也真的很像一张图。
十三、frame 是怎么在 graph 里流动的
配完 graph 之后,核心操作就是:
- 把原始 frame 塞进 source
- 从 sink 取出处理后的 frame
13.1 典型流程代码
1 | |
这段代码背后其实非常像我们前面讲过的 decode / encode send-receive 模型。
它说明:
graph 本质上是一层 frame in / frame out 的处理系统。
十四、一张时序图看懂 frame 在 filter graph 里的流动
sequenceDiagram
participant Dec as Decoder
participant Src as BufferSrc
participant Graph as Filter Graph
participant Sink as BufferSink
participant Enc as Encoder
Dec->>Src: 输入原始 frame
Src->>Graph: frame 进入 graph
Graph->>Sink: 输出处理后 frame
Sink->>Enc: 送编码器继续处理
这张图很重要,因为它把 filter graph 在系统里的位置完全接起来了:
- 前面连解码器
- 后面连编码器
- 中间处理 frame
到这里,你应该能很明确地看到它在整个多媒体系统里的职责。
十五、filter graph 为什么特别适合做“工程中的预处理层”
这也是我非常想强调的一点。
在真实系统里,filter graph 经常不只是一个“命令行功能”,而是一层真正的工程处理层。
它特别适合放这些事情:
- 编码前统一缩放
- 拉齐目标帧率
- 加 logo / 水印 / OSD
- 预览和推流共用某些处理逻辑
- 图像叠加与多路拼接
也就是说,在工程里你完全可以把它理解成:
一层专门负责 frame 加工和编排的中间层。
这也是为什么它在实际项目里非常值钱。
十六、一个更接近真实项目的链路图
flowchart LR
A[Camera / 文件 / 网络流] --> B[Decode]
B --> C[Filter Graph]
C --> D1[预览输出]
C --> D2[编码推流]
C --> D3[抓图/截图]
这张图特别有工程感,因为它说明一件事:
同一层 filter 处理结果,可以同时服务多个后续目标。
这时候你会发现,filter graph 已经不只是“转码小工具”,而是在系统里承担真正的中间处理枢纽作用。
十七、最容易踩的几个坑
17.1 坑一:忘了 filter graph 处理的是 frame,不是 packet
这会导致很多理解上的偏差。
比如你想在没解码前直接做 scale,那基本方向就错了。
17.2 坑二:多输入时 graph 标签没理清
一旦进入 filter_complex,标签一乱,整个 graph 就容易写崩。
所以最好养成习惯:
- 每一路输入先命名
- 每个关键中间结果显式标 label
- 最后明确输出 label
17.3 坑三:格式不匹配导致 graph 前后都在偷偷转格式
比如:
- 前面 decoder 给的是 NV12
- 某个滤镜更偏某种格式
- 后面 encoder 又要 YUV420P
这时 graph 里可能发生额外格式转换。
如果你不关心,就容易损失性能。
17.4 坑四:以为滤镜只是“功能正确”就够了
不是。
在工程里你还要关心:
- 性能
- copy 次数
- 延迟
- 是否能走硬件路径
十八、面试里怎么讲,才不像只会抄 FFmpeg 命令
如果面试官问:
你怎么理解 FFmpeg 的 filter graph?
我建议你按下面这个结构讲。
18.1 先讲本质
filter graph 本质上是一张围绕原始 frame 展开的处理图,它通常位于 decode 和 encode 之间,用来把缩放、裁剪、帧率调整、叠加、水印等操作编排成一条可执行处理链。
18.2 再讲为什么是 graph
因为它不只是线性串联,还支持多输入、多输出、分支和汇合,所以在复杂场景里更像一张媒体处理图,而不是简单的命令列表。
18.3 最后讲工程意义
在工程里,filter graph 可以被看成一层 frame 预处理 / 加工层,它常常承担编码前图像处理、推流前预处理、多路图像叠加等职责,是多媒体系统里的重要中间层。
如果你能这么讲,面试官通常会觉得你是真的理解了它在系统里的位置,而不是只会写 -vf scale=...。
总结:filter graph 的价值,不只是“能加滤镜”,而是它让 frame 处理变成了一张可编排的图
回到这篇文章最开始的问题:
FFmpeg filter graph 到底是什么?
我觉得最稳的主线就是:
- 它本质上是一张 frame 处理图
- 它通常位于 decode 和 encode 之间
- 它把 scale、fps、overlay、drawtext 这类处理统一纳入一套 graph 结构中
- simple filter 更像单线管道,complex filter 更像真正的图结构
- 在工程里,它常常是非常关键的一层预处理 / 加工层
只要这条主线建立起来,后面你再看:
- 复杂转码
- 多路画面拼接
- 推流前预处理
- 加 logo / 水印 / OSD
- Camera 数据进入编码链前的统一处理
都会顺很多。
因为到那时你已经不会只把滤镜当成“FFmpeg 命令花活”,而会开始把它当成:
音视频系统里真正的 frame 处理编排层。