1. 程式人生 > >ffmpeg為視訊新增特效

ffmpeg為視訊新增特效

本文包含以下內容

1、AVFilter的基本介紹

2、如何利用ffmpeg命令列工具實現各種視訊濾鏡

3、如何利用libavfilter程式設計實現在攝像頭直播流中加入各類不同濾鏡的功能

具有較強的綜合性

AVFilter的基本介紹

AVFilter的功能十分強大,可以實現對多媒體資料的各種處理,包括時間線編輯、視音訊特效濾鏡的新增或訊號處理,還可以實現多路媒體流的合併或疊加,其豐富程度令人歎為觀止。這裡主要以視訊濾鏡為例進行介紹。使用AVFilter可以為單路視訊新增單個或多個濾鏡,也可以為多路視訊分別新增不同的濾鏡並且在最後將多路視訊合併為一路視訊,AVFilter為實現這些功能定義了以下幾個概念:

Filter:代表單個filter
FilterPad:代表一個filter的輸入或輸出埠,每個filter都可以有多個輸入和多個輸出,只有輸出pad的filter稱為source,只有輸入pad的filter稱為sink
FilterLink:若一個filter的輸出pad和另一個filter的輸入pad名字相同,即認為兩個filter之間建立了link

FilterChain:代表一串相互連線的filters,除了source和sink外,要求每個filter的輸入輸出pad都有對應的輸出和輸入pad

FilterGraph:FilterChain的集合

基本和DirectShow類似,也與視訊後期調色軟體中的節點等概念類似。具體來看,以下面的命令為例

  1. [in]split[main][tmp];[tmp]crop=iw:ih/2,vflip[flip];[main][flip]overlay=0:H/2[out]  

在該命令中,輸入流[in]首先被分[split]為兩個流[main]和[tmp],然後[tmp]流經過了剪下[crop]和翻轉[vflip]兩個濾鏡後變為[flip],這時我們將[flip]疊加[overlay]到最開始的[main]上形成最後的輸出流[out],最後呈現出的是映象的效果。下圖清晰地表示了以上過程


我們可以認為圖中每一個節點就是一個Filter,每一個方括號所代表的就是FilterPad,可以看到split的輸出pad中有一個叫tmp的,而crop的輸入pad中也有一個tmp,由此在二者之間建立了link,當然input和output代表的就是source和sink,此外,圖中有三條FilterChain,第一條由input和split組成,第二條由crop和vflip組成,第三條由overlay和output組成,整張圖即是一個擁有三個FilterChain的FilterGraph。

上面的圖是人工畫出來的,也可以在程式碼中呼叫avfilter_graph_dump函式自動將FilterGraph畫出來,如下


可以看到,多出來了一個scale濾鏡,這是由ffmpeg自動新增的用於格式轉換的濾鏡。

在FFmpeg命令列工具中使用AVFilter

在命令列中使用AVFilter需要遵循專門的語法,簡單來說,就是每個Filter之間以逗號分隔,每個Filter自己的屬性之間以冒號分隔,屬性和Filter以等號相連,多個Filter組成一個FilterChain,每個FilterChain之間以分號相隔。AVFilter在命令列工具中對應的是-vf或-af或-filter_complex,前兩個分別對應於單路輸入的視訊濾鏡和音訊濾鏡,最後的filter_complex則對應於有多路輸入的情況。除了在FFMpeg命令列工具中使用外,在FFplay中同樣也可以使用AVFilter。其他一些關於單雙引號、轉義符號等更詳細的語法參考Filter Documentation

下面舉幾個例子

1、疊加水印

  1. ffmpeg -i test.flv -vf movie=test.jpg[wm];[in][wm]overlay=5:5[out] out.flv  
將test.jpg作為水印疊加到test.flv的座標為(5,5)的位置上,效果如下


2、映象

  1. ffmpeg -i test.flv -vf crop=iw/2:ih:0:0,split[left][tmp];[tmp]hflip[right];[left]pad=iw*2[a];[a][right]overlay=w out.flv  
輸入[in]和輸出[out]可以省略不寫,pad用於填充畫面,效果如下

3、調整曲線

  1. ffmpeg -i test.flv -vf curves=vintage out.flv  
類似Photoshop裡面的曲線調整,這裡的vintage是ffmpeg自帶的預設,實現復古畫風,還可以直接載入其他的Photoshop預設檔案並在其基礎上加以調整,如下
  1. ffmpeg -i test.flv -vf curves=psfile='test.acv':green='0.45/0.53' out.flv  
其中的acv預設檔案實現的是加強對比度,再次基礎上調整綠色的顯示效果,以上兩個命令的最終效果如下

4、多路輸入拼接

  1. ffmpeg -i test1.mp4 -i test2.mp4 -i test3.mp4 -i test4.mp4 -filter_complex "[0:v]pad=iw*2:ih*2[a];[a][1:v]overlay=w[b];[b][2:v]overlay=0:h[c];[c][3:v]overlay=w:h" out.mp4  
正如前面所說的,當有多個輸入時,需要使用filter_complex,效果如下

通過以上幾個例子,基本可以明白在命令列中使用AVFilter時需要遵循的語法。

使用libavfilter程式設計為直播流新增濾鏡

要使用libavfilter,首先要註冊相關元件
  1. avfilter_register_all();  
首先需要構造出一個完整可用的FilterGraph,需要用到輸入流的解碼引數,參見上一篇文章,如下
  1. AVFilterContext *buffersink_ctx;//看名字好像AVFilterContext是什麼很厲害的東西,但其實只要認為它是AVFilter的一個例項就OK了
  2. AVFilterContext *buffersrc_ctx;  
  3. AVFilterGraph *filter_graph;  
  4. AVFilter *buffersrc=avfilter_get_by_name("buffer");//Filter的具體定義,只要是libavfilter中已註冊的filter,就可以直接通過查詢filter名字的方法獲得其具體定義,所謂定義即filter的名稱、功能描述、輸入輸出pad、相關回調函式等
  5. AVFilter *buffersink=avfilter_get_by_name("buffersink");  
  6. AVFilterInOut *outputs = avfilter_inout_alloc();//AVFilterInOut對應buffer和buffersink這兩個首尾端的filter的輸入輸出
  7. AVFilterInOut *inputs = avfilter_inout_alloc();  
  8. filter_graph = avfilter_graph_alloc();  
  9.     /* buffer video source: the decoded frames from the decoder will be inserted here. */
  10.     snprintf(args, sizeof(args),  
  11.         "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",  
  12.         ifmt_ctx->streams[0]->codec->width, ifmt_ctx->streams[0]->codec->height, ifmt_ctx->streams[0]->codec->pix_fmt,  
  13.         ifmt_ctx->streams[0]->time_base.num, ifmt_ctx->streams[0]->time_base.den,  
  14.         ifmt_ctx->streams[0]->codec->sample_aspect_ratio.num, ifmt_ctx->streams[0]->codec->sample_aspect_ratio.den);  
  15.     ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",  
  16.         args, NULL, filter_graph);//根據指定的Filter,這裡就是buffer,構造對應的初始化引數args,二者結合即可建立Filter的示例,並放入filter_graph中
  17.     if (ret < 0) {  
  18.         printf("Cannot create buffer source\n");  
  19.         return ret;  
  20.     }  
  21.     /* buffer video sink: to terminate the filter chain. */
  22.     ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",  
  23.         NULL, NULL, filter_graph);  
  24.     if (ret < 0) {  
  25.         printf("Cannot create buffer sink\n");  
  26.         return ret;  
  27.     }  
  28.     /* Endpoints for the filter graph. */
  29.     outputs->name = av_strdup("in");//對應buffer這個filter的output
  30.     outputs->filter_ctx = buffersrc_ctx;  
  31.     outputs->pad_idx = 0;  
  32.     outputs->next = NULL;  
  33.     inputs->name = av_strdup("out");//對應buffersink這個filter的input
  34.     inputs->filter_ctx = buffersink_ctx;  
  35.     inputs->pad_idx = 0;  
  36.     inputs->next = NULL;  
  37.     if ((ret = avfilter_graph_parse_ptr(filter_graph, filter_descr,  
  38.         &inputs, &outputs, NULL)) < 0)//filter_descr是一個filter命令,例如"overlay=iw:ih",該函式可以解析這個命令,然後自動完成FilterGraph中各個Filter之間的聯接
  39.         return ret;  
  40.     if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0)//檢查當前所構造的FilterGraph的完整性與可用性
  41.         return ret;  
  42.     avfilter_inout_free(&inputs);  
  43.     avfilter_inout_free(&outputs);  
上面介紹的是FilterGraph的構造方法之一,即根據filter命令使用avfilter_graph_parse_ptr自動進行構造,當然也可以由我們自己將各個filter一一聯接起來,如下,這裡假設我們已經有了buffersrc_ctx、 buffersink_ctx和一個filter_ctx
  1. // connect inputs and outputs
  2. if (err >= 0) err = avfilter_link(buffersrc_ctx, 0, filter_ctx, 0);  
  3. if (err >= 0) err = avfilter_link(filter_ctx, 0, buffersink_ctx, 0);  
  4. if (err < 0) {  
  5.     av_log(NULL, AV_LOG_ERROR, "error connecting filters\n");  
  6.     return err;  
  7. }  
  8. err = avfilter_graph_config(filter_graph, NULL);  
  9. if (err < 0) {  
  10.     av_log(NULL, AV_LOG_ERROR, "error configuring the filter graph\n");  
  11.     return err;  
  12. }  
  13. return 0;  
不過在filter較多的情況下,還是直接使用avfilter_graph_parse_ptr比較方便

在構造好FilterGraph之後,就可以開始使用了,使用流程也很簡單,先將一個AVFrame幀推入FIlterGraph中,在將處理後的AVFrame從FilterGraph中拉出來即可,這裡以上一篇文章的編解碼核心模組的程式碼為例看一下實現過程。可以看到,是將解碼得到的pFrame推入filter_graph,將處理後的資料寫入picref中,他也是一個AVFrame。需要注意的是,這裡依然要將picref轉換為YUV420的幀之後再進行編碼,一方面是因為我們這裡用的是攝像頭資料,是RGB格式的,另一方面,諸如curves這樣的filter是在RGB空間進行處理的,最後得到的也是對應畫素格式的幀,所以需要進行轉換。其他部分基本和原來一樣。

  1. //start decode and encode
  2.     int64_t start_time=av_gettime();  
  3.     while (av_read_frame(ifmt_ctx, dec_pkt) >= 0){     
  4.         if (exit_thread)  
  5.             break;  
  6.         av_log(NULL, AV_LOG_DEBUG, "Going to reencode the frame\n");  
  7.         pframe = av_frame_alloc();  
  8.         if (!pframe) {  
  9.