若是有定製ffmpeg濾波器的需求,有兩個結構體是必需要了解的:AVFilter、AVFilterPad,所定製的濾波器主要就是經過填充這兩個結構體來實現的。咱們下面將詳細解析這兩個結構體,並經過對濾波器的初始化流程以及濾波流程進行分析,進一步加深對ffmpeg濾波框架的瞭解。html
AVFilter就是一個濾波器的主體,其結構體定義以下:框架
typedef struct AVFilter { const char *name; const char *description; const AVFilterPad *inputs; const AVFilterPad *outputs; const AVClass *priv_class; int flags; int (*preinit)(AVFilterContext *ctx); int (*init)(AVFilterContext *ctx); int (*init_dict)(AVFilterContext *ctx, AVDictionary **options); void (*uninit)(AVFilterContext *ctx); int (*query_formats)(AVFilterContext *); int priv_size; int flags_internal; struct AVFilter *next; int (*process_command)(AVFilterContext *, const char *cmd, const char *arg, char *res, int res_len, int flags); int (*init_opaque)(AVFilterContext *ctx, void *opaque); int (*activate)(AVFilterContext *ctx); } AVFilter;
其各個成員變量有以下含義:ide
name | 濾波器名字。 |
description | 濾波器的簡短介紹。 |
inputs | 濾波器入口(AVFilterPad)列表。 |
outputs | 濾波器出口(AVFilterPad)列表。 |
priv_class | 主要用於維護用戶傳入的參數(AVOption)的結構體,通常來講用戶向濾波器傳入參數有兩個手段:在建立濾波器實例的時候傳入指定參數的字符串,或者在建立完成濾波器實例後經過av_opt_set之類的接口傳入字符串。 |
flags | 濾波器標誌。 |
preinit | 濾波器預初始化函數。這個函數會在建立濾波器實例的開頭被調用。 |
init | 濾波器自身的特製初始化函數。初始化,即avfilter_graph_create_filter,能夠被分解成通用的初始化以及特製初始化。 通用初始化一般包含三個步驟: 1. 建立用於存放濾波器實例的內存,進行一些初始化默認的賦值處理。 2.把傳入的字符串解析進行解析獲得字典的兩要素:參數名稱key,參數值val。 3.經過priv_class所維護的AVOption,能夠找到名爲key的參數對應的內存位置(即濾波器實例的私有結構體priv中名稱爲key的參數的位置),並把val寫入該位置當中便可完成參數設置。私有結構體priv中的參數就一般就是濾波器的實際參數,在進行濾波時會根據其中的參數進行濾波處理。 特製的初始化有不少不一樣的用途,好比檢查參數,若是檢查到所輸入的參數中缺乏一些重要的參數,則能夠返回負值來表示初始化錯誤。 |
init_dict | 與上方init功能相同,不太經常使用。 |
uninit | 若是在init函數出現錯誤則會調用uninit來作一些後續處理。 |
query_formats | 爲了進行濾波器之間的濾波格式協商,AVFilter的query_formats函數會去設置AVFilterLink上的in_formats/out_formats等,這是格式協商的第一步。 |
priv_size | 濾波器實例的私有結構體(priv)的大小。咱們前面也說了priv當中的參數就是濾波器的實際參數,而不一樣濾波器的參數不一樣,那麼所佔用的空間也不會同樣,所以在建立濾波器實例的時候會根據priv_size來開闢用於存放參數的空間。 |
flags_internal | 濾波器內部標誌。 |
next | 在新版本ffmpeg中不會使用到這個next參數。 老版本的ffmpeg須要用avfilter_rigister來註冊濾波器(AVFilter),註冊的時候就會使用這個next參數,使得全部註冊了的濾波器造成一個濾波器鏈表,若是須要某個濾波器則能夠從該鏈表中獲取。 新版本的ffmpeg使用的是列表(filter_list)來列出全部的濾波器(AVFilter),通常來講,若是想得到濾波器,能夠調用avfilter_get_by_name來輪詢列表得到。 |
process_command | 通常來講,濾波參數的設置有兩種方式: 1. 在初始化時(avfilter_graph_create_filter),輸入參數字符串。 2. 在初始化後,配置整個濾波圖前(avfilter_graph_config),調用av_opt_set之類的接口輸入參數。 爲了保證濾波器正常運行,在濾波的過程當中通常是不會對濾波參數進行修改的。固然,在濾波過程當中調用av_opt_set之類的函數是能夠修改濾波參數,可是並不能保證濾波器會按照咱們預想地那樣運行。由於若是按照前面的兩種方式設置濾波參數,後面可能還會執行AVFilterPad的config_props操做,而在濾波過程當中經過av_opt_set之類的函數去設置濾波參數時是不會再回去繼續執行這一步的。 不過現實當中確實存在在濾波過程當中修改濾波參數的需求,好比說播放音樂時能夠調整EQ。此時就能夠經過實現process_command這個函數來實現濾波過程當中的各類變化。 使用avfilter_graph_send_command就能觸發所指定的濾波器調用其process_command函數。 |
init_opaque | 與init功能相同,不經常使用。 |
activate | 濾波函數。濾波函數有兩種實現方式,一種是經過activate來實現,另外一種是後面會說到的AVFilterPad中的filter_frame以及request_frame函數。若是是採用activate的方式,就須要在activate內實現如下流程: 1. 獲取前面的濾波器實例輸出的幀。具體操做就是調用ff_inlink_consume_frame來從inlink獲取前面濾波器實例輸出的幀。 若是所須要的幀未準備好,則須要通知相應的濾波器實例,代表當前濾波器須要幀。具體操做就是調用ff_inlink_request_frame來設置inlink上的frame_wanted_out,該變量就是用於代表inlink的目標濾波器實例,即當前濾波器實例須要前一個濾波器實例輸出幀。 若是所須要的幀已準備好,就能夠執行濾波操做。 2. 向後面的濾波器實例輸出濾波完成的幀。具體操做就是調用ff_filter_frame來向outlink輸出幀。 |
AVFilterPad是濾波器的出口或者入口,其結構定義以下:函數
struct AVFilterPad { const char *name; enum AVMediaType type; AVFrame *(*get_video_buffer)(AVFilterLink *link, int w, int h); AVFrame *(*get_audio_buffer)(AVFilterLink *link, int nb_samples); int (*filter_frame)(AVFilterLink *link, AVFrame *frame); int (*poll_frame)(AVFilterLink *link); int (*request_frame)(AVFilterLink *link); int (*config_props)(AVFilterLink *link); int needs_fifo; int needs_writable; };
各成員變量具備以下含義:3d
name | 出/入口(input/output pads)名字。 |
type | 支持的幀類型:AVMEDIA_TYPE_VIDEO/AVMEDIA_TYPE_AUDIO。 |
get_video_buffer (input pads only) |
提供用於寫入視頻圖像的buffer,通常是向前一個濾波器實例提供。 一個濾波器在濾波過程當中,可能須要額外的buffer來進行濾波處理,好比scale或者aresample這種格式轉換濾波器,在進行濾波處理時,有輸入幀做爲源材料,輸入幀有確實存在的buffer,而爲了進行輸出,咱們須要額外的buffer來存放格式轉換後的幀。所需的buffer除了指定的寬與高以外,還有像素格式,這三點是影響buffer大小的因素,像素格式就是輸出鏈上的格式(link->format)。 若是一個濾波器實例須要buffer,能夠經過ff_get_video_buffer(outlink, w, h)來調用下一個濾波器對應AVFilterPad上的get_video_buffer函數。不過通常來講,是不須要AVFilterPad去實現get_video_buffer這個函數的,由於若是AVFilterPad不實現這個函數,則會調用默認的ff_default_get_video_buffer,該函數會根據輸入的w,h以及link的format來提供buffer。 ※ffmpeg中僅有幾個filter實現了get_video_buffer(vflip,swapuv等),不過其內部也是調用了ff_default_get_video_buffer,而且其它部分的代碼看起來並無起到什麼實際做用。 |
get_audio_buffer (input pads only) |
含義同上,提供給上一個濾波器實例調用。 調用接口爲ff_get_audio_buffer(outlink, nb_samples),若是沒有實現該函數,則會默認調用到函數ff_default_get_audio_buffer,buffer的大小受到輸入參數的nb_samples以及link->channels,link->format的影響。 通常來講不須要濾波器實現get_audio_buffer函數。 ※ffmpeg中並無實現了get_audio_buffer的濾波器。 |
filter_frame (input pads only) |
filter_frame是最多見的濾波實現函數。若是AVFilter沒有實現activate函數,則會調用默認的activate函數ff_filter_activate_default,該函數最終會調用到filter_frame來提供濾波的實現。 filter_frame的輸入參數中包括濾波實例的inlink以及從inlink上提取的frame,通常來講filter_frame會對該frame進行濾波處理,而後調用ff_filter_frame向outlink輸出濾波後的幀。 |
poll_frame (output pads only) |
設定上poll_frame是用於查看前一個濾波器實例的request_frame能返回多少幀,不過實際上應該是沒有用到這個函數的地方。 |
request_frame (output pads only) |
request_frame其實也是一個用於產生幀的濾波函數,不過觀察request_frame的參數能夠發現該函數並無frame做爲輸入參數,這代表了request_frame有特定的應用場景: 1. 若是一個濾波器是源濾波器,僅須要輸出幀,則能夠在request_frame內生成幀,而後調用ff_filter_frame把幀輸出到outlink。ffmpeg中源濾波器的源文件都帶有src關鍵字,如buffersrc以及vsrc/asrc爲開頭的濾波器。 2. 若是一個濾波器但願在EOF後繼續輸出幀,則能夠用request_frame調用ff_filter_frame來進行輸出。 |
config_props | config_props的調用發生在query_formats以後,此時濾波格式的協調已經完成,也就已經肯定了濾波器實例的輸入以及輸出格式(inlink->format/outlink->format)。若是某些設置須要使用到這些輸入輸出格式,就能夠在config_props中進行設置。如aresampe在config_props中就利用協調完成的format、channel_layout、sample_rate來進行重採樣的參數設置。 |
needs_fifo (input pad only) |
代表只有當濾波器實例主動請求幀(調用ff_inlink_request frame或者ff_request_frame)的時候,前一個濾波器實例纔會向當前濾波器實例輸出幀(ff_filter_frame)。 若是needs_fifo爲1,會自動在當前濾波器實例與前一個濾波器實例之間插入一個名爲fifo的濾波器,該濾波器實現了上述功能。 |
needs_writable (input pad only) |
代表濾波器須要對pad對應的link所輸入的frame進行寫入。如進行字幕渲染的ass濾波器就須要對輸入的視頻幀進行寫入。 |
首先是avfilter_graph_create_filter,即建立濾波器實例。如前面所說,這個函數會在最開頭調用濾波器的preinit函數,而後建立濾波器實例並作一些簡單的初始化,解析輸入的字符串,最後調用濾波器的init函數。orm
在構建好一整個AVFilterGraph後,就能夠調用avfilter_graph_config來作graph最後的配置。視頻
其中graph_insert_fifos中就會對設定了needs_fifo=1的input pad所在的link插入名爲fifo的濾波器。htm
if (!link->dstpad->needs_fifo) continue; fifo = f->inputs[j]->type == AVMEDIA_TYPE_VIDEO ? avfilter_get_by_name("fifo") : avfilter_get_by_name("afifo"); snprintf(name, sizeof(name), "auto_fifo_%d", fifo_count++); ret = avfilter_graph_create_filter(&fifo_ctx, fifo, name, NULL, NULL, graph); ret = avfilter_insert_filter(link, fifo_ctx, 0, 0);
graph_config_formats中如前一篇文章所說,就是對整個graph中濾波格式進行協商,協商事後能夠肯定全部link上的格式。blog
graph_config_links主要目的就是調用pad中的config_props,那麼config_props就能根據前面協商獲得的link格式作進一步的操做。接口
通常來講,用戶會按照以下方式調用濾波API來進行濾波處理:
ret = av_buffersrc_add_frame(in_filter, pFrame); while((ret = av_buffersink_get_frame(out_filter, pFrame))>=0){ //TODO }
濾波的流程都是首先調用av_buffersrc_add_frame,從向buffersrc輸入幀開始的
能夠看到調用了buffersrc的request_frame函數,該函數最後用ff_filter_frame向outlink輸出幀。、
而後調用av_buffersink_get_frame,嘗試得到buffersink輸出的幀,若是返回值大於0則代表獲得了一幀,正常狀況下若是沒法得到幀一般會返回EAGAIN,這代表要求用戶向buffersrc輸入更多的幀。
av_buffersink_get_frame會向下調用到get_frame_internal,該函數主要做用就是調用濾波器進行濾波,並返回濾波完成的幀。函數實現以下:
static int get_frame_internal(AVFilterContext *ctx, AVFrame *frame, int flags, int samples) { BufferSinkContext *buf = ctx->priv; AVFilterLink *inlink = ctx->inputs[0]; int status, ret; AVFrame *cur_frame; int64_t pts; if (buf->peeked_frame) return return_or_keep_frame(buf, frame, buf->peeked_frame, flags); while (1) { ret = samples ? ff_inlink_consume_samples(inlink, samples, samples, &cur_frame) : ff_inlink_consume_frame(inlink, &cur_frame); if (ret < 0) { return ret; } else if (ret) { /* TODO return the frame instead of copying it */ return return_or_keep_frame(buf, frame, cur_frame, flags); } else if (ff_inlink_acknowledge_status(inlink, &status, &pts)) { return status; } else if ((flags & AV_BUFFERSINK_FLAG_NO_REQUEST)) { return AVERROR(EAGAIN); } else if (inlink->frame_wanted_out) { ret = ff_filter_graph_run_once(ctx->graph); if (ret < 0) return ret; } else { ff_inlink_request_frame(inlink); } } }
該函數有以下實現邏輯:
跳出get_frame_internal裏面的while循環只有下面幾種狀況:
若是是第一次調用av_buffersink_get_frame,當進入該函數時,正常狀況下會按照以下步驟執行:
咱們前面提到的ff_filter_graph_run_once就是查找已經就緒(ready)的濾波器實例,並對該濾波器進行激活。所獲得的濾波器實例會知足兩個條件:
AVFilterContext *avfilter_graph_alloc_filter(AVFilterGraph *graph, const AVFilter *filter, const char *name) { s = ff_filter_alloc(filter, name); graph->filters[graph->nb_filters++] = s; } int ff_filter_graph_run_once(AVFilterGraph *graph) { filter = graph->filters[0]; for (i = 1; i < graph->nb_filters; i++) if (graph->filters[i]->ready > filter->ready) filter = graph->filters[i]; if (!filter->ready) return AVERROR(EAGAIN); return ff_filter_activate(filter); }
選出優先級最高的濾波器實例後,首先把該濾波器就緒狀態清零,而後開始執行激活操做:若是該濾波器實現了activate函數,則調用該函數,不然調用默認的激活函數ff_filter_activate_default。
int ff_filter_activate(AVFilterContext *filter) { filter->ready = 0; ret = filter->filter->activate ? filter->filter->activate(filter) : ff_filter_activate_default(filter); }
若是調用的是默認的激活函數,則會按照如下優先級進行激活處理:
static int ff_filter_activate_default(AVFilterContext *filter) { unsigned i; for (i = 0; i < filter->nb_inputs; i++) { if (samples_ready(filter->inputs[i], filter->inputs[i]->min_samples)) { return ff_filter_frame_to_filter(filter->inputs[i]); } } for (i = 0; i < filter->nb_inputs; i++) { if (filter->inputs[i]->status_in && !filter->inputs[i]->status_out) { av_assert1(!ff_framequeue_queued_frames(&filter->inputs[i]->fifo)); return forward_status_change(filter, filter->inputs[i]); } } for (i = 0; i < filter->nb_outputs; i++) { if (filter->outputs[i]->frame_wanted_out && !filter->outputs[i]->frame_blocked_in) { return ff_request_frame_to_filter(filter->outputs[i]); } } return FFERROR_NOT_READY; }
能夠發現共有三種激活處理方式,分別爲:
前面的方式優先級更高。下面咱們會對這三種方式進行較爲詳細的分析
觸發條件是:當前濾波器實例的input link上的samples_ready。這samples_ready代表了前面的濾波器實例已經執行ff_filter_frame向當前濾波器實例的input link輸出了幀。
有了上述條件,那麼爲了完成當前濾波器實例的濾波操做,接下來會執行:
觸發條件是:當前濾波器實例的input link上有status_in && !status_out。status_in不爲0代表調用前一個濾波器實例的request_frame時出現了錯誤,status_in的值就是錯誤代碼,咱們須要向後面的濾波器實例傳播這個錯誤代碼。
forward_status_change是傳播錯誤代碼的入口,傳播錯誤代碼的很大一步分的實現都在ff_request_frame函數內。基於上述條件,即調用前一個濾波器實例的request_frame時出現了錯誤。
觸發條件是:當前濾波器的output link上有frame_wanted_out以及!frame_blocked_in。其中frame_blocked_in用於防止重複對同一個link執行request frame操做,重要的是frame_wanted_out。咱們前面也說過,若是一個link上的frame_wanted_out=1,代表該link的dst要求src輸出幀,具體一點,就是這會致使執行src的request_frame函數。那麼這裏的output link上的frame_wanted_out=1就會致使:
注意:上面所描述的三種處理方式只是比較常見方式。在實際應用中,有些濾波器在filter_frame內調用ff_request_frame,也有些濾波器在request_frame內調用ff_filter_frame的,並且實現了activate函數的濾波器並不會執行上述流程,若是所有都列出來就過於冗餘了,具體問題具體分析是很重要的。
此外,咱們能夠看到在執行ff_filter_frame時會把ready值設置爲300;在執行ff_request_frame時會吧ready值設置爲100;在保存錯誤代碼時會把ready值設置爲200。這代表了優先進行濾波處理,其次是錯誤處理,最後纔是幀請求。
濾波的激活處理能夠按照以下進行總結: