全面介绍AVFilter 的添加和使用


author: hjjdebug

date: 2025年 04月 22日 星期二 13:48:19 CST

description: 全面介绍AVFilter 的添加和使用


文章目录

  • 1.两个重要的编码思想
    • [1. 写代码不再是我们调用别人,而是别人调用我们!](#1. 写代码不再是我们调用别人,而是别人调用我们!)
    • [2. 面向对象的编程方法.](#2. 面向对象的编程方法.)
  • [2. AVFilter 开发流程](#2. AVFilter 开发流程)
    • [2.1 编写AVFilter 文件](#2.1 编写AVFilter 文件)
      • [2.1.0 定义AVFilter 对象](#2.1.0 定义AVFilter 对象)
      • [2.1.1 初始化对象必要的成员变量.](#2.1.1 初始化对象必要的成员变量.)
      • [2.1.2 完善对象的指针函数](#2.1.2 完善对象的指针函数)
      • [2.1.3 成员变量可以是对象或对象数组](#2.1.3 成员变量可以是对象或对象数组)
    • [2.2. 向ffmpeg 系统添加AVFilter文件](#2.2. 向ffmpeg 系统添加AVFilter文件)
      • [2.2.1 copy文件到libavfilter目录下](#2.2.1 copy文件到libavfilter目录下)
      • [2.2.2 修改libavfilter/allfilter.c文件,添加外部AVFilter 对象声明](#2.2.2 修改libavfilter/allfilter.c文件,添加外部AVFilter 对象声明)
    • [2.3 重新执行configure 命令](#2.3 重新执行configure 命令)
      • [2.3.1. ff_vsrc_color_screen名称的意义](#2.3.1. ff_vsrc_color_screen名称的意义)
    • [2.4. 修改Makefile](#2.4. 修改Makefile)
      • [2.4.1 CONFIG_VSRC_COLOR_SCREEN_FILTER 配置宏名称的由来? 由对象名推导来的.](#2.4.1 CONFIG_VSRC_COLOR_SCREEN_FILTER 配置宏名称的由来? 由对象名推导来的.)
    • [2.5 小结](#2.5 小结)
  • 3.代码验证:
    • [3.1 应用级验证](#3.1 应用级验证)
    • [3.2 最简单代码级验证](#3.2 最简单代码级验证)
    • [3.3 用filtergraph 创建filter验证](#3.3 用filtergraph 创建filter验证)
  • [4. 改进意见](#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的过程吧.

  1. 书写代码
  2. 修改allfilters.c,用extern AVFilter ff_xxx
  3. 修改Makefile, 添加OBJS-$(CONFIG_xxx_FILTER)= new_filter.o
  4. 重新运行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. 改进意见

  1. 程序中用了很多全局变量,应该用一个结构把它们收集起来,看得会比较正规一些.
  2. 把这个结构改用AVClass 去定义,这样可以实现从外部(过滤器名+参数)直接控制这些参数.
    例如w,h,color等等

参考:

libavfilter/vsrc_color.c

doc/examples/filtering_video.c

相关推荐
Antonio9156 小时前
【音视频】FFmpeg内存模型
ffmpeg·音视频
邪恶的贝利亚8 小时前
基于 FFmpeg 的音视频处理基础原理与实验探究
ffmpeg·音视频
Antonio91513 小时前
【音视频】AAC-ADTS分析
ffmpeg·音视频·aac
这被禁忌的游戏13 小时前
网页下载的m3u8格式文件使用FFmpeg转为MP4
ffmpeg
hunandede13 小时前
ffmpeg 硬解码相关知识
ffmpeg
StudyWinter20 小时前
【FFmpeg从入门到精通】第四章-FFmpeg转码
ffmpeg·音视频
hepherd1 天前
音视频学习 - MP3格式
java·ffmpeg·intellij idea
桃花岛主701 天前
WINDOWS下使用命令行读取本地摄像头FFMPEG+DirectShow,ffplay直接播放摄像头数据
windows·ffmpeg
大猫会长1 天前
ffmpeg无损转格式的命令行
ffmpeg