零、写在前面
书接上回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变声里面的娃娃音,大叔音,恶魔音等等,让我们的播放器变得搞怪起来。欢迎大家交流讨论,批评指正。