From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by master.gitmailbox.com (Postfix) with ESMTPS id 5CBA54D0D7 for ; Mon, 17 Feb 2025 12:17:54 +0000 (UTC) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id E59B368BE8A; Mon, 17 Feb 2025 14:17:50 +0200 (EET) Received: from haasn.dev (haasn.dev [78.46.187.166]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 1396D68B6F8 for ; Mon, 17 Feb 2025 14:17:44 +0200 (EET) Received: from haasn.dev (unknown [10.30.1.1]) by haasn.dev (Postfix) with ESMTP id C5B8940BFD; Mon, 17 Feb 2025 13:17:43 +0100 (CET) From: Niklas Haas To: ffmpeg-devel@ffmpeg.org Date: Mon, 17 Feb 2025 13:17:41 +0100 Message-ID: <20250217121741.56025-1-ffmpeg@haasn.xyz> X-Mailer: git-send-email 2.47.0 MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH] fftools/ffmpeg_filter: add -print_filter_graph option X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Niklas Haas Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Archived-At: List-Archive: List-Post: From: Niklas Haas This developer tool is especially handy when debugging filter graph auto-negotiation, although it can be useful in whatever scenario to get a canonical dump of the fully settled filter graph. To make the result slightly more useful, we omit buffersrc/buffersink filters and instead print the corresponding input/output name. Sadly, this is lossy w.r.t. the link names used in the original filter graph, although the result has the advantage of being in a normalized format. As an example, the following filter graph (taken from FATE): sws_flags=+accurate_rnd+bitexact; split [main][over]; [over] scale=88:72, pad=96:80:4:4 [overf]; [main][overf] overlay=240:16:format=yuv422 Results in this output: Filter graph: [0:0] split=thread_type=0x00000000 [L0] [L1]; [L1] scale=w=88:width=88:h=72:height=72:flags=+accurate_rnd+bitexact:thread_type=0x00000000 [L2]; [L2] pad=width=96:w=96:height=80:h=80:x=4:y=4:thread_type=0x00000000 [L3]; [L4] [L3] overlay=x=240:y=16:format=2 [#0:0]; [L0] scale=w=iw:width=iw:h=ih:height=ih:flags=+accurate_rnd+bitexact:thread_type=0x00000000 [L4]; Filter links: L0: yuv420p 352x288 [SAR 0:1] csp:unknown range:tv L1: yuv420p 352x288 [SAR 0:1] csp:unknown range:tv L2: yuva422p 88x72 [SAR 0:1] csp:unknown range:tv L3: yuva422p 96x80 [SAR 0:1] csp:unknown range:tv L4: yuva422p 352x288 [SAR 0:1] csp:unknown range:tv I do acknowledge the overlap between this and avfilter/graphdump.c, but there are a couple of important deviations: 1. graphdump.c prints a "pretty printed" ASCII art graph for human consumption, but the goal here is to print it in a format that can be passed back to -filter_complex. 2. graphdump.c does not know anything about buffersrc/buffersink filters or input/output names, which is why this implementation has to live inside ffmpeg_filter.c. It's possible that we could instead move this implementation to graphdump.c as well, though that would require some sort of explicit callback to get the names for buffer sources / sinks, to avoid breaking the first design goal. --- doc/ffmpeg.texi | 6 ++ fftools/ffmpeg.h | 1 + fftools/ffmpeg_filter.c | 132 ++++++++++++++++++++++++++++++++++++++++ fftools/ffmpeg_opt.c | 4 ++ 4 files changed, 143 insertions(+) diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi index da6549f043..4c5e9dc146 100644 --- a/doc/ffmpeg.texi +++ b/doc/ffmpeg.texi @@ -2768,6 +2768,12 @@ filter (scale, aresample) in the graph. On by default, to explicitly disable it you need to specify @code{-noauto_conversion_filters}. +@item -print_filter_graph +Print out the fully settled filter graph, after all automatic conversion +and format restriction filters have been inserted, in a format suitable for +passing back into @code{-filter_complex}. The exact formatting is subject to +change. + @item -bits_per_raw_sample[:@var{stream_specifier}] @var{value} (@emph{output,per-stream}) Declare the number of bits per raw sample in the given output stream to be @var{value}. Note that this option sets the information provided to the diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h index 9439be0f41..83cedfd1d9 100644 --- a/fftools/ffmpeg.h +++ b/fftools/ffmpeg.h @@ -715,6 +715,7 @@ extern char *filter_nbthreads; extern int filter_complex_nbthreads; extern int vstats_version; extern int auto_conversion_filters; +extern int print_filter_graph; extern const AVIOInterruptCB int_cb; diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c index a0d04fd76f..63efdb5d30 100644 --- a/fftools/ffmpeg_filter.c +++ b/fftools/ffmpeg_filter.c @@ -1896,6 +1896,13 @@ static int filter_is_buffersrc(const AVFilterContext *f) !strcmp(f->filter->name, "abuffer")); } +static int filter_is_buffersink(const AVFilterContext *f) +{ + return f->nb_outputs == 0 && + (!strcmp(f->filter->name, "buffersink") || + !strcmp(f->filter->name, "abuffersink")); +} + static int graph_is_meta(AVFilterGraph *graph) { for (unsigned i = 0; i < graph->nb_filters; i++) { @@ -1913,6 +1920,125 @@ static int graph_is_meta(AVFilterGraph *graph) return 1; } +static int get_link_id(AVFilterLink ***links, int *nb_links, AVFilterLink *link) +{ + int ret; + for (int i = 0; i < *nb_links; i++) { + if ((*links)[i] == link) + return i; + } + + ret = av_dynarray_add_nofree(links, nb_links, link); + return ret ? ret : *nb_links - 1; +} + +static int print_complex_filter_graph(FilterGraph *fg, FilterGraphThread *fgt) +{ + const AVFilterGraph *graph = fgt->graph; + + /* Keep track of seen filter links to assign a unique ID to each */ + AVFilterLink **links = NULL; + int nb_links = 0; + int ret = AVERROR(ENOMEM); + char *opts = NULL; + + AVBPrint buf; + av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC); + + for (int i = 0; i < graph->nb_filters; i++) { + AVFilterContext *filter = graph->filters[i]; + if (i == 0) + av_bprintf(&buf, "Filter graph:\n"); + if (filter_is_buffersrc(filter) || filter_is_buffersink(filter)) + continue; + + ret = av_opt_serialize(filter, AV_OPT_FLAG_FILTERING_PARAM, + AV_OPT_SERIALIZE_SKIP_DEFAULTS | + AV_OPT_SERIALIZE_SEARCH_CHILDREN, &opts, '=', ':'); + if (ret < 0) + goto fail; + + av_bprintf(&buf, " "); + for (int j = 0; j < filter->nb_inputs; j++) { + AVFilterLink *link = filter->inputs[j]; + if (filter_is_buffersrc(link->src)) { + for (int k = 0; k < fg->nb_inputs; k++) { + InputFilterPriv *ifp = ifp_from_ifilter(fg->inputs[k]); + if (ifp->filter != link->src) + continue; + av_bprintf(&buf, "[%s] ", ifp->opts.name); + break; + } + } else { + ret = get_link_id(&links, &nb_links, link); + if (ret < 0) + goto fail; + av_bprintf(&buf, "[L%d] ", ret); + } + } + + av_bprintf(&buf, "%s=%s", filter->filter->name, opts); + av_freep(&opts); + + for (int j = 0; j < filter->nb_outputs; j++) { + AVFilterLink *link = filter->outputs[j]; + if (filter_is_buffersink(link->dst)) { + for (int k = 0; k < fg->nb_outputs; k++) { + OutputFilterPriv *ofp = ofp_from_ofilter(fg->outputs[k]); + if (ofp->filter == link->dst) { + av_bprintf(&buf, " [%s]", ofp->name); + break; + } + } + } else { + ret = get_link_id(&links, &nb_links, link); + if (ret < 0) + goto fail; + av_bprintf(&buf, " [L%d]", ret); + } + } + av_bprintf(&buf, ";\n"); + } + + /* Dump a summary of all seen links */ + for (int i = 0; i < nb_links; i++) { + AVFilterLink *link = links[i]; + if (i == 0) + av_bprintf(&buf, "Filter links:\n"); + switch (link->type) { + case AVMEDIA_TYPE_VIDEO: + av_bprintf(&buf, " L%d: %s %dx%d [SAR %d:%d] csp:%s range:%s\n", + i, av_get_pix_fmt_name(link->format), link->w, link->h, + link->sample_aspect_ratio.num, link->sample_aspect_ratio.den, + av_color_space_name(link->colorspace), + av_color_range_name(link->color_range)); + break; + case AVMEDIA_TYPE_AUDIO: + av_bprintf(&buf, " L%d: %s %dHz ", + i, av_get_sample_fmt_name(link->format), link->sample_rate); + av_channel_layout_describe_bprint(&link->ch_layout, &buf); + av_bprintf(&buf, "\n"); + break; + default: continue; + } + } + + if (!av_bprint_is_complete(&buf)) { + ret = AVERROR(ENOMEM); + goto fail; + } + + if (buf.len) + av_log(NULL, AV_LOG_INFO, "%s", buf.str); + + ret = 0; +fail: + av_bprint_finalize(&buf, NULL); + av_free(links); + av_free(opts); + return ret; +} + static int sub2video_frame(InputFilter *ifilter, AVFrame *frame, int buffer); static int configure_filtergraph(FilterGraph *fg, FilterGraphThread *fgt) @@ -1990,6 +2116,12 @@ static int configure_filtergraph(FilterGraph *fg, FilterGraphThread *fgt) if ((ret = avfilter_graph_config(fgt->graph, NULL)) < 0) goto fail; + /* Print the generated filter graph after insertion of auto filters */ + if (print_filter_graph) { + if ((ret = print_complex_filter_graph(fg, fgt)) < 0) + goto fail; + } + fgp->is_meta = graph_is_meta(fgt->graph); /* limit the lists of allowed formats to the ones selected, to diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c index 3c0c682594..8795cb1107 100644 --- a/fftools/ffmpeg_opt.c +++ b/fftools/ffmpeg_opt.c @@ -76,6 +76,7 @@ char *filter_nbthreads; int filter_complex_nbthreads = 0; int vstats_version = 2; int auto_conversion_filters = 1; +int print_filter_graph = 0; int64_t stats_period = 500000; @@ -1733,6 +1734,9 @@ const OptionDef options[] = { { "auto_conversion_filters", OPT_TYPE_BOOL, OPT_EXPERT, { &auto_conversion_filters }, "enable automatic conversion filters globally" }, + { "print_filter_graph", OPT_TYPE_BOOL, 0, + { &print_filter_graph }, + "dump complex filter graph after insertion of auto-filters" }, { "stats", OPT_TYPE_BOOL, 0, { &print_stats }, "print progress report during encoding", }, -- 2.47.0 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".