FFmpeg filter_complex 基础用法解析 – wiki基地


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 通常会默认选择合适的流)。

这种简单的滤镜应用有几个限制:

  1. 局限于单个流: -vf-af 是分别作用于视频流和音频流的,它们之间通常是独立的。
  2. 难以处理多输入/多输出: 如果你需要将两个视频叠加在一起,或者将一个视频分割成两个不同尺寸的输出, -vf-af 无法直接实现。
  3. 复杂的依赖关系: 有些处理可能需要将一个流经过一系列滤镜后,再与另一个流合并,或者一个流的输出需要作为另一个滤镜的输入。简单的链式调用 (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:第一个滤镜,缩放。
* ,:分隔 scaletranspose 滤镜,表示它们在同一链中串联。
* 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_wmain_h 是主视频的宽度和高度,overlay_woverlay_h 是叠加视频的宽度和高度。这里将前景视频放在背景视频的右下角,距离边缘10像素。
* [v_out]:叠加后的视频输出。
* ;:分隔视频处理链和音频处理链。
* [0:a][1:a]amix[a_out]:第二个链。
* [0:a]:第一个输入文件的音频流。
* [1:a]:第二个输入文件的音频流。
* amix 滤镜用于混合多个音频流。默认情况下,它会将所有输入的音频混合在一起。
* [a_out]:混合后的音频输出。
* -map "[v_out]":映射视频输出。
* -map "[a_out]":映射音频输出。

这个例子非常典型,它同时处理了视频和音频的混合,并使用了 overlayamix 这两个常用的多输入滤镜。

场景 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=1concat 滤镜及其参数。
* 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]iwih 是输入视频的宽度和高度,这里缩放到原来的一半。
* [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:00: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 技能将迈上一个新的台阶。


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部