在ff_ffplay.c中AVfilter 音频过滤loudnorm全流程详细分析

开始之前:

准备:

•对AVfilter的基本结构体,例如AVFilterGraph 、AVFilterContext有基本认识。

•编译好debug版本的ffmpeg,断点可进入链接的动态库。

收获:

•以loudnorm为例子,快速了解AVfilter音频过滤一般流程。进而面对其他音频优化需求时,能更快上手。

•学习ffmpeg的AVfilter模块化设计,了解c实现复杂滤波器图中各个滤波器协作运行的基本原理。

ffplay层:

总体流程:

初始化设置所使用滤波器=>将解码后的帧送入滤波器=>从滤波器中取出滤波后的帧

详细流程:

1.用户使用滤波器,在ffplay完成初始化(首帧解码出来完成的),使用非命令行方式(audio_thread函数,ff_ffplay.c文件):

#if CONFIG_AVFILTER
            dec_channel_layout = get_valid_channel_layout(frame->channel_layout, frame->channels);

            reconfigure =
                cmp_audio_fmts(is->audio_filter_src.fmt, is->audio_filter_src.channels,
                               frame->format, frame->channels)    ||
                is->audio_filter_src.channel_layout != dec_channel_layout ||
                is->audio_filter_src.freq           != frame->sample_rate ||
                is->auddec.pkt_serial               != last_serial        ||
                ffp->af_changed;

            if (reconfigure) {
                //av_log(NULL, AV_LOG_DEBUG,"reconfigure()\n");
                SDL_LockMutex(ffp->af_mutex);
                ffp->af_changed = 0;
                char buf1[1024], buf2[1024];
                av_get_channel_layout_string(buf1, sizeof(buf1), -1, is->audio_filter_src.channel_layout);
                av_get_channel_layout_string(buf2, sizeof(buf2), -1, dec_channel_layout);
                av_log(NULL, AV_LOG_DEBUG,
                       "Audio frame changed from rate:%d ch:%d fmt:%s layout:%s serial:%d to rate:%d ch:%d fmt:%s layout:%s serial:%d\n",
                       is->audio_filter_src.freq, is->audio_filter_src.channels, av_get_sample_fmt_name(is->audio_filter_src.fmt), buf1, last_serial,
                       frame->sample_rate, frame->channels, av_get_sample_fmt_name(frame->format), buf2, is->auddec.pkt_serial);

                is->audio_filter_src.fmt            = frame->format;
                is->audio_filter_src.channels       = frame->channels;
                is->audio_filter_src.channel_layout = dec_channel_layout;
                is->audio_filter_src.freq           = frame->sample_rate;
                last_serial                         = is->auddec.pkt_serial;
                if ((ret = configure_audio_filters(ffp, "loudnorm=i=-14:LRA=2:tp=0:linear=0:", 1)) < 0) {
                    SDL_UnlockMutex(ffp->af_mutex);
                    goto the_end;
                }   
                SDL_UnlockMutex(ffp->af_mutex);
            }

Tips:

•#if CONFIG_AVFILTER这个宏默认是开启的。

•if (reconfigure)只在首帧进入,初始化configure_audio_filters。

•ret = configure_audio_filters(ffp, "loudnorm=i=-14:LRA=2:tp=0:linear=0:", 1),通过此方式将命令行参数args传入。

2.接下来两个关键函数,av_buffersrc_add_frame将帧送入滤波器中,av_buffersink_get_frame_flags将滤波后的帧取出来。(audio_thread函数,ff_ffplay.c文件)

if ((ret = av_buffersrc_add_frame(is->in_audio_filter, frame)) < 0)
            goto the_end;
        
        while ((ret = av_buffersink_get_frame_flags(is->out_audio_filter, frame, 0)) >= 0) {
          
            tb = av_buffersink_get_time_base(is->out_audio_filter);
#endif

接着进入ffmpeg层

/*******************/

ffmpeg层:

av_buffersrc_add_frame:

这个流程比较繁琐,进行了一定的简化,我们先看av_buffersrc_add_frame,完成输入滤波并将滤波后的帧存入buffersink中。

四句话总结:

这里为了完成输入滤波功能一共用了6个滤波器:1.buffersrc(输入缓冲,默认),2.auto_resampler_0(进行重采样,输入frame和目标滤波器的nb_samples不匹配时自动添加的),3.loudnorm(目标滤波器),4.vf_format(处理图像相关,默认这里不起作用),5.auto_resampler_1(进行重采样,目标滤波器和输出的nb_samples不匹配时自动添加的),6.buffersink(输出缓冲,默认)

输入滤波的核心在于,只要filter_graph中有准备好的滤波器就会在push_frame中循环调用ff_filter_graph_run_once,进而在其中激活不同的滤波器,保证frame在滤波器中运送,实现滤波功能

filter_frame滤波之后立马调用ff_filter_frame,正向传播进入下一个滤波器。当到达最后一个滤波器时,开启反向传播,并调用ff_request_frame_to_filter将当前滤波器状态设为需要帧进入,并将当前滤波器链接的输入滤波器状态设为ready,为下一次滤波做准备。

总的来看,先ready设为300前向传播完成滤波功能,然后通过设置ready为100进入反向传播为下一次正向进入设置需要帧等状态。1->2->3->4->5->6->5->4->3->2->1。

•进入默认buffersrc滤波器(进行缓冲)=>

•进入push_frame循环调用ff_filter_graph_run_once(graph)=>

•在ff_filter_graph_run_once中去遍历graph中的滤波器通过ready判断是否激活该滤波器=>

•走出buffersrc滤波器进入激活的滤波器=>

•在ff_filter_activate_default进行判断samples_ready(是否满足滤波器设定的进行滤波的最小样本数)=>

•如果满足进入ff_filter_frame_to_filter(filter_frame)进行滤波=>

•如果还没到达最后一个滤波器,调用filter_frame里面的ff_filter_frame进行正向传播=>

•到达最后一个滤波器后,开启反向传播,通过ff_request_frame_to_filter给当前滤波器状态设定为需要帧进入,为下一次滤波做准备=>

•如果filter_graph中有准备好的滤波器继续调用ff_filter_graph_run_once(graph)=>

tips:

•初学者一定要弄清楚当前被激活的滤波器是哪个,避免由于多态混淆了当前调用的函数。

•buffersrc滤波器(进行缓冲)的原因:由于目标滤波器可能需要满足一定数量的样本才能滤波。

•要搞清楚整个流程,在debug中找到事件循环很重要。

这张图能看到更详细的流程:

接下来关注下面这张图,解释了为什么需要自动添加滤波器auto_resampler_0。很显然,我们初始frame:44100,进入loudnorm要求192000(增大采样率,可以使得声音最大峰值检测更准确,自然声音调整效果也更好)

这张图可以看到采样率从1024变成了4559,满足输入loudnorm的要求。

av_buffersink_get_frame_flags:

这个流程相对于简单,之前buffer存在了buffersink.c的FIFO里。那么这个函数完成的工作其实就是一个从buffersink的FIFO读数据。可以通过debug看看具体流程,这里就不细说了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值