对音视频数据添加特效,如黑白视频、混音,同时也可以完成音视频像素格式转码工作;官方特效说明请点击;一般使用filter都是在后台服务器对视频进行特效处理,移动端的话相对比较耗资源
大致框架如下图所示,可以把AVFilter看做一些列Filter节点链组成,这个链由AVfilterGraph管理,每个AVFilter节点都会对数据处理,处理完成后交给下一个节点继续处理,直到最后一个节点处理完成。每个AVFilter节点都会有一个AVFilterContext上下文对其进行管理,第一个节点音视频名称为buffer/abuffer,最后一个节点名称为buffersink/abuffersink;内部各个节点链接方式可以自由灵活配置,前一个的输出配置在后一个的输入,可以多个节点进行过滤,也可以少数2个节点过滤
创建AVFilterGraph
AVFilterGraph* filterGraph = avfilter_graph_alloc();
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有哪些呢? 请移步
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是变量的值
每个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;
//解析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下载地址
更多精彩博文,加入我们,一同进步!