------------------------------------------------------------
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个关键函数没有分析.不想写太长,所以分开了.