FFmpeg 開發之 AVFilter 使用流程總結

在使用FFmpeg開發時,使用AVFilter的流程較爲複雜,涉及到的數據結構和函數也比較多,那麼使用FFmpeg AVFilter的整體流程是什麼樣,在其執行過程中都有哪些步驟,需要注意哪些細節?這些都是需要我們整理和總結的。

首先,我們需要引入三個概念結構體:AVFilterGraph 、AVFilterContext、AVFilter。

一、AVFilterGraph 、AVFilterContext、AVFilter

在 FFmpeg 中有多種多樣的濾鏡,你可以把他們當成一個個小工具,專門用於處理視頻和音頻數據,以便實現一定的目的。如 overlay 這個濾鏡,可以將一個圖畫覆蓋到另一個圖畫上;transport 這個濾鏡可以將圖畫做旋轉等等。

一個 filter 的輸出可以作爲另一個 filter 的輸入,因此多個 filter 可以組織成爲一個網狀的 filter graph,從而實現更加複雜或者綜合的任務。

在 libavfilter 中,我們用類型 AVFilter 來表示一個 filter,每一個 filter 都是經過註冊的,其特性是相對固定的。而 AVFilterContext 則表示一個真正的 filter 實例,這和 AVCodec 以及 AVCodecContext 的關係是類似的。

AVFilter 中最重要的特徵就是其所需的輸入和輸出。

AVFilterContext 表示一個 AVFilter 的實例,我們在實際使用 filter 時,就是使用這個結構體。AVFilterContext 在被使用前,它必須是 被初始化的,就是需要對 filter 進行一些選項上的設置,通過初始化告訴 FFmpeg 我們已經做了相關的配置。

AVFilterGraph 表示一個 filter graph,當然它也包含了 filter chain的概念。graph 包含了諸多 filter context 實例,並負責它們之間的 link,graph 會負責創建,保存,釋放 這些相關的 filter context 和 link,一般不需要用戶進行管理。除此之外,它還有線程特性和最大線程數量的字段,和filter context類似。graph 的操作有:分配一個graph,往graph中添加一個filter context,添加一個 filter graph,對 filter 進行 link 操作,檢查內部的link和format是否有效,釋放graph等。

二、AVFilter 相關Api使用方法整理

1. AVFilterContext 初始化方法

AVFilterContext 的初始化方式有三種,avfilter_init_str() 和 avfilter_init_dict()、avfilter_graph_create_filter(). 

/*
 使用提供的參數初始化 filter。
 參數args:表示用於初始化 filter 的 options。該字符串必須使用 ":" 來分割各個鍵值對, 而鍵值對的形式爲 'key=value'。如果不需要設置選項,args爲空。 
 除了這種方式設置選項之外,還可以利用 AVOptions API 直接對 filter 設置選項。
 返回值:成功返回0,失敗返回一個負的錯誤值
*/
int avfilter_init_str(AVFilterContext *ctx, const char *args);
/*
 使用提供的參數初始化filter。
 參數 options:以 dict 形式提供的 options。
 返回值:成功返回0,失敗返回一個負的錯誤值
 注意:這個函數和 avfilter_init_str 函數的功能是一樣的,只不過傳遞的參數形式不同。 但是當傳入的 options 中有不被 filter 所支持的參數時,這兩個函數的行爲是不同:
avfilter_init_str 調用會失敗,而這個函數則不會失敗,它會將不能應用於指定 filter 的 option 通過參數 options 返回,然後繼續執行任務。
*/ int avfilter_init_dict(AVFilterContext *ctx, AVDictionary **options);
/**
 * 創建一個Filter實例(根據args和opaque的參數),並添加到已存在的AVFilterGraph. 
 * 如果創建成功*filt_ctx會指向一個創建好的Filter實例,否則會指向NULL. 
 * @return 失敗返回負數,否則返回大於等於0的數
 */
int avfilter_graph_create_filter(AVFilterContext **filt_ctx, const AVFilter *filt, const char *name, 
                  const char *args, void *opaque, AVFilterGraph *graph_ctx);

2. AVFilterGraph 相關的Api

AVFilterGraph 表示一個 filter graph,當然它也包含了 filter chain的概念。graph 包含了諸多 filter context 實例,並負責它們之間的 link,graph 會負責創建,保存,釋放 這些相關的 filter context 和 link,一般不需要用戶進行管理。

graph 的操作有:分配一個graph,往graph中添加一個filter context,添加一個 filter graph,對 filter 進行 link 操作,檢查內部的link和format是否有效,釋放graph等。

根據上述操作,可以列舉的方法分別爲:

分配空的filter graph:

/*
  分配一個空的 filter graph.
  成功返回一個 filter graph,失敗返回 NULL
*/
AVFilterGraph *avfilter_graph_alloc(void);

 創建一個新的filter實例:

/*
    在 filter graph 中創建一個新的 filter 實例。這個創建的實例尚未初始化。
    詳細描述:在 graph 中創建一個名稱爲 name 的 filter類型的實例。
    創建失敗,返回NULL。創建成功,返回 filter context實例。創建成功後的實例會加入到graph中,
    可以通過 AVFilterGraph.filters 或者 avfilter_graph_get_filter() 獲取。
*/
AVFilterContext *avfilter_graph_alloc_filter(AVFilterGraph *graph, const AVFilter *filter, const char *name);                                          

返回名字爲name的filter context:

/*
   返回 graph 中的名爲 name 的 filter context。
*/
AVFilterContext *avfilter_graph_get_filter(AVFilterGraph *graph, const char *name);
 

在 filter graph 中創建一個新的 filter context 實例,並使用args和opaque初始化這個filter context:

/*
 在 filter graph 中創建一個新的 filter context 實例,並使用 args 和 opaque 初始化這個實例。
    
 參數 filt_ctx:返回成功創建的 filter context
    
 返回值:成功返回正數,失敗返回負的錯誤值。
*/
int avfilter_graph_create_filter(AVFilterContext **filt_ctx, const AVFilter *filt, const char *name, 
                       const char *args, void *opaque, AVFilterGraph *graph_ctx);

 配置 AVFilterGraph 的鏈接和格式:

/*
  檢查 graph 的有效性,並配置其中所有的連接和格式。
  有效則返回 >= 0 的數,否則返回一個負值的 AVERROR.
 */
int avfilter_graph_config(AVFilterGraph *graphctx, void *log_ctx);

釋放AVFilterGraph:

/*
   釋放graph,摧毀內部的連接,並將其置爲NULL。
*/
void avfilter_graph_free(AVFilterGraph **graph);

在一個已經存在的link中插入一個FilterContext:

/*
  在一個已經存在的 link 中間插入一個 filter context。
  參數filt_srcpad_idx和filt_dstpad_idx:指定filt要連接的輸入和輸出pad的index。
  成功返回0.
*/
int avfilter_insert_filter(AVFilterLink *link, AVFilterContext *filt, 
                  unsigned filt_srcpad_idx, unsigned filt_dstpad_idx);

將字符串描述的filter graph 加入到一個已存在的graph中:

/*
    將一個字符串描述的 filter graph 加入到一個已經存在的 graph 中。
    
    注意:調用者必須提供 inputs 列表和 outputs 列表。它們在調用這個函數之前必須是已知的。
    
    注意:inputs 參數用於描述已經存在的 graph 的輸入 pad 列表,也就是說,從新的被創建的 graph 來講,它們是 output。
        outputs 參數用於已經存在的 graph 的輸出 pad 列表,從新的被創建的 graph 來說,它們是 input。
        
    成功返回 >= 0,失敗返回負的錯誤值。
*/
int avfilter_graph_parse(AVFilterGraph *graph, const char *filters,
                         AVFilterInOut *inputs, AVFilterInOut *outputs,
                         void *log_ctx);                   
/*
    和 avfilter_graph_parse 類似。不同的是 inputs 和 outputs 參數,即做輸入參數,也做輸出參數。
        在函數返回時,它們將會保存 graph 中所有的處於 open 狀態的 pad。返回的 inout 應該使用 avfilter_inout_free() 釋放掉。
    
    注意:在字符串描述的 graph 中,第一個 filter 的輸入如果沒有被一個字符串標識,默認其標識爲"in",最後一個 filter 的輸出如果沒有被標識,默認爲"output"。
    
    intpus:作爲輸入參數是,用於保存已經存在的graph的open inputs,可以爲NULL。
        作爲輸出參數,用於保存這個parse函數之後,仍然處於open的inputs,當然如果傳入爲NULL,則並不輸出。
    outputs:同上。
*/
int avfilter_graph_parse_ptr(AVFilterGraph *graph, const char *filters,
                             AVFilterInOut **inputs, AVFilterInOut **outputs, void *log_ctx);
/*
    和 avfilter_graph_parse_ptr 函數類似,不同的是,inputs 和 outputs 函數不作爲輸入參數,
    僅作爲輸出參數,返回字符串描述的新的被解析的graph在這個parse函數後,仍然處於open狀態的inputs和outputs。
    返回的 inout 應該使用 avfilter_inout_free() 釋放掉。
    
    成功返回0,失敗返回負的錯誤值。
*/
int avfilter_graph_parse2(AVFilterGraph *graph, const char *filters,
                          AVFilterInOut **inputs, AVFilterInOut **outputs);

將graph轉換爲可讀取的字符串描述:

/*
  將 graph 轉化爲可讀的字符串描述。
  參數options:未使用,忽略它。
*/
char *avfilter_graph_dump(AVFilterGraph *graph, const char *options);

三、FFmpeg Filter Buffer 和 BufferSink 相關APi的使用方法整理

 Buffer 和 BufferSink 作爲 graph 的輸入點和輸出點來和我們交互,我們僅需要和其進行數據交互即可。其API如下:

//buffersrc flag
enum {
    //不去檢測 format 的變化
    AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT = 1,
 
    //立刻將 frame 推送到 output
    AV_BUFFERSRC_FLAG_PUSH = 4,
 
    //對輸入的frame新建一個引用,而非接管引用
    //如果 frame 是引用計數的,那麼對它創建一個新的引用;否則拷貝frame中的數據
    AV_BUFFERSRC_FLAG_KEEP_REF = 8,
};

向 buffer_src 添加一個Frame:

/*
    向 buffer_src 添加一個 frame。
    
    默認情況下,如果 frame 是引用計數的,那麼這個函數將會接管其引用並重新設置 frame。
        但這個行爲可以由 flags 來控制。如果 frame 不是引用計數的,那麼拷貝該 frame。
    
    如果函數返回一個 error,那麼 frame 並未被使用。frame爲NULL時,表示 EOF。
    成功返回 >= 0,失敗返回負的AVERROR。
*/
int av_buffersrc_add_frame_flags(AVFilterContext *buffer_src, AVFrame *frame, int flags);

添加一個frame到 src filter:

/*
    添加一個 frame 到 src filter。
    這個函數等同於沒有 AV_BUFFERSRC_FLAG_KEEP_REF 的 av_buffersrc_add_frame_flags() 函數。
 */
int av_buffersrc_add_frame(AVFilterContext *ctx, AVFrame *frame); 
/*
   添加一個 frame 到 src filter。
   這個函數等同於設置了 AV_BUFFERSRC_FLAG_KEEP_REF 的av_buffersrc_add_frame_flags() 函數。
*/
int av_buffersrc_write_frame(AVFilterContext *ctx, const AVFrame *frame);

從sink獲取已filtered處理的幀,並放到參數frame中:

/*
    從 sink 中獲取已進行 filtered 處理的幀,並將其放到參數 frame 中。
    
    參數ctx:指向 buffersink 或 abuffersink 類型的 filter context
    參數frame:獲取到的被處理後的frame,使用後必須使用av_frame_unref() / av_frame_free()釋放掉它
    
    成功返回非負數,失敗返回負的錯誤值,如 EAGAIN(表示需要新的輸入數據來產生filter後的數據),
        AVERROR_EOF(表示不會再有新的輸入數據)
 */
int av_buffersink_get_frame_flags(AVFilterContext *ctx, AVFrame *frame, int flags);
/*
     同 av_buffersink_get_frame_flags ,不過不能指定 flag。
 */
int av_buffersink_get_frame(AVFilterContext *ctx, AVFrame *frame)
/*
 和 av_buffersink_get_frame 相同,不過這個函數是針對音頻的,而且可以指定讀取的取樣數。此時 ctx 只能指向 abuffersink 類型的 filter context。
*/
int av_buffersink_get_samples(AVFilterContext *ctx, AVFrame *frame, int nb_samples);

 

四、FFmpeg AVFilter 使用整體流程 

下圖就是FFmpeg AVFilter在使用過程中的流程圖:

我們對上圖先做下說明,理解下圖中每個步驟的關係,然後,才從代碼的角度來給出其使用的步驟。

1. 最頂端的AVFilterGraph,這個結構前面介紹過,主要管理加入的過濾器,其中加入的過濾器就是通過函數avfilter_graph_create_filter來創建並加入,這個函數返回是AVFilterContext(其封裝了AVFilter的詳細參數信息)。

2. buffer和buffersink這兩個過濾器是FFMpeg爲我們實現好的,buffer表示源,用來向後面的過濾器提供數據輸入(其實就是原始的AVFrame);buffersink過濾器是最終輸出的(經過過濾器鏈處理後的數據AVFrame),其它的諸如filter 1 等過濾器是由avfilter_graph_parse_ptr函數解析外部傳入的過濾器描述字符串自動生成的,內部也是通過avfilter_graph_create_filter來創建過濾器的。

3. 上面的buffer、filter 1、filter 2、filter n、buffersink之間是通過avfilter_link函數來進行關聯的(通過AVFilterLink結構),這樣子過濾器和過濾器之間就通過AVFilterLink進行關聯上了,前一個過濾器的輸出就是下一個過濾器的輸入,注意,除了源和接收過濾器之外,其它的過濾器至少有一個輸入和輸出,這很好理解,中間的過濾器處理完AVFrame後,得到新的處理後的AVFrame數據,然後把新的AVFrame數據作爲下一個過濾器的輸入。

4. 過濾器建立完成後,首先我們通過av_buffersrc_add_frame把最原始的AVFrame(沒有經過任何過濾器處理的)加入到buffer過濾器的fifo隊列。

5. 然後調用buffersink過濾器的av_buffersink_get_frame_flags來獲取處理完後的數據幀(這個最終放入buffersink過濾器的AVFrame是通過之前創建的一系列過濾器處理後的數據)。

 使用流程圖就介紹到這裏,下面結合上面的使用流程圖詳細說下FFMpeg中使用過濾器的步驟,這個過程我們分爲三個部分:過濾器構建、數據加工、資源釋放。

1. 過濾器構建:

1)分配AVFilterGraph

AVFilterGraph* graph = avfilter_graph_alloc();

 2)創建過濾器源

char srcArgs[256] = {0};
AVFilterContext *srcFilterCtx;
AVFilter* srcFilter = avfilter_get_by_name("buffer");
avfilter_graph_create_filter(&srcFilterCtx, srcFilter ,"out_buffer", srcArgs, NULL, graph);

3)創建接收過濾器

AVFilterContext *sinkFilterCtx;
AVFilter* sinkFilter = avfilter_get_by_name("buffersink");
avfilter_graph_create_filter(&sinkFilterCtx, sinkFilter,"in_buffersink", NULL, NULL, graph);

4)生成源和接收過濾器的輸入輸出

這裏主要是把源和接收過濾器封裝給AVFilterInOut結構,使用這個中間結構來把過濾器字符串解析並鏈接進graph,主要代碼如下:

AVFilterInOut *inputs = avfilter_inout_alloc();
AVFilterInOut *outputs = avfilter_inout_alloc();
outputs->name       = av_strdup("in");
outputs->filter_ctx = srcFilterCtx;
outputs->pad_idx    = 0;
outputs->next       = NULL;
inputs->name        = av_strdup("out");
inputs->filter_ctx  = sinkFilterCtx;
inputs->pad_idx     = 0;
inputs->next        = NULL;

 這裏源對應的AVFilterInOut的name最好定義爲in,接收對應的name爲out,因爲FFMpeg源碼裏默認會通過這樣個name來對默認的輸出和輸入進行查找。

5)通過解析過濾器字符串添加過濾器

const *char filtergraph = "[in1]過濾器名稱=參數1:參數2[out1]";
int ret = avfilter_graph_parse_ptr(graph, filtergraph, &inputs, &outputs, NULL);

這裏過濾器是以字符串形式描述的,其格式爲:[in]過濾器名稱=參數[out],過濾器之間用,或;分割,如果過濾器有多個參數,則參數之間用:分割,其中[in]和[out]分別爲過濾器的輸入和輸出,可以有多個。

6)檢查過濾器的完整性

avfilter_graph_config(graph, NULL);

 2. 數據加工

1)向源過濾器加入AVFrame 

AVFrame* frame; // 這是解碼後獲取的數據幀
int ret = av_buffersrc_add_frame(srcFilterCtx, frame);

2)從buffersink接收處理後的AVFrame

int ret = av_buffersink_get_frame_flags(sinkFilterCtx, frame, 0);

 現在我們就可以使用處理後的AVFrame,比如顯示或播放出來。

3.資源釋放

使用結束後,調用avfilter_graph_free(&graph);釋放掉AVFilterGraph類型的graph。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章