FFmpeg滤镜学习之AVFilter

FFmpeg滤镜学习之AVFilter


什么是AVFilter?

对音视频数据添加特效,如黑白视频、混音,同时也可以完成音视频像素格式转码工作;官方特效说明请点击;一般使用filter都是在后台服务器对视频进行特效处理,移动端的话相对比较耗资源


AVFilter处理框架

大致框架如下图所示,可以把AVFilter看做一些列Filter节点链组成,这个链由AVfilterGraph管理,每个AVFilter节点都会对数据处理,处理完成后交给下一个节点继续处理,直到最后一个节点处理完成。每个AVFilter节点都会有一个AVFilterContext上下文对其进行管理,第一个节点音视频名称为buffer/abuffer,最后一个节点名称为buffersink/abuffersink;内部各个节点链接方式可以自由灵活配置,前一个的输出配置在后一个的输入,可以多个节点进行过滤,也可以少数2个节点过滤
在这里插入图片描述


代码介绍各个关键环节


创建Filter链的管理者

创建AVFilterGraph

AVFilterGraph* filterGraph = avfilter_graph_alloc();

配置一个AVFilter节点

  1. 创建一个AVFilter、AVFilterContext,并初始化
AVFilterContext* bufferSrcCtx;
AVFilter *bufferSrc = avfilter_get_by_name("buffer");
ret = avfilter_graph_create_filter(&bufferSrcCtx, bufferSrc, "in", 
												args, nullptr,filterGraph);

函数介绍:
int avfilter_graph_create_filter(AVFilterContext **filt_ctx,
const AVFilter *filt, const char *name, const char *args, void *opaque,
AVFilterGraph *graph_ctx);

filt_ctx: 当前节点上下文
filt: 当前节点AVfilter
name:名字自取,但是要在整个链中唯一
args:节点过滤参数
opaque:不知所意,传null即可
graph_ctx:整个链的管理者

filter有哪些呢?移步

  1. 为这个filter节点配置滤镜规则
    a. 上述avfilter_graph_create_filter函数中的args,就是配置的参数,该args配置如下:
snprintf(args, sizeof(args),
"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
 mWidth, mHeight, mInputPixelFormat, timeBase.num, timeBase.den,
 ratio.num, ratio.den);

当前节点配置是进入的第一个节点,所以配置的都是输入的视频格式

b. 除以上配置方式外,还有另一种配置方式
直接向AVFilterContext上下文结构体中进行设置:

int av_opt_set         (void *obj, const char *name, const char *val, int search_flags);
int av_opt_set_int     (void *obj, const char *name, int64_t     val, int search_flags);
int av_opt_set_double  (void *obj, const char *name, double      val, int search_flags);
int av_opt_set_q       (void *obj, const char *name, AVRational  val, int search_flags);
int av_opt_set_bin     (void *obj, const char *name, const uint8_t *val, int size, int search_flags);
int av_opt_set_image_size(void *obj, const char *name, int w, int h, int search_flags);
int av_opt_set_pixel_fmt (void *obj, const char *name, enum AVPixelFormat fmt, int search_flags);
int av_opt_set_sample_fmt(void *obj, const char *name, enum AVSampleFormat fmt, int search_flags);
int av_opt_set_video_rate(void *obj, const char *name, AVRational val, int search_flags);
int av_opt_set_channel_layout(void *obj, const char *name, int64_t ch_layout, int search_flags);

其中Obj就是上下文指针,name对应上下文结构体中的变量名,val是变量的值

  1. 同理,配置其他AVFilter也是向上面的操作一样;每个Filter的过滤规则都不一样的

AVFilter节点之间的链接

每个AVFilter都有端口,其类型为AVFilterPad,通过端口可以把两个AVFilter连接起来,如下代码:

err = avfilter_link(abuffer_ctx, 0, volume_ctx, 0); 
if (err >= 0)
    err = avfilter_link(volume_ctx, 0, aformat_ctx, 0); 
if (err >= 0)
    err = avfilter_link(aformat_ctx, 0, abuffersink_ctx, 0); 
if (err < 0) {
    fprintf(stderr, "Error connecting filters\n");
    return err;
}  

上述代码大致意思是,用abuffer_ctx的输出0号端口,输出到volume_ctx的0号输入端口;volume_ctx的输出0号端口,输出到aformat_ctx的0号输入端口,后面同理
但是,主要注意的是 buffer的AVFilter只有输出端口,buffersink的AVFilter只有输入端口,其他的AVFilter都有输入和输出

第二种链接方式:
可能大家还看过另一种链接的函数,
int avfilter_graph_parse_ptr(AVFilterGraph *graph, const char *filters,
AVFilterInOut **inputs, AVFilterInOut **outputs,
void *log_ctx);
以上使用场景,在只有buffer和buffersink两个节点的情况下,可以使用这个函数进行链接;
graph:不用说了,链表管理者
filter:以字符串的形式指定了过滤规则,如lutyuv='u=128:v=128黑白过滤规则,更多特效请移步
inputs:指定输入的节点
outputs:指定输出节点
log_ctx:null即可

AVFilterInOut,当你使用了avfilter_graph_parse_ptr和avfilter_graph_parse2的时候就需要使用AVFilterInOut,用于指定输入和输出

AVFilterInOut *output = avfilter_inout_alloc();
AVFilterInOut *in = avfilter_inout_alloc();
//绑定输入端
output->name = av_strdup("in");
output->filter_ctx = bufferSrcCtx;
//filter_ctx的序号
output->pad_idx = 0;
output->next = nullptr;

//绑定输出端
in->name = av_strdup("out");
in->filter_ctx = bufferSinkCtx;
in->pad_idx = 0;
in->next = nullptr;

检查整个Graph配置

//解析filter描述
if((ret = avfilter_graph_config(mVideoFilterGraph, nullptr)) < 0){
    LOGE("failed to call avfilter_graph_config: %s", av_err2str(ret));
    goto end;
}

输入数据开始过滤

无论有多少个AVFilter节点,主要保留AVFilterGraph和第一个以及最后一个的AVFilterContext引用,第一个AVFilterContext用于传入数据,第二个用于接收处理的数据;

输入数据

int av_buffersrc_add_frame(AVFilterContext *ctx, AVFrame *frame);
int av_buffersrc_add_frame_flags(AVFilterContext *buffer_src,
AVFrame *frame, int flags);

获取输出

int av_buffersink_get_frame(AVFilterContext *ctx, AVFrame *frame);
int av_buffersink_get_samples(AVFilterContext *ctx, AVFrame *frame, int nb_samples);

最后使用完以后记得释放AVFilterGraph
void avfilter_graph_free(AVFilterGraph **graph);

demo下载地址

更多精彩博文,加入我们,一同进步!

在这里插入图片描述

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
滤镜FFmpeg中的一个重要概念,它可以对音视频进行各种处理,例如裁剪、旋转、缩放、调整亮度、对比度等等。FFmpeg提供了丰富的滤镜库,可以通过API在代码中使用滤镜。下面是一个简单的使用FFmpeg API添加滤镜的例子: ```c #include <libavfilter/avfilter.h> #include <libavfilter/buffersink.h> #include <libavfilter/buffersrc.h> AVFilterContext *buffersink_ctx; AVFilterContext *buffersrc_ctx; AVFilterGraph *filter_graph; int video_stream_index = -1; // 初始化滤镜 int init_filters(const char *filters_descr) { char args[512]; int ret = 0; AVFilter *buffersrc = avfilter_get_by_name("buffer"); AVFilter *buffersink = avfilter_get_by_name("buffersink"); AVFilterInOut *outputs = avfilter_inout_alloc(); AVFilterInOut *inputs = avfilter_inout_alloc(); enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE }; filter_graph = avfilter_graph_alloc(); if (!outputs || !inputs || !filter_graph) { ret = AVERROR(ENOMEM); goto end; } // 设置输入参数 snprintf(args, sizeof(args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", buffersrc_ctx->inputs[0]->width, buffersrc_ctx->inputs[0]->height, buffersrc_ctx->inputs[0]->format, buffersrc_ctx->inputs[0]->time_base.num, buffersrc_ctx->inputs[0]->time_base.den, buffersrc_ctx->inputs[0]->sample_aspect_ratio.num, buffersrc_ctx->inputs[0]->sample_aspect_ratio.den); // 添加输入滤镜 ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", args, NULL, filter_graph); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot create buffer source\n"); goto end; } // 添加输出滤镜 ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", NULL, NULL, filter_graph); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot create buffer sink\n"); goto end; } // 设置输出参数 ret = av_opt_set_int_list(buffersink_ctx, "pix_fmts", pix_fmts, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot set output pixel format\n"); goto end; } // 添加滤镜 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; if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr, &inputs, &outputs, NULL)) < 0) goto end; if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0) goto end; end: avfilter_inout_free(&inputs); avfilter_inout_free(&outputs); return ret; } // 处理滤镜 int filter_encode_write_frame(AVFrame *frame, AVCodecContext *enc_ctx, AVFormatContext *fmt_ctx) { int ret; AVFrame *filt_frame = av_frame_alloc(); if (!filt_frame) { ret = AVERROR(ENOMEM); goto end; } // 添加滤镜 if (av_buffersrc_add_frame_flags(buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF) < 0) { av_log(NULL, AV_LOG_ERROR, "Error while feeding the filtergraph\n"); goto end; } // 获取输出帧 while (1) { ret = av_buffersink_get_frame(buffersink_ctx, filt_frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break; if (ret < 0) goto end; // 编码并写入文件 filt_frame->pict_type = AV_PICTURE_TYPE_NONE; ret = encode_write_frame(filt_frame, enc_ctx, fmt_ctx); av_frame_unref(filt_frame); if (ret < 0) goto end; } end: av_frame_free(&filt_frame); return ret; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

帅气好男人_Jack

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值