filter,可以翻译为过滤器,滤镜。在 FFmpeg 中有多种多样的滤镜,你可以把他们当成一个个小工具,专门用于处理视频和音频数据,以便实现一定的目的。如 overlay 这个滤镜,可以将一个图画覆盖到另一个图画上;transport 这个滤镜可以将图画做旋转等等。
一个 filter 的输出可以作为另一个 filter 的输入,因此多个 filter 可以组织成为一个网状的 filter graph,从而实现更加复杂或者综合的任务。
关于 filter 的概念和用法可以去查找 FFmpeg 的其他资料,这里不再赘述。
libavfilter 库实现了 filter 相关的功能,使得我们可以利用 FFmpeg 提供的诸多 filter 来实现自己所需的功能。
具体的使用方法见官方示例:filter_audio.c,filtering_audio.c,filtering_video.c。
1、概述
一个 filter 用于读取一个或多个输入,然后对其进行相关操作,写入到一个或者多个输出。而且,一个 filter 仅处理一种类型的数据,因此我们可以根据其所处理的数据类型对 filter 进行分类:video filter,audio filter 等等。
在 libavfilter 中,我们用类型 AVFilter 来表示一个 filter,每一个 filter 都是经过注册的,其特性是相对固定的。而 AVFilterContext 则表示一个真正的 filter 实例,这和 AVCodec 以及 AVCodecContext 的关系是类似的。
AVFilter 中最重要的特征就是其所需的输入和输出,在 FFmpeg 中,我们用类型 AVFilterPad 来表示一个“引脚”,所谓“引脚”,是硬件相关的一个名词,表示用于数据输入输出的一个物理接口,在这里也是同样的意思,表示了 filter 的一个输入或输出。那么显然,一个 filter,它会有固定的 N 个 input,N 个 output。
AVFilterContext 表示一个 AVFilter 的实例,我们在实际使用 filter 时,就是使用这个类型。
AVFilterGraph 表示由多个 filter context 组成的 filter graph,多个 filter 组成一个输入和输出相互关联的网状结构,从而互相配合以便实现更加复杂的功能。
AVFilterContext 在被使用前,它必须是 ready 状态的,也就是被初始化的。说白了,就是需要对 filter 进行一些选项上的设置,然后通过初始化告诉 FFmpeg 我们已经做了相关的配置。
AVFilterGraph 在添加完所需的 filter context 之后,需要将它们的 input 和 output 进行关联,每一个关联称为一个 link,从而形成一个有效的数据处理流程。在关联完毕之后,AVFilterGraph 同样需要进行初始化,以便检测其内部的 link 是否完整有效。
我们使用 AVFilterLink 来表示一个 filter context 之间输入输出的关联:
struct AVFilterLink {
AVFilterContext *src; ///< source filter
AVFilterPad *srcpad; ///< output pad on the source filter
AVFilterContext *dst; ///< dest filter
AVFilterPad *dstpad; ///< input pad on the dest filter
enum AVMediaType type; ///< filter media type
... ...
}
即,将 src 的 srcpad 输出关联到 dst 中的 dstpad 输入上。其传输的数据类型为 type。
除此之外,AVFilterLink 还包含了 src 和 dst 之间协商好的传输数据的相关参数。这是因为,filter 对输出和输入的数据均有一定的规范要求,因此需要两者进行协商,使得数据同时符合两者的要求。
应用程序通常不直接获取 AVFilterLink 结构体,而是使用 buffersrc 和 buffersink API,该 API 将在后续介绍。
以上,就是 libavfilter 的概括。
2、AVFilter
AVFilter 表示一个被注册的 filter 类型,如果仅是为了使用它,我们只需知道它的部分特性就可以了。
- AVFilter.name 和 AVFilter.description:filter 的名称和描述
- AVFilter.inputs 和 AVFilter.outputs:AVFilterPad 的数组,描述了filter的输入和输出
- AVFilter.flags:filter的特性
首先,针对 filter 的输入和输出数组,有以下三个帮助函数:
//获取 AVFilterPad 数组的数量,该数组以NULL结尾,一般传入AVFilter.inputs 和 AVFilter.outputs
int avfilter_pad_count(const AVFilterPad *pads);
//获取数组中指定下标的 pad 的名称,调用者应确保传入的下标有效
const char *avfilter_pad_get_name(const AVFilterPad *pads, int pad_idx);
//获取数组中指向下标的 pad 所需的数据类型,调用者应确保传入的下标有效
enum AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int pad_idx);
然后,就是 AVFilter 的 flag,其可取值如下:
/*
filter输入的数量并不只由AVFilter.inputs字段决定。filter可能会在初始化的时候加入额外的input,具体取决于应用于该filter的选项
*/
#define AVFILTER_FLAG_DYNAMIC_INPUTS (1 << 0)
/*
filter输出的数量并不仅由AVFilter.outputs字段决定。filter可能会在初始化的时候加入额外的output,具体取决于应用于该filter的选项
*/
#define AVFILTER_FLAG_DYNAMIC_OUTPUTS (1 << 1)
/*
filter支持多线程操作,它可以将输入的多个frame分为几个部分并行处理它们
*/
#define AVFILTER_FLAG_SLICE_THREADS (1 << 2)
/*
有一部分filter,它们支持一个公有选项 enable,这个选项可以在时间线(timeline)中启用或者否用该 filter。
当enable表达式为false时,一个无任何操作的 filter_frame() 将会替代默认的 filter_frame()函数被调用,该函数是针对 input 操作的一个回调函数,此时,frame将会原样传递给下一个filter
*/
#define AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC (1 << 16)
/*
和 AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC 一样,只不过不同的是,filter仍然会调用默认的 filter_frame() 回调函数。
但当 enable 的表达式为 false 时,该函数会跳过 filtering input 的处理部分,比如根据 AVFilterContext->is_disabled 这个值来决定是否真的进行 filtering 操作。
*/
#define AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL (1 << 17)
/*
为了方便检测 filter 是否支持timeline中的enable特征的一个宏
*/
#define AVFILTER_FLAG_SUPPORT_TIMELINE (AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL)
以上就是对AVFilter结构体的说明。而关于 AVFilter 的操作有:根据名称获取到相关的 AVFilter,遍历所有注册的 filter等。
const AVFilter *avfilter_get_by_name(const char *name);
/*
在 FFmpeg 已经注册的 filter 中,查找对应name的 filter。
*/
const AVFilter *av_filter_iterate(void **opaque);
/*
对 FFmpeg 中注册的 filter 做遍历。
参数opaque:一个用于保存当前遍历状态的指针,不需要用户理解。仅需知道在刚开始遍历时,它必须指向NULL。
*/
const AVFilter *avfilter_next(const AVFilter *prev);
/*
对 FFmpeg 中注册的 filter 做遍历。
*/
3、AVFilterContext
AVFilterContext 表示 AVFilter 的一个实例。其重要字段如下:
- AVFilterContext .filter:这个 filter context 是那种 filter 的实例
- AVFilterContext .name:这个 filter context 的名称
- AVFilterContext.graph:这个 filter context 属于哪个 graph
- AVFilterContext .input_pads,AVFilterContext.inputs ,AVFilterContext.nb_inputs:分别描述了input pads数组,input links数组,以及input 数量
- AVFilterContext .output_pads,AVFilterContext.outputs ,AVFilterContext.nb_outputs:分别描述了output pads数组,output links数组,以及 output 数量
- AVFilterContext.thread_type,AVFilterContext.nb_threads:线程特性和可开启的最大线程数量
- AVFilterContext.is_disabled:filter 关于 timeline 特征的开启标志
- AVFilterContext.ready:表示 filter 是否被初始化,非 0 值表示 filter 需要被初始化,且值越大表示越急切
filter context 初始化的方法有三种: avfilter_init_str() 和 avfilter_init_dict() ,以及通过 AVOption API 直接操作filter context。
注意初始化的 avfilter_init_str() 和 avfilter_init_dict() 在行为上的差别:当传入的 options 中有不被 filter 所支持的参数时,这两个函数的行为是不同,avfilter_init_str() 调用会失败,而 avfilter_init_dict() 函数则不会失败,它会将不能应用于指定 filter 的 option 通过参数返回,然后继续执行任务。
int avfilter_init_str(AVFilterContext *ctx, const char *args);
/*
使用提供的参数初始化 filter。
参数args:表示用于初始化 filter 的 options。该字符串必须使用 ":" 来分割各个键值对,
而键值对的形式为 'key=value'。如果不需要设置选项,args为空。
除了这种方式设置选项之外,还可以利用 AVOptions API 直接对 filter 设置选项。
返回值:成功返回0,失败返回一个负的错误值
*/
int avfilter_init_dict(AVFilterContext *ctx, AVDictionary **options);
/*
使用提供的参数初始化filter。
参数 options:以 dict 形式提供的 options。
返回值:成功返回0,失败返回一个负的错误值
注意:这个函数和 avfilter_init_str 函数的功能是一样的,只不过传递的参数形式不同。
但是当传入的 options 中有不被 filter 所支持的参数时,这两个函数的行为是不同:
avfilter_init_str 调用会失败,而这个函数则不会失败,它会将不能应用于指定 filter 的 option 通过参数 options 返回,然后继续执行任务。
*/
void avfilter_free(AVFilterContext *filter);
/*
释放 filter context。
*/
4、AVFilterGraph
AVFilterGraph 表示一个 filter graph,当然它也包含了 filter chain的概念。
graph 包含了诸多 filter context 实例,并负责它们之间的 link,graph 会负责创建,保存,释放 这些相关的 filter context 和 link,一般不需要用户进行管理。除此之外,它还有线程特性和最大线程数量的字段,和filter context类似。
graph 的操作有:分配一个graph,往graph中添加一个filter context,添加一个 filter graph,对 filter 进行 link 操作,检查内部的link和format是否有效,释放graph等。
考察结构体 AVFilterInOut:
typedef struct AVFilterInOut {
/** unique name for this input/output in the list */
char *name;
/** filter context associated to this input/output */
AVFilterContext *filter_ctx;
/** index of the filt_ctx pad to use for linking */
int pad_idx;
/** next input/input in the list, NULL if this is the last */
struct AVFilterInOut *next;
} AVFilterInOut;
它描述了一个 input 和 output 的链表,这是通过其字段 next 指向下一个 AVFilterInOut 实现的。注意,一个列表中要么都是 input ,要么都是 output。AVFilterInOut 用在下面的函数avfilter_graph_parse() / avfilter_graph_parse2() 中,此时,AVFilterInOut 用于表示 尚未 link 或者说是 open 的 inputs 和 outputs,函数获取到这些尚未链接的pad信息,以便在它们之间建立 link。
AVFilterInOut的API有:
AVFilterInOut *avfilter_inout_alloc(void);
/*
分配一个 inout。
*/
void avfilter_inout_free(AVFilterInOut **inout);
/*
释放一个 input。
*/
AVFilterLink的相关API有:
int avfilter_link(AVFilterContext *src, unsigned srcpad,
AVFilterContext *dst, unsigned dstpad);
/*
连接两个filter。
src和dst指定要连接的两个filter,而且只能将src的输出连接到dst的输入上。
srcpad指定输出的下标,dstpad指定输入的下标,这是因为一个filter可能有多个输入或者输出。
成功时返回0.
*/
void avfilter_link_free(AVFilterLink **link);
/*
释放一个link。
一般而言,graph会在内部保存其filter context之间的link,并负责释放这些link,因此一般不需要我们手动调用这个函数。
*/
int avfilter_config_links(AVFilterContext *filter);
/*
为 filter 的 inputs 协商相关的format,width,height等参数。
成功返回0。
*/
以下是 AVFilterGraph 的常用API:
AVFilterGraph *avfilter_graph_alloc(void);
/*
分配一个空的 filter graph.
成功返回一个 filter graph,失败返回 NULL
*/
AVFilterContext *avfilter_graph_alloc_filter(AVFilterGraph *graph,
const AVFilter *filter,
const char *name);
/*
在 filter graph 中创建一个新的 filter 实例。这个创建的实例尚未初始化。
详细描述:在 graph 中创建一个名称为 name 的 filter类型的实例。
创建失败,返回NULL。创建成功,返回 filter context实例。创建成功后的实例会加入到graph中,
可以通过 AVFilterGraph.filters 或者 avfilter_graph_get_filter() 获取。
*/
AVFilterContext *avfilter_graph_get_filter(AVFilterGraph *graph, const char *name);
/*
返回 graph 中的名为 name 的 filter context。
*/
int avfilter_graph_create_filter(AVFilterContext **filt_ctx, const AVFilter *filt,
const char *name, const char *args, void *opaque,
AVFilterGraph *graph_ctx);
/*
在 filter graph 中创建一个新的 filter context 实例,并使用 args 和 opaque 初始化这个实例。
参数 filt_ctx:返回成功创建的 filter context
成功返回正数,失败返回负的错误值。
*/
int avfilter_graph_config(AVFilterGraph *graphctx, void *log_ctx);
/*
检查 graph 的有效性,并配置其中所有的连接和格式。
有效则返回 >= 0 的数,否则返回一个负值的 AVERROR.
*/
void avfilter_graph_free(AVFilterGraph **graph);
/*
释放graph,摧毁内部的连接,并将其置为NULL。
*/
int avfilter_insert_filter(AVFilterLink *link, AVFilterContext *filt,
unsigned filt_srcpad_idx, unsigned filt_dstpad_idx);
/*
在一个已经存在的 link 中间插入一个 filter context。
参数filt_srcpad_idx和filt_dstpad_idx:指定filt要连接的输入和输出pad的index。
成功返回0.
*/
int avfilter_graph_parse(AVFilterGraph *graph, const char *filters,
AVFilterInOut *inputs, AVFilterInOut *outputs,
void *log_ctx);
/*
将一个字符串描述的 filter graph 加入到一个已经存在的 graph 中。
注意:调用者必须提供 inputs 列表和 outputs 列表。它们在调用这个函数之前必须是已知的。
注意:inputs 参数用于描述已经存在的 graph 的输入 pad 列表,也就是说,从新的被创建的 graph 来讲,它们是 output。
outputs 参数用于已经存在的 graph 的输出 pad 列表,从新的被创建的 graph 来说,它们是 input。
成功返回 >= 0,失败返回负的错误值。
*/
int avfilter_graph_parse_ptr(AVFilterGraph *graph, const char *filters,
AVFilterInOut **inputs, AVFilterInOut **outputs,
void *log_ctx);
/*
和 avfilter_graph_parse 类似。不同的是 inputs 和 outputs 参数,即做输入参数,也做输出参数。
在函数返回时,它们将会保存 graph 中所有的处于 open 状态的 pad。返回的 inout 应该使用 avfilter_inout_free() 释放掉。
注意:在字符串描述的 graph 中,第一个 filter 的输入如果没有被一个字符串标识,默认其标识为"in",最后一个 filter 的输出如果没有被标识,默认为"output"。
intpus:作为输入参数是,用于保存已经存在的graph的open inputs,可以为NULL。
作为输出参数,用于保存这个parse函数之后,仍然处于open的inputs,当然如果传入为NULL,则并不输出。
outputs:同上。
*/
int avfilter_graph_parse2(AVFilterGraph *graph, const char *filters,
AVFilterInOut **inputs,
AVFilterInOut **outputs);
/*
和 avfilter_graph_parse_ptr 函数类似,不同的是,inputs 和 outputs 函数不作为输入参数,
仅作为输出参数,返回字符串描述的新的被解析的graph在这个parse函数后,仍然处于open状态的inputs和outputs。
返回的 inout 应该使用 avfilter_inout_free() 释放掉。
成功返回0,失败返回负的错误值。
*/
char *avfilter_graph_dump(AVFilterGraph *graph, const char *options);
/*
将 graph 转化为人类可读的字符串描述。
参数options:未使用,忽略它。
*/
5、buffersrc 和 buffersink
libavfilter 提供了 buffersrc,buffersink,abuffersrc 和 abuffersink 四种类型的 filter。它们分别表示视频的 source 和 sink,音频的 source 和 sink。
它们会作为 graph 的输入点和输出点来和我们做交互,我们仅需要和它们进行数据交互即可,其 API 如下:
//buffersrc flag
enum {
//不去检测 format 的变化
AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT = 1,
//立刻将 frame 推送到 output
AV_BUFFERSRC_FLAG_PUSH = 4,
//对输入的frame新建一个引用,而非接管引用
//如果 frame 是引用计数的,那么对它创建一个新的引用;否则拷贝frame中的数据
AV_BUFFERSRC_FLAG_KEEP_REF = 8,
};
int av_buffersrc_add_frame_flags(AVFilterContext *buffer_src,
AVFrame *frame, int flags);
/*
向 buffer_src 添加一个 frame。
默认情况下,如果 frame 是引用计数的,那么这个函数将会接管其引用并重新设置 frame。
但这个行为可以由 flags 来控制。如果 frame 不是引用计数的,那么拷贝该 frame。
如果函数返回一个 error,那么 frame 并未被使用。
frame为NULL时,表示 EOF。
成功返回 >= 0,失败返回负的AVERROR。
*/
int av_buffersrc_add_frame(AVFilterContext *ctx, AVFrame *frame);
/*
添加一个 frame 到 src filter。
这个函数等同于没有 AV_BUFFERSRC_FLAG_KEEP_REF 的 av_buffersrc_add_frame_flags() 函数。
*/
int av_buffersrc_write_frame(AVFilterContext *ctx, const AVFrame *frame);
/*
添加一个 frame 到 src filter。
这个函数等同于设置了 AV_BUFFERSRC_FLAG_KEEP_REF 的av_buffersrc_add_frame_flags() 函数。
*/
//告诉 av_buffersink_get_buffer_ref() 函数,采用新建引用的方式读取 video/sample 数据,
//但不删除缓存中的相关数据。这在只需要获取相关数据,但不想要删除它们时非常有用。
#define AV_BUFFERSINK_FLAG_PEEK 1
//告诉 av_buffersink_get_buffer_ref() 不要从它的输入去请求新的 frame,如果当前有已经缓冲
//的处理后的帧,那么读取它们(并将它们从缓冲中移除),如果没有缓冲,则返回 AVERROR(EAGAIN)
//说白了,就是不再处理输入
#define AV_BUFFERSINK_FLAG_NO_REQUEST 2
int av_buffersink_get_frame_flags(AVFilterContext *ctx, AVFrame *frame, int flags);
/*
从 sink 中获取已进行 filtered 处理的帧,并将其放到参数 frame 中。
参数ctx:指向 buffersink 或 abuffersink 类型的 filter context
参数frame:获取到的被处理后的frame,使用后必须使用av_frame_unref() / av_frame_free()释放掉它
成功返回非负数,失败返回负的错误值,如 EAGAIN(表示需要新的输入数据来产生filter后的数据),
AVERROR_EOF(表示不会再有新的输入数据)
*/
int av_buffersink_get_frame(AVFilterContext *ctx, AVFrame *frame);
/*
同 av_buffersink_get_frame_flags ,不过不能指定 flag。
*/
int av_buffersink_get_samples(AVFilterContext *ctx, AVFrame *frame, int nb_samples);
/*
和 av_buffersink_get_frame 相同,不过这个函数是针对音频的,而且可以指定读取的取样数。
此时 ctx 只能指向 abuffersink 类型的 filter context。
*/