1、AVFilter簡介
FFmpeg中的AVFilter模塊進行幀數據處理的開發,AVFilter模塊對幀數據處理進行了很好的抽象,對其中的幀數據處理(包括音頻和視頻數據)則相對要多樣化一些,比如對視頻做尺寸變換,進行音頻音量均衡,直播中的美顏處理,多路流合成等等,這些都是屬於流程中的幀數據處理。
一般的編解碼流程就是;
原始音視頻–>解碼–>幀數據處理–>編碼–>輸出音視頻
在編碼前,ffmpeg可以對raw(真實/原)音頻和視頻使用libavfilter庫中的濾鏡進行處理。(非壓縮數據幀)
多個濾鏡可以組成濾鏡鏈圖(濾鏡鏈圖filtergraphs )
在ffmpeg看來只有2種濾鏡:簡單濾鏡,複合濾鏡。
**簡單濾鏡;**就是隻有1個輸入和輸出的濾鏡,濾鏡兩邊的數據都是同一類型的,可以理解為在非壓縮數據幀到再次編碼前簡單附加了一步:
**複合濾鏡;**complex filtergraph,通常是具有多個輸入輸出文件,並有多條執行路徑;ffmpeg命令行中使用-lavfi、-filter_complex,
如ffmpeg官網中的一個列子;
[main]
input --> split ---------------------> overlay --> output
| ^
|[tmp] [flip]|
+-----> crop --> vflip -------+
input就是原輸入流,上圖整個流程就做了這些操作,
首先使用split濾波器將input流分成兩路流(main和tmp),然後分別對兩路流進行處理。對於tmp流,先經過crop濾波器進行裁剪處理,再經過flip濾波器進行垂直方向上的翻轉操作,輸出的結果命名為flip流。再將main流和flip流輸入到overlay濾波器進行合成操作。上圖的input就是上面提過的buffer源濾波器,output就是上面的提過的buffersink濾波器。
以上操作使用命令實現是
ffmpeg -i INPUT -vf "split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2" OUTPUT
同一個線性鏈中的過濾器用逗號分隔,不同的過濾器線性鏈用分號分隔。
在我們的例子中,裁剪,vflip在一個線性鏈中,分割和疊加在另一個鏈中是分開的。線形鏈連接的點用方括號括起來的名稱進行標記。
在本例中,拆分過濾器生成與標籤[main]和[tmp]相關聯的兩個輸出。
一些過濾器接受輸入的參數列表:它們在過濾器名稱和等號之後指定,並用冒號分隔。
2、AVFilter的一些概念
可以在http://www.ffmpeg.org/ffmpeg-filters.html進行系統學習查看
2.1、濾波器圖AVFilterGraph
如上面的幾個濾波器構成的一套邏輯就是濾波器圖了,對filters系統的整體管理。
struct AVFilterGraph
{
AVFilterContext **filters;
unsigned nb_filters;
}
2.1.1、graph2dot程序
FFmpeg tools目錄中包含的graph2dot程序可用於解析filtergraph描述,並使用dot語言發出相應的文本表示。然後,您可以將點描述傳遞給點程序(從graphviz程序組),並獲得filtergraph的圖形表示。可以用於創建和顯示代表GRAPH_DESCRIPTION字符串所描述的圖形的圖像。注意,這個字符串必須是一個完整的自包含圖,其輸入和輸出都明確定義。
2.1.2、Filtergraph description
濾波器圖是連接濾波器的有向圖。它可以包含循環,並且一對過濾器之間可以有多個鏈接。每個鏈接的一側有一個輸入pad,連接到一個過濾器,從過濾器中獲取輸入,另一側有一個輸出pad,連接到一個接受輸出的過濾器。
filtergraph中的每個過濾器都是在應用程序中註冊的過濾器類的實例,它定義了過濾器的特性以及輸入和輸出pad的數量。
沒有輸入pad的過濾器稱為“source”,沒有輸出pad的過濾器稱為“sink"。
2.1.3、Filtergraph syntax Filtergraph語法
filtergraph有一個文本表示,ffmpeg中的-filter/-vf/-af和-filter_complex選項可以識別,ffplay中的-vf/-af選項可以識別,libavfilter/avfilter.h中定義的avfilter_graph_parse_ptr()函數也可以識別。
過濾器鏈由一系列連接的過濾器組成,每個過濾器都與序列中的前一個過濾器相連接。過濾器鏈由一系列“,”分隔的過濾器描述來表示。
一個過濾圖由一系列過濾鏈組成。filterchains序列由一個";“分開filterchain描述。詳情可以查看官網描述。
2.1.4、Notes on filtergraph escaping filtergraph 轉義
2.1.5、Timeline editing 時間軸編輯
一些過濾器支持一個通用的啓用選項。對於支持時間線編輯的過濾器,可以將此選項設置為一個表達式,該表達式在將幀發送給過濾器之前進行計算。如果求值是非零,則該過濾器將被啓用,否則幀將被不變地發送到filtergraph中的下一個過濾器。
2.1.6、使用命令在運行時更改選項
在過濾器的操作過程中,可以使用命令更改一些選項。這些選項在ffmpeg -h filter=< filter>的輸出上標記為’ T '。命令的名稱是選項的名稱,參數是新值。
2.1.7、有多個輸入的過濾器選項(framesync)
一些具有多個輸入的過濾器支持一組通用的選項。這些選項只能通過名稱設置,不能使用短符號。
關於AVFilterGraph的代碼相關流程
1、首先都要進行濾波器設備註冊
//進行濾波器註冊
avfilter_register_all();
2、創建濾波器圖AVFilterGraph
//創建濾波器圖
AVFilterGraph* filter_graph = avfilter_graph_alloc();
if (!filter_graph) {
printf("Fail to create filter graph!\n");
return -1;
}
創建濾波器圖的另外一種方法,直接一次性創建字符串指定的圖 並且濾波器也要提前初始化好
//向圖中添加由字符串描述的圖。hflip豎直翻轉
if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr,
&inputs, &outputs, NULL)) < 0) {
return false;
}
3、將創建的濾波器實例化並添加到濾波器圖中
/*
創建並添加一個過濾器實例到現有的圖形。
過濾器實例由filter filt創建,並使用參數args和opaque初始化。
在成功的情況下,將指針放入filt_ctx過濾實例,否則將*filt_ctx設置為空。
*/
AVFilterContext* bufferSrc_ctx;
ret = avfilter_graph_create_filter(&bufferSrc_ctx, bufferSrc, "in", args, NULL, filter_graph);
if (ret < 0) {
printf("Fail to create filter bufferSrc\n");
return -1;
}
4、將加入的多個濾波器實例進行鏈接link
//進行鏈接濾波器buffer -- split
// src filter to split filter
ret = avfilter_link(bufferSrc_ctx, 0, splitFilter_ctx, 0);
if (ret != 0) {
printf("Fail to link src filter and split filter\n");
return -1;
}
5、檢查濾波器圖的正確性
//檢查有效性並配置圖中的所有鏈接和格式。
// check filter graph
ret = avfilter_graph_config(filter_graph, NULL);
if (ret < 0) {
printf("Fail in filter graph\n");
return -1;
}
6、可以輸出濾波器圖以字符串的形式打印出來方便觀看(可選)
//將圖形轉儲為人類可讀的字符串表示形式。
char *graph_str = avfilter_graph_dump(filter_graph, NULL);
2.2、各種濾波器AVFilter
在libavfilter中,一個過濾器可以有多個輸入和多個輸出。
AVFilter相關的源碼結構
// 定義filter本身的能力,擁有的pads,回調函數接口定義
struct AVFilter
{
const char *name;
const AVFilterPad *inputs;
const AVFilterPad *outputs;
}
// filter實例,管理filter與外部的聯繫
struct AVFilterContext
{
const AVFilter *filter;
char *name;
AVFilterPad *input_pads;
AVFilterLink **inputs;
unsigned nb_inputs
AVFilterPad *output_pads;
AVFilterLink **outputs;
unsigned nb_outputs;
struct AVFilterGraph *graph;
}
// 定義兩個filters之間的聯接
struct AVFilterLink
{
AVFilterContext *src;
AVFilterPad *srcpad;
AVFilterContext *dst;
AVFilterPad *dstpad;
struct AVFilterGraph *graph;
}
// 定義filter的輸入/輸出接口
struct AVFilterPad
{
const char *name;
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 (*request_frame)(AVFilterLink *link);
}
struct AVFilterInOut
{
char *name;
AVFilterContext *filter_ctx;
int pad_idx;
struct AVFilterInOut *next;
}
在AVFilter模塊中定義了AVFilter結構,很個AVFilter都是具有獨立功能的節點,
如scale filter的作用就是進行圖像尺寸變換,
overlay filter的作用就是進行圖像的疊加,
濾波器buffer代表filter graph中的源頭,原始數據就往這個filter節點輸入的;
濾波器buffersink代表filter graph中的輸出節點,處理完成的數據從這個filter節點輸出。
暫時先介紹用到的,其他的用到再補上 總共分類就這幾大類
Audio Filters
Audio Sources
Audio Sinks
Video Filters
crop 裁剪輸入視頻到給定的尺寸。
overlay 將一個視頻疊加在另一個視頻上。它有兩個輸入,一個輸出。第一個輸入是覆蓋第二個輸入的“主”視頻。
OpenCL Video Filters
VAAPI Video Filters
Video Sources
buffer 濾波器buffer代表filter graph中的源頭,原始數據就往這個filter節點輸入的
Video Sinks
buffersink 濾波器buffersink代表filter graph中的輸出節點,處理完成的數據從這個filter節點輸出。
Multimedia Filters
split, asplit 把輸入分成幾個相同的輸出。asplit工作在音頻輸入,split只分割視頻。過濾器只接受一個參數,該參數指定輸出的數量。如果未指定,則默認為2。
Multimedia Sources
濾波器相關的代碼有
// 獲取FFmpeg中定義的filter,調用該方法前需要先調用avfilter_register_all();進行濾波器註冊
AVFilter *avfilter_get_by_name(const char *name);
//濾波器buffer代表filter graph中的源頭,原始數據就往這個filter節點輸入的
AVFilter* bufferSrc = avfilter_get_by_name("buffer");
avfilter_get_by_name的源碼,具體的name有哪些在源碼的什麼位置需要追源代碼
const AVFilter *avfilter_get_by_name(const char *name)
{
const AVFilter *f = NULL;
void *opaque = 0;
if (!name)
return NULL;
while ((f = av_filter_iterate(&opaque)))
if (!strcmp(f->name, name))
return (AVFilter *)f;
return NULL;
}
// 往源濾波器buffer中輸入待處理的數據
int av_buffersrc_add_frame(AVFilterContext *ctx, AVFrame *frame);
//向緩衝區源添加一個幀。
if (av_buffersrc_add_frame(bufferSrc_ctx, frame_in) < 0) {
printf("Error while add frame.\n");
break;
}
// 從目的濾波器buffersink中輸出處理完的數據
int av_buffersink_get_frame(AVFilterContext *ctx, AVFrame *frame);
//從接收器獲得一個過濾後的數據幀,並將其放入幀中。
ret = av_buffersink_get_frame(bufferSink_ctx, frame_out);
if (ret < 0)
break;
// 創建一個濾波器圖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);
AVFilterContext* bufferSrc_ctx;
ret = avfilter_graph_create_filter(&bufferSrc_ctx, bufferSrc, "in", args, NULL, filter_graph);
//需要傳參的 輸出2路 之後鏈接split節點的時候需要指定哪路數據
AVFilterContext *splitFilter_ctx;
ret = avfilter_graph_create_filter(&splitFilter_ctx, splitFilter, "split", "outputs=2", NULL, filter_graph);
// 連接兩個濾波器節點
int avfilter_link(AVFilterContext *src, unsigned srcpad,
AVFilterContext *dst, unsigned dstpad);
//split分出第2pad--crop
ret = avfilter_link(splitFilter_ctx, 1, cropFilter_ctx, 0);
3、AVFilter實現上面濾波器圖實例
[main]
input --> split ---------------------> overlay --> output
| ^
|[tmp] [flip]|
+-----> crop --> vflip -------+
實例
核心代碼
//生成濾波器圖抽象成一個函數出來,其實可以關於濾波器抽象出一個類來,之後需要的濾波器圖可以統一管理
int InitAvFilter(AVFilterGraph *&filter_graph, AVFilterContext * &bufferSrc_ctx, AVFilterContext* &bufferSink_ctx, const char *args)
{
int ret = 0;
avfilter_register_all();
filter_graph = avfilter_graph_alloc();
if (!filter_graph)
{
cout << "create filter graph Fial" << endl;
return -1;
}
//創建一個buffer源過濾器並創建對應的過濾器實列並加入過濾器圖中,之後就需要將圖裏面的不同過濾器鏈接到一起即可
AVFilter* bufferSrc = avfilter_get_by_name("buffer");
ret = avfilter_graph_create_filter(&bufferSrc_ctx, bufferSrc ,"in", args, NULL, filter_graph);
if (ret < 0) {
printf("Fail to create filter\n");
return -1;
}
//創建buffersink目的過濾器
//bufferSink需要設置參數 允許的像素格式列表,以AV_PIX_FMT_NONE結束
AVBufferSinkParams *bufferSink_params = NULL;
enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE };
bufferSink_params = av_buffersink_params_alloc();
bufferSink_params->pixel_fmts = pix_fmts;
AVFilter* bufferSink = avfilter_get_by_name("buffersink"); //ffbuffersink帶緩衝的buffersink
ret = avfilter_graph_create_filter(&bufferSink_ctx, bufferSink, "out", NULL, bufferSink_params, filter_graph);
if (ret < 0) {
printf("Fail to create filter sink filter\n");
return -1;
}
//創建split分割過濾器 可以傳入參數outputs=2表示分割出兩路 注意鏈接的時候需要指定哪一路 從0開始計數
AVFilter *splitFilter = avfilter_get_by_name("split");
AVFilterContext *splitFilter_ctx;
ret = avfilter_graph_create_filter(&splitFilter_ctx, splitFilter, "split", "outputs=2", NULL, filter_graph);
if (ret < 0) {
printf("Fail to create split filter\n");
return -1;
}
//創建crop裁剪處理過濾器
AVFilter *cropFilter = avfilter_get_by_name("crop");
AVFilterContext *cropFilter_ctx;
ret = avfilter_graph_create_filter(&cropFilter_ctx, cropFilter, "crop", "out_w=iw:out_h=ih/2:x=0:y=0", NULL, filter_graph);
if (ret < 0) {
printf("Fail to create crop filter\n");
return -1;
}
//創建vflip水平翻轉過濾器
AVFilter *vflipFilter = avfilter_get_by_name("vflip");
AVFilterContext *vflipFilter_ctx;
ret = avfilter_graph_create_filter(&vflipFilter_ctx, vflipFilter, "vflip", NULL, NULL, filter_graph);
if (ret < 0) {
printf("Fail to create vflip filter\n");
return -1;
}
//創建overlay覆蓋過濾器,將一個視頻覆蓋再另外一個視頻上
AVFilter *overlayFilter = avfilter_get_by_name("overlay");
AVFilterContext *overlayFilter_ctx;
ret = avfilter_graph_create_filter(&overlayFilter_ctx, overlayFilter, "overlay", "y=0:H/2", NULL, filter_graph);
if (ret < 0) {
printf("Fail to create overlay filter\n");
return -1;
}
//對上面添加到過濾器圖中的過濾器進行鏈接生成邏輯關係
//進行鏈接濾波器buffer -- split
ret = avfilter_link(bufferSrc_ctx, 0, splitFilter_ctx, 0);
if (ret != 0) {
printf("Fail to link src filter and split filter\n");
return -1;
}
//鏈接split分出第1pad--overlay
ret = avfilter_link(splitFilter_ctx, 0, overlayFilter_ctx, 0);
if (ret != 0) {
printf("Fail to link split filter and overlay filter main pad\n");
return -1;
}
//鏈接split分出第2pad--crop
ret = avfilter_link(splitFilter_ctx, 1, cropFilter_ctx, 0);
if (ret != 0) {
printf("Fail to link split filter's second pad and crop filter\n");
return -1;
}
//鏈接crop到vflip
ret = avfilter_link(cropFilter_ctx, 0, vflipFilter_ctx, 0);
if (ret != 0) {
printf("Fail to link crop filter and vflip filter\n");
return -1;
}
//鏈接vflip到overlay的第二個輸入pad 主屏幕 第一個輸入是覆蓋第二個輸入的“主”視頻。
ret = avfilter_link(vflipFilter_ctx, 0, overlayFilter_ctx, 1);
if (ret != 0) {
printf("Fail to link vflip filter and overlay filter's second pad\n");
return -1;
}
//鏈接overlay的輸出到sink目的過濾器
ret = avfilter_link(overlayFilter_ctx, 0, bufferSink_ctx, 0);
if (ret != 0) {
printf("Fail to link overlay filter and sink filter\n");
return -1;
}
//檢查有效性並配置圖中的所有鏈接和格式。
ret = avfilter_graph_config(filter_graph, NULL);
if (ret < 0) {
printf("Fail in filter graph\n");
return -1;
}
//測試使用;
//將圖形轉儲為人類可讀的字符串表示形式。
char *graph_str = avfilter_graph_dump(filter_graph, NULL);
FILE* graphFile = NULL;
fopen_s(&graphFile, "graphFile.txt", "w");
fprintf(graphFile, "%s", graph_str);
av_free(graph_str);
return 0;
}
調用
//初始化avfilter濾波器
AVFilterGraph *filter_graph = NULL;
AVFilterContext * bufferSrc_ctx = NULL;
AVFilterContext* bufferSink_ctx = NULL;
char args[512];
_snprintf_s(args, sizeof(args),
"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
pFormatCtx->streams[videoindex]->codecpar->width, pFormatCtx->streams[videoindex]->codecpar->height, AV_PIX_FMT_YUV420P,
1, 25, 1, 1);
ret = InitAvFilter(filter_graph, bufferSrc_ctx, bufferSink_ctx, args);
if (ret != 0)
{
cout << "InitAvFilter faile" << endl;
return -1;
}
顯示調用
//向緩衝區源添加一個幀。
if (av_buffersrc_add_frame(bufferSrc_ctx, pFrame) < 0) {
printf("Error while add frame.\n");
break;
}
//從接收器獲得一個過濾後的數據幀,並將其放入幀中。
/* pull filtered pictures from the filtergraph */
ret = av_buffersink_get_frame(bufferSink_ctx, frame_out);
if (ret < 0)
break;
//將返回的數據更新到SDL紋理當中
SDL_UpdateYUVTexture(texture, NULL,
frame_out->data[0], frame_out->linesize[0],
frame_out->data[1], frame_out->linesize[1],
frame_out->data[2], frame_out->linesize[2]);
//進行SDL刷新顯示
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, NULL, &rect);
SDL_RenderPresent(renderer);
4、實現視頻上添加圖片水印
運行結果
//核心代碼
int InitAvFilter(AVFilterGraph *&filter_graph, AVFilterContext * &bufferSrc_ctx, AVFilterContext* &bufferSink_ctx, const char *args)
{
int ret = 0;
avfilter_register_all();
AVFilter *buffersrc = avfilter_get_by_name("buffer");
AVFilter *buffersink = avfilter_get_by_name("buffersink");//ffbuffersink 實例化的時候錯誤返回-12
AVFilterInOut *outputs = avfilter_inout_alloc();
AVFilterInOut *inputs = avfilter_inout_alloc();
enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE };
AVBufferSinkParams *buffersink_params;
filter_graph = avfilter_graph_alloc();
/* buffer video source: the decoded frames from the decoder will be inserted here. */
ret = avfilter_graph_create_filter(&bufferSrc_ctx, buffersrc, "in",
args, NULL, filter_graph);
if (ret < 0) {
printf("Cannot create buffer source\n");
return ret;
}
/* buffer video sink: to terminate the filter chain. */
buffersink_params = av_buffersink_params_alloc();
buffersink_params->pixel_fmts = pix_fmts;
ret = avfilter_graph_create_filter(&bufferSink_ctx, buffersink, "out",
NULL, buffersink_params, filter_graph);
av_free(buffersink_params);
if (ret < 0) {
printf("Cannot create buffer sink\n");
return ret;
}
/* Endpoints for the filter graph. */
outputs->name = av_strdup("in");
outputs->filter_ctx = bufferSrc_ctx;
outputs->pad_idx = 0;
outputs->next = NULL;
inputs->name = av_strdup("out");
inputs->filter_ctx = bufferSink_ctx;
inputs->pad_idx = 0;
inputs->next = NULL;
//向圖中添加由字符串描述的圖。hflip豎直翻轉
//採用AVFilterInOut 的輸入輸出
const char *filter_descr = "movie=hello.png[wm];[in][wm]overlay=5:5[out]";
if ((ret = avfilter_graph_parse_ptr(filter_graph, filter_descr,
&inputs, &outputs, NULL)) < 0)
return ret;
if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0)
return ret;
//測試使用;
//將圖形轉儲為人類可讀的字符串表示形式。
char *graph_str = avfilter_graph_dump(filter_graph, NULL);
FILE* graphFile = NULL;
fopen_s(&graphFile, "graphFile.txt", "w");
fprintf(graphFile, "%s", graph_str);
av_free(graph_str);
return 0;
}
使用還是與上面一樣的,只有構建濾波器圖的步驟不同 這裏採用avfilter_graph_parse_ptr方式
後續需補上一些濾波器官網介紹的使用方法;