[ffmpeg] 定製濾波器

若是有定製ffmpeg濾波器的需求,有兩個結構體是必需要了解的:AVFilter、AVFilterPad,所定製的濾波器主要就是經過填充這兩個結構體來實現的。咱們下面將詳細解析這兩個結構體,並經過對濾波器的初始化流程以及濾波流程進行分析,進一步加深對ffmpeg濾波框架的瞭解。html

 

AVFilter

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

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

image

在構建好一整個AVFilterGraph後,就能夠調用avfilter_graph_config來作graph最後的配置。視頻

image

其中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
    }

向graph輸入幀

濾波的流程都是首先調用av_buffersrc_add_frame,從向buffersrc輸入幀開始的

image

能夠看到調用了buffersrc的request_frame函數,該函數最後用ff_filter_frame向outlink輸出幀。、

 

從graph提取幀

而後調用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);
        }
    }
}

該函數有以下實現邏輯:

  1. 調用ff_inlink_consume_frame,看是否能夠得到濾波完成的幀,若是獲得了濾波後的幀,則調用return_or_keep_frame進行返回。
  2. 調用ff_inlink_acknowledge_status,查看濾波過程當中是否出了差錯,或者是否到了EOF,是則返回錯誤。
  3. frame_wanted_out用於代表當前濾波器是否已經得到了前一個濾波器輸出的一幀,等於1則表示未得到,那麼須要調用ff_filter_graph_run_once;等於0則表示已得到一幀,或者是第一次調用該函數。

跳出get_frame_internal裏面的while循環只有下面幾種狀況:

  1. ff_inlink_consume_frame返回值不爲0,代表能夠返回濾波後的幀。
  2. ff_inlink_acknowledge_status返回值不爲0,代表濾波器運行過程當中出錯。
  3. ff_filter_graph_run_once返回值小於0,通常狀況下爲EAGAIN,代表濾波器須要輸入更多的幀做爲原料。

若是是第一次調用av_buffersink_get_frame,當進入該函數時,正常狀況下會按照以下步驟執行:

  1. 因爲是第一次調用,所以frame_wanted_out爲0,那麼第一次循環會去執行ff_inlink_request_frame,這個函數會把frame_wanted_out設置爲1,代表當前濾波器未得到前一個濾波器輸出的幀。
  2. 而後下一次循環時因爲frame_wanted_out爲1,會去調用ff_filter_graph_run_once。
  3. 一般來講,通過幾回循環調用ff_filter_graph_run_once後,會獲得濾波後的幀,此時ff_inlink_consume_frame會返回1,所以就能調用return_or_keep_frame來向用戶返回濾波後的幀了。
  4. 若是須要輸入更多的幀才能繼續進行濾波,那麼會從ff_filter_graph_run_once返回EAGAIN。

 

激活濾波器

咱們前面提到的ff_filter_graph_run_once就是查找已經就緒(ready)的濾波器實例,並對該濾波器進行激活。所獲得的濾波器實例會知足兩個條件:

  1. 濾波器的ready值(優先級)更高的會被選上
  2. 在此基礎上,由於查找順序是按照用戶調用avfilter_graph_create_filter的順序,所以最早建立的濾波器實例會被選中。
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;
}

能夠發現共有三種激活處理方式,分別爲:

  • 從上往下傳遞濾波完成的幀。
  • 從上往下傳遞錯誤代碼。
  • 從下往上傳遞幀的請求。

前面的方式優先級更高。下面咱們會對這三種方式進行較爲詳細的分析

ff_filter_frame_to_filter

觸發條件是:當前濾波器實例的input link上的samples_ready。這samples_ready代表了前面的濾波器實例已經執行ff_filter_frame向當前濾波器實例的input link輸出了幀。

有了上述條件,那麼爲了完成當前濾波器實例的濾波操做,接下來會執行:

  1. 從input link上提取出幀。
  2. 調用當前濾波器的filter_frame函數來執行濾波操做。
  3. 通常來講濾波處理完成事後也會調用ff_filter_frame來把濾波完成的幀輸出到output link。
  4. ff_filter_frame接下來會把下一個濾波器實例設爲就緒狀態。
  5. 若是在執行filter_frame時出錯,沒能完成濾波處理,則會把錯誤代碼寫入input link的status_out,代表該link沒法順利輸出frame到當前filter。
  6. 若是執行filter_frame成功,考慮到前一個濾波器實例可能輸出了不止一幀,所以再次把當前濾波器實例設爲就緒狀態,若是前一個濾波器確實輸出了多個幀,這部操做會使得這多個幀都被當前濾波器實例濾波完成後纔會執行下一個濾波器實例的濾波處理。

image

 

foward_status_change

觸發條件是:當前濾波器實例的input link上有status_in && !status_out。status_in不爲0代表調用前一個濾波器實例的request_frame時出現了錯誤,status_in的值就是錯誤代碼,咱們須要向後面的濾波器實例傳播這個錯誤代碼。

forward_status_change是傳播錯誤代碼的入口,傳播錯誤代碼的很大一步分的實現都在ff_request_frame函數內。基於上述條件,即調用前一個濾波器實例的request_frame時出現了錯誤。

  1. forward_status_change會調用當前濾波器實例的request_frame來傳輸錯誤代碼,request_frame通常內部都會調用到ff_request_farme函數。
  2. 錯誤代碼status_in會致使ff_request_frame走入錯誤處理分支。
  3. 把status_out的值設置爲status_in。
  4. 那麼返回值也會是該錯誤代碼,這用於表示當前request_frame也出現了錯誤。
  5. 所以在返回的時候會把下一個濾波器實例的input link(即當前濾波器實例的output link)上的status_in設置爲錯誤代碼。
  6. 把frame_wanted_out設置爲0用於表示當前濾波器實例的錯誤處理已完成,避免再次調用當前濾波器實例的request_frame函數作重複的錯誤處理。
  7. 把下一個濾波器實例設置爲就緒狀態。

image

 

ff_request_frame_to_filter

觸發條件是:當前濾波器的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就會致使:

  1. 當前的濾波器實例的request_frame被執行。
  2. request_frame內通常會調用ff_request_frame函數。通常狀況下,該函數會把input link的frame_wanted_out設置爲1。
  3. 而後把前一個濾波器實例設爲就緒狀態。
  4. 若是request_frame出錯的話,就是request_frame沒能處理完成,則會把錯誤代碼寫入outlink的status_in,代表該link沒法順序求得當前濾波器實例的幀。

image

 

注意:上面所描述的三種處理方式只是比較常見方式。在實際應用中,有些濾波器在filter_frame內調用ff_request_frame,也有些濾波器在request_frame內調用ff_filter_frame的,並且實現了activate函數的濾波器並不會執行上述流程,若是所有都列出來就過於冗餘了,具體問題具體分析是很重要的。

 

此外,咱們能夠看到在執行ff_filter_frame時會把ready值設置爲300;在執行ff_request_frame時會吧ready值設置爲100;在保存錯誤代碼時會把ready值設置爲200。這代表了優先進行濾波處理,其次是錯誤處理,最後纔是幀請求。

濾波的激活處理能夠按照以下進行總結:

  1. 優先執行濾波處理,若是前面的濾波器實例輸出幀,當前的濾波器實例就能夠執行濾波處理,並把濾波後獲得的幀向後傳遞。
  2. 若是在濾波時缺少幀做爲原料,則向前要求前面的濾波器實例輸出幀,這個要求會一直往前發送,直到有濾波器實例能夠輸出幀。
  3. 若是在濾波或者請求幀的過程當中出錯了,就把錯誤日後傳遞,典型的如EOF就是這種傳遞方式。
相關文章
相關標籤/搜索