=====================================================
FFmpeg簡單使用:過濾器 ---- h264_mp4toannexb
FFmpeg簡單使用:解封裝h264 ---- 提取SPS PPS
=====================================================
1. 簡介
FFmpeg filter提供了很多⾳視頻特效處理的功能,⽐如視頻縮放、截取、翻轉、疊加等。
其中定義了很多的filter,例如以下常⽤的⼀些filter。
scale:視頻/圖像的縮放
overlay:視頻/圖像的疊加
crop:視頻/圖像的裁剪
trim:截取視頻的⽚段
rotate:以任意⻆度旋轉視頻
⽀持的filter的列表可以通過以下命令獲得。
ffmpeg -filters
也可以查看⽂檔[2],具體某個版本的⽀持情況以命令⾏獲取到的結果為准。
以下是filter的⼀個簡單的應⽤示例,對視頻的寬和⾼減半。
ffmpeg -i input -vf scale=iw/2:ih/2 output
2. filter使用方法
FFmpeg中filter包含三個層次,filter->filterchain->filtergraph。
2.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的參數,是可選的。
“=parameters”有以下⼏種形式
1. 使⽤':'字符分隔的⼀個“鍵=值”對列表。如下所示。
ffmpeg -i input -vf scale=w=iw/2:h=ih/2 output ffmpeg -i input -vf scale=h=ih/2:w=iw/2 output
2. 使⽤':'字符分割的“值”的列表。在這種情況下,鍵按照聲明的順序被假定為選項名。例如,scale filter 的前兩個選項分別是w和h,當參數列表為“iw/2:ih/2”時,iw/2的值賦給w,ih/2的值賦給h。如下所示。
ffmpeg -i input -vf scale=iw/2:ih/2 output
3. 使⽤':' 字符分隔混合“值”和“鍵=值”對的列表。“值”必須位於“鍵=值”對之前,並遵循與前⼀點相同的約束順序。之后的“鍵=值”對的順序不受約束。如下所示。
ffmpeg -i input -vf scale=iw/2:h=ih/2 output
2.2 filterchain的語法
⽤⼀個字符串描述filterchain的組成,形式如下
"filter1, filter2, ... filterN-1, filterN"
說明:
1. 由⼀個或多個filter的連接⽽成,filter之間以逗號“,”分隔。
2. 每個filter都連接到序列中的前⼀個filter,即前⼀個filter的輸出是后⼀個filter的輸⼊。
⽐如示例
ffmpeg -i INPUT -vf "split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2" OUTPUT
示例說明:
1. crop、vflip在同⼀個filterchain中
2.3 filtergraph的語法
⽤⼀個字符串描述filtergraph的組成,形式如下
"filterchain1;filterchain2;...filterchainN-1;fiterchainN"
說明:
1. 由⼀個或多個filter的組合⽽成,filterchain之間⽤分號";"分隔。
2. filtergraph是連接filter的有向圖。它可以包含循環,⼀對filter之間可以有多個連接。
3. 當在filtergraph中找到兩個相同名稱的標簽時,將創建相應輸⼊和輸出之間的連接。
4. 如果輸出沒有被打標簽,則默認將其連接到filterchain中下⼀個filter的第⼀個未打標簽的輸⼊。例如
以下filterchain中。
nullsrc, split[L1], [L2]overlay, nullsink
說明:split filter有兩個輸出,overlay filter有兩個輸⼊。split的第⼀個輸出標記為“L1”,overlay的第⼀個輸⼊pad標記為“L2”。split的第⼆個輸出將連接到overlay的第⼆個輸⼊。
5. 在⼀個filter描述中,如果沒有指定第⼀個filter的輸⼊標簽,則假定為“In”。如果沒有指定最后⼀個filter的輸出標簽,則假定為“out”。
6. 在⼀個完整的filterchain中,所有沒有打標簽的filter輸⼊和輸出必須是連接的。如果所有filterchain的所有filter輸⼊和輸出pad都是連接的,則認為filtergraph是有效的[2]。
⽐如示例
ffmpeg -i INPUT -vf "split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2" OUTPUT
其中有三個filterchain, 分別是:
1. "split [main][tmp]"。它只有⼀個filter,即 split,它有⼀個默認的輸⼊,即INPUT解碼后的frame。有兩個輸出, 以 [main], [tmp] 標識。
2. "[tmp] crop=iw:ih/2:0:0, vflip [flip]"。它由兩個filter組成,crop和vflip,crop的輸⼊ 為[tmp],vflip的輸出標識為[flip]。
3. "[main][flip] overlay=0:H/2"。它由⼀個filter組成,即overlay。有兩個輸⼊,[main]和[flip]。有⼀個默認的輸出。
3. 基本結構
我們把⼀整個濾波的流程稱為濾波過程。下⾯是⼀個濾波過程的結構
圖中簡要指示出了濾波所⽤到的各個結構體,各個結構體有如下作⽤:
AVFilterGraph | ⽤於統合這整個濾波過程的結構體。 |
AVFilter | 濾波器,濾波器的實現是通過AVFilter以及位於其下的結構體/函數來維護的。 |
AVFilterContext | ⼀個濾波器實例,即使是同⼀個濾波器,但是在進⾏實際的濾波時,也會由於輸⼊的參數不同⽽有不同的濾波效果,AVFilterContext就是在實際進⾏濾波時⽤於維護濾波相關信息的實體。 |
AVFilterLink | 濾波器鏈,作⽤主要是⽤於連接相鄰的兩個AVFilterContext。為了實現⼀個濾波過程,可能會需要多個濾波器協同完成,即⼀個濾波器的輸出可能會是另⼀個濾波器的輸⼊,AVFilterLink的作⽤是串聯兩個相鄰的濾波器實例,形成兩個濾波器之間的通道。 |
AVFilterPad | 濾波器的輸⼊輸出端⼝,⼀個濾波器可以有多個輸⼊以及多個輸出端⼝,相鄰濾波器之間是通過AVFilterLink來串聯的,⽽位於AVFilterLink兩端的分別就是前⼀個濾波器的輸出端⼝以及后⼀個濾波器的輸⼊端⼝。 |
buffersrc | ⼀個特殊的濾波器,這個濾波器的作⽤就是充當整個濾波過程的⼊⼝,通過調⽤該濾波器提供的函數(如av_buffersrc_add_frame)可以把需要濾波的幀傳輸進⼊濾波過程。在創建該濾波器實例的時候需要提供⼀些關於所輸⼊的幀的格式的必要參數(如:time_base、圖像的寬⾼、圖像像素格式等)。 |
buffersink | ⼀個特殊的濾波器,這個濾波器的作⽤就是充當整個濾波過程的出⼝,通過調⽤該濾波器提供的函數(如av_buffersink_get_frame)可以提取出被濾波過程濾波完成后的幀。 |
demo
#include <stdio.h> #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavfilter/avfilter.h> #include <libavfilter/buffersink.h> #include <libavfilter/buffersrc.h> #include <libavutil/opt.h> #include <libavutil/imgutils.h> AVFilterContext *mainsrc_ctx = NULL; AVFilterContext *resultsink_ctx = NULL; AVFilterGraph *filter_graph = NULL; int init_filters(const int width, const int height, const int format) { int ret = 0; AVFilterInOut *inputs = NULL; AVFilterInOut *outputs = NULL; char filter_args[1024] = { 0 }; filter_graph = avfilter_graph_alloc(); if (!filter_graph) { printf("Error: allocate filter graph failed\n"); return -1; } // snprintf(filter_args, sizeof(filter_args), // "buffer=video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d[v0];" // Parsed_buffer_0 // "[v0]split[main][tmp];" // Parsed_split_1 // "[tmp]crop=iw:ih/2:0:0,vflip[flip];" // Parsed_crop_2 Parsed_vflip_3 // "[main][flip]overlay=0:H/2[result];" // Parsed_overlay_4 // "[result]buffersink", // Parsed_buffersink_5 // width, height, format, 1, 25, 1, 1); snprintf(filter_args, sizeof(filter_args), "buffer=video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d[v0];" // Parsed_buffer_0 "[v0]split[main][tmp];" // Parsed_split_1 "[tmp]crop=iw:ih/2:0:0,vflip[flip];" // Parsed_crop_2 Parsed_vflip_3 "[main]buffersink;" // Parsed_buffersink_4 "[flip]buffersink", // Parsed_buffersink_5 width, height, format, 1, 25, 1, 1); ret = avfilter_graph_parse2(filter_graph, filter_args, &inputs, &outputs); if (ret < 0) { printf("Cannot parse graph\n"); return ret; } ret = avfilter_graph_config(filter_graph, NULL); // 提交過濾器 if (ret < 0) { printf("Cannot configure graph\n"); return ret; } // Get AVFilterContext from AVFilterGraph parsing from string mainsrc_ctx = avfilter_graph_get_filter(filter_graph, "Parsed_buffer_0"); if(!mainsrc_ctx) { printf("avfilter_graph_get_filter Parsed_buffer_0 failed\n"); return -1; } resultsink_ctx = avfilter_graph_get_filter(filter_graph, "Parsed_buffersink_4"); if(!resultsink_ctx) { printf("avfilter_graph_get_filter Parsed_buffersink_5 failed\n"); return -1; } printf("sink_width:%d, sink_height:%d\n", av_buffersink_get_w(resultsink_ctx), av_buffersink_get_h(resultsink_ctx)); return 0; } // ffmpeg -i 9.5.flv -vf "split[main][tmp];[tmp]crop=iw:ih/2:0:0,vflip [flip];[main][flip]overlay=0:H/2" -b:v 500k -vcodec libx264 9.5_out.flv int main(int argc, char** argv) { int ret = 0; int in_width = 768; int in_height = 320; avfilter_register_all(); if(init_filters(in_width, in_height, AV_PIX_FMT_YUV420P) < 0) { printf("init_filters failed\n"); return -1; } // input yuv FILE* inFile = NULL; const char* inFileName = "768x320.yuv"; fopen_s(&inFile, inFileName, "rb+"); if (!inFile) { printf("Fail to open file\n"); return -1; } // output yuv FILE* outFile = NULL; const char* outFileName = "out_crop_vfilter_4.yuv"; fopen_s(&outFile, outFileName, "wb"); if (!outFile) { printf("Fail to create file for output\n"); return -1; } char *graph_str = avfilter_graph_dump(filter_graph, NULL); FILE* graphFile = NULL; fopen_s(&graphFile, "graphFile.txt", "w"); // 打印filtergraph的具體情況 fprintf(graphFile, "%s", graph_str); av_free(graph_str); AVFrame *frame_in = av_frame_alloc(); unsigned char *frame_buffer_in = (unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, in_width, in_height, 1)); av_image_fill_arrays(frame_in->data, frame_in->linesize, frame_buffer_in, AV_PIX_FMT_YUV420P, in_width, in_height, 1); AVFrame *frame_out = av_frame_alloc(); unsigned char *frame_buffer_out = (unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, in_width, in_height, 1)); av_image_fill_arrays(frame_out->data, frame_out->linesize, frame_buffer_out, AV_PIX_FMT_YUV420P, in_width, in_height, 1); frame_in->width = in_width; frame_in->height = in_height; frame_in->format = AV_PIX_FMT_YUV420P; uint32_t frame_count = 0; while (1) { // 讀取yuv數據 if (fread(frame_buffer_in, 1, in_width*in_height * 3 / 2, inFile) != in_width*in_height * 3 / 2) { break; } //input Y,U,V frame_in->data[0] = frame_buffer_in; frame_in->data[1] = frame_buffer_in + in_width*in_height; frame_in->data[2] = frame_buffer_in + in_width*in_height * 5 / 4; if (av_buffersrc_add_frame(mainsrc_ctx, frame_in) < 0) { printf("Error while add frame.\n"); break; } // filter內部自己處理 /* pull filtered pictures from the filtergraph */ ret = av_buffersink_get_frame(resultsink_ctx, frame_out); if (ret < 0) break; //output Y,U,V if (frame_out->format == AV_PIX_FMT_YUV420P) { for (int i = 0; i < frame_out->height; i++) { fwrite(frame_out->data[0] + frame_out->linesize[0] * i, 1, frame_out->width, outFile); } for (int i = 0; i < frame_out->height / 2; i++) { fwrite(frame_out->data[1] + frame_out->linesize[1] * i, 1, frame_out->width / 2, outFile); } for (int i = 0; i < frame_out->height / 2; i++) { fwrite(frame_out->data[2] + frame_out->linesize[2] * i, 1, frame_out->width / 2, outFile); } } ++frame_count; if(frame_count % 25 == 0) printf("Process %d frame!\n",frame_count); av_frame_unref(frame_out); } fclose(inFile); fclose(outFile); av_frame_free(&frame_in); av_frame_free(&frame_out); avfilter_graph_free(&filter_graph); // 內部去釋放AVFilterContext產生的內存 printf("finish\n"); return 0; }