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ǔ)言不定長(zhǎng)數(shù)組及初始化方法
今天小編就為大家分享一篇C語(yǔ)言不定長(zhǎng)數(shù)組及初始化方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-07-07基于C++的攝像頭圖像采集及拼接程序的簡(jiǎn)單實(shí)現(xiàn)
本程序是在?ubuntu14.04?平臺(tái)下實(shí)現(xiàn)的,在本項(xiàng)目目錄下,已經(jīng)有編譯生成的可執(zhí)行程序,其中Camera_to_Frmae.cpp是我們從雙攝像頭實(shí)時(shí)抓取單幀圖像的源碼,對(duì)基于C++的攝像頭圖像采集及拼接程序的實(shí)現(xiàn)感興趣的朋友一起看看吧2022-01-01結(jié)合C++11新特性來(lái)學(xué)習(xí)C++中l(wèi)ambda表達(dá)式的用法
這篇文章主要介紹了C++中l(wèi)ambda表達(dá)式的用法,lambda表達(dá)式的引入可謂是C++11中的一大亮點(diǎn),同時(shí)文中也涉及到了C++14標(biāo)準(zhǔn)中關(guān)于lambda的一些內(nèi)容,需要的朋友可以參考下2016-01-01c++將字符串轉(zhuǎn)數(shù)字的實(shí)例方法
在本篇文章里小編給大家整理的是關(guān)于c++將字符串轉(zhuǎn)數(shù)字的實(shí)例方法,有需要的朋友們可以參考下。2020-02-02c++實(shí)現(xiàn)reactor高并發(fā)服務(wù)器的詳細(xì)教程
這篇文章主要介紹了c++從零實(shí)現(xiàn)reactor高并發(fā)服務(wù)器,包括環(huán)境準(zhǔn)備和基礎(chǔ)知識(shí)介紹,本文給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-03-03一篇文章帶你了解C++(STL基礎(chǔ)、Vector)
這篇文章主要為大家詳細(xì)介紹了C++ STL基礎(chǔ),vector向量容器使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能給你帶來(lái)幫助2021-08-08解析C/C++指針、函數(shù)、結(jié)構(gòu)體、共用體
這篇文章主要介紹了C/C++指針、函數(shù)、結(jié)構(gòu)體、共用體的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-01-01C語(yǔ)言實(shí)現(xiàn)繪制可愛(ài)的橘子鐘表
這篇文章主要為大家詳細(xì)介紹了如何利用C語(yǔ)言實(shí)現(xiàn)繪制可愛(ài)的橘子鐘表,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的可以了解一下2022-12-12