背景:
本文介紹FFmpeg中libavfilter的使用方法,並以其實現音視頻倍速功能。
libavfilter介紹:
libavfilter是FFmpeg提供的濾波器類,可以用其做一些音視頻處理,如音視頻倍速、水平翻轉、裁剪、加方框、疊加文字等功能。
例如之前介紹過的音頻重採樣,視頻的像素格式轉換,本質上也是濾波,所以libavfilter也可以實現libswresample、libswscale提供的對音視頻格式變換的功能。
1、基本概念:
- libavfilter結構類似於directShow,可以同時存在多個filter(濾波器)。
- 每個filter具有input及output端口,經過filter後,音視頻數據會根據濾波器特性進行相應處理。
- 一個filter的output可以和另一個filter的input link起來,多個filter組合後統一到libavfilter的Graph(濾波器組圖表)之中。第一個濾波器稱爲src,它只有output端口,最後一個濾波器稱爲sink,它只有input端口。
- 使用時,用戶將數據放入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;
}