零、写在前面
我正在参与「启航计划」;书接上回FFmpeg音频解码-音频可视化。假如只解码音频然后就拿去给播放器播放那也太单调了,FFmpeg带有音频滤镜功用,咱们把它加进去,让咱们的播放器完结一些搞怪的音频作用。咱们以完结音频滤镜为中心,讲一下完结滤镜的过程,当然视频滤镜的完结原理一致,不同的是FFmpeg支持音视频滤镜的品种。
一、结构体
首要咱们来了解三个重要的结构体
1.1、AVFilterGraph
该目标是滤镜的图表结构体,首要存储一些滤镜的根本操作装备和滤镜上下文。
typedef struct AVFilterGraph {
const AVClass *av_class;
AVFilterContext **filters;
unsigned nb_filters;
char *scale_sws_opts; ///< sws options to use for the auto-inserted scale filters
#if FF_API_LAVR_OPTS
attribute_deprecated char *resample_lavr_opts; ///< libavresample options to use for the auto-inserted resample filters
#endif
int thread_type;
int nb_threads;
AVFilterGraphInternal *internal;
void *opaque;
avfilter_execute_func *execute;
char *aresample_swr_opts; ///< swr options to use for the auto-inserted aresample filters, Access ONLY through AVOptions
AVFilterLink **sink_links;
int sink_links_count;
unsigned disable_auto_convert;
} AVFilterGraph;
咱们简单点看,咱们在调用的时分咱们真正能触及到的一般首要是const AVClass av_class;AVFilterContext ** filters; 这两个目标
av_class在初始化AVFilterGraph 的时分就已经一同初始化了,它便是存储滤镜一些相关装备的,在初始化时就已经有一些默许的装备被赋值。
filters需求后续滤镜生成后再进行赋值绑定的,它是滤镜上下文指针类型的指针。
1.2、AVFilter
滤镜结构体,是用来存储详细滤镜信息和一些函数指针,函数指针能让咱们外部的进行重写逻辑,作为回调等等。但对于咱们完结根本滤镜来说咱们根本不需求了解,咱们只需关注一些根本信息,例如滤镜姓名,滤镜参数等。
/**
* Filter definition. This defines the pads a filter contains, and all the
* callback functions used to interact with the filter.
*/
typedef struct AVFilter {
/**
* Filter name. Must be non-NULL and unique among filters.
*/
const char *name;
const char *description;
const AVFilterPad *inputs;
const AVFilterPad *outputs;
const AVClass *priv_class;
int flags;
/*****************************************************************
* All fields below this line are not part of the public API. They
* may not be used outside of libavfilter and can be changed and
* removed at will.
* New public fields should be added right above.
*****************************************************************
*/
int (*preinit)(AVFilterContext *ctx);
int (*init)(AVFilterContext *ctx);
int (*init_dict)(AVFilterContext *ctx, AVDictionary **options);
void (*uninit)(AVFilterContext *ctx);
int (*query_formats)(AVFilterContext *);
int priv_size; ///< size of private data to allocate for the filter
int flags_internal; ///< Additional flags for avfilter internal use only.
#if FF_API_NEXT
struct AVFilter *next;
#endif
int (*process_command)(AVFilterContext *, const char *cmd, const char *arg, char *res, int res_len, int flags);
int (*init_opaque)(AVFilterContext *ctx, void *opaque);
int (*activate)(AVFilterContext *ctx);
} AVFilter;
1.3、AVFilterContext
/** An instance of a filter */
struct AVFilterContext {
const AVClass *av_class; ///< needed for av_log() and filters common options
const AVFilter *filter; ///< the AVFilter of which this is an instance
char *name; ///< name of this filter instance
AVFilterPad *input_pads; ///< array of input pads
AVFilterLink **inputs; ///< array of pointers to input links
unsigned nb_inputs; ///< number of input pads
AVFilterPad *output_pads; ///< array of output pads
AVFilterLink **outputs; ///< array of pointers to output links
unsigned nb_outputs; ///< number of output pads
void *priv; ///< private data for use by the filter
struct AVFilterGraph *graph; ///< filtergraph this filter belongs to
int thread_type;
AVFilterInternal *internal;
struct AVFilterCommand *command_queue;
char *enable_str; ///< enable expression string
void *enable; ///< parsed expression (AVExpr*)
double *var_values; ///< variable values for the enable expression
int is_disabled; ///< the enabled state from the last expression evaluation
AVBufferRef *hw_device_ctx;
int nb_threads;
unsigned ready;
int extra_hw_frames;
};
滤镜的上下文结构体,存储了AVFilter 的地址,指向了和它相关的AVFilterGraph ,每个AVFilterContext是经过一种链式结构链接,有AVFilterLink输入输出作为头尾节点,在后续的链接中会绑定联系。
二、流程
流程中滤镜有四个角色,第一个便是输入滤镜,作为咱们的头节点,其次是咱们的自定义滤镜,自定义滤镜个数并不局限于一个,有些滤镜能够多个组合构成一个滤镜作用,接下来便是格局滤镜,它改动咱们的音视频帧格局参数,它其实并不是必要角色,咱们也能够不把它参加咱们的滤镜流程。最终便是输出滤镜。咱们不只要取得这四个角色的AVFilter结构体,还要取得他们的AVFilterContext结构体。其实最终是取得AVFilterContext,并把一切的AVFilterContext 链接起来,AVFilter只是一个过程中的变量。 下面我将按流程讲一下流程中用到的各个办法:
avfilter_graph_alloc:初始化AVFilterGraph 目标,为后续的一切过程打下根底;
avfilter_get_by_name:经过姓名获取FFmpeg的内置滤镜的AVFilter结构体,假如不是内置滤镜会初始化失利,姓名的定义见FFmpeg官方文档
avfilter_graph_alloc_filter:拿到上面的AVFilterGraph和AVFilter结构体开始初始化AVFilterContext结构体,该办法的第三个参数是你定义的滤镜名称,主张同名,但也能够自定义,条件是你自己搞得清楚。
avfilter_init_str:经过字符串命令的方式初始化滤镜的参数,该办法会解析相应的参数并进行滤镜参数赋值
avfilter_link:当一切滤镜角色都经过2-4步办法完结初始化,咱们能够将他们链接起来,咱们就把它们从输入串到输出
avfilter_graph_config:咱们的一切的滤镜工作都预备完结了,只需求经过该办法完结装备,一切的原料都预备好了就能够拼装预备生产了。
av_buffersrc_add_frame:咱们把需求滤镜处理的帧经过它传入,它的第一个参数便是咱们的输入滤镜,第二参数是传入数据的音视频帧地址,中间的操作咱们不必管它,咱们直接去出口等数据出来。
av_buffersink_get_frame:该办法第一个参数是咱们的输出滤镜,第二个参数是接收音视频帧的地址,假如成功的话,此刻第二参数便是已经是处理好的音频帧数据了,咱们此刻把这个帧拿去继续解码,解码后送给播放器,你就能听到你想要的音频滤镜作用了。
avfilter_free/ avfilter_graph_free:最终记得用完的结构体记得开释收回,C大佬可不像Java惯着你给你擦屁股收垃圾。
三、总结
其实拿到AVFilterContext和AVFilter结构体的办法并不是局限于上面那一种,并且对于滤镜参数的装备也不是只有avfilter_init_str的办法一整串命令传进去解析参数,它也能够一个一个参数进行装备,代码上大致都相同就不贴了,假如有需求能够继续看看官方的滤镜示例程序1-filter_audio.c 滤镜示例程序2-filtering_audio.c。咱们多个滤镜混合道理是相同的,例如把倍速atempo和改动采样率asetrate滤镜一同运用咱们就能得到一个变调不变速的音频作用,结合参数不同以此完结相似以前QQ变声里边的娃娃音,大叔音,恶魔音等等,让咱们的播放器变得搞怪起来。欢迎大家沟通评论,批评指正。