ffmpeg中有很多已經實現好的濾波器,這些濾波器的實現位於libavfilter目錄之下,用戶需要進行濾波時,就是是調用這些濾波器來實現的。ffmpeg對於調用濾波器有一整套的調用機制。
基本結構
我們把一整個濾波的流程稱為濾波過程。下面是一個濾波過程的結構
圖中簡要指示出了濾波所用到的各個結構體,各個結構體有如下作用:
AVFilterGraph | 用於統合這整個濾波過程的結構體。 |
AVFilter | 濾波器,濾波器的實現是通過AVFilter以及位於其下的結構體/函數來維護的。 |
AVFilterContext | 一個濾波器實例,即使是同一個濾波器,但是在進行實際的濾波時,也會由於輸入的參數不同而有不同的濾波效果,AVFilterContext就是在實際進行濾波時用於維護濾波相關信息的實體。 |
AVFilterLink | 濾波器鏈,作用主要是用於連接相鄰的兩個AVFilterContext。為了實現一個濾波過程,可能會需要多個濾波器協同完成,即一個濾波器的輸出可能會是另一個濾波器的輸入,AVFilterLink的作用是串聯兩個相鄰的濾波器實例,形成兩個濾波器之間的通道。 |
AVFilterPad | 濾波器的輸入輸出端口,一個濾波器可以有多個輸入以及多個輸出端口,相鄰濾波器之間是通過AVFilterLink來串聯的,而位於AVFilterLink兩端的分別就是前一個濾波器的輸出端口以及后一個濾波器的輸入端口。 |
buffersrc | 一個特殊的濾波器,這個濾波器的作用就是充當整個濾波過程的入口,通過調用該濾波器提供的函數(如av_buffersrc_add_frame)可以把需要濾波的幀傳輸進入濾波過程。在創建該濾波器實例的時候需要提供一些關於所輸入的幀的格式的必要參數(如:time_base、圖像的寬高、圖像像素格式等)。 |
buffersink | 一個特殊的濾波器,這個濾波器的作用就是充當整個濾波過程的出口,通過調用該濾波器提供的函數(如av_buffersink_get_frame)可以提取出被濾波過程濾波完成后的幀。 |
創建簡單的濾波過程
創建整個濾波過程包含以下步驟:
首先需要得到整個濾波過程所需的濾波器(AVFilter),其中buffersrc以及buffersink是作為輸入以及輸出所必須的兩個濾波器。
const AVFilter *buffersrc = avfilter_get_by_name("buffer"); const AVFilter *buffersink = avfilter_get_by_name("buffersink"); const AVFilter *myfilter = avfilter_get_by_name("myfilter");
創建統合整個濾波過程的濾波圖結構體(AVFilterGraph)
filter_graph = avfilter_graph_alloc();
創建用於維護濾波相關信息的濾波器實例(AVFilterContext)
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);
用AVFilterLink把相鄰的兩個濾波實例連接起來
avfilter_link(in_video_filter, 0, my_video_filter, 0); avfilter_link(my_video_filter, 0, out_video_filter, 0);
提交整個濾波圖
avfilter_graph_config(filter_graph, NULL);
創建復雜的濾波過程
當濾波過程復雜到一定程度時,即需要多個濾波器進行復雜的連接來實現整個濾波過程,這時候對於調用者來說,繼續采用上述方法來構建濾波圖就顯得不夠效率。對於復雜的濾波過程,ffmpeg提供了一個更為方便的濾波過程創建方式。
這種復雜的濾波器過程創建方式要求用戶以字符串的方式描述各個濾波器之間的關系。如下是一個描述復雜濾波過程的字符串的例子:
[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。
按照這種規則,上面的濾波過程可以被描繪成以下濾波圖:
ffmpeg提供一個函數用於解析這種字符串:avfilter_graph_parse2。這個函數會把輸入的字符串生成如上面的濾波圖,不過我們需要自行生成buffersrc以及buffersink的實例,並通過該函數提供的輸入以及輸出接口把buffersrc、buffersink與該濾波圖連接起來。整個流程包含以下步驟:
創建統合整個濾波過程的濾波圖結構體(AVFilterGraph)
filter_graph = avfilter_graph_alloc();
解析字符串,並構建該字符串所描述的濾波圖
avfilter_graph_parse2(filter_graph, graph_desc, &inputs, &outputs);
其中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);
提交整個濾波圖
avfilter_graph_config(filter_graph, NULL);
濾波API
上面主要討論了如何創建濾波過程,不過要進行濾波還需要把幀傳輸進入該過程,並在濾波完成后從該過程中提取出濾波完成的幀。
buffersrc提供了向濾波過程輸入幀的API:av_buffersrc_add_frame。向指定的buffersrc實例輸入想要進行濾波的幀就可以把幀傳入濾波過程。
av_buffersrc_add_frame(c->in_filter, pFrame);
buffersink提供了從濾波過程提取幀的API:av_buffersink_get_frame。可以從指定的buffersink實例提取濾波完成的幀。
av_buffersink_get_frame(c->out_filter, pFrame);
當av_buffersink_get_frame返回值大於0則表示提取成功。