零、写在前面

我正在参与「启航计划」;书接上回FFmpeg音频解码-音频可视化。假如只解码音频然后就拿去给播放器播放那也太单调了,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:拿到上面的AVFilterGraphAVFilter结构体开始初始化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惯着你给你擦屁股收垃圾。

三、总结

其实拿到AVFilterContextAVFilter结构体的办法并不是局限于上面那一种,并且对于滤镜参数的装备也不是只有avfilter_init_str的办法一整串命令传进去解析参数,它也能够一个一个参数进行装备,代码上大致都相同就不贴了,假如有需求能够继续看看官方的滤镜示例程序1-filter_audio.c 滤镜示例程序2-filtering_audio.c。咱们多个滤镜混合道理是相同的,例如把倍速atempo和改动采样率asetrate滤镜一同运用咱们就能得到一个变调不变速的音频作用,结合参数不同以此完结相似以前QQ变声里边的娃娃音,大叔音,恶魔音等等,让咱们的播放器变得搞怪起来。欢迎大家沟通评论,批评指正。