|
|
|
@ -0,0 +1,437 @@ |
|
|
|
|
在使用FFmpeg开发时,使用AVFilter的流程较为复杂,涉及到的数据结构和函数也比较多,那么使用FFmpeg AVFilter的整体流程是什么样,在其执行过程中都有哪些步骤,需要注意哪些细节?这些都是需要我们整理和总结的。 |
|
|
|
|
|
|
|
|
|
首先,我们需要引入三个概念结构体:AVFilterGraph 、AVFilterContext、AVFilter。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 一、AVFilterGraph 、AVFilterContext、AVFilter |
|
|
|
|
|
|
|
|
|
在 FFmpeg 中有多种多样的滤镜,你可以把他们当成一个个小工具,专门用于处理视频和音频数据,以便实现一定的目的。如 overlay 这个滤镜,可以将一个图画覆盖到另一个图画上;transport 这个滤镜可以将图画做旋转等等。 |
|
|
|
|
|
|
|
|
|
一个 filter 的输出可以作为另一个 filter 的输入,因此多个 filter 可以组织成为一个网状的 filter graph,从而实现更加复杂或者综合的任务。 |
|
|
|
|
|
|
|
|
|
在 libavfilter 中,我们用类型 AVFilter 来表示一个 filter,每一个 filter 都是经过注册的,其特性是相对固定的。而 AVFilterContext 则表示一个真正的 filter 实例,这和 AVCodec 以及 AVCodecContext 的关系是类似的。 |
|
|
|
|
|
|
|
|
|
AVFilter 中最重要的特征就是其所需的输入和输出。 |
|
|
|
|
|
|
|
|
|
AVFilterContext 表示一个 AVFilter 的实例,我们在实际使用 filter 时,就是使用这个结构体。AVFilterContext 在被使用前,它必须是 被初始化的,就是需要对 filter 进行一些选项上的设置,通过初始化告诉 FFmpeg 我们已经做了相关的配置。 |
|
|
|
|
|
|
|
|
|
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等。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 二、AVFilter 相关Api使用方法整理 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 1. AVFilterContext 初始化方法 |
|
|
|
|
|
|
|
|
|
AVFilterContext 的初始化方式有三种,avfilter_init_str() 和 avfilter_init_dict()、avfilter_graph_create_filter(). |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
/* |
|
|
|
|
使用提供的参数初始化 filter。 |
|
|
|
|
参数args:表示用于初始化 filter 的 options。该字符串必须使用 ":" 来分割各个键值对, 而键值对的形式为 'key=value'。如果不需要设置选项,args为空。 |
|
|
|
|
除了这种方式设置选项之外,还可以利用 AVOptions API 直接对 filter 设置选项。 |
|
|
|
|
返回值:成功返回0,失败返回一个负的错误值 |
|
|
|
|
*/ |
|
|
|
|
int avfilter_init_str(AVFilterContext *ctx, const char *args); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
/* |
|
|
|
|
使用提供的参数初始化filter。 |
|
|
|
|
参数 options:以 dict 形式提供的 options。 |
|
|
|
|
返回值:成功返回0,失败返回一个负的错误值 |
|
|
|
|
注意:这个函数和 avfilter_init_str 函数的功能是一样的,只不过传递的参数形式不同。 但是当传入的 options 中有不被 filter 所支持的参数时,这两个函数的行为是不同: avfilter_init_str 调用会失败,而这个函数则不会失败,它会将不能应用于指定 filter 的 option 通过参数 options 返回,然后继续执行任务。 |
|
|
|
|
*/ |
|
|
|
|
int avfilter_init_dict(AVFilterContext *ctx, AVDictionary **options); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
/** |
|
|
|
|
* 创建一个Filter实例(根据args和opaque的参数),并添加到已存在的AVFilterGraph. |
|
|
|
|
* 如果创建成功*filt_ctx会指向一个创建好的Filter实例,否则会指向NULL. |
|
|
|
|
* @return 失败返回负数,否则返回大于等于0的数 |
|
|
|
|
*/ |
|
|
|
|
int avfilter_graph_create_filter(AVFilterContext **filt_ctx, const AVFilter *filt, const char *name, const char *args, void *opaque, AVFilterGraph *graph_ctx); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 2. AVFilterGraph 相关的Api |
|
|
|
|
|
|
|
|
|
AVFilterGraph 表示一个 filter graph,当然它也包含了 filter chain的概念。graph 包含了诸多 filter context 实例,并负责它们之间的 link,graph 会负责创建,保存,释放 这些相关的 filter context 和 link,一般不需要用户进行管理。 |
|
|
|
|
|
|
|
|
|
graph 的操作有:分配一个graph,往graph中添加一个filter context,添加一个 filter graph,对 filter 进行 link 操作,检查内部的link和format是否有效,释放graph等。 |
|
|
|
|
|
|
|
|
|
根据上述操作,可以列举的方法分别为: |
|
|
|
|
|
|
|
|
|
**分配空的filter graph:** |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
/* |
|
|
|
|
分配一个空的 filter graph. |
|
|
|
|
成功返回一个 filter graph,失败返回 NULL |
|
|
|
|
*/ |
|
|
|
|
AVFilterGraph *avfilter_graph_alloc(void); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
**创建一个新的filter实例:** |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
/* |
|
|
|
|
在 filter graph 中创建一个新的 filter 实例。这个创建的实例尚未初始化。 |
|
|
|
|
详细描述:在 graph 中创建一个名称为 name 的 filter类型的实例。 |
|
|
|
|
创建失败,返回NULL。创建成功,返回 filter context实例。创建成功后的实例会加入到graph中, |
|
|
|
|
可以通过 AVFilterGraph.filters 或者 avfilter_graph_get_filter() 获取。 |
|
|
|
|
*/ |
|
|
|
|
AVFilterContext *avfilter_graph_alloc_filter(AVFilterGraph *graph, const AVFilter *filter, const char *name); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**返回名字为name的filter context:** |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
/* |
|
|
|
|
返回 graph 中的名为 name 的 filter context。 |
|
|
|
|
*/ |
|
|
|
|
AVFilterContext *avfilter_graph_get_filter(AVFilterGraph *graph, const char *name); |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
**在 filter graph 中创建一个新的 filter context 实例,并使用args和opaque初始化这个filter context:** |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
/* |
|
|
|
|
在 filter graph 中创建一个新的 filter context 实例,并使用 args 和 opaque 初始化这个实例。 |
|
|
|
|
|
|
|
|
|
参数 filt_ctx:返回成功创建的 filter context |
|
|
|
|
|
|
|
|
|
返回值:成功返回正数,失败返回负的错误值。 |
|
|
|
|
*/ |
|
|
|
|
int avfilter_graph_create_filter(AVFilterContext **filt_ctx, const AVFilter *filt, const char *name, const char *args, void *opaque, AVFilterGraph *graph_ctx); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**配置 AVFilterGraph 的链接和格式:** |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
/* |
|
|
|
|
检查 graph 的有效性,并配置其中所有的连接和格式。 |
|
|
|
|
有效则返回 >= 0 的数,否则返回一个负值的 AVERROR. |
|
|
|
|
*/ |
|
|
|
|
int avfilter_graph_config(AVFilterGraph *graphctx, void *log_ctx); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
**释放AVFilterGraph:** |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
/* |
|
|
|
|
释放graph,摧毁内部的连接,并将其置为NULL。 |
|
|
|
|
*/ |
|
|
|
|
void avfilter_graph_free(AVFilterGraph **graph); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
**在一个已经存在的link中插入一个FilterContext:** |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
/* |
|
|
|
|
在一个已经存在的 link 中间插入一个 filter context。 |
|
|
|
|
参数filt_srcpad_idx和filt_dstpad_idx:指定filt要连接的输入和输出pad的index。 |
|
|
|
|
成功返回0. |
|
|
|
|
*/ |
|
|
|
|
int avfilter_insert_filter(AVFilterLink *link, AVFilterContext *filt, unsigned filt_srcpad_idx, unsigned filt_dstpad_idx); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
将字符串描述的filter graph 加入到一个已存在的graph中: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
/* |
|
|
|
|
将一个字符串描述的 filter graph 加入到一个已经存在的 graph 中。 |
|
|
|
|
|
|
|
|
|
注意:调用者必须提供 inputs 列表和 outputs 列表。它们在调用这个函数之前必须是已知的。 |
|
|
|
|
|
|
|
|
|
注意:inputs 参数用于描述已经存在的 graph 的输入 pad 列表,也就是说,从新的被创建的 graph 来讲,它们是 output。 |
|
|
|
|
outputs 参数用于已经存在的 graph 的输出 pad 列表,从新的被创建的 graph 来说,它们是 input。 |
|
|
|
|
|
|
|
|
|
成功返回 >= 0,失败返回负的错误值。 |
|
|
|
|
*/ |
|
|
|
|
int avfilter_graph_parse(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_parse_ptr(AVFilterGraph *graph, const char *filters, |
|
|
|
|
AVFilterInOut **inputs, AVFilterInOut **outputs, void *log_ctx); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
/* |
|
|
|
|
和 avfilter_graph_parse_ptr 函数类似,不同的是,inputs 和 outputs 函数不作为输入参数, |
|
|
|
|
仅作为输出参数,返回字符串描述的新的被解析的graph在这个parse函数后,仍然处于open状态的inputs和outputs。 |
|
|
|
|
返回的 inout 应该使用 avfilter_inout_free() 释放掉。 |
|
|
|
|
|
|
|
|
|
成功返回0,失败返回负的错误值。 |
|
|
|
|
*/ |
|
|
|
|
int avfilter_graph_parse2(AVFilterGraph *graph, const char *filters, |
|
|
|
|
AVFilterInOut **inputs, AVFilterInOut **outputs); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**将graph转换为可读取的字符串描述:** |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
/* |
|
|
|
|
将 graph 转化为可读的字符串描述。 |
|
|
|
|
参数options:未使用,忽略它。 |
|
|
|
|
*/ |
|
|
|
|
char *avfilter_graph_dump(AVFilterGraph *graph, const char *options); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 三、FFmpeg Filter Buffer 和 BufferSink 相关APi的使用方法整理 |
|
|
|
|
|
|
|
|
|
Buffer 和 BufferSink 作为 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, |
|
|
|
|
}; |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**向 buffer_src 添加一个Frame:** |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
/* |
|
|
|
|
向 buffer_src 添加一个 frame。 |
|
|
|
|
|
|
|
|
|
默认情况下,如果 frame 是引用计数的,那么这个函数将会接管其引用并重新设置 frame。 |
|
|
|
|
但这个行为可以由 flags 来控制。如果 frame 不是引用计数的,那么拷贝该 frame。 |
|
|
|
|
|
|
|
|
|
如果函数返回一个 error,那么 frame 并未被使用。frame为NULL时,表示 EOF。 |
|
|
|
|
成功返回 >= 0,失败返回负的AVERROR。 |
|
|
|
|
*/ |
|
|
|
|
int av_buffersrc_add_frame_flags(AVFilterContext *buffer_src, AVFrame *frame, int flags); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**添加一个frame到 src filter:** |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
/* |
|
|
|
|
添加一个 frame 到 src filter。 |
|
|
|
|
这个函数等同于没有 AV_BUFFERSRC_FLAG_KEEP_REF 的 av_buffersrc_add_frame_flags() 函数。 |
|
|
|
|
*/ |
|
|
|
|
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); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
**从sink获取已filtered处理的帧,并放到参数frame中:** |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
/* |
|
|
|
|
从 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_flags(AVFilterContext *ctx, AVFrame *frame, int flags); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
/* |
|
|
|
|
同 av_buffersink_get_frame_flags ,不过不能指定 flag。 |
|
|
|
|
*/ |
|
|
|
|
int av_buffersink_get_frame(AVFilterContext *ctx, AVFrame *frame) |
|
|
|
|
/* |
|
|
|
|
和 av_buffersink_get_frame 相同,不过这个函数是针对音频的,而且可以指定读取的取样数。此时 ctx 只能指向 abuffersink 类型的 filter context。 |
|
|
|
|
*/ |
|
|
|
|
int av_buffersink_get_samples(AVFilterContext *ctx, AVFrame *frame, int nb_samples); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 四、FFmpeg AVFilter 使用整体流程 |
|
|
|
|
|
|
|
|
|
下图就是FFmpeg AVFilter在使用过程中的流程图: |
|
|
|
|
|
|
|
|
|
![img](https://img2020.cnblogs.com/blog/682616/202104/682616-20210415141146493-1433896880.jpg) |
|
|
|
|
|
|
|
|
|
我们对上图先做下说明,理解下图中每个步骤的关系,然后,才从代码的角度来给出其使用的步骤。 |
|
|
|
|
|
|
|
|
|
1. 最顶端的AVFilterGraph,这个结构前面介绍过,主要管理加入的过滤器,其中加入的过滤器就是通过函数avfilter_graph_create_filter来创建并加入,这个函数返回是AVFilterContext(其封装了AVFilter的详细参数信息)。 |
|
|
|
|
|
|
|
|
|
2. buffer和buffersink这两个过滤器是FFMpeg为我们实现好的,buffer表示源,用来向后面的过滤器提供数据输入(其实就是原始的AVFrame);buffersink过滤器是最终输出的(经过过滤器链处理后的数据AVFrame),其它的诸如filter 1 等过滤器是由avfilter_graph_parse_ptr函数解析外部传入的过滤器描述字符串自动生成的,内部也是通过avfilter_graph_create_filter来创建过滤器的。 |
|
|
|
|
|
|
|
|
|
3. 上面的buffer、filter 1、filter 2、filter n、buffersink之间是通过avfilter_link函数来进行关联的(通过AVFilterLink结构),这样子过滤器和过滤器之间就通过AVFilterLink进行关联上了,前一个过滤器的输出就是下一个过滤器的输入,注意,除了源和接收过滤器之外,其它的过滤器至少有一个输入和输出,这很好理解,中间的过滤器处理完AVFrame后,得到新的处理后的AVFrame数据,然后把新的AVFrame数据作为下一个过滤器的输入。 |
|
|
|
|
|
|
|
|
|
4. 过滤器建立完成后,首先我们通过av_buffersrc_add_frame把最原始的AVFrame(没有经过任何过滤器处理的)加入到buffer过滤器的fifo队列。 |
|
|
|
|
|
|
|
|
|
5. 然后调用buffersink过滤器的av_buffersink_get_frame_flags来获取处理完后的数据帧(这个最终放入buffersink过滤器的AVFrame是通过之前创建的一系列过滤器处理后的数据)。 |
|
|
|
|
|
|
|
|
|
使用流程图就介绍到这里,下面结合上面的使用流程图详细说下FFMpeg中使用过滤器的步骤,这个过程我们分为三个部分:过滤器构建、数据加工、资源释放。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 过滤器构建: |
|
|
|
|
|
|
|
|
|
1)分配AVFilterGraph |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
AVFilterGraph* graph = avfilter_graph_alloc(); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
2)创建过滤器源 |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
char srcArgs[256] = {0}; |
|
|
|
|
AVFilterContext *srcFilterCtx; |
|
|
|
|
AVFilter* srcFilter = avfilter_get_by_name("buffer"); |
|
|
|
|
avfilter_graph_create_filter(&srcFilterCtx, srcFilter ,"out_buffer", srcArgs, NULL, graph); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
3)创建接收过滤器 |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
AVFilterContext *sinkFilterCtx; |
|
|
|
|
AVFilter* sinkFilter = avfilter_get_by_name("buffersink"); |
|
|
|
|
avfilter_graph_create_filter(&sinkFilterCtx, sinkFilter,"in_buffersink", NULL, NULL, graph); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
4)生成源和接收过滤器的输入输出 |
|
|
|
|
|
|
|
|
|
这里主要是把源和接收过滤器封装给AVFilterInOut结构,使用这个中间结构来把过滤器字符串解析并链接进graph,主要代码如下: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
AVFilterInOut *inputs = avfilter_inout_alloc(); |
|
|
|
|
AVFilterInOut *outputs = avfilter_inout_alloc(); |
|
|
|
|
outputs->name = av_strdup("in"); |
|
|
|
|
outputs->filter_ctx = srcFilterCtx; |
|
|
|
|
outputs->pad_idx = 0; |
|
|
|
|
outputs->next = NULL; |
|
|
|
|
inputs->name = av_strdup("out"); |
|
|
|
|
inputs->filter_ctx = sinkFilterCtx; |
|
|
|
|
inputs->pad_idx = 0; |
|
|
|
|
inputs->next = NULL; |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
这里源对应的AVFilterInOut的name最好定义为in,接收对应的name为out,因为FFMpeg源码里默认会通过这样个name来对默认的输出和输入进行查找。 |
|
|
|
|
|
|
|
|
|
5)通过解析过滤器字符串添加过滤器 |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
const *char filtergraph = "[in1]过滤器名称=参数1:参数2[out1]"; |
|
|
|
|
int ret = avfilter_graph_parse_ptr(graph, filtergraph, &inputs, &outputs, NULL); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
这里过滤器是以字符串形式描述的,其格式为:[in]过滤器名称=参数[out],过滤器之间用,或;分割,如果过滤器有多个参数,则参数之间用:分割,其中[in]和[out]分别为过滤器的输入和输出,可以有多个。 |
|
|
|
|
|
|
|
|
|
6)检查过滤器的完整性 |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
avfilter_graph_config(graph, NULL); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 数据加工 |
|
|
|
|
|
|
|
|
|
1)向源过滤器加入AVFrame |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
AVFrame* frame; // 这是解码后获取的数据帧 |
|
|
|
|
int ret = av_buffersrc_add_frame(srcFilterCtx, frame); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
2)从buffersink接收处理后的AVFrame |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
int ret = av_buffersink_get_frame_flags(sinkFilterCtx, frame, 0); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
现在我们就可以使用处理后的AVFrame,比如显示或播放出来。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 3.资源释放 |
|
|
|
|
|
|
|
|
|
使用结束后,调用avfilter_graph_free(&graph);释放掉AVFilterGraph类型的graph。 |