天天看點

[ffmpeg] 濾波

ffmpeg中有很多已經實作好的濾波器,這些濾波器的實作位于libavfilter目錄之下,使用者需要進行濾波時,就是是調用這些濾波器來實作的。ffmpeg對于調用濾波器有一整套的調用機制。

基本結構

我們把一整個濾波的流程稱為濾波過程。下面是一個濾波過程的結構

[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是作為輸入以及輸出所必須的兩個濾波器。

1

2

3

<code>const AVFilter *buffersrc  = avfilter_get_by_name(</code><code>"buffer"</code><code>);</code>

<code>const AVFilter *buffersink = avfilter_get_by_name(</code><code>"buffersink"</code><code>);</code>

<code>const AVFilter *myfilter   = avfilter_get_by_name(</code><code>"myfilter"</code><code>);</code>

建立統合整個濾波過程的濾波圖結構體(AVFilterGraph)

<code>filter_graph = avfilter_graph_alloc();</code>

建立用于維護濾波相關資訊的濾波器執行個體(AVFilterContext)

4

5

6

<code>AVFilterContext *in_video_filter = NULL;</code>

<code>AVFilterContext *out_video_filter = NULL;</code>

<code>AVFilterContext *my_video_filter = NULL;</code>

<code>avfilter_graph_create_filter(&amp;in_video_filter, buffersrc,</code><code>"in"</code><code>, args, NULL, filter_graph);</code>

<code>avfilter_graph_create_filter(&amp;out_video_filter, buffersink,</code><code>"out"</code><code>, NULL, NULL, filter_graph);</code>

<code>avfilter_graph_create_filter(&amp;my_video_filter, myfilter,</code><code>"myfilter"</code><code>, NULL, NULL, filter_graph);</code>

用AVFilterLink把相鄰的兩個濾波執行個體連接配接起來

<code>avfilter_link(in_video_filter, 0, my_video_filter, 0);</code>

<code>avfilter_link(my_video_filter, 0, out_video_filter, 0);</code>

送出整個濾波圖

<code>avfilter_graph_config(filter_graph, NULL);</code>

建立複雜的濾波過程

當濾波過程複雜到一定程度時,即需要多個濾波器進行複雜的連接配接來實作整個濾波過程,這時候對于調用者來說,繼續采用上述方法來建構濾波圖就顯得不夠效率。對于複雜的濾波過程,ffmpeg提供了一個更為友善的濾波過程建立方式。

這種複雜的濾波器過程建立方式要求使用者以字元串的方式描述各個濾波器之間的關系。如下是一個描述複雜濾波過程的字元串的例子:

<code>[0]trim=start_frame=10:end_frame=20[v0];\</code>

<code>[0]trim=start_frame=30:end_frame=40[v1];\</code>

<code>[v0][v1]concat=n=2[v2];\</code>

<code>[1]hflip[v3];\</code>

<code>[v2][v3]overlay=eof_action=repeat[v4];\</code>

<code>[v4]drawbox=50:50:120:120:red:t=5[v5]</code>

以上是一個連續的字元串,為了友善分析我們把該字元串進行了劃分,每一行都是一個濾波器執行個體,對于一行:

開頭是一對中括号,中括号内的是輸入的辨別名0。

中括号後面接着的是濾波器名稱trim。

名稱後的第一個等号後面是濾波器參數start_frame=10:end_frame=20,這裡有兩組參數,兩組參數用冒号分開。

第一組參數名稱為start_frame,參數值為10,中間用等号分開。

第二組參數名稱為end_frame,參數值為20,中間用等号分開。

最後也有一對中括号,中括号内的是輸出的辨別名v0。

如果一個濾波執行個體的輸入辨別名與另一個濾波執行個體的輸出辨別名相同,則表示這兩個濾波執行個體構成濾波鍊。

如果一個濾波執行個體的輸入辨別名或者輸出辨別名一直沒有與其它濾波執行個體的輸出辨別名或者輸入辨別名相同,則表明這些為外部的輸入輸出,通常我們會為其接上buffersrc以及buffersink。

按照這種規則,上面的濾波過程可以被描繪成以下濾波圖:

[ffmpeg] 濾波

ffmpeg提供一個函數用于解析這種字元串:avfilter_graph_parse2。這個函數會把輸入的字元串生成如上面的濾波圖,不過我們需要自行生成buffersrc以及buffersink的執行個體,并通過該函數提供的輸入以及輸出接口把buffersrc、buffersink與該濾波圖連接配接起來。整個流程包含以下步驟:

解析字元串,并建構該字元串所描述的濾波圖

<code>avfilter_graph_parse2(filter_graph, graph_desc, &amp;inputs, &amp;outputs);</code>

其中inputs與outputs分别為輸入與輸出的接口集合,我們需要為這些接口接上輸入以及輸出。

7

8

9

10

11

12

13

<code>for</code> <code>(cur = inputs, i = 0; cur; cur = cur-&gt;next, i++) {</code>

<code>    </code><code>const AVFilter *buffersrc = avfilter_get_by_name(</code><code>"buffer"</code><code>);</code>

<code>    </code><code>avfilter_graph_create_filter(&amp;filter, buffersrc, name, args, NULL, filter_graph);</code>

<code>    </code><code>avfilter_link(filter, 0, cur-&gt;filter_ctx, cur-&gt;pad_idx);</code>

<code>}</code>

<code>avfilter_inout_free(&amp;inputs);</code>

<code>for</code> <code>(cur = outputs, i = 0; cur; cur = cur-&gt;next, i++) {</code>

<code>    </code><code>const AVFilter *buffersink = avfilter_get_by_name(</code><code>"buffersink"</code><code>);</code>

<code>    </code><code>avfilter_graph_create_filter(&amp;filter, buffersink, name, NULL, NULL, filter_graph);</code>

<code>    </code><code>avfilter_link(cur-&gt;filter_ctx, cur-&gt;pad_idx, filter, 0);</code>

<code>avfilter_inout_free(&amp;outputs);</code>

濾波API

上面主要讨論了如何建立濾波過程,不過要進行濾波還需要把幀傳輸進入該過程,并在濾波完成後從該過程中提取出濾波完成的幀。

buffersrc提供了向濾波過程輸入幀的API:av_buffersrc_add_frame。向指定的buffersrc執行個體輸入想要進行濾波的幀就可以把幀傳入濾波過程。

<code>av_buffersrc_add_frame(c-&gt;in_filter, pFrame);</code>

buffersink提供了從濾波過程提取幀的API:av_buffersink_get_frame。可以從指定的buffersink執行個體提取濾波完成的幀。

<code>av_buffersink_get_frame(c-&gt;out_filter, pFrame);</code>

當av_buffersink_get_frame傳回值大于0則表示提取成功。