开始之前:
准备:
•对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看看具体流程,这里就不细说了。