背景:
本文介绍FFmpeg中libavfilter的使用方法,并以其实现音视频倍速功能。
libavfilter介绍:
libavfilter是FFmpeg提供的滤波器类,可以用其做一些音视频处理,如音视频倍速、水平翻转、裁剪、加方框、叠加文字等功能。
例如之前介绍过的音频重采样,视频的像素格式转换,本质上也是滤波,所以libavfilter也可以实现libswresample、libswscale提供的对音视频格式变换的功能。
1、基本概念:
libavfilter结构类似于directShow,可以同时存在多个filter(滤波器)。
1、每个filter具有input及output端口,经过filter后,音视频数据会根据滤波器特性进行相应处理。
2、一个filter的output可以和另一个filter的input link起来,多个filter组合后统一到libavfilter的Graph(滤波器组图表)之中。第一个滤波器称为src,它只有output端口,最后一个滤波器称为sink,它只有input端口。
3、整合后提交整个Graph
4、使用时,用户将数据放入src中,经过多个filter处理后,从sink获取处理后的数据。
2、基本结构:
一整个滤波的流程称为滤波过程。下面是一个滤波过程的结构
结构体AVFilterGraph 用于统合这整个滤波过程的结构体,代表所有滤波器整合的图表(如上图)。
图中简要指示出了滤波所用到的各个结构体,各个结构体有如下作用:
AVFilter | 滤波器,滤波器的实现是通过AVFilter以及位于其下的结构体/函数来维护的。 |
---|---|
AVFilterContext | 一个滤波器实例,即使是同一个滤波器,但是在进行实际的滤波时,也会由于输入的参数不同而有不同的滤波效果,AVFilterContext就是在实际进行滤波时用于维护滤波相关信息的实体。 |
AVFilterLink | 滤波器链,作用主要是用于连接相邻的两个AVFilterContext。为了实现一个滤波过程,可能会需要多个滤波器协同完成,即一个滤波器的输出可能会是另一个滤波器的输入,AVFilterLink的作用是串联两个相邻的滤波器实例,形成两个滤波器之间的通道。 |
AVFilterPad | 滤波器的输入输出端口,一个滤波器可以有多个输入以及多个输出端口,相邻滤波器之间是通过AVFilterLink来串联的,而位于AVFilterLink两端的分别就是前一个滤波器的输出端口以及后一个滤波器的输入端口。 |
buffersrc | 一个特殊的滤波器,这个滤波器的作用就是充当整个滤波过程的入口,通过调用该滤波器提供的函数(如av_buffersrc_add_frame)可以把需要滤波的帧传输进入滤波过程。在创建该滤波器实例的时候需要提供一些关于所输入的帧的格式的必要参数(如:time_base、图像的宽高、图像像素格式等)。 |
buffersink | 一个特殊的滤波器,这个滤波器的作用就是充当整个滤波过程的出口,通过调用该滤波器提供的函数(如av_buffersink_get_frame)可以提取出被滤波过程滤波完成后的帧。 |
3、使用方法:
步骤:
- avfilter_register_all():注册所有AVFilter;
- avfilter_graph_alloc():创建AVFilterGraph ;
- avfilter_graph_create_filter():创建并向AVFilterGraph中添加一个 AVFilter;
- 链接滤波器
- avfilter_graph_config():检查AVFilterGraph的配置,并使其生效;
- 通过av_buffersrc_add_frame()向src中放入一个AVFrame,通过av_buffersink_get_frame()从sink中取出一个处理后AVFrame。
第4步中,libavfilter提供了两种两种方法进行滤波器的创建于链接:
- 第一种方式是创建一个个AVFilter ,然后通过avfilter_link链接。
例子如下:
1、创建3个滤波器实例 in,out,myfilter
AVFilterContext *in_video_filter = NULL;
AVFilterContext *out_video_filter = NULL;
AVFilterContext *my_video_filter = NULL;
avfilter_graph_create_filter(&in_video_filter, buffersrc, "in", args, NULL, filter_graph);
avfilter_graph_create_filter(&out_video_filter, buffersink, "out", NULL, NULL, filter_graph);
avfilter_graph_create_filter(&my_video_filter, myfilter, "myfilter", NULL, NULL, filter_graph);
2、用AVFilterLink把相邻的两个滤波实例连接起来
avfilter_link(in_video_filter, 0, my_video_filter, 0);
avfilter_link(my_video_filter, 0, out_video_filter, 0);
- 第二种方式是用户以字符串的方式描述各个滤波器之间的关系,使用avfilter_graph_parse_ptr()解析字符串,自动链接多个滤波器,生成到AVFilterGraph,不过我们需要自行生成buffersrc以及buffersink的实例,并通过该函数提供的输入以及输出接口把buffersrc、buffersink与该滤波图连接起来。
例子如下:
1、解析字符串,并构建该字符串所描述的滤波图
avfilter_graph_parse_ptr(filter_graph, graph_desc, &inputs, &outputs,0);
2、inputs与outputs分别为输入与输出的接口集合,我们需要为这些接口接上输入以及输出。
for (cur = inputs, i = 0; cur; cur = cur->next, i++) {
const AVFilter *buffersrc = avfilter_get_by_name("buffer");
avfilter_graph_create_filter(&filter, buffersrc, name, args, NULL, filter_graph);
avfilter_link(filter, 0, cur->filter_ctx, cur->pad_idx);
}
avfilter_inout_free(&inputs);
for (cur = outputs, i = 0; cur; cur = cur->next, i++) {
const AVFilter *buffersink = avfilter_get_by_name("buffersink");
avfilter_graph_create_filter(&filter, buffersink, name, NULL, NULL, filter_graph);
avfilter_link(cur->filter_ctx, cur->pad_idx, filter, 0);
}
avfilter_inout_free(&outputs);
滤波器描述字符串说明:
引用自https://www.cnblogs.com/TaigaCon/p/10067871.html
如下是一个描述复杂滤波过程的字符串的例子:
[0]trim=start_frame=10:end_frame=20[v0];\
[0]trim=start_frame=30:end_frame=40[v1];\ [v0][v1]concat=n=2[v2];\
[1]hflip[v3];\ [v2][v3]overlay=eof_action=repeat[v4];\
[v4]drawbox=50:50:120:120:red:t=5[v5]
说明:
以上是一个连续的字符串,为了方便分析我们把该字符串进行了划分,每一行都是一个滤波器实例,对于一行:
开头是一对中括号,中括号内的是输入的标识名0。 中括号后面接着的是滤波器名称trim。
名称后的第一个等号后面是滤波器参数start_frame=10:end_frame=20,这里有两组参数,两组参数用冒号分开。
第一组参数名称为start_frame,参数值为10,中间用等号分开。 第二组参数名称为end_frame,参数值为20,中间用等号分开。
最后也有一对中括号,中括号内的是输出的标识名v0。
如果一个滤波实例的输入标识名与另一个滤波实例的输出标识名相同,则表示这两个滤波实例构成滤波链。
如果一个滤波实例的输入标识名或者输出标识名一直没有与其它滤波实例的输出标识名或者输入标识名相同,则表明这些为外部的输入输出,通常我们会为其接上buffersrc以及buffersink。
按照这种规则,上面的滤波过程可以被描绘成以下滤波图:
音视频倍速实例:
音频倍速,同时将音频输出为S16,立体声。
题外话,FFmpeg使用libavfilter通过设置atempo=2.0来进行2倍速,会有一些杂音现象。音频的倍速是一个比较麻烦的问题,我们知道单纯的改变音频的采样率及输出采用率,音频会倍速,但这种情况下也会有严重的变调。FFmpeg的libavfilter是使用了一些算法来进行处理,但测试结果来看,效果并不理想,目前比较常用的是使用soundtouch 框架对声音进行处理(可以参考ijkplayer)
1、filter初始化
const char *filter_descr = "atempo=2.0,aformat=sample_fmts=s16:channel_layouts=stereo";
static int init_filters(const char *filters_descr, AudioState *audio)
{
char args[512];
int ret = 0;
AVFilter *abuffersrc = avfilter_get_by_name("abuffer");
AVFilter *abuffersink = avfilter_get_by_name("abuffersink");
AVFilterInOut *outputs = avfilter_inout_alloc();
AVFilterInOut *inputs = avfilter_inout_alloc();
static const enum AVSampleFormat out_sample_fmts[] = { AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_NONE };
static const int64_t out_channel_layouts[] = { AV_CH_LAYOUT_STEREO, -1 };
static const int out_sample_rates[] = { audio->audio_ctx->sample_rate, -1 };
AVRational time_base = audio->stream->time_base;
AVCodecContext *dec_ctx = audio->audio_ctx;
do
{
audio->filter_graph = avfilter_graph_alloc();
if (!outputs || !inputs || !audio->filter_graph)
{
ret = AVERROR(ENOMEM);
break;
}
/* buffer audio source: the decoded frames from the decoder will be inserted here. */
if (!dec_ctx->channel_layout)
dec_ctx->channel_layout = av_get_default_channel_layout(dec_ctx->channels);
snprintf(args, sizeof(args),
"time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%x",
time_base.num, time_base.den, dec_ctx->sample_rate,
av_get_sample_fmt_name(dec_ctx->sample_fmt), dec_ctx->channel_layout);
ret = avfilter_graph_create_filter(&audio->buffersrc_ctx, abuffersrc, "in",
args, NULL, audio->filter_graph);
if (ret < 0)
{
printf("Cannot create audio buffer source\n");
break;
}
/* buffer audio sink: to terminate the filter chain. */
ret = avfilter_graph_create_filter(&audio->buffersink_ctx, abuffersink, "out",
NULL, NULL, audio->filter_graph);
if (ret < 0)
{
printf("Cannot create audio buffer sink\n");
break;
}
ret = av_opt_set_int_list(audio->buffersink_ctx, "sample_fmts", out_sample_fmts, -1,
AV_OPT_SEARCH_CHILDREN);
if (ret < 0)
{
printf( "Cannot set output sample format\n");
break;
}
ret = av_opt_set_int_list(audio->buffersink_ctx, "channel_layouts", out_channel_layouts, -1,
AV_OPT_SEARCH_CHILDREN);
if (ret < 0)
{
printf( "Cannot set output channel layout\n");
break;
}
ret = av_opt_set_int_list(audio->buffersink_ctx, "sample_rates", out_sample_rates, -1,
AV_OPT_SEARCH_CHILDREN);
if (ret < 0)
{
printf( "Cannot set output sample rate\n");
break;
}
outputs->name = av_strdup("in");
outputs->filter_ctx = audio->buffersrc_ctx;
outputs->pad_idx = 0;
outputs->next = NULL;
inputs->name = av_strdup("out");
inputs->filter_ctx = audio->buffersink_ctx;
inputs->pad_idx = 0;
inputs->next = NULL;
if ((ret = avfilter_graph_parse_ptr(audio->filter_graph, filters_descr,
&inputs, &outputs, NULL)) < 0)
break;
if ((ret = avfilter_graph_config(audio->filter_graph, NULL)) < 0)
break;
}
while(0);
avfilter_inout_free(&inputs);
avfilter_inout_free(&outputs);
return ret;
}
2、音频解码后进行倍速滤波
static int audio_decode_frame(AudioState *audio_state, uint8_t *audio_buf)
{
......省略
int ret = avcodec_send_packet(audio_state->audio_ctx, &pkt);
if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
{
av_frame_free(&frame);
return -1;
}
while(avcodec_receive_frame(audio_state->audio_ctx, frame) >= 0)
{
/* push the audio data from decoded frame into the filtergraph */
if (av_buffersrc_add_frame_flags(audio_state->buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF) < 0)
{
av_log(NULL, AV_LOG_ERROR, "Error while feeding the audio filtergraph\n");
break;
}
/* pull filtered audio from the filtergraph */
while (1)
{
ret = av_buffersink_get_frame(audio_state->buffersink_ctx, filt_frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
break;
if (ret < 0)
{
av_frame_free(&frame);
av_frame_free(&filt_frame);
return -1;
}
......省略
}
av_frame_unref(frame);
}
av_frame_free(&frame);
av_frame_free(&filt_frame);
return data_size;
}
视频倍速,并设置视频宽高,转换为RGB,流程基本与音频无区别。
视频的倍速比较简单,实际上就是改变PTS。其实在做了音视频同步的情况下,以音频PTS为基准时钟,那么并不需要处理视频的倍速
1、filter初始化
const char *filter_descr = "setpts=0.5*PTS,scale=720:480";
static int init_filters(const char *filters_descr, VideoState *video)
{
char args[512];
int ret = 0;
AVFilter *buffersrc = avfilter_get_by_name("buffer");
AVFilter *buffersink = avfilter_get_by_name("buffersink");
AVFilterInOut *outputs = avfilter_inout_alloc();
AVFilterInOut *inputs = avfilter_inout_alloc();
AVRational time_base = video->stream->time_base;
enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_RGB32, AV_PIX_FMT_NONE };
AVCodecContext *dec_ctx = video->video_ctx;
video->filter_graph = avfilter_graph_alloc();
do
{
if (!outputs || !inputs || !video->filter_graph)
{
ret = AVERROR(ENOMEM);
break;
}
/* buffer video source: the decoded frames from the decoder will be inserted here. */
snprintf(args, sizeof(args),
"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt,
time_base.num, time_base.den,
dec_ctx->sample_aspect_ratio.num, dec_ctx->sample_aspect_ratio.den);
ret = avfilter_graph_create_filter(&video->buffersrc_ctx, buffersrc, "in",
args, NULL, video->filter_graph);
if (ret < 0)
{
printf("Cannot create buffer source\n");
break;
}
/* buffer video sink: to terminate the filter chain. */
ret = avfilter_graph_create_filter(&video->buffersink_ctx, buffersink, "out",
NULL, NULL, video->filter_graph);
if (ret < 0)
{
printf( "Cannot create buffer sink\n");
break;
}
ret = av_opt_set_int_list(video->buffersink_ctx, "pix_fmts", pix_fmts,
AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
if (ret < 0)
{
printf( "Cannot set output pixel format\n");
break;
}
outputs->name = av_strdup("in");
outputs->filter_ctx = video->buffersrc_ctx;
outputs->pad_idx = 0;
outputs->next = NULL;
inputs->name = av_strdup("out");
inputs->filter_ctx = video->buffersink_ctx;
inputs->pad_idx = 0;
inputs->next = NULL;
if ((ret = avfilter_graph_parse_ptr(video->filter_graph, filters_descr,
&inputs, &outputs, NULL)) < 0)
break;
if ((ret = avfilter_graph_config(video->filter_graph, NULL)) < 0)
break;
}
while(0);
avfilter_inout_free(&inputs);
avfilter_inout_free(&outputs);
return ret;
}
2、视频解码后进行倍速滤波
static int vdecode_thread(void *arg)
{
VideoState *video = (VideoState *)arg;
AVFrame *frame = av_frame_alloc();
AVFrame *filt_frame = av_frame_alloc();
AVPacket packet;
double pts;
while (!quit)
{
....省略
int ret = avcodec_send_packet(video->video_ctx, &packet);
if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
{
continue;
}
while(avcodec_receive_frame(video->video_ctx, frame) >= 0)
{
...省略
/* push the decoded frame into the filtergraph */
if (av_buffersrc_add_frame_flags(video->buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF) < 0)
{
printf("Error while feeding the filtergraph\n");
break;
}
/* pull filtered frames from the filtergraph */
while (1)
{
ret = av_buffersink_get_frame(video->buffersink_ctx, filt_frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
break;
}
else if (ret < 0)
{
printf("Error while av_buffersink_get_frame\n");
break;
}
......省略
av_frame_unref(frame);
}
usleep(10);
}
av_frame_free(&frame);
av_frame_free(&filt_frame);
return 0;
}
来源:https://blog.csdn.net/myvest/article/details/95024652