注:本文是我在学习FFmpeg Filters Documentation过程中的中文翻译笔记,为避免英译中过程中一些死板的字面意思引起读者错误理解,有时候我还会加上一些个人注解,若有不同见解,请最好参考原文档学习。本翻译仅供参考,转载请注明!
1. 简介
本文档将描述由libavfilter
库提供的过滤器(filters
),源(sources
),接收器(sinks
)。
关于将Filter翻译为过滤器还是滤波器一直没定论,同时sink也没有比较贴切的中文名词可以放在本语境中。为避免中文翻译之后产生歧义且便于音视频开发者阅读,后续文章中提及上面三个对象以及类似
input pad
这种不好直接翻译的名词时均用英文表示。
2. Filter
FFmpeg中的Filter相关功能是由库libavfilter
提供的。一个Filter可以有N个输入和N个输出(N ≥ 0)。
假设我们现在想将一个视频是上半部分垂直翻转到视频的下半部分镜像显示,我们该怎么做呢?莫慌,我们将以下图作为例子介绍filter
处理这个事情的大致流程:
[main]
input --> split ---------------------> overlay --> output
| ^
|[tmp] [flip]|
+-----> crop --> vflip -------+
从上面的filtergraph
中可以看出,我们将输入流先分割成两个流并分别被标记为[main]
和[tmp]
标签,然后将流[tmp]
经过crop
(裁剪)和vflip
(垂直翻转)两个过滤器的处理后标记为流[flip]
,最终将流[flip]
这路流overlay
(覆盖)到[main]
这路流上面去。于是我们可以使用以下命令来实现我们上面的思路:
ffmpeg -i INPUT -vf "split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2" OUTPUT
执行上述命令进行实验之后你会发现,输出的视频是原始视频上半部分垂直翻转镜像的结果,符合预期。
同个链路的Filters
使用逗号,
来分隔,每条链路之间用分号;
来分隔。从上面的例子中我们可以看出,crop
和vflip
属于同一条链路,而split
和overlay
属于另外独立的两个链路。同时,在每个链路连接点处我们还是用方括号[]
的形式来标记链路的输入输出(可以理解为给它们起别名)。
一些过滤器能接受输入参数列表,形式通常是在过滤器名称和等号后面指定,并用冒号彼此隔开,如crop=iw:ih/2:0:0
表示要从原始视频中裁剪出一样宽,一半高的视频。(详见下文的crop
过滤器详细介绍)
通常情况下filter
都有输入输出,但是在FFmpeg
的世界里还存在两种特殊的filter
,即:
-
source filters
: 没有任何输入 -
sink filters
: 没有任何输出
3. graph2dot
不重要,先不翻译
4. Filtergraph
Filtergraph是链接多个Filter的有向图。
它可以包含循环,各个Filter之间也可以有多个链接。每个链接有一个input pad
连接到一个Filter并从那里获取输入,有一个output pad
连接到另一个Filter提供输出。所有的Filter都是已经注册在程序中的。没有输入的Filter叫source
,没有输出的Filter叫sink
。
4.1 Filtergraph 语法
每个Filtergraph都有对应的结构化的文本表示:
- 对于命令
ffmpeg
来说有-filter
,-vf
,-af
和-filter_complex
这些选项 - 对于命令
ffplay
来说有-vf
,-af
这些选项
可参考源码
libavfilter/avfilter.h
的avfilter_graph_parse_ptr()
了解更多细节
从整体看,Filtergraph包含Filterchain,而Filterchain又包含了Filter:
-
filtergraph
是由一个或多个filterchain
组成,filterchain
之间使用分号;
来分隔 -
filterchain
是由一个或多个filter
线性连接而成,filter
之间使用逗号,
来分隔 -
filter
的语法格式为:[in_link_1]...[in_link_N]filter_name@id=arguments[out_link_1]...[out_link_M]
。顾名思义,filter_name
就是你所要使用的filter
的名称,它必定是已经在libavfilter
中有注册的,后面的@id
是可选的(基本没用到);紧跟着的=arguments
也是可选填的(有些filter
并不需要参数),arguments
通常有如下格式:- 用
:
来分隔一系列值。(这种情况下参数的顺序就是固定的,值的顺序必须按照filter
声明的参数顺序来一个个占坑,类似平时写程序时函数调用填实参一样) - 用
:
来分隔一系列的key=value
的键值对。(这种情况下参数的顺序就不是固定的了,类似JavaScript或者Python的指定关键字参数) - 用
:
来分隔一系列值,之后跟着一系列的key=value
的键值对,值必须在键值对前面。(同上,类似JavaScript或者Python这类语言的函数调用方式) - 有时候
value
本身就是要填一系列参数值的,那么需要用|
来拼接(参考format
这个filter)
- 用
从上面可以看出,[]=;,
这些字符对于参数列表都是特殊字符,所以在参数列表中若要用到特殊字符,可以用单引号''
将参数列表包起来,然后用\
对特殊字符进行转义。
Filter的名称和参数前后是允许放置一个或多个链接标签(link label
)的。所谓的链接标签就是一个链接(link
)的别名,可以将它们关联到Filter的输入/输出。放在前面的链接标签in_link_1 ... in_link_N
被关联到Filter的input pad
,后面的out_link_1 ... out_link_M
被关联到Filter的output pad
。当在Filtergraph中匹配到两个同名的链接标签时,Filter将创建其对应的input pad
和output pad
之间的链接。
如果一个Filter的output pad
没被标记链接标签,则它会被默认链接到后续的Filter中第一个未标记链接标签的input pad
,如:
nullsrc, split[L1], [L2]overlay, nullsink
上面的split
有两个output pad
,一个是[L1]
,另一个是匿名的;而overlay
又拥有两个input pad
,一个是[L2]
,另一个是匿名的;因此这两个匿名的pad
会被链接起来:split -> overlay
。
缺省情况下,第一个Filter的输入标签为[in]
,最后一个Filter的输出标签为[out]
。如下面两个Filtergraph是一样的:
ffmpeg -i INPUT -vf "split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2" OUTPUT
ffmpeg -i INPUT -vf "[in] split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2 [out]" OUTPUT
对于一个完整可用的Filterchain来说,所有匿名的链接标签必须有被链接。
对于一个Filtergraph来说,只有所有的Filterchain的input pad
和output pad
都被连接起来,才是一个有效可用的Filtergraph。
libavfilter
在处理Filtergraph过程中当遇到有格式转换的时候会自动插入scale
这个Filter,因此你可以在Filtergraph描述中的可能会隐式包含scale
的地方前面加上你要指定的缩放参数:sws_flags=flags;
。
下面是针对Filtergraph语法的一个BNF描述:
NAME ::= sequence of alphanumeric characters and '_'
FILTER_NAME ::= NAME["@"NAME]
LINKLABEL ::= "[" NAME "]"
LINKLABELS ::= LINKLABEL [LINKLABELS]
FILTER_ARGUMENTS ::= sequence of chars (possibly quoted)
FILTER ::= [LINKLABELS] FILTER_NAME ["=" FILTER_ARGUMENTS] [LINKLABELS]
FILTERCHAIN ::= FILTER [,FILTERCHAIN]
FILTERGRAPH ::= [sws_flags=flags;] FILTERCHAIN [;FILTERGRAPH]
4.2 关于Filtergraph的字符转义
详见:(ffmpeg-utils)the "Quoting and escaping" section in the ffmpeg-utils(1) manual。
- 优先考虑Filter的参数值本身是否包含
:
或\
这些特殊字符 - 再考虑整个Filterchain中是否包含
\
或者[],=;
这些特殊字符 - 最后如果你是使用命令行,还要从shell命令行的角度去考虑shell语句级别的字符转义
举个例子,假设你要用drawtext
这个Filter来给视频加上文字:
this is a 'string': may contain one, or more, special characters
根据第1点提到的,它包含了'
和:
两个特殊字符,于是你要这么做:
text=this is a \'string\'\: may contain one, or more, special characters
根据第2点提到的,它包含了\
转义字符和',
这些特殊字符,于是你要这么做:
drawtext=text=this is a \\\'string\\\'\\: may contain one\, or more\, special characters
根据第3点提到的,如果你要在shell上使用这个命令,还需要这么做:
-vf "drawtext=text=this is a \\\\\\'string\\\\\\'\\\\: may contain one\\, or more\\, special characters"