新手學習FFmpeg - 通過API實現可控的Filter呼叫鏈
雖然通過宣告[x][y]avfilter=a=x:b=y;avfilter=xxx
的方式可以建立一個可用的Filter
呼叫鏈,並且在絕大多數場合下這種方式都是靠譜和實用的。 但如果想精細化
的管理AVFilter
呼叫鏈,例如根據某些條件來動態生成AVFilter Graph
。這種宣告方式就不太靈活(也可以通過if判斷來動態組裝字串,如果你非常喜歡這種字串宣告方式,到此為止不在建議你往下閱讀了)。
首先快速溫習一下,如何建立一個AVFilter Graph
。
+-------+ +---------------------+ +---------------+ |buffer | |Filter ..... Filter N| | buffersink | ----------> | |output|------>|input| |output|---> |input| |--------> +-------+ +---------------------+ +---------------+
建立三部曲:
- 初始化
buffer
和buffersink
。 - 初始化其它filter
- 設定Filter Graph的Input和Output。
其中buffer
和buffersink
分別代表Graph
的起始和結束。
然後快速封裝args
也就是movie=t.png[wm];[in][wm]overlay=10:20[out]
這樣的filter-complex命令。 而且通過avfilter_graph_parse_ptr
完成中間filter的初始化,
最後指定各個filter的input和output,一個graph就算搞定了。
好,下面來看如何通過API精細化
生成AVFilter Graph
。 生成下面的Graph:
+-------+ +---------------------+ +---------------+ |buffer | | Filter | | buffersink | ----------> | |output|------>|input| Fade |output|---> |input| |--------> +-------+ +---------------------+ +---------------+
首先初始化各個AVFilter。所有的AVFilter的初始化都可以簡化為兩步操作:
- 通過
avfilter_get_by_name
查詢指定的AVFilter - 通過
avfilter_graph_create_filter
初始化AVFilterContext
同AVcodec
和AVCodecContext
的關係一樣, 所有的AVFilter的執行都依靠對應的AVFilterContext(在ffmpeg開發中,每個元件都會對應一個上下文管理器,由這個上下文管理器封裝各種引數然後呼叫元件執行)。
通過avfilter_get_by_name
生成AVFilter例項之後,緊跟著就需要呼叫avfilter_graph_create_filter
初始化上下文管理器。
按照下面的流程,依次初始化三個AVFilter:
buffer_src = avfilter_get_by_name("buffer");
ret = avfilter_graph_create_filter(&buffersrc_ctx, buffer_src, "in", args, NULL, filter_graph);
這裡重點聊一下avfilter_graph_create_filter
。 下面是函式原型:
int avfilter_graph_create_filter(AVFilterContext **filt_ctx, const AVFilter *filt,
const char *name, const char *args, void *opaque,
AVFilterGraph *graph_ctx);
filt_ctx表示這個AVFilter的上下文管理器。
name表明的是AVFilter在Graph中的名稱,這個名稱叫啥不重要但必須唯一。 例如Fade AVFilter
就可以叫做fade1
,fade2
或者ifade
等等。
args則是這個AVFilter的引數, 注意僅僅是這個AVFilter的引數,不是整個graph的引數。再拿Fade
舉例,args就可以是t=in:st=3:d=3
。
opaque一般給NULL就可以了。
初始完這三個AVFilter之後,就進入到本次文件的重點: avfilter_link
.
int avfilter_link( AVFilterContext * src,
unsigned srcpad,
AVFilterContext * dst,
unsigned dstpad
)
avfilter_link
分別用來連結兩個AVFilter(傳說中的一手託兩家)。 src
和dst
分別表示源Filter和目標Filter。 srcpad
表示src
第N個輸出端, dstpad
表示dst
第N個輸入端。 如果不好理解,直接看下面的圖:
+-------------+ +-------------+
| src srcpad 1 -----> dstpad 3 dst |
| srcpad 2 -----> dstpad 2 |
| srcpad 3 -----> dstpad 1 |
+-------------+ +-------------+
上圖假設src
和dst
分別有三個輸出端和三個輸入端(不是所有avfilter都有這麼多的輸入輸出端,像fade只有一個,但overlay就有多個)。
而srcpad
和dstpad
表示的就是輸出/輸入端的序號。假如將buffer
第一個輸出端對應fade
第一個輸入端。 那麼就應該這麼寫:
avfilter_link(buffersrc_ctx, 0, ifade_ctx, 0);
然後將fade
的第一個輸出端對應buffersink
的輸入端,就這麼寫:
avfilter_link(ifade_ctx, 0, buffersink_ctx, 0);
而所謂的精細化
就是在這裡體現的,通過程式碼的邏輯判斷,可以動態的組合不同的AVFilter
生成不同的Filter Graph
。並且還可以組合不同的輸入/輸出端。
本次程式碼示例可以參考ifilter
。同時也可以參考 ffmpeg-go-server(一個嘗試為ffmpeg提供restful API的web server)