FFmpeg filter_complex
基础用法解析:构建强大的媒体处理流程
FFmpeg 是一个功能强大的开源多媒体处理工具,广泛应用于视频、音频的编码、解码、转码、流处理等各种场景。对于许多基础任务,例如简单的格式转换、剪切、改变分辨率,我们通常会使用 FFmpeg 的基本命令行选项或 -vf
(video filter) 和 -af
(audio filter) 选项。然而,当需要进行更复杂的媒体操作时,比如画面叠加(overlay)、多视频拼接、混合音频、同时对同一视频应用多个互相依赖的滤镜链,或者处理多输入/多输出的场景, -vf
和 -af
的能力就显得捉襟见底了。
这时,FFmpeg 的 -filter_complex
选项应运而生。它提供了一个构建 滤镜图 (Filtergraph) 的强大机制,允许用户以前所未有的灵活性和控制力定义复杂的媒体处理流程。本文将深入浅出地解析 -filter_complex
的基础用法、核心概念和常见应用场景,帮助你掌握这一 FFmpeg 的高级利器。
1. 为什么需要 filter_complex
?理解滤镜图的概念
在理解 filter_complex
之前,我们先回顾一下简单的滤镜应用。使用 -vf
或 -af
时,你通常是在某个特定的流(stream)上应用一个或一串滤镜。例如:
bash
ffmpeg -i input.mp4 -vf scale=640:-1 output.mp4
这条命令将 input.mp4
的视频流缩放为宽度640像素,高度自动调整。这里的 scale
滤镜直接作用于输入文件的第一个视频流(FFmpeg 通常会默认选择合适的流)。
这种简单的滤镜应用有几个限制:
- 局限于单个流:
-vf
和-af
是分别作用于视频流和音频流的,它们之间通常是独立的。 - 难以处理多输入/多输出: 如果你需要将两个视频叠加在一起,或者将一个视频分割成两个不同尺寸的输出,
-vf
和-af
无法直接实现。 - 复杂的依赖关系: 有些处理可能需要将一个流经过一系列滤镜后,再与另一个流合并,或者一个流的输出需要作为另一个滤镜的输入。简单的链式调用 (
filter1,filter2
) 不足以表达这种复杂的连接关系。
filter_complex
解决了这些问题,它引入了 滤镜图 (Filtergraph) 的概念。
一个滤镜图是一个 有向图 (Directed Graph),其中的节点是滤镜 (filters),边代表着数据流(视频帧或音频采样)在滤镜之间的传递。数据从图的 输入端 (input pads) 进入,流经一个或多个滤镜,最终从图的 输出端 (output pads) 输出。
filter_complex
选项后面的参数就是对这个滤镜图的文本描述。通过这个描述,你可以精确地指定:
- 哪些输入流进入滤镜图。
- 数据流经过哪些滤镜,以及滤镜的参数。
- 滤镜之间如何连接。
- 哪些数据流从滤镜图输出,并如何对应到最终的输出文件。
想象一下,filter_complex
就像是一个强大的图形化界面,只不过你需要用代码来绘制它。它允许你构建任意复杂的处理管线,将不同的输入流混合、分流、处理,然后定向到不同的输出。
2. filter_complex
的基本语法和核心概念
filter_complex
选项的通用格式如下:
bash
-filter_complex "filtergraph_description"
filtergraph_description
是一个字符串,它描述了整个滤镜图的结构。这个描述字符串由一个或多个 滤镜链 (filterchains) 组成,滤镜链之间使用分号 ;
分隔。
每个滤镜链描述了数据流经过一系列串联滤镜的过程。一个滤镜链的语法如下:
[in_link_1][in_link_2]...filter1=param1:param2,...filter2=paramA:paramB,...[out_link_1][out_link_2]...
我们来解析一下这个语法中的关键组成部分:
[in_link]
(输入链接标签): 用方括号[]
包围的文本是 链接标签 (link labels)。它们是滤镜图中的“命名管道”或“连接点”。[in_link]
出现在滤镜或滤镜链的左侧,表示该滤镜或滤镜链从哪个指定的链接获取输入数据。- 一个滤镜或滤镜链可以有零个、一个或多个输入。
- 如果没有指定输入链接标签,FFmpeg 会尝试使用 隐式链接 (implicit links)。对于
filter_complex
的第一个滤镜链,其未指定输入的视频流通常默认为第一个输入的第一个视频流 ([0:v]
),音频流默认为第一个输入的第一个音频流 ([0:a]
)。后续滤镜链的隐式输入则取决于前一个链的输出(如果没有显式指定输出标签)。然而,强烈建议总是使用显式标签来避免混淆,尤其是在复杂的图谱中。
filter=params
(滤镜描述): 这是应用的具体滤镜及其参数。多个滤镜在同一链中串联时,使用逗号,
分隔。例如scale=640:-1,transpose=1
表示先缩放再旋转。filter
是滤镜的名称(例如scale
,overlay
,amix
,concat
,drawtext
等)。params
是滤镜的参数,通常使用等号=
连接,多个参数使用冒号:
分隔(例如scale=width=640:height=-1
或简写为scale=640:-1
)。参数也可以用单引号或双引号括起来,特别是当参数值中包含特殊字符时。
[out_link]
(输出链接标签): 出现在滤镜或滤镜链的右侧,表示该滤镜或滤镜链将输出数据发送到哪个指定的链接。- 一个滤镜或滤镜链可以有零个、一个或多个输出。
- 如果没有指定输出链接标签,该输出链接也是隐式的。隐式输出可以作为后续滤镜链的隐式输入,或者作为最终输出流。同样,建议使用显式标签。
连接输入文件流到滤镜图:
滤镜图的初始输入数据来自 FFmpeg 的输入文件。在 filter_complex
的描述中,你可以使用 [file_index:stream_specifier]
的格式来引用输入文件中的特定流,作为滤镜图的输入链接标签。
file_index
:输入文件的索引,从 0 开始计数(对应于命令行中第一个-i
后面的文件是 0,第二个是 1,以此类推)。stream_specifier
:指定文件中的流。v
代表所有视频流,v:0
代表第一个视频流,a
代表所有音频流,a:0
代表第一个音频流,s
代表字幕流等。通常我们指定具体的流,例如0:v:0
或简写为0:v
(第一个输入的第一个视频流),1:a:0
或简写为1:a
(第二个输入的第一个音频流)。
例如,[0:v]
表示第一个输入文件中的第一个视频流,[1:a]
表示第二个输入文件中的第一个音频流。这些可以直接用作滤镜链的输入链接标签。
将滤镜图的输出连接到输出文件:
滤镜图处理完毕的数据需要输出到最终文件。这通过 -map
选项来实现。在 filter_complex
中,你可以为滤镜链的最终输出指定一个输出链接标签(例如 [out_v]
)。然后,使用 -map [out_v]
选项将这个带有标签的输出链接映射到输出文件中的某个流。
例如:
bash
ffmpeg -i input.mp4 -filter_complex "[0:v]scale=640:-1[v_out]" -map "[v_out]" output.mp4
这条命令中:
* [0:v]
引用第一个输入文件的视频流。
* scale=640:-1
是应用的滤镜。
* [v_out]
是缩放滤镜的输出链接标签。
* -map "[v_out]"
将名为 v_out
的视频流输出映射到 output.mp4
文件。
总结核心概念:
filter_complex
:用于描述整个滤镜图的选项。- 滤镜图 (Filtergraph):由滤镜和它们之间的连接组成的有向图。
- 滤镜链 (Filterchain):图中的一条路径,由一个或多个串联的滤镜组成,用逗号
,
分隔。多个滤镜链用分号;
分隔。 - 链接标签 (Link Labels):用
[]
括起来的名称,代表滤镜图中的数据流连接点(管道)。用于连接滤镜、滤镜链以及输入/输出流。 - 输入链接 (Input Pads):滤镜或滤镜链左侧的标签,表示输入。
- 输出链接 (Output Pads):滤镜或滤镜链右侧的标签,表示输出。
- 输入流引用:
[file_index:stream_specifier]
格式,用于将输入文件中的流连接到滤镜图的输入。 - 输出流映射:
-map [output_link_label]
格式,用于将滤镜图的输出链接连接到输出文件中的流。
3. 构建简单的 filter_complex
示例
让我们从一些简单的例子开始,逐步理解如何构建滤镜图描述。
示例 1:在一个链中串联多个滤镜
需求:将一个视频先缩放,然后旋转。
bash
ffmpeg -i input.mp4 -filter_complex "[0:v]scale=640:-1,transpose=1[v_out]" -map "[v_out]" output.mp4
解释:
* [0:v]
:引用第一个输入文件的视频流作为滤镜链的输入。
* scale=640:-1
:第一个滤镜,缩放。
* ,
:分隔 scale
和 transpose
滤镜,表示它们在同一链中串联。
* transpose=1
:第二个滤镜,旋转(1表示顺时针90度)。
* [v_out]
:整个滤镜链的输出链接标签。
* -map "[v_out]"
:将名为 v_out
的视频流输出到 output.mp4
。
示例 2:使用多个滤镜链(并行处理或独立处理)
需求:同时对视频应用缩放和对音频应用音量调整,它们之间没有直接的数据依赖。
bash
ffmpeg -i input.mp4 -filter_complex "[0:v]scale=640:-1[v_out];[0:a]volume=0.5[a_out]" -map "[v_out]" -map "[a_out]" output.mp4
解释:
* [0:v]scale=640:-1[v_out]
:第一个滤镜链,处理视频流,并标记输出为 [v_out]
。
* ;
:分隔两个独立的滤镜链。
* [0:a]volume=0.5[a_out]
:第二个滤镜链,处理音频流,并标记输出为 [a_out]
。
* -map "[v_out]"
:映射处理后的视频流。
* -map "[a_out]"
:映射处理后的音频流。
示例 3:利用标签连接不同的滤镜链
需求:将视频流分割成两份,一份缩放,一份保持原样,然后将缩放后的视频叠加到原样视频的某个位置(虽然这个例子有点奇怪,但展示了如何连接)。
bash
ffmpeg -i input.mp4 -filter_complex "[0:v]split[v_orig][v_scaled];[v_scaled]scale=320:-1[v_small];[v_orig][v_small]overlay=x=10:y=10[v_out]" -map "[v_out]" -map 0:a output.mp4
解释:
* [0:v]split[v_orig][v_scaled]
:第一个链。split
滤镜将输入视频流复制成两个输出,分别标记为 [v_orig]
和 [v_scaled]
。
* ;
:分隔第一个链和第二个链。
* [v_scaled]scale=320:-1[v_small]
:第二个链。输入是 [v_scaled]
(split 的一个输出),应用 scale
滤镜,输出标记为 [v_small]
。
* ;
:分隔第二个链和第三个链。
* [v_orig][v_small]overlay=x=10:y=10[v_out]
:第三个链。overlay
滤镜需要两个视频输入:主视频和叠加视频。它接收 [v_orig]
(split 的另一个输出,原样视频) 和 [v_small]
(缩放后的视频) 作为输入,将 [v_small]
叠加到 [v_orig]
的 (10,10) 位置,输出标记为 [v_out]
。
* -map "[v_out]"
:映射最终的视频输出。
* -map 0:a
:直接从第一个输入文件复制音频流到输出(因为我们没有在 filter_complex
中处理音频)。
这个例子清晰地展示了如何使用链接标签 [v_orig]
, [v_scaled]
, [v_small]
将不同的滤镜链连接起来,构建一个有向图。
4. 常见的 filter_complex
应用场景及详细例子
掌握了基本语法,我们来看一些非常实用的 filter_complex
应用。
场景 1:画面叠加 (Overlay)
将一个视频叠加到另一个视频上,例如制作画中画或添加水印。这需要至少两个视频输入。
bash
ffmpeg -i background.mp4 -i foreground.mp4 -filter_complex "[0:v][1:v]overlay=x=main_w-overlay_w-10:y=main_h-overlay_h-10[v_out];[0:a][1:a]amix[a_out]" -map "[v_out]" -map "[a_out]" output_with_overlay.mp4
解释:
* -i background.mp4
:第一个输入 (索引 0),作为背景视频。
* -i foreground.mp4
:第二个输入 (索引 1),作为前景叠加视频。
* [0:v][1:v]overlay=x=main_w-overlay_w-10:y=main_h-overlay_h-10[v_out]
:第一个链。
* [0:v]
:第一个输入文件的视频流(背景)。
* [1:v]
:第二个输入文件的视频流(前景)。
* overlay
滤镜接收两个视频输入,第一个是主视频(背景),第二个是叠加视频(前景)。
* x=main_w-overlay_w-10:y=main_h-overlay_h-10
:叠加的位置参数。main_w
和 main_h
是主视频的宽度和高度,overlay_w
和 overlay_h
是叠加视频的宽度和高度。这里将前景视频放在背景视频的右下角,距离边缘10像素。
* [v_out]
:叠加后的视频输出。
* ;
:分隔视频处理链和音频处理链。
* [0:a][1:a]amix[a_out]
:第二个链。
* [0:a]
:第一个输入文件的音频流。
* [1:a]
:第二个输入文件的音频流。
* amix
滤镜用于混合多个音频流。默认情况下,它会将所有输入的音频混合在一起。
* [a_out]
:混合后的音频输出。
* -map "[v_out]"
:映射视频输出。
* -map "[a_out]"
:映射音频输出。
这个例子非常典型,它同时处理了视频和音频的混合,并使用了 overlay
和 amix
这两个常用的多输入滤镜。
场景 2:视频和音频的连接/合并 (Concat)
将多个视频文件或音频文件连接起来。concat
滤镜非常强大,可以处理视频、音频或同时处理。它需要指定输入的数量以及每个输入包含多少个视频流和音频流。
将两个视频文件 (file1.mp4 和 file2.mp4) 连接起来:
bash
ffmpeg -i file1.mp4 -i file2.mp4 -filter_complex "[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[v_out][a_out]" -map "[v_out]" -map "[a_out]" output_concat.mp4
解释:
* -i file1.mp4 -i file2.mp4
:两个输入文件。
* [0:v][0:a][1:v][1:a]
:concat
滤镜的输入。concat
要求所有视频输入在前,所有音频输入在后,且顺序对应。这里是 file1 的视频、file1 的音频、file2 的视频、file2 的音频。
* concat=n=2:v=1:a=1
:concat
滤镜及其参数。
* n=2
:表示有2个输入片段。
* v=1
:表示每个输入片段有1个视频流。
* a=1
:表示每个输入片段有1个音频流。
* [v_out][a_out]
:concat
滤镜的输出。它会输出指定数量的视频流和音频流,这里是1个视频流和1个音频流。
* -map "[v_out]"
和 -map "[a_out]"
:映射连接后的视频和音频输出。
注意: 使用 concat
滤镜连接文件时,所有输入的对应流(例如所有输入的视频流)必须具有相同的编解码器、分辨率、帧率、像素格式等属性,所有音频流必须具有相同的编解码器、采样率、通道布局等属性。如果属性不一致,你需要在 concat
之前使用其他滤镜(如 scale
, fps
, format
, aresample
, aconvert
等)进行统一。
例如,如果 file2.mp4 的分辨率不同,你需要先缩放它:
bash
ffmpeg -i file1.mp4 -i file2.mp4 -filter_complex "[0:v]setpts=PTS-STARTPTS[v1];[0:a]asetpts=PTS-STARTPTS[a1];[1:v]scale=iw*0.5:-1,setpts=PTS-STARTPTS[v2];[1:a]asetpts=PTS-STARTPTS[a2];[v1][a1][v2][a2]concat=n=2:v=1:a=1[v_out][a_out]" -map "[v_out]" -map "[a_out]" output_concat_scaled.mp4
(这里添加了 setpts
/asetpts
滤镜来重置时间戳,这在使用 concat
滤镜连接不同来源的片段时通常是必要的,以避免时间戳问题导致播放异常。iw
表示输入宽度,iw*0.5
表示缩放到一半宽度。)
场景 3:文本叠加 (Drawtext)
在视频的指定位置绘制文本,例如添加字幕或版权信息。
bash
ffmpeg -i input.mp4 -filter_complex "[0:v]drawtext=fontfile=/path/to/font.ttf:text='Hello FFmpeg':x=100:y=H-th-10:fontsize=24:[email protected]:box=1:[email protected]:enable='gte(t,5)'[v_out]" -map "[v_out]" -map 0:a -c:a copy output_with_text.mp4
解释:
* [0:v]drawtext=...[v_out]
:将输入视频流作为 drawtext
滤镜的输入,输出标记为 [v_out]
。
* fontfile=/path/to/font.ttf
:指定字体文件路径。
* text='Hello FFmpeg'
:要绘制的文本内容。
* x=100:y=H-th-10
:文本位置。H
是视频高度,th
是文本高度。这里将文本放在底部,距离左边100像素,底部10像素。
* fontsize=24
:字体大小。
* [email protected]
:字体颜色(白色)和透明度(0.8)。
* box=1:[email protected]
:绘制一个背景框(黑色,半透明)。
* enable='gte(t,5)'
:文本出现的条件。t
是当前时间戳(秒)。gte(t,5)
表示当时间大于等于5秒时显示文本。这允许你控制文本的出现和消失时间。
* -map 0:a -c:a copy
:直接复制原始音频流到输出。
场景 4:复杂布局与排版
将多个视频流组合成一个画面,例如四宫格布局。
bash
ffmpeg -i input1.mp4 -i input2.mp4 -i input3.mp4 -i input4.mp4 \
-filter_complex "
[0:v]scale=iw/2:ih/2[v1];
[1:v]scale=iw/2:ih/2[v2];
[2:v]scale=iw/2:ih/2[v3];
[3:v]scale=iw/2:ih/2[v4];
[v1][v2]hstack[top];
[v3][v4]hstack[bottom];
[top][bottom]vstack[v_out]
" \
-map "[v_out]" -map 0:a -c:a copy output_grid.mp4
解释:
* 四个输入文件 (-i input1.mp4
等)。
* [0:v]scale=iw/2:ih/2[v1];
等:将每个输入视频流缩放到原尺寸的一半,并分别标记为 [v1]
、[v2]
、[v3]
、[v4]
。iw
和 ih
是输入视频的宽度和高度,这里缩放到原来的一半。
* [v1][v2]hstack[top];
:使用 hstack
(horizontal stack) 滤镜将 [v1]
和 [v2]
水平堆叠在一起,输出标记为 [top]
。
* [v3][v4]hstack[bottom];
:将 [v3]
和 [v4]
水平堆叠,输出标记为 [bottom]
。
* [top][bottom]vstack[v_out]
:使用 vstack
(vertical stack) 滤镜将 [top]
(上半部分) 和 [bottom]
(下半部分) 垂直堆叠在一起,形成最终的四宫格,输出标记为 [v_out]
。
* -map "[v_out]"
:映射最终的视频输出。
* -map 0:a -c:a copy
:这里只取第一个输入文件的音频,并直接复制。如果需要混合所有音频,可以使用 amix
滤镜。
如果需要混合音频,可以修改 filter_complex
并添加音频混合链:
bash
ffmpeg -i input1.mp4 -i input2.mp4 -i input3.mp4 -i input4.mp4 \
-filter_complex "
[0:v]scale=iw/2:ih/2[v1];
[1:v]scale=iw/2:ih/2[v2];
[2:v]scale=iw/2:ih/2[v3];
[3:v]scale=iw/2:ih/2[v4];
[v1][v2]hstack[top];
[v3][v4]hstack[bottom];
[top][bottom]vstack[v_out];
[0:a][1:a][2:a][3:a]amix=inputs=4[a_out]
" \
-map "[v_out]" -map "[a_out]" output_grid_mixed_audio.mp4
这里增加了一行 [0:a][1:a][2:a][3:a]amix=inputs=4[a_out]
来混合所有输入的音频流,并使用 -map "[a_out]"
映射混合后的音频。
5. filter_complex
的一些高级使用和技巧
- 使用
split
滤镜分流: 正如之前的例子所示,split
滤镜可以将一个输入流复制成多个完全相同的输出流,方便在图谱中进行并行处理或送往不同的滤镜。[in]split=N[out1][out2]...[outN]
。 - 使用
pad
滤镜增加画面黑边: 当你需要叠加一个小尺寸视频到大尺寸背景上,或者需要统一不同分辨率视频以便concat
时,pad
滤镜非常有用。[in]pad=width=1920:height=1080:x=(ow-iw)/2:y=(oh-ih)/2[out]
将输入视频居中放置在一个1920×1080的画布上。 - 使用
setpts
/asetpts
重置时间戳: 在连接(concat
)或处理多个不同来源的片段时,时间戳可能会不连续或不一致。使用setpts=PTS-STARTPTS
对视频流、asetpts=PTS-STARTPTS
对音频流可以重置时间戳,使其从0开始连续递增,避免播放问题。这通常放在输入流引用之后、其他滤镜之前。 - 使用
-filter_complex_script
文件: 当filter_complex
的描述字符串变得非常长和复杂时,直接写在命令行中会难以阅读和维护。可以将滤镜图描述保存在一个文本文件中(例如graph.txt
),然后使用-filter_complex_script graph.txt
来引用它。这样可以提高命令的可读性,特别是当包含换行和注释时。 - 调试滤镜图: 如果滤镜图描述有错误,FFmpeg 会报错。理解错误信息是关键。可以使用
-hide_banner
选项减少输出干扰。对于非常复杂的图谱,可以使用-lavfi_graph
或-filter_complex_graph
选项(取决于 FFmpeg 版本)来可视化解析后的滤镜图(通常输出 Dot 语言格式,可以转换为图片)。
6. 注意事项
- 引用流的索引: 确保正确引用输入文件的索引(从0开始)和流的类型及索引(如
0:v:0
或0:a
)。 - 滤镜的输入输出数量和类型: 每个滤镜都有其预期的输入和输出数量及类型(视频/音频)。例如,
scale
需要一个视频输入,产生一个视频输出;overlay
需要两个视频输入,产生一个视频输出;amix
需要多个音频输入,产生一个音频输出;concat
需要多个视频和音频输入,产生多个对应的输出。连接时必须匹配。 - 隐式链接的风险: 尽管 FFmpeg 支持隐式链接,但在构建复杂图谱时,强烈建议总是使用显式链接标签,以清晰地表达数据流向,避免意外行为。
- 性能: 复杂的滤镜图会显著增加处理所需的计算资源和时间。合理设计滤镜图,避免不必要的处理。注意硬件加速选项 (
-hwaccel
) 通常在进入/离开滤镜图时应用,滤镜图本身的执行可能仍然是软件的,除非特定滤镜支持硬件加速。 - 参数转义: 如果滤镜参数值中包含特殊字符(如冒号
:
, 等号=
, 单引号'
, 双引号"
等),可能需要进行转义或使用不同的引号包围整个参数值。
7. 总结
FFmpeg 的 filter_complex
选项是其最强大和灵活的特性之一。通过构建和描述滤镜图,你可以定义任意复杂的媒体处理流程,实现简单滤镜命令无法完成的任务,如多输入/多输出处理、复杂的画面合成、音频混合等。
掌握 filter_complex
的关键在于理解滤镜图的概念,熟悉链接标签的作用,以及学会如何将滤镜链用分号和逗号分隔并用标签连接起来。通过实践各种常见的滤镜(如 scale
, overlay
, amix
, concat
, drawtext
, split
, pad
等)在 filter_complex
中的用法,并尝试组合它们来解决具体的媒体处理问题,你将能够充分发挥 FFmpeg 的潜力。
虽然 filter_complex
的语法初看起来可能有些令人望而却步,但一旦理解了其核心原理,它将成为你进行高级媒体处理的强大工具。多查阅 FFmpeg 官方文档中关于 filter_complex
和各种滤镜的详细说明,结合实际操作,你的 FFmpeg 技能将迈上一个新的台阶。