FFmpeg中avfilter模塊的介紹與使用
FFmpeg中的libavfilter模塊(或庫(kù))用于filter(過(guò)濾器), filter可以有多個(gè)輸入和多個(gè)輸出。為了說(shuō)明可能發(fā)生的事情,考慮以下filtergraph(過(guò)濾器圖):

該filtergraph將輸入流(stream)分成兩個(gè)流,然后通過(guò)crop過(guò)濾器和vflip過(guò)濾器發(fā)送一個(gè)流,然后將其與另一個(gè)流合并。 可以使用以下命令來(lái)實(shí)現(xiàn):
ffmpeg -i INPUT -vf "split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2" OUTPUT
結(jié)果是將視頻的上半部分鏡像(mirrored)到輸出視頻的下半部分:
(1).同一線性鏈(same linear chain)中的過(guò)濾器用逗號(hào)分隔,不同線性鏈中的過(guò)濾器用分號(hào)分隔。此示例中,crop、vflip位于一個(gè)線性鏈中,split和overlay分別位于另一線性鏈中。
(2).線性鏈的連接點(diǎn)用方括號(hào)內(nèi)的名稱來(lái)標(biāo)記,此示例中,split filter生成兩個(gè)與標(biāo)簽[main]和[tmp]相關(guān)聯(lián)的輸出。
(3).發(fā)送到split的第二個(gè)輸出(標(biāo)記為[tmp])的流,通過(guò)crop過(guò)濾器進(jìn)行處理,crop掉視頻的下半部分,然后垂直翻轉(zhuǎn)(vertically flipped)。
(4).overlay(覆蓋或疊加)過(guò)濾器接收split過(guò)濾器的第一個(gè)未改變的輸出(標(biāo)記為[main]),并在其下半部分overlay由crop,vflip過(guò)濾器鏈生成的輸出。
(5).某些過(guò)濾器接受輸入?yún)?shù)列表:它們?cè)谶^(guò)濾器名稱和等號(hào)之后指定,并以冒號(hào)彼此分隔。
(6).存在沒(méi)有音頻/視頻輸入的所謂源過(guò)濾器(so-called source filters),以及沒(méi)有音頻/視頻輸出的接收器(sink)過(guò)濾器。
graph2dot:FFmpeg工具目錄中包含的graph2dot程序可用于解析filtergraph描述,并用點(diǎn)語(yǔ)言(dot language)發(fā)出相應(yīng)的文本表示。
filtergraph:
(1).過(guò)濾器圖是連接過(guò)濾器的有向圖。它可以包含循環(huán),并且一對(duì)過(guò)濾器之間可以有多個(gè)鏈接(multiple links)。每個(gè)鏈接的一側(cè)都有一個(gè)輸入板(input pad),將其連接到從中獲取輸入(input)的一個(gè)過(guò)濾器,另一側(cè)有一個(gè)輸出板(output pad),將其連接到接受其輸出(output)的一個(gè)過(guò)濾器。
(2).過(guò)濾器圖中的每個(gè)過(guò)濾器都是在應(yīng)用程序中注冊(cè)的過(guò)濾器類的一個(gè)實(shí)例(an instance of a filter),它定義了過(guò)濾器的功能(features)以及輸入和輸出板的數(shù)量。
(3).沒(méi)有輸入板的過(guò)濾器稱為"source",沒(méi)有輸出板的過(guò)濾器稱為"sink"。
注:以上內(nèi)容主要來(lái)自于:http://ffmpeg.org/ffmpeg-filters.html
以下為測(cè)試代碼:將一幅圖像疊加在視頻上,類似于為視頻添加logo
1.函數(shù)open_input_file:
int open_input_file(const char* filename, AVFormatContext** fmt_ctx, AVCodecContext** dec_ctx, int& video_stream_index)
{
auto ret = avformat_open_input(fmt_ctx, filename, nullptr, nullptr);
if (ret < 0) {
av_log(nullptr, AV_LOG_ERROR, "Cannot open input file\n");
print_error_string(ret);
return ret;
}
if ((ret = avformat_find_stream_info(*fmt_ctx, nullptr)) < 0) {
av_log(nullptr, AV_LOG_ERROR, "Cannot find stream information\n");
print_error_string(ret);
return ret;
}
// select the video stream
AVCodec* dec = nullptr;
ret = av_find_best_stream(*fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &dec, 0);
if (ret < 0) {
av_log(nullptr, AV_LOG_ERROR, "Cannot find a video stream in the input file\n");
print_error_string(ret);
return ret;
}
video_stream_index = ret;
// create decoding context
*dec_ctx = avcodec_alloc_context3(dec);
if (!dec_ctx) {
ret = AVERROR(ENOMEM);
print_error_string(ret);
return ret;
}
ret = avcodec_parameters_to_context(*dec_ctx, (*fmt_ctx)->streams[video_stream_index]->codecpar);
if (ret < 0) {
print_error_string(ret);
return ret;
}
// init the video decoder
if ((ret = avcodec_open2(*dec_ctx, dec, nullptr)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot open video decoder\n");
return ret;
}
return 0;
}2.函數(shù)init_filters:
int init_filters(const char* filters_descr, const AVCodecContext* dec_ctx, const AVRational& time_base, AVFilterGraph** filter_graph, AVFilterContext** buffersink_ctx, AVFilterContext** buffersrc_ctx)
{
const AVFilter* buffersrc = avfilter_get_by_name("buffer");
const AVFilter* buffersink = avfilter_get_by_name("buffersink");
AVFilterInOut* outputs = avfilter_inout_alloc();
AVFilterInOut* inputs = avfilter_inout_alloc();
*filter_graph = avfilter_graph_alloc();
if (!outputs || !inputs || !filter_graph || !buffersrc || !buffersink) {
return AVERROR(ENOMEM);
}
// buffer video source: the decoded frames from the decoder will be inserted here.
char args[512];
snprintf(args, sizeof(args),
"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt,
time_base.num, time_base.den,
dec_ctx->sample_aspect_ratio.num, dec_ctx->sample_aspect_ratio.den);
auto ret = avfilter_graph_create_filter(buffersrc_ctx, buffersrc, "in",
args, nullptr, *filter_graph);
if (ret < 0) {
av_log(nullptr, AV_LOG_ERROR, "Cannot create buffer source\n");
print_error_string(ret);
return ret;
}
// buffer video sink: to terminate the filter chain.
ret = avfilter_graph_create_filter(buffersink_ctx, buffersink, "out",
nullptr, nullptr, *filter_graph);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot create buffer sink\n");
print_error_string(ret);
return ret;
}
enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE };
ret = av_opt_set_int_list(*buffersink_ctx, "pix_fmts", pix_fmts,
AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot set output pixel format\n");
print_error_string(ret);
return ret;
}
//Set the endpoints for the filter graph. The filter_graph will be linked to the graph described by filters_descr.
// The buffer source output must be connected to the input pad of the first filter described by filters_descr;
// since the first filter input label is not specified, it is set to "in" by default.
outputs->name = av_strdup("in");
outputs->filter_ctx = *buffersrc_ctx;
outputs->pad_idx = 0;
outputs->next = nullptr;
// The buffer sink input must be connected to the output pad of the last filter described by filters_descr;
// since the last filter output label is not specified, it is set to "out" by default.
inputs->name = av_strdup("out");
inputs->filter_ctx = *buffersink_ctx;
inputs->pad_idx = 0;
inputs->next = nullptr;
if ((ret = avfilter_graph_parse_ptr(*filter_graph, filters_descr,
&inputs, &outputs, nullptr)) < 0) {
print_error_string(ret);
return ret;
}
if ((ret = avfilter_graph_config(*filter_graph, nullptr)) < 0) {
print_error_string(ret);
return ret;
}
avfilter_inout_free(&inputs);
avfilter_inout_free(&outputs);
return ret;
}3.入口函數(shù)test_ffmpeg_libavfilter_movie:接收一個(gè)參數(shù),視頻文件;為了便于顯示,將圖像寬、高各縮小一半
int test_ffmpeg_libavfilter_movie(const char* filename)
{
AVFormatContext* fmt_ctx = nullptr;
AVCodecContext* dec_ctx = nullptr;
int video_stream_index = -1;
auto ret = open_input_file(filename, &fmt_ctx, &dec_ctx, video_stream_index);
if (ret < 0 || !fmt_ctx || !dec_ctx) {
fprintf(stderr, "fail to open_input_file: %d\n", ret);
return -1;
}
// ffmpeg.exe -i abc.mp4 -i 1.jpg -filter_complex "overlay=x=10:y=20" out.mp4
const char* filter_descr = "movie=1.jpg[logo];[in][logo]overlay=10:20[out]";
AVFilterGraph* filter_graph = nullptr;
AVFilterContext* buffersink_ctx = nullptr;
AVFilterContext* buffersrc_ctx = nullptr;
AVRational time_base = fmt_ctx->streams[video_stream_index]->time_base;
ret = init_filters(filter_descr, dec_ctx, time_base, &filter_graph, &buffersink_ctx, &buffersrc_ctx);
if (ret < 0) {
fprintf(stderr, "fail to init_filters: %d\n", ret);
return -1;
}
AVFrame* frame = av_frame_alloc();
AVFrame* filt_frame = av_frame_alloc();
if (!frame || !filt_frame) {
fprintf(stderr, "fail to av_frame_alloc\n");
return -1;
}
const int width_src = dec_ctx->width, height_src = dec_ctx->height;
const int width_dst = dec_ctx->width / 2, height_dst = dec_ctx->height / 2;
SwsContext* sws_ctx = sws_getContext(width_src, height_src, AV_PIX_FMT_YUV420P,
width_dst, height_dst, AV_PIX_FMT_BGR24, 0, nullptr, nullptr, nullptr);
if (!sws_ctx) {
fprintf(stderr, "fail to sws_getContext\n");
return -1;
}
const int stride_src[4] = { width_src, width_src / 2, width_src / 2, 0 };
const int stride_dst[4] = { width_dst * 3, 0, 0, 0 };
AVPacket packet;
bool exit = false;
cv::Mat mat(height_dst, width_dst, CV_8UC3);
const char* winname = "show video";
cv::namedWindow(winname);
// read all packets
while (1) {
if (exit) break;
if ((ret = av_read_frame(fmt_ctx, &packet)) < 0)
break;
if (packet.stream_index == video_stream_index) {
ret = avcodec_send_packet(dec_ctx, &packet);
if (ret < 0) {
av_log(nullptr, AV_LOG_ERROR, "Error while sending a packet to the decoder\n");
break;
}
while (ret >= 0) {
ret = avcodec_receive_frame(dec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
}
else if (ret < 0) {
av_log(nullptr, AV_LOG_ERROR, "Error while receiving a frame from the decoder\n");
print_error_string(ret);
return -1;
}
frame->pts = frame->best_effort_timestamp;
// push the decoded frame into the filtergraph
if ((ret = av_buffersrc_add_frame_flags(buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) {
av_log(nullptr, AV_LOG_ERROR, "Error while feeding the filtergraph\n");
print_error_string(ret);
break;
}
// pull filtered frames from the filtergraph
while (1) {
ret = av_buffersink_get_frame(buffersink_ctx, filt_frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
break;
if (ret < 0) {
fprintf(stderr, "fail to av_buffersink_get_frame: %d\n", ret);
return -1;
}
sws_scale(sws_ctx, filt_frame->data, stride_src, 0, height_src, &mat.data, stride_dst);
cv::imshow(winname, mat);
av_frame_unref(filt_frame);
int key = cv::waitKey(30);
if (key == 27) {
exit = true;
break;
}
av_frame_unref(filt_frame);
}
av_frame_unref(frame);
}
}
av_packet_unref(&packet);
}
avfilter_graph_free(&filter_graph);
avcodec_free_context(&dec_ctx);
avformat_close_input(&fmt_ctx);
av_frame_free(&frame);
av_frame_free(&filt_frame);
sws_freeContext(sws_ctx);
//if (ret < 0 && ret != AVERROR_EOF) {
// print_error_string(ret);
// return ret;
//}
return 0;
}執(zhí)行結(jié)果如下圖所示:

GitHub:https://github.com/fengbingchun/OpenCV_Test
到此這篇關(guān)于FFmpeg中avfilter模塊的介紹與使用的文章就介紹到這了,更多相關(guān)FFmpeg avfilter內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言實(shí)現(xiàn)電話簿管理系統(tǒng)課程設(shè)計(jì)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)電話簿管理系統(tǒng)課程設(shè)計(jì),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
C++ 壓縮文件及文件夾方法 使用zlib開(kāi)源庫(kù)
下面小編就為大家分享一篇C++ 壓縮文件及文件夾方法 使用zlib開(kāi)源庫(kù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-03-03
UE4 Unlua 調(diào)用異步藍(lán)圖節(jié)點(diǎn)AIMoveTo函數(shù)示例詳解
這篇文章主要為大家介紹了UE4 Unlua 調(diào)用AIMoveTo函數(shù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
C++實(shí)現(xiàn)統(tǒng)計(jì)代碼運(yùn)行時(shí)間的示例詳解
這篇文章主要為大家詳細(xì)介紹了C++一個(gè)有趣的小項(xiàng)目——統(tǒng)計(jì)代碼運(yùn)行時(shí)間,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-05-05
C++自帶的sort函數(shù)如何對(duì)vector容器元素進(jìn)行排序
這篇文章主要介紹了C++自帶的sort函數(shù)如何對(duì)vector容器元素進(jìn)行排序問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10
C語(yǔ)言 pthread_create() 函數(shù)講解
這篇文章主要介紹了C語(yǔ)言 pthread_create() 函數(shù)講解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08
C++詳解哈夫曼樹(shù)的概念與實(shí)現(xiàn)步驟
給定N個(gè)權(quán)值作為N個(gè)葉子結(jié)點(diǎn),構(gòu)造一棵二叉樹(shù),若該樹(shù)的帶權(quán)路徑長(zhǎng)度達(dá)到最小,稱這樣的二叉樹(shù)為最優(yōu)二叉樹(shù),也稱為哈夫曼樹(shù)(Huffman?Tree)。哈夫曼樹(shù)是帶權(quán)路徑長(zhǎng)度最短的樹(shù),權(quán)值較大的結(jié)點(diǎn)離根較近2022-04-04

