FFMPEG中的兩輸入Filter實現(一)
開帖大吉!
利用FFMPEG工作已有一年多,許多學習文件散落在電腦各處,沒有一個清晰明確的組織脈絡;還有踩過又填平的各種坑,時間久了難免遺忘,再次遭遇時仍然要從頭查起;而且事必躬親也是毫無疑問的低效率,不利於後來同事的成長。因此有了開博的決定,希望記錄下自己走過的腳印,見證自己的成長,也能幫助後來人。
近期在ffmpeg3.3版本上開發一個基於overlay濾鏡的新功能,因此花了些時間把雙輸入濾鏡的實現梳理了一番,整理的大致的函式呼叫關係圖如下:最左側淺黃色分支為濾鏡註冊;中間分支淺綠色部分是濾鏡的解析和初始化;最右側分支的淺紫色部分是濾鏡的主體實現過程。因為涉及到的函式比較多,因此本篇先分析濾鏡註冊和初始化部分,濾鏡的具體實現留到下一篇分析。
使用的命令列如下:
ffmpeg -i input.ts -i logo.png -c:v libx264 -s 1280x720 -b:v 2000k -filter_complex "[0:v][1:v]overlay=100:100" -c:a copy -f mpegts -y overlayout.ts
一、濾鏡的註冊
avfilter_register_all():ffmpeg中,任何濾鏡在使用之前,必須先經過註冊。overlay濾鏡的註冊如下:
REGISTER_FILTER(OVERLAY, overlay, vf);
再來看看最終呼叫的註冊函式:
int avfilter_register(AVFilter *filter) { AVFilter **f = last_filter; /* the filter must select generic or internal exclusively */ av_assert0((filter->flags & AVFILTER_FLAG_SUPPORT_TIMELINE) != AVFILTER_FLAG_SUPPORT_TIMELINE); filter->next = NULL; while(*f || avpriv_atomic_ptr_cas((void * volatile *)f, NULL, filter)) f = &(*f)->next; last_filter = &filter->next; return 0; }
所謂的濾鏡註冊,也就是把目標濾鏡的結構體加入到濾鏡結構體連結串列中。
二、濾鏡的解析與初始化
由於overlay的輸入含有兩個或以上,因此採用的是complex filter。complex filter的解析和初始化在ffmpeg_parse_options階段完成,下面我們來逐層分析該階段對濾鏡的操作。
1. init_complex_filters():
逐個初始化濾鏡圖,nb_filtergraphs在解析引數階段被賦值,在本例中,nb_filtergraphs = 1。static int init_complex_filters(void) { int i, ret = 0; for (i = 0; i < nb_filtergraphs; i++) { ret = init_complex_filtergraph(filtergraphs[i]); if (ret < 0) return ret; } return 0; }
2. init_complex_filtergraph():
int init_complex_filtergraph(FilterGraph *fg)
{
AVFilterInOut *inputs, *outputs, *cur;
AVFilterGraph *graph;
int ret = 0;
/* this graph is only used for determining the kinds of inputs
* and outputs we have, and is discarded on exit from this function */
graph = avfilter_graph_alloc(); //為臨時filter graph分配空間
if (!graph)
return AVERROR(ENOMEM);
ret = avfilter_graph_parse2(graph, fg->graph_desc, &inputs, &outputs); //解析並建立filter
if (ret < 0)
goto fail;
for (cur = inputs; cur; cur = cur->next) //初始化輸入
init_input_filter(fg, cur);
for (cur = outputs; cur;) { //初始化輸出,此例中只有一個輸出
GROW_ARRAY(fg->outputs, fg->nb_outputs);
fg->outputs[fg->nb_outputs - 1] = av_mallocz(sizeof(*fg->outputs[0]));
if (!fg->outputs[fg->nb_outputs - 1])
exit_program(1);
fg->outputs[fg->nb_outputs - 1]->graph = fg;
fg->outputs[fg->nb_outputs - 1]->out_tmp = cur;
fg->outputs[fg->nb_outputs - 1]->type = avfilter_pad_get_type(cur->filter_ctx->output_pads,
cur->pad_idx);
fg->outputs[fg->nb_outputs - 1]->name = describe_filter_link(fg, cur, 0);
cur = cur->next;
fg->outputs[fg->nb_outputs - 1]->out_tmp->next = NULL;
}
fail:
avfilter_inout_free(&inputs); //刪除分配的臨時空間
avfilter_graph_free(&graph);
return ret;
}
3. av_filter_graph_parse2():
int avfilter_graph_parse2(AVFilterGraph *graph, const char *filters,
AVFilterInOut **inputs,
AVFilterInOut **outputs)
{
int index = 0, ret = 0;
char chr = 0;
AVFilterInOut *curr_inputs = NULL, *open_inputs = NULL, *open_outputs = NULL;
filters += strspn(filters, WHITESPACES);
if ((ret = parse_sws_flags(&filters, graph)) < 0)
goto fail;
do {
AVFilterContext *filter;
filters += strspn(filters, WHITESPACES);
if ((ret = parse_inputs(&filters, &curr_inputs, &open_outputs, graph)) < 0) //根據命令列解析filter的輸入,此例中為[0:v]和[1:v]
goto end;
if ((ret = parse_filter(&filter, &filters, graph, index, graph)) < 0) //解析filter名稱及其options,並建立該filter
goto end;
if ((ret = link_filter_inouts(filter, &curr_inputs, &open_inputs, graph)) < 0) //將輸入輸出加入filter link連結串列
goto end;
if ((ret = parse_outputs(&filters, &curr_inputs, &open_inputs, &open_outputs, //解析filter輸出
graph)) < 0)
goto end;
filters += strspn(filters, WHITESPACES);
chr = *filters++;
if (chr == ';' && curr_inputs)
append_inout(&open_outputs, &curr_inputs);
index++;
} while (chr == ',' || chr == ';');
if (chr) {
av_log(graph, AV_LOG_ERROR,
"Unable to parse graph description substring: \"%s\"\n",
filters - 1);
ret = AVERROR(EINVAL);
goto end;
}
append_inout(&open_outputs, &curr_inputs);
*inputs = open_inputs;
*outputs = open_outputs;
return 0;
fail:end:
while (graph->nb_filters)
avfilter_free(graph->filters[0]);
av_freep(&graph->filters);
avfilter_inout_free(&open_inputs);
avfilter_inout_free(&open_outputs);
avfilter_inout_free(&curr_inputs);
*inputs = NULL;
*outputs = NULL;
return ret;
}
4. init_input_filter():
static void init_input_filter(FilterGraph *fg, AVFilterInOut *in)
{
InputStream *ist = NULL;
enum AVMediaType type = avfilter_pad_get_type(in->filter_ctx->input_pads, in->pad_idx); //獲取filter輸出型別,目前只支援視訊型別和音訊型別
int i;
// TODO: support other filter types
if (type != AVMEDIA_TYPE_VIDEO && type != AVMEDIA_TYPE_AUDIO) {
av_log(NULL, AV_LOG_FATAL, "Only video and audio filters supported "
"currently.\n");
exit_program(1);
}
if (in->name) { //輸入name,此例中為[0:v]或[1:v]
AVFormatContext *s;
AVStream *st = NULL;
char *p;
int file_idx = strtol(in->name, &p, 0); //通過輸入name得到當前輸入的file_index
if (file_idx < 0 || file_idx >= nb_input_files) {
av_log(NULL, AV_LOG_FATAL, "Invalid file index %d in filtergraph description %s.\n",
file_idx, fg->graph_desc);
exit_program(1);
}
s = input_files[file_idx]->ctx; //當前file的ACFormatContext結構體
for (i = 0; i < s->nb_streams; i++) {
enum AVMediaType stream_type = s->streams[i]->codecpar->codec_type;
if (stream_type != type &&
!(stream_type == AVMEDIA_TYPE_SUBTITLE &&
type == AVMEDIA_TYPE_VIDEO /* sub2video hack */))
continue;
if (check_stream_specifier(s, s->streams[i], *p == ':' ? p + 1 : p) == 1) {
st = s->streams[i]; //確定當前輸入的stream
break;
}
}
if (!st) {
av_log(NULL, AV_LOG_FATAL, "Stream specifier '%s' in filtergraph description %s "
"matches no streams.\n", p, fg->graph_desc);
exit_program(1);
}
ist = input_streams[input_files[file_idx]->ist_index + st->index]; //根據file_index和st->index確定input_stream[]陣列中的index
} else { //如果命令列中沒有指定filter的輸入,則根據media type確定相應的輸入流
/* find the first unused stream of corresponding type */
for (i = 0; i < nb_input_streams; i++) {
ist = input_streams[i];
if (ist->dec_ctx->codec_type == type && ist->discard)
break;
}
if (i == nb_input_streams) {
av_log(NULL, AV_LOG_FATAL, "Cannot find a matching stream for "
"unlabeled input pad %d on filter %s\n", in->pad_idx,
in->filter_ctx->name);
exit_program(1);
}
}
av_assert0(ist);
ist->discard = 0;
ist->decoding_needed |= DECODING_FOR_FILTER;
ist->st->discard = AVDISCARD_NONE;
GROW_ARRAY(fg->inputs, fg->nb_inputs); //擴充套件filter graph中input結構體
if (!(fg->inputs[fg->nb_inputs - 1] = av_mallocz(sizeof(*fg->inputs[0])))) //為新擴充套件的輸入分配空間
exit_program(1);
fg->inputs[fg->nb_inputs - 1]->ist = ist; //為新擴充套件的filter graph輸入賦值
fg->inputs[fg->nb_inputs - 1]->graph = fg;
fg->inputs[fg->nb_inputs - 1]->format = -1;
fg->inputs[fg->nb_inputs - 1]->type = ist->st->codecpar->codec_type;
fg->inputs[fg->nb_inputs - 1]->name = describe_filter_link(fg, in, 1);
fg->inputs[fg->nb_inputs - 1]->frame_queue = av_fifo_alloc(8 * sizeof(AVFrame*));
if (!fg->inputs[fg->nb_inputs - 1]->frame_queue)
exit_program(1);
GROW_ARRAY(ist->filters, ist->nb_filters); //擴充套件輸入流中的filter結構體,並將當前的filter graph寫入到當前輸入流中相應filter結構體
ist->filters[ist->nb_filters - 1] = fg->inputs[fg->nb_inputs - 1];
}
5. parse_filter():
下面我們再來分析一下如何解析並建立一個filter例項。
static int parse_filter(AVFilterContext **filt_ctx, const char **buf, AVFilterGraph *graph,
int index, void *log_ctx)
{
char *opts = NULL;
char *name = av_get_token(buf, "=,;["); //根據命令列得到filter的名稱,這裡是"overlay"
int ret;
if (**buf == '=') { //提取命令列中filter的引數和選項,此處應該是100:00
(*buf)++;
opts = av_get_token(buf, "[],;");
}
ret = create_filter(filt_ctx, graph, index, name, opts, log_ctx); //根據filter的名稱和引數建立filter例項
av_free(name);
av_free(opts);
return ret;
}
6. create_filter():
最後,建立filter例項。
static int create_filter(AVFilterContext **filt_ctx, AVFilterGraph *ctx, int index,
const char *filt_name, const char *args, void *log_ctx)
{
AVFilter *filt;
char inst_name[30];
char *tmp_args = NULL;
int ret;
snprintf(inst_name, sizeof(inst_name), "Parsed_%s_%d", filt_name, index);
filt = avfilter_get_by_name(filt_name); //根據filter name從註冊的AVFilter連結串列中提取出該filter
if (!filt) {
av_log(log_ctx, AV_LOG_ERROR,
"No such filter: '%s'\n", filt_name);
return AVERROR(EINVAL);
}
*filt_ctx = avfilter_graph_alloc_filter(ctx, filt, inst_name); //建立filter例項,並加入到filter graph
if (!*filt_ctx) {
av_log(log_ctx, AV_LOG_ERROR,
"Error creating filter '%s'\n", filt_name);
return AVERROR(ENOMEM);
}
if (!strcmp(filt_name, "scale") && (!args || !strstr(args, "flags")) &&
ctx->scale_sws_opts) {
if (args) {
tmp_args = av_asprintf("%s:%s",
args, ctx->scale_sws_opts);
if (!tmp_args)
return AVERROR(ENOMEM);
args = tmp_args;
} else
args = ctx->scale_sws_opts;
}
ret = avfilter_init_str(*filt_ctx, args); //解析filter的引數並加入到該filter的私有域,並完成例項的初始化工作
if (ret < 0) {
av_log(log_ctx, AV_LOG_ERROR,
"Error initializing filter '%s'", filt_name);
if (args)
av_log(log_ctx, AV_LOG_ERROR, " with args '%s'", args);
av_log(log_ctx, AV_LOG_ERROR, "\n");
avfilter_free(*filt_ctx);
*filt_ctx = NULL;
}
av_free(tmp_args);
return ret;
}
到此,overlay filter就建立成功了。至於該filter在轉碼過程中是如何使用的,將會在另一篇文章中來做詳細說明。
以上內容只分析了filter的註冊、解析、建立和初始化,而對於filter的一些基本概念,例如濾鏡圖(filter graph)、濾鏡鏈(filter link)、輸入輸出pad等等,由於篇幅原因,未做詳細說明。有時間準備單獨開一篇來整理這些概念。