[ffmpeg] 定制滤波器

若是有定制ffmpeg滤波器的需求,有两个结构体是必需要了解的:AVFilter、AVFilterPad,所定制的滤波器主要就是经过填充这两个结构体来实现的。咱们下面将详细解析这两个结构体,并经过对滤波器的初始化流程以及滤波流程进行分析,进一步加深对ffmpeg滤波框架的了解。html

 

AVFilter

AVFilter就是一个滤波器的主体,其结构体定义以下:框架

typedef struct AVFilter {
    const char *name;
    const char *description;
    const AVFilterPad *inputs;
    const AVFilterPad *outputs;
    const AVClass *priv_class;
    int flags;
    int (*preinit)(AVFilterContext *ctx);
    int (*init)(AVFilterContext *ctx);
    int (*init_dict)(AVFilterContext *ctx, AVDictionary **options);
    void (*uninit)(AVFilterContext *ctx);
    int (*query_formats)(AVFilterContext *);
    int priv_size;      
    int flags_internal; 
    struct AVFilter *next;
    int (*process_command)(AVFilterContext *, const char *cmd, const char *arg, char *res, int res_len, int flags);
    int (*init_opaque)(AVFilterContext *ctx, void *opaque);
    int (*activate)(AVFilterContext *ctx);
} AVFilter;

其各个成员变量有以下含义:ide

name 滤波器名字。
description 滤波器的简短介绍。
inputs 滤波器入口(AVFilterPad)列表。
outputs 滤波器出口(AVFilterPad)列表。
priv_class 主要用于维护用户传入的参数(AVOption)的结构体,通常来讲用户向滤波器传入参数有两个手段:在建立滤波器实例的时候传入指定参数的字符串,或者在建立完成滤波器实例后经过av_opt_set之类的接口传入字符串。
flags 滤波器标志。
preinit 滤波器预初始化函数。这个函数会在建立滤波器实例的开头被调用。
init 滤波器自身的特制初始化函数。初始化,即avfilter_graph_create_filter,能够被分解成通用的初始化以及特制初始化。
通用初始化一般包含三个步骤:
  1. 建立用于存放滤波器实例的内存,进行一些初始化默认的赋值处理。
  2.把传入的字符串解析进行解析获得字典的两要素:参数名称key,参数值val。
  3.经过priv_class所维护的AVOption,能够找到名为key的参数对应的内存位置(即滤波器实例的私有结构体priv中名称为key的参数的位置),并把val写入该位置当中便可完成参数设置。私有结构体priv中的参数就一般就是滤波器的实际参数,在进行滤波时会根据其中的参数进行滤波处理。
特制的初始化有不少不一样的用途,好比检查参数,若是检查到所输入的参数中缺乏一些重要的参数,则能够返回负值来表示初始化错误。
init_dict 与上方init功能相同,不太经常使用。
uninit 若是在init函数出现错误则会调用uninit来作一些后续处理。
query_formats 为了进行滤波器之间的滤波格式协商,AVFilter的query_formats函数会去设置AVFilterLink上的in_formats/out_formats等,这是格式协商的第一步。
priv_size 滤波器实例的私有结构体(priv)的大小。咱们前面也说了priv当中的参数就是滤波器的实际参数,而不一样滤波器的参数不一样,那么所占用的空间也不会同样,所以在建立滤波器实例的时候会根据priv_size来开辟用于存放参数的空间。
flags_internal 滤波器内部标志。
next 在新版本ffmpeg中不会使用到这个next参数。
老版本的ffmpeg须要用avfilter_rigister来注册滤波器(AVFilter),注册的时候就会使用这个next参数,使得全部注册了的滤波器造成一个滤波器链表,若是须要某个滤波器则能够从该链表中获取。
新版本的ffmpeg使用的是列表(filter_list)来列出全部的滤波器(AVFilter),通常来讲,若是想得到滤波器,能够调用avfilter_get_by_name来轮询列表得到。
process_command 通常来讲,滤波参数的设置有两种方式:
  1. 在初始化时(avfilter_graph_create_filter),输入参数字符串。
  2. 在初始化后,配置整个滤波图前(avfilter_graph_config),调用av_opt_set之类的接口输入参数。
为了保证滤波器正常运行,在滤波的过程当中通常是不会对滤波参数进行修改的。固然,在滤波过程当中调用av_opt_set之类的函数是能够修改滤波参数,可是并不能保证滤波器会按照咱们预想地那样运行。由于若是按照前面的两种方式设置滤波参数,后面可能还会执行AVFilterPad的config_props操做,而在滤波过程当中经过av_opt_set之类的函数去设置滤波参数时是不会再回去继续执行这一步的。
不过现实当中确实存在在滤波过程当中修改滤波参数的需求,好比说播放音乐时能够调整EQ。此时就能够经过实现process_command这个函数来实现滤波过程当中的各类变化。
使用avfilter_graph_send_command就能触发所指定的滤波器调用其process_command函数。
init_opaque 与init功能相同,不经常使用。
activate 滤波函数。滤波函数有两种实现方式,一种是经过activate来实现,另外一种是后面会说到的AVFilterPad中的filter_frame以及request_frame函数。若是是采用activate的方式,就须要在activate内实现如下流程:
  1. 获取前面的滤波器实例输出的帧。具体操做就是调用ff_inlink_consume_frame来从inlink获取前面滤波器实例输出的帧。
        若是所须要的帧未准备好,则须要通知相应的滤波器实例,代表当前滤波器须要帧。具体操做就是调用ff_inlink_request_frame来设置inlink上的frame_wanted_out,该变量就是用于代表inlink的目标滤波器实例,即当前滤波器实例须要前一个滤波器实例输出帧。
        若是所须要的帧已准备好,就能够执行滤波操做。
  2. 向后面的滤波器实例输出滤波完成的帧。具体操做就是调用ff_filter_frame来向outlink输出帧。

 

 

AVFilterPad

AVFilterPad是滤波器的出口或者入口,其结构定义以下:函数

struct AVFilterPad {
    const char *name;
    enum AVMediaType type;
    AVFrame *(*get_video_buffer)(AVFilterLink *link, int w, int h);
    AVFrame *(*get_audio_buffer)(AVFilterLink *link, int nb_samples);
    int (*filter_frame)(AVFilterLink *link, AVFrame *frame);
    int (*poll_frame)(AVFilterLink *link);
    int (*request_frame)(AVFilterLink *link);
    int (*config_props)(AVFilterLink *link);
    int needs_fifo;
    int needs_writable;
};

各成员变量具备以下含义:3d

name 出/入口(input/output pads)名字。
type 支持的帧类型:AVMEDIA_TYPE_VIDEO/AVMEDIA_TYPE_AUDIO。
get_video_buffer
(input pads only)
提供用于写入视频图像的buffer,通常是向前一个滤波器实例提供。
一个滤波器在滤波过程当中,可能须要额外的buffer来进行滤波处理,好比scale或者aresample这种格式转换滤波器,在进行滤波处理时,有输入帧做为源材料,输入帧有确实存在的buffer,而为了进行输出,咱们须要额外的buffer来存放格式转换后的帧。所需的buffer除了指定的宽与高以外,还有像素格式,这三点是影响buffer大小的因素,像素格式就是输出链上的格式(link->format)。
若是一个滤波器实例须要buffer,能够经过ff_get_video_buffer(outlink, w, h)来调用下一个滤波器对应AVFilterPad上的get_video_buffer函数。不过通常来讲,是不须要AVFilterPad去实现get_video_buffer这个函数的,由于若是AVFilterPad不实现这个函数,则会调用默认的ff_default_get_video_buffer,该函数会根据输入的w,h以及link的format来提供buffer。
※ffmpeg中仅有几个filter实现了get_video_buffer(vflip,swapuv等),不过其内部也是调用了ff_default_get_video_buffer,而且其它部分的代码看起来并无起到什么实际做用。
get_audio_buffer
(input pads only)
含义同上,提供给上一个滤波器实例调用。
调用接口为ff_get_audio_buffer(outlink, nb_samples),若是没有实现该函数,则会默认调用到函数ff_default_get_audio_buffer,buffer的大小受到输入参数的nb_samples以及link->channels,link->format的影响。
通常来讲不须要滤波器实现get_audio_buffer函数。
※ffmpeg中并无实现了get_audio_buffer的滤波器。
filter_frame
(input pads only)
filter_frame是最多见的滤波实现函数。若是AVFilter没有实现activate函数,则会调用默认的activate函数ff_filter_activate_default,该函数最终会调用到filter_frame来提供滤波的实现。
filter_frame的输入参数中包括滤波实例的inlink以及从inlink上提取的frame,通常来讲filter_frame会对该frame进行滤波处理,而后调用ff_filter_frame向outlink输出滤波后的帧。
poll_frame
(output pads only)
设定上poll_frame是用于查看前一个滤波器实例的request_frame能返回多少帧,不过实际上应该是没有用到这个函数的地方。
request_frame
(output pads only)
request_frame其实也是一个用于产生帧的滤波函数,不过观察request_frame的参数能够发现该函数并无frame做为输入参数,这代表了request_frame有特定的应用场景:
1. 若是一个滤波器是源滤波器,仅须要输出帧,则能够在request_frame内生成帧,而后调用ff_filter_frame把帧输出到outlink。ffmpeg中源滤波器的源文件都带有src关键字,如buffersrc以及vsrc/asrc为开头的滤波器。
2. 若是一个滤波器但愿在EOF后继续输出帧,则能够用request_frame调用ff_filter_frame来进行输出。
config_props config_props的调用发生在query_formats以后,此时滤波格式的协调已经完成,也就已经肯定了滤波器实例的输入以及输出格式(inlink->format/outlink->format)。若是某些设置须要使用到这些输入输出格式,就能够在config_props中进行设置。如aresampe在config_props中就利用协调完成的format、channel_layout、sample_rate来进行重采样的参数设置。
needs_fifo
(input pad only)
代表只有当滤波器实例主动请求帧(调用ff_inlink_request frame或者ff_request_frame)的时候,前一个滤波器实例才会向当前滤波器实例输出帧(ff_filter_frame)。
若是needs_fifo为1,会自动在当前滤波器实例与前一个滤波器实例之间插入一个名为fifo的滤波器,该滤波器实现了上述功能。
needs_writable
(input pad only)
代表滤波器须要对pad对应的link所输入的frame进行写入。如进行字幕渲染的ass滤波器就须要对输入的视频帧进行写入。

 

 

初始化流程

首先是avfilter_graph_create_filter,即建立滤波器实例。如前面所说,这个函数会在最开头调用滤波器的preinit函数,而后建立滤波器实例并作一些简单的初始化,解析输入的字符串,最后调用滤波器的init函数。orm

image

在构建好一整个AVFilterGraph后,就能够调用avfilter_graph_config来作graph最后的配置。视频

image

其中graph_insert_fifos中就会对设定了needs_fifo=1的input pad所在的link插入名为fifo的滤波器。htm

            if (!link->dstpad->needs_fifo)
                continue;

            fifo = f->inputs[j]->type == AVMEDIA_TYPE_VIDEO ?
                   avfilter_get_by_name("fifo") :
                   avfilter_get_by_name("afifo");

            snprintf(name, sizeof(name), "auto_fifo_%d", fifo_count++);

            ret = avfilter_graph_create_filter(&fifo_ctx, fifo, name, NULL,
                                               NULL, graph);

            ret = avfilter_insert_filter(link, fifo_ctx, 0, 0);

graph_config_formats中如前一篇文章所说,就是对整个graph中滤波格式进行协商,协商事后能够肯定全部link上的格式。blog

graph_config_links主要目的就是调用pad中的config_props,那么config_props就能根据前面协商获得的link格式作进一步的操做。接口

 

 

滤波流程

通常来讲,用户会按照以下方式调用滤波API来进行滤波处理:

    ret = av_buffersrc_add_frame(in_filter, pFrame);

    while((ret = av_buffersink_get_frame(out_filter, pFrame))>=0){
        //TODO
    }

向graph输入帧

滤波的流程都是首先调用av_buffersrc_add_frame,从向buffersrc输入帧开始的

image

能够看到调用了buffersrc的request_frame函数,该函数最后用ff_filter_frame向outlink输出帧。、

 

从graph提取帧

而后调用av_buffersink_get_frame,尝试得到buffersink输出的帧,若是返回值大于0则代表获得了一帧,正常状况下若是没法得到帧一般会返回EAGAIN,这代表要求用户向buffersrc输入更多的帧。

av_buffersink_get_frame会向下调用到get_frame_internal,该函数主要做用就是调用滤波器进行滤波,并返回滤波完成的帧。函数实现以下:

static int get_frame_internal(AVFilterContext *ctx, AVFrame *frame, int flags, int samples)
{
    BufferSinkContext *buf = ctx->priv;
    AVFilterLink *inlink = ctx->inputs[0];
    int status, ret;
    AVFrame *cur_frame;
    int64_t pts;

    if (buf->peeked_frame)
        return return_or_keep_frame(buf, frame, buf->peeked_frame, flags);

    while (1) {
        ret = samples ? ff_inlink_consume_samples(inlink, samples, samples, &cur_frame) :
                        ff_inlink_consume_frame(inlink, &cur_frame);
        if (ret < 0) {
            return ret;
        } else if (ret) {
            /* TODO return the frame instead of copying it */
            return return_or_keep_frame(buf, frame, cur_frame, flags);
        } else if (ff_inlink_acknowledge_status(inlink, &status, &pts)) {
            return status;
        } else if ((flags & AV_BUFFERSINK_FLAG_NO_REQUEST)) {
            return AVERROR(EAGAIN);
        } else if (inlink->frame_wanted_out) {
            ret = ff_filter_graph_run_once(ctx->graph);
            if (ret < 0)
                return ret;
        } else {
            ff_inlink_request_frame(inlink);
        }
    }
}

该函数有以下实现逻辑:

  1. 调用ff_inlink_consume_frame,看是否能够得到滤波完成的帧,若是获得了滤波后的帧,则调用return_or_keep_frame进行返回。
  2. 调用ff_inlink_acknowledge_status,查看滤波过程当中是否出了差错,或者是否到了EOF,是则返回错误。
  3. frame_wanted_out用于代表当前滤波器是否已经得到了前一个滤波器输出的一帧,等于1则表示未得到,那么须要调用ff_filter_graph_run_once;等于0则表示已得到一帧,或者是第一次调用该函数。

跳出get_frame_internal里面的while循环只有下面几种状况:

  1. ff_inlink_consume_frame返回值不为0,代表能够返回滤波后的帧。
  2. ff_inlink_acknowledge_status返回值不为0,代表滤波器运行过程当中出错。
  3. ff_filter_graph_run_once返回值小于0,通常状况下为EAGAIN,代表滤波器须要输入更多的帧做为原料。

若是是第一次调用av_buffersink_get_frame,当进入该函数时,正常状况下会按照以下步骤执行:

  1. 因为是第一次调用,所以frame_wanted_out为0,那么第一次循环会去执行ff_inlink_request_frame,这个函数会把frame_wanted_out设置为1,代表当前滤波器未得到前一个滤波器输出的帧。
  2. 而后下一次循环时因为frame_wanted_out为1,会去调用ff_filter_graph_run_once。
  3. 一般来讲,通过几回循环调用ff_filter_graph_run_once后,会获得滤波后的帧,此时ff_inlink_consume_frame会返回1,所以就能调用return_or_keep_frame来向用户返回滤波后的帧了。
  4. 若是须要输入更多的帧才能继续进行滤波,那么会从ff_filter_graph_run_once返回EAGAIN。

 

激活滤波器

咱们前面提到的ff_filter_graph_run_once就是查找已经就绪(ready)的滤波器实例,并对该滤波器进行激活。所获得的滤波器实例会知足两个条件:

  1. 滤波器的ready值(优先级)更高的会被选上
  2. 在此基础上,由于查找顺序是按照用户调用avfilter_graph_create_filter的顺序,所以最早建立的滤波器实例会被选中。
AVFilterContext *avfilter_graph_alloc_filter(AVFilterGraph *graph,
                                             const AVFilter *filter,
                                             const char *name)
{
    s = ff_filter_alloc(filter, name);
    graph->filters[graph->nb_filters++] = s;
}

int ff_filter_graph_run_once(AVFilterGraph *graph)
{
    filter = graph->filters[0];
    for (i = 1; i < graph->nb_filters; i++)
        if (graph->filters[i]->ready > filter->ready)
            filter = graph->filters[i];
    if (!filter->ready)
        return AVERROR(EAGAIN);
    return ff_filter_activate(filter);
}

 

选出优先级最高的滤波器实例后,首先把该滤波器就绪状态清零,而后开始执行激活操做:若是该滤波器实现了activate函数,则调用该函数,不然调用默认的激活函数ff_filter_activate_default。

int ff_filter_activate(AVFilterContext *filter)
{
    filter->ready = 0;
    ret = filter->filter->activate ? filter->filter->activate(filter) :
          ff_filter_activate_default(filter);
}

若是调用的是默认的激活函数,则会按照如下优先级进行激活处理:

static int ff_filter_activate_default(AVFilterContext *filter)
{
    unsigned i;

    for (i = 0; i < filter->nb_inputs; i++) {
        if (samples_ready(filter->inputs[i], filter->inputs[i]->min_samples)) {
            return ff_filter_frame_to_filter(filter->inputs[i]);
        }
    }
    for (i = 0; i < filter->nb_inputs; i++) {
        if (filter->inputs[i]->status_in && !filter->inputs[i]->status_out) {
            av_assert1(!ff_framequeue_queued_frames(&filter->inputs[i]->fifo));
            return forward_status_change(filter, filter->inputs[i]);
        }
    }
    for (i = 0; i < filter->nb_outputs; i++) {
        if (filter->outputs[i]->frame_wanted_out &&
            !filter->outputs[i]->frame_blocked_in) {
            return ff_request_frame_to_filter(filter->outputs[i]);
        }
    }
    return FFERROR_NOT_READY;
}

能够发现共有三种激活处理方式,分别为:

  • 从上往下传递滤波完成的帧。
  • 从上往下传递错误代码。
  • 从下往上传递帧的请求。

前面的方式优先级更高。下面咱们会对这三种方式进行较为详细的分析

ff_filter_frame_to_filter

触发条件是:当前滤波器实例的input link上的samples_ready。这samples_ready代表了前面的滤波器实例已经执行ff_filter_frame向当前滤波器实例的input link输出了帧。

有了上述条件,那么为了完成当前滤波器实例的滤波操做,接下来会执行:

  1. 从input link上提取出帧。
  2. 调用当前滤波器的filter_frame函数来执行滤波操做。
  3. 通常来讲滤波处理完成事后也会调用ff_filter_frame来把滤波完成的帧输出到output link。
  4. ff_filter_frame接下来会把下一个滤波器实例设为就绪状态。
  5. 若是在执行filter_frame时出错,没能完成滤波处理,则会把错误代码写入input link的status_out,代表该link没法顺利输出frame到当前filter。
  6. 若是执行filter_frame成功,考虑到前一个滤波器实例可能输出了不止一帧,所以再次把当前滤波器实例设为就绪状态,若是前一个滤波器确实输出了多个帧,这部操做会使得这多个帧都被当前滤波器实例滤波完成后才会执行下一个滤波器实例的滤波处理。

image

 

foward_status_change

触发条件是:当前滤波器实例的input link上有status_in && !status_out。status_in不为0代表调用前一个滤波器实例的request_frame时出现了错误,status_in的值就是错误代码,咱们须要向后面的滤波器实例传播这个错误代码。

forward_status_change是传播错误代码的入口,传播错误代码的很大一步分的实现都在ff_request_frame函数内。基于上述条件,即调用前一个滤波器实例的request_frame时出现了错误。

  1. forward_status_change会调用当前滤波器实例的request_frame来传输错误代码,request_frame通常内部都会调用到ff_request_farme函数。
  2. 错误代码status_in会致使ff_request_frame走入错误处理分支。
  3. 把status_out的值设置为status_in。
  4. 那么返回值也会是该错误代码,这用于表示当前request_frame也出现了错误。
  5. 所以在返回的时候会把下一个滤波器实例的input link(即当前滤波器实例的output link)上的status_in设置为错误代码。
  6. 把frame_wanted_out设置为0用于表示当前滤波器实例的错误处理已完成,避免再次调用当前滤波器实例的request_frame函数作重复的错误处理。
  7. 把下一个滤波器实例设置为就绪状态。

image

 

ff_request_frame_to_filter

触发条件是:当前滤波器的output link上有frame_wanted_out以及!frame_blocked_in。其中frame_blocked_in用于防止重复对同一个link执行request frame操做,重要的是frame_wanted_out。咱们前面也说过,若是一个link上的frame_wanted_out=1,代表该link的dst要求src输出帧,具体一点,就是这会致使执行src的request_frame函数。那么这里的output link上的frame_wanted_out=1就会致使:

  1. 当前的滤波器实例的request_frame被执行。
  2. request_frame内通常会调用ff_request_frame函数。通常状况下,该函数会把input link的frame_wanted_out设置为1。
  3. 而后把前一个滤波器实例设为就绪状态。
  4. 若是request_frame出错的话,就是request_frame没能处理完成,则会把错误代码写入outlink的status_in,代表该link没法顺序求得当前滤波器实例的帧。

image

 

注意:上面所描述的三种处理方式只是比较常见方式。在实际应用中,有些滤波器在filter_frame内调用ff_request_frame,也有些滤波器在request_frame内调用ff_filter_frame的,并且实现了activate函数的滤波器并不会执行上述流程,若是所有都列出来就过于冗余了,具体问题具体分析是很重要的。

 

此外,咱们能够看到在执行ff_filter_frame时会把ready值设置为300;在执行ff_request_frame时会吧ready值设置为100;在保存错误代码时会把ready值设置为200。这代表了优先进行滤波处理,其次是错误处理,最后才是帧请求。

滤波的激活处理能够按照以下进行总结:

  1. 优先执行滤波处理,若是前面的滤波器实例输出帧,当前的滤波器实例就能够执行滤波处理,并把滤波后获得的帧向后传递。
  2. 若是在滤波时缺少帧做为原料,则向前要求前面的滤波器实例输出帧,这个要求会一直往前发送,直到有滤波器实例能够输出帧。
  3. 若是在滤波或者请求帧的过程当中出错了,就把错误日后传递,典型的如EOF就是这种传递方式。
相关文章
相关标签/搜索