avfilter 使用与代码分析1

------------------------------------------------------------
author:hjjdebug
date: 2024年 08月 07日 星期三 17:25:35 CST
description: avfilter 使用与代码分析1
------------------------------------------------------------
虽然我也能写出avfilter过滤器的代码,但总是感觉理解不过深入,所以需要认真分析一下.

想知其然又知其所以然的途径就是分析代码.

1. 简单过滤器,一个输入,一个输出
_____________        _____________________        _____________
|            |      |                     |      |             |
| buffer_src | ---> | simple filter graph | ---> | buffer_sink |
|____________|      |_____________________|      |_____________|

2. 复杂过滤器,可以多个输入,多个输出.
_________
|         |
| input 0 |\                    __________
|_________| \                  |          |
             \   _________    /| output 0 |
              \ |         |  / |__________|
 _________     \| complex | /
|         |     |         |/
| input 1 |---->| filter  |\
|_________|     |         | \   __________
               /| graph   |  \ |          |
              / |         |   \| output 1 |
 _________   /  |_________|    |__________|
|         | /
| input 2 |/
|_________|

过滤器就像画硬件原理图一样, 看起来很爽, 有输入,有处理,有输出,怎么实现的呢?

我们分析一个实例,就是Doc/Example/filtering_video.c, 代码我就不copy了,节省篇幅。
这是一个简单的过滤器。
------------------------------------------------------------
1. 根据文件名称打开输入文件. 
------------------------------------------------------------
   这里简单说一下,就是找到文件格式并打开文件, 
   找到了decoder,再打开了对应的codec.
------------------------------------------------------------
2. 根据过滤器字符串初始化过滤器
------------------------------------------------------------
   例如这里的字符串如下:
   const char *filter_descr = "scale=240:135";
   这是一个scale 过滤器,它的参数是240:135,即将frame的宽度,高度缩放为240:135
   那怎样初始化呢?
2.1: 先分配一个过滤图,filter_graph
    AVFilterGraphh *filter_graph = avfilter_graph_alloc();
    这个意思就是说,我要创建一个空白图, 上面要放什么元件一会再说.

2.2: 在图上放上一个AVFilterContext 名字叫buffsrc_ctx
    const AVFilter *buffersrc  = avfilter_get_by_name("buffer"); 
    ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
                                       args, NULL, filter_graph);

   给名称"buffer" 就能找到对应的filter, 相当于一个map 项
   由这个filter 就能创建一个 AVFilterContext *buffersrc_ctx;
   所谓Context 就是一个filter的具体实例, filter在处理具体数据时,可能要保留一些参数或结果,
   就放到context下方便使用, 这就是对象的概念.

   创建src filter 还要传一个参数args. 
      "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
  从这个args可以了解到 frame 数据的一些属性.
  
2.3: 在图上再放上一个AVFilterContext , 名字叫buffersink_ctx
    const AVFilter *buffersink = avfilter_get_by_name("buffersink");
    ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
                                       NULL, NULL, filter_graph);
    还要为buffersink 设置一些属性
    enum AVPixelFormat out_pix_fmts[] = { AV_PIX_FMT_GRAY8, AV_PIX_FMT_NONE };
    ret = av_opt_set_int_list(buffersink_ctx, "pix_fmts", out_pix_fmts,
                              AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);

2.4: 创建一个连接线,它的变量名称叫input.它的一端已经与buffersink_ctx相连      
     另一端浮空.  给它起名叫"out". 就是说有一个浮空的input连线,它的名字叫"out"
     为什么叫"out", 这是规定, 因为最后一个filter的浮空的输出脚,其默认的名称就叫out,  
     将来由程序将filter 的 out 与 sink的out 连接起来,靠得就是这根连接线

    AVFilterInOut *inputs  = avfilter_inout_alloc();
    inputs->filter_ctx = buffersink_ctx; //连buffersink_ctx
    inputs->pad_idx    = 0;
    inputs->next       = NULL;
    inputs->name       = av_strdup("out"); //连filter的out脚

2.5: 创建一个output连接线.它的一端已经与buffersrc_ctx相连,起名叫"in"
     为什么叫"in"? 因为第一个filter的输入脚默认要与"in"名称的浮空的输出线相连


    AVFilterInOut *outputs = avfilter_inout_alloc(); 
    这个引脚连buffersrc_ctx 的输出脚.
    outputs->filter_ctx = buffersrc_ctx;
    outputs->pad_idx    = 0;
    outputs->next       = NULL;
    它要与filter 的第一个引脚相连,默认是in
    outputs->name       = av_strdup("in");


2.6: 把filter 加入到过滤图中,并连接输入线,输出线.
    if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr,
                                    &inputs, &outputs, NULL)) < 0)
2.7: 图都画好了,然后进行一下配置,进行最后的检查,初始化完成.
    if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0)

    最后,input,output连接线是会占内存的,现在不用了,把它释放掉.
    avfilter_inout_free(&inputs);
    avfilter_inout_free(&outputs);

------------------------------------------------------------
3. 使用过滤器
------------------------------------------------------------
使用过滤器很简单,就是把frame 扔进去, 再从过滤器中接受过来就可以了.
av_buffersrc_add_frame_flags(buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF) 
ret = av_buffersink_get_frame(buffersink_ctx, filt_frame);
但它的实际执行可能会有一个很长的调用过程,或许下一次分析.

********************************************************************************
流程都说完了,再看看具体的代码是怎样实现的. 代码有简化,忽略了出错打印及参数检查等不重要部分.

------------------------------------------------------------
1. graph内存分配: avfilter_graph_alloc
------------------------------------------------------------
AVFilterGraph *avfilter_graph_alloc(void)
{
    AVFilterGraph *ret = av_mallocz(sizeof(*ret));  //分配一个对象
    ret->internal = av_mallocz(sizeof(*ret->internal));//分配一个内部对象
    ret->av_class = &filtergraph_class; //指向一个对象
    av_opt_set_defaults(ret); //把自己根据av_class初始化一下. 注意:其默认thread_type 为1
    return ret;
}
thread_type 为1 意味着它是thread_slice, 一个frame的多个部分可以同时进行.
#define AVFILTER_THREAD_SLICE (1<<0)


------------------------------------------------------------
2. 由名称查找filter avfilter_get_by_name
------------------------------------------------------------
有一个AVFilter* 数据,穷举查就可以了.一个for 循环就可以
但它还是采用了2个函数,一个函数取到下一个的指针,一个while循环进行比较.

取下一个指针做成函数叫做数据解耦,可以让你把数据由数组改成链表或其它自定义数据

这是c++的概念,数据封装.
const AVFilter *avfilter_get_by_name(const char *name)
{
    const AVFilter *f = NULL;
    void *opaque = 0;
    while ((f = av_filter_iterate(&opaque))) //取到下一个filter指针
        if (!strcmp(f->name, name)) return f;
    return NULL;
}

const AVFilter *av_filter_iterate(void **opaque)
{
    uintptr_t i = (uintptr_t)*opaque;
    const AVFilter *f = filter_list[i]; //原来filter_list 是一个数组
    if (f) *opaque = (void*)(i + 1); //保留下一个filter的地址
    return f; //放回请求的filter地址
}

//AVFilter 是一个类,但却不是标准的avclass类,即它的第一项不是avclass *

// 但是AVFilter 却包含一个私有的avclass类并定义了它的大小

 //const AVClass *priv_class;

//int priv_size

//所以AVFilter 对象会是五花八门,各具特色的对象,因为它包含私有类

static const AVFilter * const filter_list[] = {
    &ff_af_abench,
    &ff_af_acompressor,
    &ff_af_acontrast,
    ...
}
------------------------------------------------------------
3. 在图上创建filter_context
------------------------------------------------------------
代码比叙述还简单,就是依据filter及名称在graph上创建ctx, 根据args初始化ctx
int avfilter_graph_create_filter(AVFilterContext **filt_ctx, const AVFilter *filt,
                                 const char *name, const char *args, void *opaque,
                                 AVFilterGraph *graph_ctx)
{
    *filt_ctx = avfilter_graph_alloc_filter(graph_ctx, filt, name); //name常见有"in","out"等
    int ret = avfilter_init_str(*filt_ctx, args); //buffer_src 就有args
    return 0;
}

具体的实现:
AVFilterContext *avfilter_graph_alloc_filter(AVFilterGraph *graph,
                                             const AVFilter *filter,
                                             const char *name)
{  //当graph->thread_type 为真并且还没有初始化
    if (graph->thread_type && !graph->internal->thread_execute) {
        if (graph->execute) {
            graph->internal->thread_execute = graph->execute;
        } else {
            //会初始化graph->internal->thread_execute
            //有多少个cpu核,它就创建多少个线程,等以后调用

            int ret = ff_graph_thread_init(graph); 
        }
    }
    AVFilterContext *s = ff_filter_alloc(filter, name); //根据filter创建context
    //在图上增加一个filterContext*, 用realloc实现
    AVFilterContext **filters = av_realloc(graph->filters, sizeof(*filters) * (graph->nb_filters + 1));
    graph->filters = filters;
    graph->filters[graph->nb_filters++] = s;
    s->graph = graph;
    return s;
}

AVFilterContext 是一个类,现在要创建它的一个实例.

因为filter 是context的最重要成员,所以传来了filter的一个对象指针.

AVFilterContext 对象主要保留一些对象指针,本身的直接属性成员并不多

AVFilterContext *ff_filter_alloc(const AVFilter *filter, const char *inst_name)
{
    //分配context,并初始化它的变量,每一个filter_context 相当于一个模块
    AVFilterContext* ret = av_mallocz(sizeof(AVFilterContext)); 
    ret->av_class = &avfilter_class;
    ret->filter   = filter;
    ret->name     = inst_name ? av_strdup(inst_name) : NULL;
    if (filter->priv_size) {
        ret->priv     = av_mallocz(filter->priv_size);
    }
    if (filter->preinit) {
        if (filter->preinit(ret) < 0)
    }
    av_opt_set_defaults(ret); //本对象成员初始化
    if (filter->priv_class) {
        *(const AVClass**)ret->priv = filter->priv_class;
        av_opt_set_defaults(ret->priv); //私有类对象成员初始化
    }
    ret->internal = av_mallocz(sizeof(*ret->internal)); //为internal 分配内存
    ret->internal->execute = default_execute;

    //为input_pads,input,output_pads,outputs 分配内存, 
    //inputs,outputs是AVFilterLink, 做引脚配对用的.
    //filter->inputs是AVFilterPad 指针数组

    ret->nb_inputs = avfilter_pad_count(filter->inputs);
    if (ret->nb_inputs ) { //把filter的pads, 向context来copy
        ret->input_pads   = av_malloc_array(ret->nb_inputs, sizeof(AVFilterPad));
        memcpy(ret->input_pads, filter->inputs, sizeof(AVFilterPad) * ret->nb_inputs);
        ret->inputs       = av_mallocz_array(ret->nb_inputs, sizeof(AVFilterLink*));
    }

    ret->nb_outputs = avfilter_pad_count(filter->outputs); //由filter就可以知道它的pads个数
    if (ret->nb_outputs) { //把filter的pads, 向context来copy
        ret->output_pads  = av_malloc_array(ret->nb_outputs, sizeof(AVFilterPad));
        memcpy(ret->output_pads, filter->outputs, sizeof(AVFilterPad) * ret->nb_outputs);
        ret->outputs      = av_mallocz_array(ret->nb_outputs, sizeof(AVFilterLink*));
    }
    return ret;
}

int avfilter_pad_count(const AVFilterPad *pads)
{
    if (!pads) return 0;
    for (int count = 0; pads->name; count++) //可见pads 是一个空节点结尾的AVFilterPad 数组
        pads++;
    return count;
}

//把args 设置到filteContext中
int avfilter_init_str(AVFilterContext *filter, const char *args)
{
    AVDictionary *options = NULL;
    int  ret = process_options(filter, &options, args); //把args 分析到options中
    ret = avfilter_init_dict(filter, &options); //把options 应用到filter类或filter->priv_class中
    return ret;
}
avfilter_graph_parse_ptr //忽略 
avfilter_graph_config //忽略

小结: 说明了AVFilter 的使用流程,分析了一部分代码,还有2个关键函数没有分析.不想写太长,所以分开了.
 

  • 17
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值