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.hqwc.cn/news/62600.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【遍历】非递归法 二叉树的前中后序遍历

文章目录 非递归法前序遍历后序遍历中序遍历 递归法DFS 非递归法 通过栈Stack来模拟递归。 前序遍历 LeetCode 144 前序遍历&#xff1a;1 2 3 定义&#xff1a;存放答案的List、栈Stack 将root入栈出栈&#xff1a;node&#xff0c;为null则舍弃将node放入list将node.r…

安全测试中常见的业务安全问题

“在测试过程中&#xff0c;特殊的操作往往容易触发异常场景&#xff0c;而这些异常场景也很容易引起安全问题&#xff01;” 常见的安全漏洞就不多说了&#xff0c;这里主要介绍常见的业务安全问题及修复建议。 01 刷短信 问题描述&#xff1a; 当发送短信的请求接口只需要…

股票自动交易接口开发原理及源码分享

股票自动交易接口的开发原理涉及多个方面&#xff0c;主要包括以下几个步骤&#xff1a; 1. 数据接口获取&#xff1a;通过连接到证券交易所或第三方数据提供商的API&#xff0c;获取实时市场数据&#xff0c;包括股票报价、交易成交量、买卖盘口等信息。 2. 策略定义&#x…

田间农业数字管理系统-高标准农田建设

政策背景 2019年11月&#xff0c;国务院办公厅印发的《国务院办公厅关于切实加强高标准农田建设提升粮食安全保障能力的意见》明确提出&#xff0c;到2022年&#xff0c;全国要建成10亿亩高标准农田。 2021年9月16日&#xff0c;由农业农村部印发的《全国高标准农田建设规划&a…

Ajax 笔记(二)—— Ajax 案例

笔记目录 2. Ajax 综合案例2.1 案例一-图书管理2.1.1 渲染列表2.1.2 新增图书2.1.3 删除图书2.1.4 编辑图书 2.2 案例二-背景图的上传和更换2.2.1 上传2.2.2 更换 2.3 案例三-个人信息设置2.3.1 信息渲染2.3.2 头像修改2.2.3 信息修改2.3.4 提示框 Ajax 笔记&#xff1a; Ajax…

创新零售,京东重新答题?

继新一轮组织架构调整后&#xff0c;京东从低价到下沉动作不断。 新成立的创新零售部在京东老将闫小兵的带领下悄然完成了整合。近日&#xff0c;京喜拼拼已改名为京东拼拼&#xff0c;与七鲜、前置仓等业务共同承载起京东线上线下加速融合的梦想。 同时&#xff0c;拼拼的更…

JVM系统优化实践(24):ZGC(一)

您好&#xff0c;这里是「码农镖局」CSDN博客&#xff0c;欢迎您来&#xff0c;欢迎您再来&#xff5e; 截止到目前&#xff0c;算上ZGC&#xff0c;Java一共有九种类型的GC&#xff0c;它们分别是&#xff1a; 1、Serial GC 串行/作用于新生代/复制算法/响应速度优先/适用于单…

安科瑞物联网表在虚拟电厂的应用

安科瑞 崔丽洁 应用场景 一般应用于控制中心 功能 能计量当前组合有功电能&#xff0c;正向有功电能&#xff0c;反向有功电能&#xff0c;正向无功电能&#xff0c;反向无功电能&#xff1b; ADW300支持RS485通讯、LORA通讯、NB、4G及Wifi通讯&#xff1b; 三套时段表,一年可以…

【Gitee的使用】Gitee的简单使用,查看/创建SSH公匙、创建版本库、拉取代码、提交代码

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 本篇文章简单介绍&#xff0c;如何在Gitee上面创建版本库、拉取…

IDEA新建类时自动设置类注释信息,署名和日期

IDEA设置路径 File --> Settings --> Editor --> File and Code Templates --> Include --> File Header 官方模板 这里 ${USER} 会读取计算机的用户名 ${DATE}是日期 ${TIME}是时间 /*** Author ${USER}* Date ${DATE} ${TIME}* Version 1.0*/

利用 Splashtop Enterprise 改善公司的网络安全

在我们日益数字化的世界中&#xff0c;对强有力的网络安全措施的需求从未像现在这样迫切。随着组织扩大其数字足迹并采用远程办公解决方案&#xff0c;他们面临着一系列不断变化的挑战。 威胁行为者不断寻找利用漏洞的新方法&#xff0c;这使得企业保持领先地位至关重要。俗话…

Rest 优雅的url请求处理风格及注意事项

&#x1f600;前言 本篇博文是关于Rest 风格请求的应用和注意事项&#xff0c;希望能够帮助到您&#x1f60a; &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以帮助到大家&#xff0c;您…