author: hjjdebug
date: 2025年 04月 22日 星期二 13:48:19 CST
description: 全面介绍AVFilter 的添加和使用
文章目录
- 1.两个重要的编码思想
- 1. 写代码不再是我们调用别人,而是别人调用我们!
- 2. 面向对象的编程方法.
- 2. AVFilter 开发流程
- 2.1 编写AVFilter 文件
- 2.1.0 定义AVFilter 对象
- 2.1.1 初始化对象必要的成员变量.
- 2.1.2 完善对象的指针函数
- 2.1.3 成员变量可以是对象或对象数组
- 2.2. 向ffmpeg 系统添加AVFilter文件
- 2.2.1 copy文件到libavfilter目录下
- 2.2.2 修改libavfilter/allfilter.c文件,添加外部AVFilter 对象声明
- 2.3 重新执行configure 命令
- 2.3.1. ff_vsrc_color_screen名称的意义
- 2.4. 修改Makefile
- 2.4.1 CONFIG_VSRC_COLOR_SCREEN_FILTER 配置宏名称的由来? 由对象名推导来的.
- 2.5 小结
- 3.代码验证:
- 3.1 应用级验证
- 3.2 最简单代码级验证
- 3.3 用filtergraph 创建filter验证
- 4. 改进意见
目的,本博客将引导你完成一个最简单的视频过滤器. 叫.vsrc_color_screen
它的功能很简单,就是产生一幅彩色屏幕画面.
通过本试验,你可以了解filter 是怎样工作的,我们怎样把自己的代码加入到ffmpeg框架中.
1.两个重要的编码思想
框架人家已经写好了,不能更改,你要想让自己的代码让框架调用,必需要符合一定的规范.
我们习惯了写代码调用别人的代码, 例如libc 库函数, printf 函数等等,
现在是别人调用自己. 这是第一个要转变的思想.
1. 写代码不再是我们调用别人,而是别人调用我们!
你可能听说过, 这不就是回调函数吗! 我注册一个函数,对方调用我.
对,是的,回调函数就是代码级别人调用自己的例子, 它要求函数的参数必需按对方要求的类型和个数来写.
这就是一个规范. 即参数的个数及类型早已经确定了,你不能更改.
ffmpeg 采用的代码接口是对象, 对象的概念一两句不易说明白,在使用中需慢慢体会.
这里需要知道,对象就是带指针函数的结构变量就可以了.
2. 面向对象的编程方法.
对象是c++中提出的概念, ffmpeg c语言所写也有对象的概念吗?
是的,ffmpeg 的对象才能更让你深刻的理解对象的本质. 它们是想通的.
c++说, 对象是由类创建的, 类由成员变量和成员函数构成.其中成员函数也可以是虚函数.
其实,c++的类就对应c的结构,对象就对应结构变量. c++有成员函数,c结构也可以有成员函数.
c++有虚函数,c结构也可以有虚函数,只是定义上有点区别而已.
后边介绍过滤器时,也会把这种面向对象的思想进行介绍,并与c++对比.
有了上面2个概念,开始介绍filter,vsrc_color_screen的开发.
2. AVFilter 开发流程
2.1 编写AVFilter 文件
2.1.0 定义AVFilter 对象
首先,因为它是filter, 我们就定义一个AVFilter 对象
AVFilter ff_vsrc_color_screen;
为什么叫这个名字,为什么前面加ff_?
嗯, 先理解为这就是规定, 因为filter对象都是这样起名字的叫ff_xxx
至于为什么这样,以后再介绍.
看到了吧,约束从起名字就开始了.
你只要把这个对象都处理好了,那这个彩色视频过滤器就算写好了.
且慢. c++里我创建一个对象一般都是写好类,然后一个new操作就把对象创建出来了.
ffmpeg 这里是在干啥呢?对象创建完了,我还没有写代码呢?它则么调用输出彩色屏幕的函数?
2.1.1 初始化对象必要的成员变量.
目前 ff_vsrc_color_screen 它就是一个默认的AVFilter对象,
是个架子,是个空壳,是个还缺少初始化的结构变量, 你需要把这个结构变量中的各成员变量都付给正确的值,
这个对象才算创建完成.
我们继续定义这个对象,添加几个变量, 没写的变量都是默认的空了.
AVFilter ff_vsrc_color_screen = {
.name = “color_screen”, //对象属性.name名称,可以叫做对外的对象名称
.activate = activate, // 获取frame 就会回调这个函数
.inputs = NULL,
.outputs = color_outputs, //指向过滤器的输出脚 AVFilterPad 数组
};
AVFilter 类(或说结构) 就不用我们写了,ffmpeg 都写好了, 我们只需要创建出一个完整的对象就可以了.
new 出来的对象不完整,成员变量全是空的,我们正在补全有用的信息.
.name 就是一个字符串, 给对象起个名, 以后把这个对象注册了, 系统就知道我们叫这个名,它以后要找我们,
也是找这个名就能找到我们
.activate 是一个函数指针, 需要我们去完成这个函数
又有问题了 !!
c++的成员函数是属于类的, 所有的对象都调用同一个成员函数,只是传递的this指针不同而以.
这里.activate 是一个函数, 为什么要我们写这个函数呢?它自己不会写吗?
.activate 函数指针, 可以理解为c++的虚函数指针. 虚函数指针在子类中是可以被改变的. 使得不同的子类有不同的表现.
从c的层面来理解, 虚函数是属于对象的,因为对象中虚函数指针可以被改变,使指向不同的函数,从而有不同的表现.
AVFilter 结构中能写的函数它都写了, 那些是属于类的,包括隐含的,你看不见的和无需关心的函数
对象中定义的.activate 就是一个回调函数, 参数的个数和类型已经确定. 具体执行什么操作需要你自己去完成.
它是上层调用AVReadFrame时 的回调函数
可见对象中可以定义很多个函数指针,它们都是回调函数,都是接口函数. 你不需要实现的,可以不实现.
这里,可以给对象另一个定义.
对象是代码级接口,包含很多成员变量和一系列回调函数.
2.1.2 完善对象的指针函数
虽然AVFilter可能很复杂, 但定义一个AVFilter对象还是比较简单,
只需要定义有限的几个变量和实现少数几个接口就可以了. 继续!
static int activate(AVFilterContext* ctx)
{AVFilterLink* outlink = ctx->outputs[0]; //定义的输出脚,见后.AVFrame* frame;if (!ff_outlink_frame_wanted(outlink)) //安全检查return FFERROR_NOT_READY;if (!test_picref){ //第一次调用生成一幅图片test_picref = ff_get_video_buffer(outlink, 640, 480);test_fill_picture_fn(outlink->src, test_picref); //自己写的函数,要补充完整.}frame = av_frame_clone(test_picref); //每次调用,克隆这幅图片frame->pts = test_pts;frame->key_frame = 1;frame->interlaced_frame = 0;frame->pict_type = AV_PICTURE_TYPE_I;frame->sample_aspect_ratio = AVRational(1,1);test_pts++;return ff_filter_frame(outlink, frame);
}
2.1.3 成员变量可以是对象或对象数组
例如 color_outputs 就是一个过滤器引脚AVFilterPad 数组,
而AVFilterPad 对象又包含函数.
//具体填充图片frame的代码,也是调用的库函数完成的,draw,color需要先初始化
//人家都写好了,不需要你再去写了.
static void color_fill_picture(AVFilterContext* ctx, AVFrame* picref)
{
//下面是ffmpeg utils提供的函数,画实体矩形. x,y,w,h; color
ff_fill_rectangle(&draw, &color, picref->data, picref->linesize, 0, 0, 640, 480);
}
//关于引脚的套路函数,你需要对你使用的变量进行初始化, 而初始化又是调用的ffmpeg库函数,
// 你自己就没干什么事. 是啊,牵着牛鼻子走,让牛干活就可以了.
static int color_config_props(AVFilterLink* inlink)
{AVFilterContext* ctx = inlink->src;TestSourceContext* test = ctx->priv;int ret;ff_draw_init(&test_draw, inlink->format, 0);ff_draw_color(&test_draw, &test_color, test_color_rgba);if ((ret = config_props(inlink)) < 0)return ret;return 0;
}static const AVFilterPad color_outputs[] = {{.name = "default",.type = AVMEDIA_TYPE_VIDEO,.config_props = color_config_props,},{ NULL }
};
代码中函数及变量的来历都说清楚了, 还要加上头文件,适当的变量声明. 搞定gcc 也需要2把刷子.
下面是编译通过后的完整代码,放到顶部资源中下载吧.
2.2. 向ffmpeg 系统添加AVFilter文件
现在看看怎样加到ffmpeg系统中进行编译的. 及如何注册给ffmpeg系统.
2.2.1 copy文件到libavfilter目录下
把我们写的文件命名为vsrc_color_screen.c文件,
为啥叫这个名字,加 "vsrc__“是什么意思? 嗯一会再说, 你看看libavfilter下的filter文件都是
“vf_”,“af_”,“vsrc_”,”asrc_"开头的文件,最起码它是为了分类.
把这个文件copy到libavfilter下.
怎样向系统注册你新建的对象.
2.2.2 修改libavfilter/allfilter.c文件,添加外部AVFilter 对象声明
打开libavfilter/allfilter.c, 添加 extern AVFilter ff_vsrc_color_screen 声明
你可以看到很多exptern AVFilter ff_xxx的声明,
其中还有extern AVFilter ff_vsrc_xxx的声明
这说明这个文件使用了很多AVFilter 对象, 这里你就能体会对象和类是不一样的,AVFilter 是类,
AVFilter xxxx, 是声明的对象, extern AVFilter xxxx 是说这个对象是在别的文件定义的.
这个文件只是使用了一下.
我们就在 ff_vsrc_xxx 的尾巴上添加一条(其实在哪填都一样),说明有那么一个对象
extern AVFilter ff_vsrc_color_screen;
2.3 重新执行configure 命令
把你编译ffmpeg时执行的configure 命令重新运行一遍.
为啥呢? 干吗要重新配置呢?
因为configure 命令要扫描这个allfilter.c 文件, 把该文件中"extern AVFilter ff_“开头的声明
重新处理生成一个文件,叫"filter_list.c”, 这个文件也是被allfilter.c 包含的文件
打开这个文件你就明白了,你写的这个对象就在这个表中
static const AVFilter * const filter_list[] = {//一堆af过滤器,copy两个说明一下就可以了.&ff_af_abench,&ff_af_acompressor,....//一堆vsrc, 少copy几个&ff_vsrc_allrgb,&ff_vsrc_allyuv,&ff_vsrc_cellauto,&ff_vsrc_color_screen // 我们定义的过滤器//其它过滤器对象就忽略了....
}
2.3.1. ff_vsrc_color_screen名称的意义
ff_vsrc_color_screen, 这个名称是代码中的对象名称,对应一个地址
"ff_"这3个字符,是AVFilter 对象的标识.
所有的AVFilter 对象都是以"ff_"开始的,硬性规定,为什么呢?
因为configure 工具解析了allfilter.c文件,
碰到extern AVFilter "ff_"开始的代码行. 就知道它遇到了一个对象, 它会把所有对象地址形成一个文件
叫filter_list.c文件
工具对代码的改变,也算是你改变的,只是它节省了你的时间且不会改错.
由此我们知道,"ff_"开始的名称,是给configure 工具看的,告诉它这是一个AVFilter对象
vsrc 代表一种分类,视频源.
名称其它部分自由定义.
configure 命令一定要执行,而不要去手改这个filter_list.c, 因为configure 还生成其它文件,例如
我们要做的第二项改动,修改Makefile
2.4. 修改Makefile
我们先看看目前的Makefile
OBJS- ( C O N F I G G R A D I E N T S F I L T E R ) + = v s r c g r a d i e n t s . o O B J S − (CONFIG_GRADIENTS_FILTER) += vsrc_gradients.o OBJS- (CONFIGGRADIENTSFILTER)+=vsrcgradients.oOBJS−(CONFIG_HALDCLUTSRC_FILTER) += vsrc_testsrc.o
OBJS- ( C O N F I G L I F E F I L T E R ) + = v s r c l i f e . o O B J S − (CONFIG_LIFE_FILTER) += vsrc_life.o OBJS- (CONFIGLIFEFILTER)+=vsrclife.oOBJS−(CONFIG_MANDELBROT_FILTER) += vsrc_mandelbrot.o
我们知道,Makefile 是支持宏变量的, 宏变量是这样定义的
haha=“我很高兴”
当我们引用haha时,用$(var)来引用
$(warning $(haha)) 就会输出"我很高兴"
我们以第一项为例
OBJS-$(CONFIG_GRADIENTS_FILTER) += vsrc_gradients.o
“+=” 是把这个宏的定义又增加了一项的意思. 还是定义宏的问题
$(CONFIG_GRADIENTS_FILTER) ,是一个变量展开, 那就要看看CONFIG_GRADIENTS_FILTER是怎样定义的.
我们打开ffbuild/config.mak 查看CONFIG_GRADIENTS_FILTER的宏定义
CONFIG_GRADIENTS_FILTER=yes
我们就知道这一行的意思是
OBJS-yes += vsrc_gradients.o
在ffmpeg Makefile 中, OBJS-yes宏变量就是要生成的所有的目标文件的集合,
我们有了新编的vsrc_color_screen.c文件,当然也要把它编译成vsrc_color_screen.o文件,这样才能使用.
OBJS-$(CONFIG_VSRC_COLOR_SCREEN_FILTER) += vsrc_color_screen.o
2.4.1 CONFIG_VSRC_COLOR_SCREEN_FILTER 配置宏名称的由来? 由对象名推导来的.
宏名称CONFIG_VSRC_COLOR_SCREEN_FILTER, 为啥叫这个名?
看看别的filter中定义的, 找葫芦画瓢来配置
它的过程是在obj名称(去掉ff_)前面加上CONFIG,在后面加上FILTER,
也就是这个宏名是从object名称推导的.
###3.4.2 vsrc_color_screen.c 源代码名称的由来 ? 可以随便设.
用对象名命名文件名显得更有意义.
如果这个config宏是yes 的话, OBJS-yes 宏中就能添加vsrc_color_screen.o,
编译器gcc就能从源文件vsrc_color_screen.c 把它编译出来
好消息来了! 这个宏我们不用自己亲自定义了, 因为当你运行configure 命令时, configure给我们生成了这个宏定义
它就存放在 ffbuild/config.mak 文件中, 每次configure,这个文件都会被更新.
我知道你又有问题了,你想问config.mak这个文件是根据什么生成的?
这也难不住咱,我对它有研究. 其实绕了一圈还是configure 文件对那个allfilter.c文件进行了解析
抽取了所有extern AVFilter “ff_” 文本行, 然后定义了CONFIG_xxx_FILTER宏并书写到ffbuild/config.mak中
configure 不仅生成了config.mak 文件,还生成了config.h文件供代码调用,还生成了许多别的文件filter_list.c等等.
configure 是个脚本工具,它干了很多事情,你也可以修改它让它干更多的事如果需要的话.
那如果我修改了Makefile 和 allfilter.c 而忘记运行configure 怎么办?
没关系,你只管运行make, 它会给出提示信息:
WARNING: libavfilter/allfilters.c newer than config.h, rerun configure
连Makefile 也已经做的很贴心了.
2.5 小结
现在总结一下添加filter的过程吧.
- 书写代码
- 修改allfilters.c,用extern AVFilter ff_xxx
- 修改Makefile, 添加OBJS-$(CONFIG_xxx_FILTER)= new_filter.o
- 重新运行configure
再执行make, 看看你的代码是否已经编译出来了.!
都重编了,太多内容了,看不见.
没关系, 再touch一下你的源代码,再make,这次只编译你的代码,就能看清楚了.
代码有问题或有警告, 你可以修改代码再编译, 于时进入filter代码开发循环了.恭喜你上了正路!
3.代码验证:
验证就很简单了.
辛辛苦苦写的代码就是为了符合它的框架.
验证当然要用它的框架来验证了.
3.1 应用级验证
用ffplay 可以验证,我们可以指定ffplay 用我们的filter, 最简单的验证方式.
$ffplay -f lavfi vsrc_color_screen
没有按期望运行,那你就要检查一下了! 使用简单的东西,出了问题检查解决问题可就要凭真本事了.
如果你真要是理解了它的运行过程,那后面的就更容易理解了.
3.2 最简单代码级验证
ffmpeg filter的上层管理对象是 “lavfi” 对象, 它是一个AVInputFormat对象,是一个虚拟设备源,
让它的实现类匹配我们的filter,使它的数据直接从我们的filter来取. 就可以验证我们的filter.
这是标准的ffmpeg操控数据的流程,用avformat_open_input 打开文件,用avcodec_open2打开codec.
用av_read_frame 来读取数据, ffplay,ffprobe,ffmpeg也是这样处理过滤器的.
核心思想是把filter当文件使用.
直接给代码.都是通过调试的.
3.3 用filtergraph 创建filter验证
"lavfi"对象的执行过程其实也是创建filtrgraph,创建filter取数的过程, 如果不用lavfi虚拟设备
而是直接自己书写filtergraph,也可以.
这次代码更底层一些,也更直接一些,对其中的过程会辽解的更细致一些.
直接给代码,见附件
4. 改进意见
- 程序中用了很多全局变量,应该用一个结构把它们收集起来,看得会比较正规一些.
- 把这个结构改用AVClass 去定义,这样可以实现从外部(过滤器名+参数)直接控制这些参数.
例如w,h,color等等
参考:
libavfilter/vsrc_color.c
doc/examples/filtering_video.c