FFmpeg API 之 libavfilter

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。
*/

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值