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

猜你喜欢

转载自blog.csdn.net/qq_37841321/article/details/124523354