FFMpeg-7、libavfilter滤波器相关介绍,实现翻转叠加和水印

1、AVFilter简介

FFmpeg中的AVFilter模块进行帧数据处理的开发,AVFilter模块对帧数据处理进行了很好的抽象,对其中的帧数据处理(包括音频和视频数据)则相对要多样化一些,比如对视频做尺寸变换,进行音频音量均衡,直播中的美颜处理,多路流合成等等,这些都是属于流程中的帧数据处理。

一般的编解码流程就是;
原始音视频–>解码–>帧数据处理–>编码–>输出音视频

在编码前,ffmpeg可以对raw(真实/原)音频和视频使用libavfilter库中的滤镜进行处理。(非压缩数据帧)
多个滤镜可以组成滤镜链图(滤镜链图filtergraphs )
在ffmpeg看来只有2种滤镜:简单滤镜,复合滤镜。
**简单滤镜;**就是只有1个输入和输出的滤镜,滤镜两边的数据都是同一类型的,可以理解为在非压缩数据帧到再次编码前简单附加了一步:
**复合滤镜;**complex filtergraph,通常是具有多个输入输出文件,并有多条执行路径;ffmpeg命令行中使用-lavfi、-filter_complex,

如ffmpeg官网中的一个列子;

                [main]
input --> split ---------------------> overlay --> output
            |                             ^
            |[tmp]                  [flip]|
            +-----> crop --> vflip -------+
input就是原输入流,上图整个流程就做了这些操作,
首先使用split滤波器将input流分成两路流(main和tmp),然后分别对两路流进行处理。对于tmp流,先经过crop滤波器进行裁剪处理,再经过flip滤波器进行垂直方向上的翻转操作,输出的结果命名为flip流。再将main流和flip流输入到overlay滤波器进行合成操作。上图的input就是上面提过的buffer源滤波器,output就是上面的提过的buffersink滤波器。

以上操作使用命令实现是
ffmpeg -i INPUT -vf "split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2" OUTPUT
同一个线性链中的过滤器用逗号分隔,不同的过滤器线性链用分号分隔。
在我们的例子中,裁剪,vflip在一个线性链中,分割和叠加在另一个链中是分开的。线形链连接的点用方括号括起来的名称进行标记。
在本例中,拆分过滤器生成与标签[main][tmp]相关联的两个输出。
一些过滤器接受输入的参数列表:它们在过滤器名称和等号之后指定,并用冒号分隔。

2、AVFilter的一些概念

可以在http://www.ffmpeg.org/ffmpeg-filters.html进行系统学习查看

2.1、滤波器图AVFilterGraph

如上面的几个滤波器构成的一套逻辑就是滤波器图了,对filters系统的整体管理。

struct AVFilterGraph
{
    AVFilterContext **filters;
    unsigned nb_filters;
}

2.1.1、graph2dot程序
FFmpeg tools目录中包含的graph2dot程序可用于解析filtergraph描述,并使用dot语言发出相应的文本表示。然后,您可以将点描述传递给点程序(从graphviz程序组),并获得filtergraph的图形表示。可以用于创建和显示代表GRAPH_DESCRIPTION字符串所描述的图形的图像。注意,这个字符串必须是一个完整的自包含图,其输入和输出都明确定义。
2.1.2、Filtergraph description
滤波器图是连接滤波器的有向图。它可以包含循环,并且一对过滤器之间可以有多个链接。每个链接的一侧有一个输入pad,连接到一个过滤器,从过滤器中获取输入,另一侧有一个输出pad,连接到一个接受输出的过滤器。
filtergraph中的每个过滤器都是在应用程序中注册的过滤器类的实例,它定义了过滤器的特性以及输入和输出pad的数量。
没有输入pad的过滤器称为“source”,没有输出pad的过滤器称为“sink"。
2.1.3、Filtergraph syntax Filtergraph语法
filtergraph有一个文本表示,ffmpeg中的-filter/-vf/-af和-filter_complex选项可以识别,ffplay中的-vf/-af选项可以识别,libavfilter/avfilter.h中定义的avfilter_graph_parse_ptr()函数也可以识别。
过滤器链由一系列连接的过滤器组成,每个过滤器都与序列中的前一个过滤器相连接。过滤器链由一系列“,”分隔的过滤器描述来表示。
一个过滤图由一系列过滤链组成。filterchains序列由一个";“分开filterchain描述。详情可以查看官网描述。
2.1.4、Notes on filtergraph escaping filtergraph 转义

2.1.5、Timeline editing 时间轴编辑
一些过滤器支持一个通用的启用选项。对于支持时间线编辑的过滤器,可以将此选项设置为一个表达式,该表达式在将帧发送给过滤器之前进行计算。如果求值是非零,则该过滤器将被启用,否则帧将被不变地发送到filtergraph中的下一个过滤器。

2.1.6、使用命令在运行时更改选项
在过滤器的操作过程中,可以使用命令更改一些选项。这些选项在ffmpeg -h filter=< filter>的输出上标记为’ T '。命令的名称是选项的名称,参数是新值。

2.1.7、有多个输入的过滤器选项(framesync)
一些具有多个输入的过滤器支持一组通用的选项。这些选项只能通过名称设置,不能使用短符号。

关于AVFilterGraph的代码相关流程

1、首先都要进行滤波器设备注册
//进行滤波器注册
avfilter_register_all();

2、创建滤波器图AVFilterGraph
//创建滤波器图
AVFilterGraph* filter_graph = avfilter_graph_alloc();
if (!filter_graph) {
	printf("Fail to create filter graph!\n");
	return -1;
}
创建滤波器图的另外一种方法,直接一次性创建字符串指定的图 并且滤波器也要提前初始化好
//向图中添加由字符串描述的图。hflip竖直翻转
if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr,
	&inputs, &outputs, NULL)) < 0) {
	return false;
}


3、将创建的滤波器实例化并添加到滤波器图中
/*
创建并添加一个过滤器实例到现有的图形。
过滤器实例由filter filt创建,并使用参数args和opaque初始化。
在成功的情况下,将指针放入filt_ctx过滤实例,否则将*filt_ctx设置为空。
*/
AVFilterContext* bufferSrc_ctx;
ret = avfilter_graph_create_filter(&bufferSrc_ctx, bufferSrc, "in", args, NULL, filter_graph);
if (ret < 0) {
	printf("Fail to create filter bufferSrc\n");
	return -1;
}

4、将加入的多个滤波器实例进行链接link
//进行链接滤波器buffer -- split
// src filter to split filter
ret = avfilter_link(bufferSrc_ctx, 0, splitFilter_ctx, 0);
if (ret != 0) {
	printf("Fail to link src filter and split filter\n");
	return -1;
}
5、检查滤波器图的正确性
//检查有效性并配置图中的所有链接和格式。
// check filter graph
ret = avfilter_graph_config(filter_graph, NULL);
if (ret < 0) {
	printf("Fail in filter graph\n");
	return -1;
}
6、可以输出滤波器图以字符串的形式打印出来方便观看(可选)
//将图形转储为人类可读的字符串表示形式。
char *graph_str = avfilter_graph_dump(filter_graph, NULL);

2.2、各种滤波器AVFilter

在libavfilter中,一个过滤器可以有多个输入和多个输出。
AVFilter相关的源码结构

// 定义filter本身的能力,拥有的pads,回调函数接口定义
struct AVFilter
{
    const char *name;
    const AVFilterPad *inputs;
    const AVFilterPad *outputs;
}
// filter实例,管理filter与外部的联系
struct AVFilterContext
{
    const AVFilter *filter;
    char *name;
    
    AVFilterPad *input_pads;
    AVFilterLink **inputs;
    unsigned nb_inputs
    
    AVFilterPad *output_pads;
    AVFilterLink **outputs;
    unsigned nb_outputs;
    
    struct AVFilterGraph *graph;
}
// 定义两个filters之间的联接
struct AVFilterLink
{
    AVFilterContext *src;
    AVFilterPad *srcpad;
    
    AVFilterContext *dst;
    AVFilterPad *dstpad;
    
    struct AVFilterGraph *graph;
}
// 定义filter的输入/输出接口
struct AVFilterPad
{
    const char *name;
    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 (*request_frame)(AVFilterLink *link);
}
struct AVFilterInOut
{
    char *name;
    AVFilterContext *filter_ctx;
    int pad_idx;
    struct AVFilterInOut *next;
}
在AVFilter模块中定义了AVFilter结构,很个AVFilter都是具有独立功能的节点,
如scale filter的作用就是进行图像尺寸变换,
overlay filter的作用就是进行图像的叠加,
滤波器buffer代表filter graph中的源头,原始数据就往这个filter节点输入的;
滤波器buffersink代表filter graph中的输出节点,处理完成的数据从这个filter节点输出。

暂时先介绍用到的,其他的用到再补上 总共分类就这几大类

Audio Filters
Audio Sources
Audio Sinks
Video Filters
	crop 裁剪输入视频到给定的尺寸。
	overlay 将一个视频叠加在另一个视频上。它有两个输入,一个输出。第一个输入是覆盖第二个输入的“主”视频。
OpenCL Video Filters
VAAPI Video Filters
Video Sources
	buffer  滤波器buffer代表filter graph中的源头,原始数据就往这个filter节点输入的
Video Sinks
	buffersink 滤波器buffersink代表filter graph中的输出节点,处理完成的数据从这个filter节点输出。
Multimedia Filters
	split, asplit 把输入分成几个相同的输出。asplit工作在音频输入,split只分割视频。过滤器只接受一个参数,该参数指定输出的数量。如果未指定,则默认为2。
Multimedia Sources

滤波器相关的代码有

// 获取FFmpeg中定义的filter,调用该方法前需要先调用avfilter_register_all();进行滤波器注册
AVFilter *avfilter_get_by_name(const char *name);
//滤波器buffer代表filter graph中的源头,原始数据就往这个filter节点输入的
AVFilter* bufferSrc = avfilter_get_by_name("buffer");
avfilter_get_by_name的源码,具体的name有哪些在源码的什么位置需要追源代码
const AVFilter *avfilter_get_by_name(const char *name)
{
    const AVFilter *f = NULL;
    void *opaque = 0;

    if (!name)
        return NULL;

    while ((f = av_filter_iterate(&opaque)))
        if (!strcmp(f->name, name))
            return (AVFilter *)f;

    return NULL;
}


// 往源滤波器buffer中输入待处理的数据
int av_buffersrc_add_frame(AVFilterContext *ctx, AVFrame *frame);
//向缓冲区源添加一个帧。
if (av_buffersrc_add_frame(bufferSrc_ctx, frame_in) < 0) {
		printf("Error while add frame.\n");
		break;
}

// 从目的滤波器buffersink中输出处理完的数据
int av_buffersink_get_frame(AVFilterContext *ctx, AVFrame *frame);
//从接收器获得一个过滤后的数据帧,并将其放入帧中。
ret = av_buffersink_get_frame(bufferSink_ctx, frame_out);
if (ret < 0)
	break;
	
// 创建一个滤波器图filter graph
AVFilterGraph *avfilter_graph_alloc(void);

// 创建一个滤波器实例AVFilterContext,并添加到AVFilterGraph中
int avfilter_graph_create_filter(AVFilterContext **filt_ctx, const AVFilter *filt,
                                 const char *name, const char *args, void *opaque,
                                 AVFilterGraph *graph_ctx);
AVFilterContext* bufferSrc_ctx;
ret = avfilter_graph_create_filter(&bufferSrc_ctx, bufferSrc, "in", args, NULL, filter_graph);
//需要传参的  输出2路 之后链接split节点的时候需要指定哪路数据
AVFilterContext *splitFilter_ctx;
	ret = avfilter_graph_create_filter(&splitFilter_ctx, splitFilter, "split", "outputs=2", NULL, filter_graph);

// 连接两个滤波器节点
int avfilter_link(AVFilterContext *src, unsigned srcpad,
                  AVFilterContext *dst, unsigned dstpad);
//split分出第2pad--crop
ret = avfilter_link(splitFilter_ctx, 1, cropFilter_ctx, 0);

3、AVFilter实现上面滤波器图实例

                [main]
input --> split ---------------------> overlay --> output
            |                             ^
            |[tmp]                  [flip]|
            +-----> crop --> vflip -------+

实例
在这里插入图片描述
核心代码

//生成滤波器图抽象成一个函数出来,其实可以关于滤波器抽象出一个类来,之后需要的滤波器图可以统一管理
int InitAvFilter(AVFilterGraph *&filter_graph, AVFilterContext * &bufferSrc_ctx, AVFilterContext* &bufferSink_ctx, const char *args)
{
	int ret = 0;

	avfilter_register_all();

	filter_graph = avfilter_graph_alloc();
	if (!filter_graph)
	{
		cout << "create filter graph Fial" << endl;
		return -1;
	}

	//创建一个buffer源过滤器并创建对应的过滤器实列并加入过滤器图中,之后就需要将图里面的不同过滤器链接到一起即可
	
	AVFilter* bufferSrc = avfilter_get_by_name("buffer");
	ret = avfilter_graph_create_filter(&bufferSrc_ctx, bufferSrc ,"in", args, NULL, filter_graph);
	if (ret < 0) {
		printf("Fail to create filter\n");
		return -1;
	}

	//创建buffersink目的过滤器
	//bufferSink需要设置参数 允许的像素格式列表,以AV_PIX_FMT_NONE结束
	AVBufferSinkParams *bufferSink_params = NULL;
	enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE };
	bufferSink_params = av_buffersink_params_alloc();
	bufferSink_params->pixel_fmts = pix_fmts;
	AVFilter* bufferSink = avfilter_get_by_name("buffersink"); //ffbuffersink带缓冲的buffersink
	ret = avfilter_graph_create_filter(&bufferSink_ctx, bufferSink, "out", NULL, bufferSink_params, filter_graph);
	if (ret < 0) {
		printf("Fail to create filter sink filter\n");
		return -1;
	}
	
	//创建split分割过滤器  可以传入参数outputs=2表示分割出两路 注意链接的时候需要指定哪一路 从0开始计数
	AVFilter *splitFilter = avfilter_get_by_name("split");
	AVFilterContext *splitFilter_ctx;
	ret = avfilter_graph_create_filter(&splitFilter_ctx, splitFilter, "split", "outputs=2", NULL, filter_graph);
	if (ret < 0) {
		printf("Fail to create split filter\n");
		return -1;
	}

	//创建crop裁剪处理过滤器
	AVFilter *cropFilter = avfilter_get_by_name("crop");
	AVFilterContext *cropFilter_ctx;
	ret = avfilter_graph_create_filter(&cropFilter_ctx, cropFilter, "crop", "out_w=iw:out_h=ih/2:x=0:y=0", NULL, filter_graph);
	if (ret < 0) {
		printf("Fail to create crop filter\n");
		return -1;
	}

	//创建vflip水平翻转过滤器
	AVFilter *vflipFilter = avfilter_get_by_name("vflip");
	AVFilterContext *vflipFilter_ctx;
	ret = avfilter_graph_create_filter(&vflipFilter_ctx, vflipFilter, "vflip", NULL, NULL, filter_graph);
	if (ret < 0) {
		printf("Fail to create vflip filter\n");
		return -1;
	}

	//创建overlay覆盖过滤器,将一个视频覆盖再另外一个视频上
	AVFilter *overlayFilter = avfilter_get_by_name("overlay");
	AVFilterContext *overlayFilter_ctx;
	ret = avfilter_graph_create_filter(&overlayFilter_ctx, overlayFilter, "overlay", "y=0:H/2", NULL, filter_graph);
	if (ret < 0) {
		printf("Fail to create overlay filter\n");
		return -1;
	}

	//对上面添加到过滤器图中的过滤器进行链接生成逻辑关系
	//进行链接滤波器buffer -- split
	ret = avfilter_link(bufferSrc_ctx, 0, splitFilter_ctx, 0);
	if (ret != 0) {
		printf("Fail to link src filter and split filter\n");
		return -1;
	}
	//链接split分出第1pad--overlay
	ret = avfilter_link(splitFilter_ctx, 0, overlayFilter_ctx, 0);
	if (ret != 0) {
		printf("Fail to link split filter and overlay filter main pad\n");
		return -1;
	}
	//链接split分出第2pad--crop
	ret = avfilter_link(splitFilter_ctx, 1, cropFilter_ctx, 0);
	if (ret != 0) {
		printf("Fail to link split filter's second pad and crop filter\n");
		return -1;
	}
	//链接crop到vflip
	ret = avfilter_link(cropFilter_ctx, 0, vflipFilter_ctx, 0);
	if (ret != 0) {
		printf("Fail to link crop filter and vflip filter\n");
		return -1;
	}
	//链接vflip到overlay的第二个输入pad 主屏幕   第一个输入是覆盖第二个输入的“主”视频。
	ret = avfilter_link(vflipFilter_ctx, 0, overlayFilter_ctx, 1);
	if (ret != 0) {
		printf("Fail to link vflip filter and overlay filter's second pad\n");
		return -1;
	}
	//链接overlay的输出到sink目的过滤器
	ret = avfilter_link(overlayFilter_ctx, 0, bufferSink_ctx, 0);
	if (ret != 0) {
		printf("Fail to link overlay filter and sink filter\n");
		return -1;
	}

	//检查有效性并配置图中的所有链接和格式。
	ret = avfilter_graph_config(filter_graph, NULL);
	if (ret < 0) {
		printf("Fail in filter graph\n");
		return -1;
	}

	//测试使用;
	//将图形转储为人类可读的字符串表示形式。
	char *graph_str = avfilter_graph_dump(filter_graph, NULL);
	FILE* graphFile = NULL;
	fopen_s(&graphFile, "graphFile.txt", "w");
	fprintf(graphFile, "%s", graph_str);
	av_free(graph_str);

	return 0;
}

调用
//初始化avfilter滤波器
	AVFilterGraph *filter_graph = NULL;
	AVFilterContext * bufferSrc_ctx = NULL;
	AVFilterContext* bufferSink_ctx = NULL;
	char args[512];
	_snprintf_s(args, sizeof(args),
		"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
		pFormatCtx->streams[videoindex]->codecpar->width, pFormatCtx->streams[videoindex]->codecpar->height, AV_PIX_FMT_YUV420P,
		1, 25, 1, 1);
	ret = InitAvFilter(filter_graph, bufferSrc_ctx, bufferSink_ctx, args);
	if (ret != 0)
	{
		cout << "InitAvFilter faile" << endl;
		return -1;
	}

显示调用
//向缓冲区源添加一个帧。
if (av_buffersrc_add_frame(bufferSrc_ctx, pFrame) < 0) {
	printf("Error while add frame.\n");
	break;
}

//从接收器获得一个过滤后的数据帧,并将其放入帧中。
/* pull filtered pictures from the filtergraph */
ret = av_buffersink_get_frame(bufferSink_ctx, frame_out);
if (ret < 0)
	break;

//将返回的数据更新到SDL纹理当中
SDL_UpdateYUVTexture(texture, NULL,
	frame_out->data[0], frame_out->linesize[0],
	frame_out->data[1], frame_out->linesize[1],
	frame_out->data[2], frame_out->linesize[2]);

//进行SDL刷新显示
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, NULL, &rect);
SDL_RenderPresent(renderer);

4、实现视频上添加图片水印

运行结果
在这里插入图片描述
//核心代码

int InitAvFilter(AVFilterGraph *&filter_graph, AVFilterContext * &bufferSrc_ctx, AVFilterContext* &bufferSink_ctx, const char *args)
{
	int ret = 0;
	avfilter_register_all();
	AVFilter *buffersrc = avfilter_get_by_name("buffer");
	AVFilter *buffersink = avfilter_get_by_name("buffersink");//ffbuffersink 实例化的时候错误返回-12
	AVFilterInOut *outputs = avfilter_inout_alloc();
	AVFilterInOut *inputs = avfilter_inout_alloc();
	enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE };
	AVBufferSinkParams *buffersink_params;

	filter_graph = avfilter_graph_alloc();
	/* buffer video source: the decoded frames from the decoder will be inserted here. */
	

	ret = avfilter_graph_create_filter(&bufferSrc_ctx, buffersrc, "in",
		args, NULL, filter_graph);
	if (ret < 0) {
		printf("Cannot create buffer source\n");
		return ret;
	}

	/* buffer video sink: to terminate the filter chain. */
	buffersink_params = av_buffersink_params_alloc();
	buffersink_params->pixel_fmts = pix_fmts;
	ret = avfilter_graph_create_filter(&bufferSink_ctx, buffersink, "out",
		NULL, buffersink_params, filter_graph);
	av_free(buffersink_params);
	if (ret < 0) {
		printf("Cannot create buffer sink\n");
		return ret;
	}

	/* Endpoints for the filter graph. */
	outputs->name = av_strdup("in");
	outputs->filter_ctx = bufferSrc_ctx;
	outputs->pad_idx = 0;
	outputs->next = NULL;

	inputs->name = av_strdup("out");
	inputs->filter_ctx = bufferSink_ctx;
	inputs->pad_idx = 0;
	inputs->next = NULL;


	//向图中添加由字符串描述的图。hflip竖直翻转
	//采用AVFilterInOut 的输入输出
	const char *filter_descr = "movie=hello.png[wm];[in][wm]overlay=5:5[out]";
	if ((ret = avfilter_graph_parse_ptr(filter_graph, filter_descr,
		&inputs, &outputs, NULL)) < 0)
		return ret;

	if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0)
		return ret;

	//测试使用;
	//将图形转储为人类可读的字符串表示形式。
	char *graph_str = avfilter_graph_dump(filter_graph, NULL);
	FILE* graphFile = NULL;
	fopen_s(&graphFile, "graphFile.txt", "w");
	fprintf(graphFile, "%s", graph_str);
	av_free(graph_str);

	return 0;
}

使用还是与上面一样的,只有构建滤波器图的步骤不同 这里采用avfilter_graph_parse_ptr方式

后续需补上一些滤波器官网介绍的使用方法;

工程地址;
git项目地址
参考博客
http://www.360doc.com/content/20/1030/20/21412_943270026.shtml
http://www.ffmpeg.org/ffmpeg-filters.html#overlay-1
https://blog.csdn.net/leixiaohua1020/article/details/29368911/
https://www.jianshu.com/p/b1b79984d860
https://blog.csdn.net/leixiaohua1020/article/details/29368911/

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值