ffmpeg命令行是如何打開vf_scale濾鏡的

前言

在ffmpeg命令行中,ffmpeg -i test -pix_fmt rgb24 test.rgb,會自動打開ff_vf_scale濾鏡,本章主要追蹤這個流程。

通過gdb可以發現其基本調用棧如下:
在這里插入圖片描述
可以看到,query_formats()中創建的vf_scale濾鏡,
這是ffmpeg濾鏡框架中的操作,當avfilter進行query format的時候,如果發現前后兩個filter的pixformat不一致的時候就會在中間插入一個vf_scale的濾鏡。這就是標題的答案。
但是本章內容主要討論ffmpeg工具里面是如何調用avfilter的,也就是從哪里開始創建,哪里開始銷毀,以及中間是如何傳遞信息的。特別是當命令行中沒有vf_scale的操作時,ffmpeg工具是否會打開filter?

為了分析上述問題,我們先用一定使用到vf_scale的命令:

ffmpeg -i test  -pix_fmt rgb24  test.rgb

先找到調用avfilter的兩個函數:

ret = av_buffersrc_add_frame_flags(ifilter->filter, frame, buffersrc_flags);
ret = av_buffersink_get_frame_flags(filter, filtered_frame,AV_BUFFERSINK_FLAG_NO_REQUEST);

ffmpeg.h中定義了有關filter的三個結構體:

typedef struct InputFilter {AVFilterContext    *filter;struct InputStream *ist;struct FilterGraph *graph;uint8_t            *name;enum AVMediaType    type;   // AVMEDIA_TYPE_SUBTITLE for sub2videoAVFifoBuffer *frame_queue;// parameters configured for this inputint format;int width, height;AVRational sample_aspect_ratio;int sample_rate;int channels;uint64_t channel_layout;AVBufferRef *hw_frames_ctx;int32_t *displaymatrix;int eof;
} InputFilter;typedef struct OutputFilter {AVFilterContext     *filter;struct OutputStream *ost;struct FilterGraph  *graph;uint8_t             *name;/* temporary storage until stream maps are processed */AVFilterInOut       *out_tmp;enum AVMediaType     type;/* desired output stream properties */int width, height;AVRational frame_rate;int format;int sample_rate;uint64_t channel_layout;// those are only set if no format is specified and the encoder gives us multiple options// They point directly to the relevant lists of the encoder.const int *formats;const uint64_t *channel_layouts;const int *sample_rates;
} OutputFilter;typedef struct FilterGraph {int            index;const char    *graph_desc;AVFilterGraph *graph;int reconfiguration;// true when the filtergraph contains only meta filters// that do not modify the frame dataint is_meta;InputFilter   **inputs;int          nb_inputs;OutputFilter **outputs;int         nb_outputs;
} FilterGraph;

我們通過上述線索尋找buffersrc和buffersink是在哪里創建的。
首先看buffersink,通過:

decode_video(InputStream *ist, AVPacket *pkt, int *got_output, int64_t *duration_pts, int eof,int *decode_failed)decode(ist->dec_ctx, decoded_frame, got_output, pkt)send_frame_to_filters(ist, decoded_frame)ifilter_send_frame(ist->filters[i], decoded_frame, i < ist->nb_filters - 1)configure_filtergraph(fg)//reinitav_buffersrc_add_frame_flags(ifilter->filter, frame, buffersrc_flags)

通過上面結構可知其中的configure_filtergraph(fg)//reinit是關鍵,在這里初始化整個avfilter。
在這里,我們回顧一下avfilter的關鍵調用流程:

	char args[512];int ret = 0;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();AVRational time_base = fmt_ctx->streams[video_stream_index]->time_base;enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_GRAY8, AV_PIX_FMT_NONE };filter_graph = avfilter_graph_alloc();if (!outputs || !inputs || !filter_graph) {ret = AVERROR(ENOMEM);goto end;}/* buffer video source: the decoded frames from the decoder will be inserted here. */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);ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",args, NULL, filter_graph);if (ret < 0) {av_log(NULL, AV_LOG_ERROR, "Cannot create buffer source\n");goto end;}/* buffer video sink: to terminate the filter chain. */ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",NULL, NULL, filter_graph);if (ret < 0) {av_log(NULL, AV_LOG_ERROR, "Cannot create buffer sink\n");goto end;}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");goto end;}outputs->name       = av_strdup("in");outputs->filter_ctx = buffersrc_ctx;outputs->pad_idx    = 0;outputs->next       = NULL;/** 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       = NULL;if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr,&inputs, &outputs, NULL)) < 0)goto end;if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0)goto end;...

回到ifilter_send_frame()中來:


static int ifilter_send_frame(InputFilter *ifilter, AVFrame *frame, int keep_reference)
{FilterGraph *fg = ifilter->graph;AVFrameSideData *sd;int need_reinit, ret;int buffersrc_flags = AV_BUFFERSRC_FLAG_PUSH;if (keep_reference)buffersrc_flags |= AV_BUFFERSRC_FLAG_KEEP_REF;/* determine if the parameters for this input changed *///如果輸入和frame中的format不一致,就會引起reinitneed_reinit = ifilter->format != frame->format;switch (ifilter->ist->st->codecpar->codec_type) {case AVMEDIA_TYPE_VIDEO:need_reinit |= ifilter->width  != frame->width ||ifilter->height != frame->height;break;}if (!ifilter->ist->reinit_filters && fg->graph)need_reinit = 0;if (!!ifilter->hw_frames_ctx != !!frame->hw_frames_ctx ||(ifilter->hw_frames_ctx && ifilter->hw_frames_ctx->data != frame->hw_frames_ctx->data))need_reinit = 1;if (sd = av_frame_get_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX)) {if (!ifilter->displaymatrix || memcmp(sd->data, ifilter->displaymatrix, sizeof(int32_t) * 9))need_reinit = 1;} else if (ifilter->displaymatrix)need_reinit = 1;if (need_reinit) {//ifilter從這里獲取到w,h,pix等信息ret = ifilter_parameters_from_frame(ifilter, frame);if (ret < 0)return ret;}/* (re)init the graph if possible, otherwise buffer the frame and return */if (need_reinit || !fg->graph) {ret = configure_filtergraph(fg);if (ret < 0) {av_log(NULL, AV_LOG_ERROR, "Error reinitializing filters!\n");return ret;}}ret = av_buffersrc_add_frame_flags(ifilter->filter, frame, buffersrc_flags);if (ret < 0) {if (ret != AVERROR_EOF)av_log(NULL, AV_LOG_ERROR, "Error while filtering: %s\n", av_err2str(ret));return ret;}return 0;
}

繼續回到onfigure_filtergraph(fg) 刪除了與本題無關的代碼:

int configure_filtergraph(FilterGraph *fg)
{AVFilterInOut *inputs, *outputs, *cur;//這里判斷是否是simple,因為我們的命令行中沒有avfilter,故此為trueint ret, i, simple = filtergraph_is_simple(fg);//int filtergraph_is_simple(FilterGraph *fg)//{return !fg->graph_desc;}//這里其實就是NULLconst char *graph_desc = simple ? fg->outputs[0]->ost->avfilter :fg->graph_desc;cleanup_filtergraph(fg);//創建圖if (!(fg->graph = avfilter_graph_alloc()))return AVERROR(ENOMEM);if (simple) {//獲取到outputstreamOutputStream *ost = fg->outputs[0]->ost;char args[512];const AVDictionaryEntry *e = NULL;} else {fg->graph->nb_threads = filter_complex_nbthreads;}//這里在我們這里可以跳過,因為graph_desc為nullif ((ret = avfilter_graph_parse2(fg->graph, graph_desc, &inputs, &outputs)) < 0)goto fail;//這里是配置buffersrc的地方for (cur = inputs, i = 0; cur; cur = cur->next, i++){if ((ret = configure_input_filter(fg, fg->inputs[i], cur)) < 0) {avfilter_inout_free(&inputs);avfilter_inout_free(&outputs);goto fail;}}avfilter_inout_free(&inputs);//這里是配置buffersink的地方for (cur = outputs, i = 0; cur; cur = cur->next, i++)configure_output_filter(fg, fg->outputs[i], cur);avfilter_inout_free(&outputs);if (!auto_conversion_filters)avfilter_graph_set_auto_convert(fg->graph, AVFILTER_AUTO_CONVERT_NONE);//avfilter的標準調用if ((ret = avfilter_graph_config(fg->graph, NULL)) < 0)goto fail;fg->is_meta = graph_is_meta(fg->graph);return 0;
}

接下來繼續看如何配置input_filter的:

static int configure_input_video_filter(FilterGraph *fg, InputFilter *ifilter,AVFilterInOut *in)
{AVFilterContext *last_filter;//創建bufferconst AVFilter *buffer_filt = avfilter_get_by_name("buffer");const AVPixFmtDescriptor *desc;InputStream *ist = ifilter->ist;InputFile     *f = input_files[ist->file_index];AVRational tb = ist->framerate.num ? av_inv_q(ist->framerate) :ist->st->time_base;AVRational fr = ist->framerate;AVRational sar;AVBPrint args;char name[255];int ret, pad_idx = 0;int64_t tsoffset = 0;AVBufferSrcParameters *par = av_buffersrc_parameters_alloc();if (!par)return AVERROR(ENOMEM);memset(par, 0, sizeof(*par));par->format = AV_PIX_FMT_NONE;if (!fr.num)fr = av_guess_frame_rate(input_files[ist->file_index]->ctx, ist->st, NULL);sar = ifilter->sample_aspect_ratio;if(!sar.den)sar = (AVRational){0,1};av_bprint_init(&args, 0, AV_BPRINT_SIZE_AUTOMATIC);//這里是配置buffersrc的地方av_bprintf(&args,"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:""pixel_aspect=%d/%d",ifilter->width, ifilter->height, ifilter->format,tb.num, tb.den, sar.num, sar.den);if (fr.num && fr.den)av_bprintf(&args, ":frame_rate=%d/%d", fr.num, fr.den);snprintf(name, sizeof(name), "graph %d input from stream %d:%d", fg->index,ist->file_index, ist->st->index);//創建filterctxif ((ret = avfilter_graph_create_filter(&ifilter->filter, buffer_filt, name,args.str, NULL, fg->graph)) < 0)goto fail;par->hw_frames_ctx = ifilter->hw_frames_ctx;ret = av_buffersrc_parameters_set(ifilter->filter, par);if (ret < 0)goto fail;av_freep(&par);last_filter = ifilter->filter;desc = av_pix_fmt_desc_get(ifilter->format);av_assert0(desc);snprintf(name, sizeof(name), "trim_in_%d_%d",ist->file_index, ist->st->index);if (copy_ts) {tsoffset = f->start_time == AV_NOPTS_VALUE ? 0 : f->start_time;if (!start_at_zero && f->ctx->start_time != AV_NOPTS_VALUE)tsoffset += f->ctx->start_time;}//插入trimret = insert_trim(((f->start_time == AV_NOPTS_VALUE) || !f->accurate_seek) ?AV_NOPTS_VALUE : tsoffset, f->recording_time,&last_filter, &pad_idx, name);if (ret < 0)return ret;//鏈接if ((ret = avfilter_link(last_filter, 0, in->filter_ctx, in->pad_idx)) < 0)return ret;return 0;
fail:av_freep(&par);return ret;
}

下面是configure_output_filter

static int configure_output_video_filter(FilterGraph *fg, OutputFilter *ofilter, AVFilterInOut *out)
{OutputStream *ost = ofilter->ost;OutputFile    *of = output_files[ost->file_index];AVFilterContext *last_filter = out->filter_ctx;AVBPrint bprint;int pad_idx = out->pad_idx;int ret;const char *pix_fmts;char name[255];snprintf(name, sizeof(name), "out_%d_%d", ost->file_index, ost->index);//創建buffersinkret = avfilter_graph_create_filter(&ofilter->filter,avfilter_get_by_name("buffersink"),name, NULL, NULL, fg->graph);if (ret < 0)return ret;//這個scale完全就是尺寸的resizeif ((ofilter->width || ofilter->height) && ofilter->ost->autoscale) {char args[255];AVFilterContext *filter;const AVDictionaryEntry *e = NULL;//這里只有size的scale,并沒有顏色空間的轉換snprintf(args, sizeof(args), "%d:%d",ofilter->width, ofilter->height);while ((e = av_dict_get(ost->sws_dict, "", e,AV_DICT_IGNORE_SUFFIX))) {av_strlcatf(args, sizeof(args), ":%s=%s", e->key, e->value);}snprintf(name, sizeof(name), "scaler_out_%d_%d",ost->file_index, ost->index);if ((ret = avfilter_graph_create_filter(&filter, avfilter_get_by_name("scale"),name, args, NULL, fg->graph)) < 0)return ret;if ((ret = avfilter_link(last_filter, pad_idx, filter, 0)) < 0)return ret;last_filter = filter;pad_idx = 0;}av_bprint_init(&bprint, 0, AV_BPRINT_SIZE_UNLIMITED);//如果設置了輸出的pix_fmt 那么就會在這里增加一個format的的avfilter//這個format其實什么也不做,就是指定一個中間format,用來在協商的時候//確定是否增加中間的csc swscaleif ((pix_fmts = choose_pix_fmts(ofilter, &bprint))) {AVFilterContext *filter;ret = avfilter_graph_create_filter(&filter,avfilter_get_by_name("format"),"format", pix_fmts, NULL, fg->graph);av_bprint_finalize(&bprint, NULL);if (ret < 0)return ret;if ((ret = avfilter_link(last_filter, pad_idx, filter, 0)) < 0)return ret;last_filter = filter;pad_idx     = 0;}if (ost->frame_rate.num && 0) {AVFilterContext *fps;char args[255];snprintf(args, sizeof(args), "fps=%d/%d", ost->frame_rate.num,ost->frame_rate.den);snprintf(name, sizeof(name), "fps_out_%d_%d",ost->file_index, ost->index);ret = avfilter_graph_create_filter(&fps, avfilter_get_by_name("fps"),name, args, NULL, fg->graph);if (ret < 0)return ret;ret = avfilter_link(last_filter, pad_idx, fps, 0);if (ret < 0)return ret;last_filter = fps;pad_idx = 0;}snprintf(name, sizeof(name), "trim_out_%d_%d",ost->file_index, ost->index);ret = insert_trim(of->start_time, of->recording_time,&last_filter, &pad_idx, name);if (ret < 0)return ret;if ((ret = avfilter_link(last_filter, pad_idx, ofilter->filter, 0)) < 0)return ret;return 0;
}

所以對于ffmpeg中的graph來說,依次為:
ff_vsrc_buffer->ff_vf_null->(scale resize)->ff_vf_format(命令行中由format的參數)->(fps )->(trim)->ff_buffersink。
中間括號內的是幾個選擇的avfilter。
協商format的時候最后會協商一個ff_vsrc_buffer的pix fomat.

最后用一個rawenc去將frame編碼為pkt…

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/37091.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/37091.shtml
英文地址,請注明出處:http://en.pswp.cn/news/37091.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Unity框架學習--2

接上文 IOC 容器是一個很方便的模塊管理工具。 除了可以用來注冊和獲取模塊&#xff0c;IOC 容器一般還會有一個隱藏的功能&#xff0c;即&#xff1a; 注冊接口模塊 抽象-實現 這種形式注冊和獲取對象的方式是符合依賴倒置原則的。 依賴倒置原則&#xff08;Dependence I…

maven install

maven install maven 的 install 命令&#xff0c;當我們的一個 maven 模塊想要依賴其他目錄下的模塊時&#xff0c;直接添加會找不到對應的模塊&#xff0c;只需要找到需要引入的模塊&#xff0c;執行 install 命令&#xff0c;就會將該模塊放入本地倉庫&#xff0c;就可以進…

Linux tar包安裝 Prometheus 和 Grafana(知識點:systemd Unit/重定向)

0. 介紹 用tar包的方式安裝 Prometheus 和 Grafana Prometheus:開源的監控方案Grafana:將Prometheus的數據可視化平臺 Prometheus已經有了查詢功能為什么還需要grafana呢?Prometheus基于promQL這一SQL方言,有一定門檻!Grafana基于瀏覽器的操作與可視化圖表大大降低了理解難…

Vue3 setup tsx 子組件向父組件傳值 emit

需求&#xff1a;Vue3 setup 父組件向子組件傳值&#xff0c;子組件接收父組件傳入的值&#xff1b;子組件向父組件傳值&#xff0c;父組件接收的子組件傳遞的值。 父組件&#xff1a;parent.tsx&#xff1a; import { defineComponent, ref, reactive } from vue; import To…

【Android】okhttp爆java.lang.IllegalStateException: closed的解決方法

解決 java.lang.IllegalStateException: closed異常通常是由于OkHttp中的Response對象在調用response.body().string()后被關閉而導致的。 在代碼中&#xff0c;在onResponse()方法中如果兩次調用了response.body().string()&#xff0c;每次調用都會消耗掉響應體并關閉Respo…

如何優化PHP Smarty模板的性能?

Smarty模板是一種非常強大的模板引擎&#xff0c;但是如果不正確地使用&#xff0c;可能會導致你的網站慢得像一只樹懶&#xff01; 那么&#xff0c;如何優化Smarty模板的性能呢&#xff1f; 減少Smarty對象的創建 你可能會在代碼中多次創建Smarty對象。但是&#xff0c;每次…

Server - 文字轉語音 (Text to Speech) 的在線服務 TTSMaker

歡迎關注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/132287193 TTSMaker 是一款免費的文本轉語音工具&#xff0c;提供語音合成服務&#xff0c;支持多種語言&#xff0c;包括英語、法語、德語、西班…

什么是冒煙測試?

冒煙測試&#xff0c;剛進公司就接觸到了。只是剛開始一直沒有體會到冒煙的含義和精髓&#xff0c;一直以為是冒煙測試就是把待測產品的主要功能測試一下就行了。后面回想一下&#xff0c;不是那么回事的。 冒煙測試源自硬件行業&#xff0c;對一個硬件或者硬件組件改動后&…

Exams/ece241 2013 q4

蓄水池問題 S3 S2 S1 例如&#xff1a;000 代表 無水 &#xff0c;需要使FR3, FR2, FR1 都打開&#xff08;111&#xff09; S3 S2 S1 FR3 FR2 FR1 000 111 001 011 011 001 111 000 fr代表水變深為…

快手商品詳情數據API 抓取快手商品價格、銷量、庫存、sku信息

快手商品詳情數據API是用來獲取快手商品詳情頁數據的接口&#xff0c;請求參數為商品ID&#xff0c;這是每個商品唯一性的標識。返回參數有商品標題、商品標題、商品簡介、價格、掌柜昵稱、庫存、寶貝鏈接、寶貝圖片、商品SKU等。 接口名稱&#xff1a;item_get 公共參數 名…

【PostgreSQL的CLOG解析】

同樣還是這張圖&#xff0c;之前發過shared_buffer和os cache、wal buffer和work mem的文章&#xff0c;今天的主題是圖中的clog&#xff0c;即 commit log&#xff0c;PostgreSQL10之前放在數據庫目錄的pg_clog下面。PostgreSQL10之后修更名為xact,數據目錄變更為pg_xact下面&…

WPF 本地化的最佳做法

WPF 本地化的最佳做法 資源文件英文資源文件 en-US.xaml中文資源文件 zh-CN.xaml 資源使用App.xaml主界面布局cs代碼 App.config輔助類語言切換操作類資源 binding 解析類 實現效果 應用程序本地化有很多種方式&#xff0c;選擇合適的才是最好的。這里只討論一種方式&#xff0…

pytorch單機多卡后臺運行

nohup sh ./train_chat.sh > train_chat20230814.log 2>1&參考資料 Pytorch單機多卡后臺運行的解決辦法

kafka-2.12使用記錄

kafka-2.12使用記錄 安裝kafka 2.12版本 下載安裝包 根據你的系統下載rpm /deb /zip包等等, 這里我使用的是rpm包 安裝命令 rpm -ivh kafka-2.12-1.nfs.x86_64.rpm啟動內置Zookeeper 以下命令要寫在同一行上 /opt/kafka-2.12/bin/zookeeper-server-start.sh /opt/kafka-2…

實驗二十八、三角波發生電路參數的確認

一、題目 利用 Multisim 確定圖1所示電路中各元件的參數&#xff0c;使輸出電壓的頻率為 500 Hz 500\,\textrm{Hz} 500Hz、幅值為 6 V 6\,\textrm{V} 6V 的三角波。 圖 1 三角波發生電路 圖1\,\,三角波發生電路 圖1三角波發生電路 2、仿真電路 A 1 \textrm A_1 A1? 采用…

深入解析 MyBatis 中的 lt;foreachgt; 標簽:優雅處理批量操作與動態 SQL

在當今的Java應用程序開發中&#xff0c;數據庫操作是一個不可或缺的部分。MyBatis作為一款頗受歡迎的持久層框架&#xff0c;為我們提供了一種優雅而高效的方式來管理數據庫操作。在MyBatis的眾多特性中&#xff0c;<foreach>標簽無疑是一個強大的工具&#xff0c;它使得…

sift-1M數據集的讀取及ES插入數據

sift是檢查ann近鄰召回率的標準數據集,ann可以選擇faiss,milvus等庫或者方法;sift數據分為query和base,以及label(groundtruth)數據。本文采用sift-1M進行解讀,且看如下: 1、sift-1m數據集 官方鏈接地址:Evaluation of Approximate nearest neighbors: large datase…

Java:簡單算法:冒泡排序、選擇排序、二分查找

冒泡排序 // 1、準備一個數組 int[] arr {5&#xff0c;2&#xff0c;3&#xff0c;1};//2、定義一個循環控制排幾輪 for (int i 0; i < arr.length - 1; i) { // i 0 1 2 【5&#xff0c;2&#xff0c;3&#xff0c;1】 次數 // i 0 第一輪 0 1 2 …

P4377 [USACO18OPEN] Talent Show G

P4377 [USACO18OPEN] Talent Show G [P4377 USACO18OPEN] Talent Show G - 洛谷 | 計算機科學教育新生態 (luogu.com.cn) 文章目錄 P4377 [USACO18OPEN] Talent Show G題目題目描述輸入格式輸出格式樣例 #1樣例輸入 #1樣例輸出 #1 提示樣例解釋數據規模與約定 思路code 題目 …

onlyoffice

一、功能描述 技術實現 選型&#xff1a;minio 文件存儲服務器 onlyoffice 文檔編輯器 選項特征&#xff1a;免費&#xff0c;支持私有化部署&#xff0c;不依賴第三方網絡接口 Podman是RedHat開發的一個用戶友好的容器調度器&#xff0c;是一種開源的Linux原生工具&#x…