天天看點

FFmpeg filter過濾器使用詳解

作者:音視訊流媒體技術

1、FFmpeg過濾器簡介

FFmpeg filter 提供了很多音視訊特效處理功能,比如視訊縮放、截取、翻轉、疊加等。

FFmpeg有很多已經實作好的濾波器,這些濾波器的實作位于libavfilter目錄之下,使用者需要可以調用這些濾波器實作濾波。

我們可以通過ffmpeg -filters指令檢視FFmpeg支援的過濾器。FFmpeg常用的filter包括:

scale:視訊/圖像的縮放;

overlay:視訊/圖檔的疊加;

crop:視訊/圖像的裁剪;

trim:截取視訊的片段;

rotate:以任意⻆度旋轉視訊

movie:加載第三方的視訊;

yadif:去隔行。

使用示例(對視訊的寬和高減半):ffmpeg -i input -vf scale=iw/2:ih/2 output

2、過濾器使用方法

ffmpeg的filter用起來是和Gstreamer的plugin是一樣的概念,通過avfilter_link,将各個建立好的filter按自己想要的次序連結到一起,然後avfilter_graph_config之後,就可以正常使用。

FFmpeg的filter包含3個層次:filter -> filterchain -> filtergraph

filtergraph可以用文本形式表示,可以作為ffmpeg中的 -filter/-vf/-af和-filter_complex 選項以及ffplay中的- vf/-af 和libavfilter/avfilter.h中定義的avfilter_graph_parse2()函數的參數。

示例”把視訊的上部分鏡像到下半部分“ , 流程如下:

FFmpeg filter過濾器使用詳解

(1)使用 split filter 将輸入流分割為兩個流 [main] 和 [temp];

(2)其中一個流[temp]通過crop filter把下半部分裁剪掉;

(3)步驟2中的輸出再經過vflip filter對視訊進行垂直翻轉,輸出[flip];

(4)把步驟3中輸出[flip]疊加到[main]的下半部分。

這個流程可以程式設計實作,也可以使用指令實作。指令實作如下:ffmpeg -i INPUT -vf "split main; [tmp] crop=iw:ih/2:0:0, vflip[flip]; main overlay=0:H/2" OUTPUT

處理結果如下:

FFmpeg filter過濾器使用詳解

3、過濾器文法

3.1 filter文法

用一個字元串描述filter的組成:

形式:[in_link_1]…[in_link_N]filter_name=parameters[out_link_1]…[out_link_M]

參數:

1)[in_link_N]、[out_link_N]:用來辨別輸⼊和輸出的标簽。in_link_N是标簽名,可以任意命名,需要使用方括号括起來。在filter_name的前面的标簽用于辨別輸入,在filter_name的後面的标簽用于辨別輸出。一個filter可以有多個輸入和多個輸出,沒有輸入的filter稱為source filter,沒有輸出的filter稱為sink filter。對于輸入或輸出打标簽是可選的,打上标簽是為了連接配接其他filter時使用。

2)filter_name:filter的名稱。

3) “=parameters”:包含初始化filter的參數,是可選的。有以下幾種形式:

使用 : 字元分隔的一個“鍵=值”對清單。如:ffmpeg -i input -vf scale=w=iw/2:h=ih/2 output

使用 : 字元分割的“值”的清單。在這種情況下,鍵按照聲明的順序被假定為選項名 。例如,scale filter的前兩個選項分别是w和h,當參數清單為“iw/2:ih/2”時,iw/2的值賦給w,ih/2的值賦給h。如:ffmpeg -i input -vf scale=iw/2:ih/2 output

使用 : 字元分隔混合“值”和“鍵=值”對的清單。“值”必須位于“鍵=值”對之前,并遵循與前一點相同的

限制順序。之後的“鍵=值”對的順序不受限制。如:ffmpeg -i input -vf scale=iw/2:h=ih/2 output

filter類定義了filter的特性以及輸入和輸出的數量,某個filter的使用方式可以通過以下指令獲知:ffmpeg -h filter=filter_name。

抽取視訊Y、U、V分量到不同的檔案示例(extractplanes filter指定了三個輸出,分别是 y[v],抽取後,将不同的輸出儲存到不同的檔案中):ffmpeg -i input.mp4 -filter_complex "extractplanes=y+u+vy[v]" -map "[y]" input_y.mp4 -map "[u]" input_u.mp4 -map "[v]" input_v.mp4

3.2 filterchain的文法

用一個字元串描述filterchain的組成:

形式:"filter1, filter2, ... filterN-1, filterN"

說明:

1)由1個或多個filter的連接配接而成,filter之間以逗号“,”分隔。

2)每個filter都連接配接到序列中的前1個filter,即1個filter的輸出是後1個filter的輸入。比如示例(crop)

【免費分享】音視訊學習資料包、大廠面試題、技術視訊和學習路線圖,資料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以加企鵝qun:788280672免費領取~

FFmpeg filter過濾器使用詳解

3.3 filtergraph的文法

用一個字元串描述filtergraph的組成:

形式:"filterchain1;filterchain2;...filterchainN-1;fiterchainN"

說明:

1)由1個或多個filter的組合而成,filterchain之間用分号";"分隔。

2)filtergraph是連接配接filter的有向圖。它可以包含循環,一對filter之間可以有多個連接配接。

3)當在filtergraph中找到兩個相同名稱的标簽時,将建立相應輸入和輸出之間的連接配接。

4)如果輸出沒有被打标簽,則預設将其連接配接到filterchain中下1個filter的第1個未打标簽的輸入。

例如:以下filterchain中:nullsrc, split[L1], [L2]overlay, nullsink,

說明:split filter有兩個輸出,overlay filter有兩個輸入。split的第1個輸出标記為“L1”,overlay的第1個輸入pad标記為“L2”。split的第1個輸出将連接配接到overlay的第1個輸入。

5)在一個filter描述中,如果沒有指定第1個filter的輸入标簽,則假定為“In”。如果沒有指定最後一個filter的輸出标簽,則假定為“out”。

6)在一個完整的filterchain中,所有沒有打标簽的filter輸入和輸出必須是連接配接的。如果所有filterchain的所有filter輸入和輸出pad都是連接配接的,則認為filtergraph是有效的[2]。

示例:ffmpeg -i INPUT -vf "split main; [tmp] crop=iw:ih/2:0:0, vflip[flip]; main overlay=0:H/2" OUTPUT

說明:其中有三個filterchain, 分别是,

“split main”。它隻有1個filter,即 split,它有1個預設的輸入,即INPUT解碼後的frame。有兩個輸出, 以 [main], [tmp] 辨別。

“[tmp] crop=iw:ih/2:0:0, vflip [flip]”。它由兩個filter組成,crop和vflip,crop的輸入 為[tmp],vflip的輸出辨別為[flip]。

“main overlay=0:H/2”。它由⼀個filter組成,即overlay。有兩個輸入,[main]和[flip]。有1個預設的輸出。

4、過濾器主要結構體

(1)AVFilterGraph結構體

定義描述:對filter系統的整體管理;主要用于統合這整個濾波過程。

重點字段:

typedef struct AVFilterGraph {
    AVFilterContext **filters;
    unsigned nb_filters;
    ....
}           

(2)AVFilter 定義描述:濾波器的實作是通過AVFilter以及位于其下的結構體/函數來維護的。

重點字段:

typedef struct AVFilter {
    const char *name;           //overlay, Filter name. Must be non-NULL and unique among filters.
    const AVFilterPad *inputs;  //List of inputs, terminated by a zeroed element.
    const AVFilterPad *outputs; //List of outputs, terminated by a zeroed element.
    ...
}           

使用示例:定義filter本身的能力,擁有pads,回調函數接口定義。

AVFilter ff_vf_overlay = {
 .name = "overlay",
 .description = NULL_IF_CONFIG_SMALL("Overlay a video source on top of the input."),
 .preinit = overlay_framesync_preinit,
 .init = init,
 .uninit = uninit,
 .priv_size = sizeof(OverlayContext),
 .priv_class = &overlay_class,
 .query_formats = query_formats,
 .activate = activate,
 .process_command = process_command,
 .inputs = avfilter_vf_overlay_inputs,
 .outputs = avfilter_vf_overlay_outputs,
 .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL | AVFILTER_FLAG_SLICE_THREADS,
 };           

(3)AVFilterContext 定義描述:一個濾波器執行個體,即使是同一個濾波器,在進行實際的濾波時,也會由于輸入的參數不同而有不同的濾波效果,AVFilterContext就是在實際進行濾波時用于維護濾波相關資訊的實體。

重點字段:

struct AVFilterContext {
    const AVFilter *filter;     ///< the AVFilter of which this is an instance
    char *name;                 ///< name of this filter instance
    AVFilterPad *input_pads;    ///< array of input pads
    AVFilterLink **inputs;      ///< array of pointers to input links
    unsigned nb_inputs;         ///< number of input pads
    AVFilterPad *output_pads;   ///< array of output pads
    AVFilterLink **outputs;     ///< array of pointers to output links
    unsigned nb_outputs;        ///< number of output pads
    struct AVFilterGraph *graph; ///< filtergraph this filter belongs to 從屬于哪個AVFilterGraph
    ...
};           

(4)AVFilterLink

定義描述:濾波器鍊,主要是用于連結相鄰的兩個AVFilterContext,為了實作一個濾波過程,可能會需要多個濾波器協同完成,即一個濾波器的輸出可能會是另一個濾波器輸入,AVFilterLink的作用是串聯兩個相鄰的濾波器執行個體,形成兩個濾波器之間的通道。

重點參數:

struct AVFilterLink
{
    AVFilterContext *src;           ///< source filter
    AVFilterPad *srcpad;            ///< output pad on the source filte
    AVFilterContext *dst;           ///< dest filter
    AVFilterPad *dstpad;            ///< input pad on the dest filter
    struct AVFilterGraph *graph;    //Graph the filter belongs to.
    ...
};           

(5)AVFilterPad 定義描述:濾波器的輸入輸出端口,一個濾波器可以有多個輸入以及多個輸出端口,相鄰濾波器之間是通過AVFilterLink來串聯的,而位于AVFilterLink兩端的分别就是前一個濾波器的輸出端口以及一個濾波器的輸入端口。

重點參數:

struct AVFilterPad
{
    const char *name;   //Pad name. The name is unique among inputs and among outputs,
    AVFrame *(*get_video_buffer)(AVFilterLink *link, int w, int h); // Callback function to get a video buffer. 
    AVFrame *(*get_audio_buffer)(AVFilterLink *link, int nb_samples);   //Callback function to get an audio buffer. I
    int (*filter_frame)(AVFilterLink *link, AVFrame *frame);    //Filtering callback. 
    int (*request_frame)(AVFilterLink *link);   //Frame poll callback.
    ...
}           

(6)AVFilterInOut 定義描述:過濾器鍊輸入/輸出的連結清單。

重點參數:

typedef struct AVFilterInOut {
    /** unique name for this input/output in the list */
     char *name;
    
     /** filter context associated to this input/output */
     AVFilterContext *filter_ctx;
    
     /** index of the filt_ctx pad to use for linking */
     int pad_idx;
    
     /** next input/input in the list, NULL if this is the last */
     struct AVFilterInOut *next;
 } AVFilterInOut;           

在AVFilter子產品中定義了AVFilter結構,每個AVFilter結構都是具有獨立功能的節點,如scale filter的作用就是進行圖像尺寸變換,overlay filter的作用就是進行圖像的疊加。

這裡需要重點提的兩個特别的filter:buffer和buffersink。

(1)buffer:濾波器buffer代表filter graph中的源頭,原始資料就往這個filter節點輸入的。通過調用該濾波器提供的函數(如av_buffersrc_add_frame)可以把需要濾波的幀傳輸進入濾波過程。在建立該濾波器執行個體的時候需要提供一些關于所輸入的幀的格式的必要參數(如:time_base、圖像的寬高、圖像像素格式等)。

(2)buffersink:一個特殊的濾波器,濾波器buffersink代表filter graph中的輸出節點,處理完成的資料從這個filter節點輸出。通過調用濾波器提供的函數(如av_buffersink_get_frame)可以提供出被濾波過程過濾完成後的幀。

5、主要函數

//進行濾波器注冊
avfilter_register_all();

// 擷取FFmpeg中定義的filter,調用該方法前需要先調用avfilter_register_all();進行濾波器注冊
AVFilter avfilter_get_by_name(const char name);

// 往源濾波器buffer中輸入待處理的資料
int av_buffersrc_add_frame(AVFilterContext ctx, AVFrame frame);

// 從目的濾波器buffersink中擷取處理完的資料
int av_buffersink_get_frame(AVFilterContext ctx, AVFrame frame);

// 建立一個濾波器圖filter graph
AVFilterGraph *avfilter_graph_alloc(void);

// 建立一個濾波器執行個體AVFilterContext,并添加到AVFilterGraph中
int avfilter_graph_create_filter(AVFilterContext **filt_ctx, 
                                const AVFilter *filt,const char name, 
                                const char args, void *opaque,AVFilterGraph *graph_ctx);

// 連接配接兩個濾波器節點
int avfilter_link(AVFilterContext *src, unsigned srcpad,AVFilterContext *dst, unsigned dstpad);           

6、濾波執行個體過程

step1:注冊過濾器

avfilter_register_all();//注冊           

step2:建立統合整個濾波過程的濾波圖結構體(AVFilterGraph)

AVFilterGraph* filter_graph = avfilter_graph_alloc();           

step3:擷取濾波過程需要的濾波器(AVFilter)

const AVFilter *buffersrc = avfilter_get_by_name("buffer");     //AVFilterGraph的輸入源
const AVFilter *buffersink = avfilter_get_by_name("buffersink");//輸出濾波器
const AVFilter *myfilter = avfilter_get_by_name("myfilter");    //處理業務的濾波器           

step4:建立用于維護濾波相關資訊的濾波器執行個體(AVFilterContext)

AVFilterContext *in_video_filter = NULL;
AVFilterContext *out_video_filter = NULL;
AVFilterContext *my_video_filter = NULL;

  char args[512];
    sprintf(args,
        "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
        in_width, in_height, AV_PIX_FMT_YUV420P,
        1, 25, 1, 1);
avfilter_graph_create_filter(&in_video_filter, buffersrc, "in", args,NULL, filter_graph);
avfilter_graph_create_filter(&out_video_filter, buffersink, "out", NULL, NULL, filter_graph);
avfilter_graph_create_filter(&my_video_filter, myfilter, "myfilter",NULL, NULL, filter_graph);           

tep5:用AVFilterLink把相鄰的兩個濾波執行個體連接配接起來

avfilter_link(in_video_filter, 0, my_video_filter, 0);
avfilter_link(my_video_filter, 0, out_video_filter, 0);           

step6:送出整個濾波圖

avfilter_graph_config(filter_graph, NULL);           
作者:夏之七