Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
From: Anton Khirnov <anton@khirnov.net>
To: ffmpeg-devel@ffmpeg.org
Subject: [FFmpeg-devel] [PATCH 30/31] fftools/ffmpeg_filter: implement filtergraph chaining
Date: Fri,  5 Apr 2024 18:12:11 +0200
Message-ID: <20240405161212.26167-30-anton@khirnov.net> (raw)
In-Reply-To: <20240405161212.26167-1-anton@khirnov.net>

This allows one complex filtergraph's output to be sent as input to
another one, which is useful in certain situations (one is described in
the docs).

Chaining filtergraphs was already effectively possible by using a
wrapped_avframe encoder connected to a loopback decoder, but it is ugly,
non-obvious and inefficient.
---
 Changelog               |  1 +
 doc/ffmpeg.texi         | 58 ++++++++++++++++++++++++---
 fftools/ffmpeg_filter.c | 89 +++++++++++++++++++++++++++++++++++++++--
 3 files changed, 140 insertions(+), 8 deletions(-)

diff --git a/Changelog b/Changelog
index 18e83b99a1..b7a1af4083 100644
--- a/Changelog
+++ b/Changelog
@@ -4,6 +4,7 @@ releases are sorted from youngest to oldest.
 version <next>:
 - Raw Captions with Time (RCWT) closed caption demuxer
 - LC3/LC3plus decoding/encoding using external library liblc3
+- ffmpeg CLI filtergraph chaining
 
 
 version 7.0:
diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
index 801c083705..9bd548ce4e 100644
--- a/doc/ffmpeg.texi
+++ b/doc/ffmpeg.texi
@@ -2145,14 +2145,62 @@ type -- see the @option{-filter} options. @var{filtergraph} is a description of
 the filtergraph, as described in the ``Filtergraph syntax'' section of the
 ffmpeg-filters manual.
 
-Input link labels must refer to either input streams or loopback decoders. For
-input streams, use the @code{[file_index:stream_specifier]} syntax (i.e. the
-same as @option{-map} uses). If @var{stream_specifier} matches multiple streams,
-the first one will be used.
+Inputs to a complex filtergraph may come from different source types,
+distinguished by the format of the corresponding link label:
+@itemize
+@item
+To connect an input stream, use @code{[file_index:stream_specifier]} (i.e. the
+same syntax as @option{-map}). If @var{stream_specifier} matches multiple
+streams, the first one will be used.
 
-For decoders, the link label must be [dec:@var{dec_idx}], where @var{dec_idx} is
+@item
+To connect a loopback decoder use [dec:@var{dec_idx}], where @var{dec_idx} is
 the index of the loopback decoder to be connected to given input.
 
+@item
+To connect an output from another complex filtergraph, use its link label. E.g
+the following example:
+
+@example
+ffmpeg -i input.mkv \
+  -filter_complex '[0:v]scale=size=hd1080,split=outputs=2[for_enc][orig_scaled]' \
+  -c:v libx264 -map '[for_enc]' output.mkv \
+  -dec 0:0 \
+  -filter_complex '[dec:0][orig_scaled]hstack[stacked]' \
+  -map '[stacked]' -c:v ffv1 comparison.mkv
+@end example
+
+reads an input video and
+@itemize
+@item
+(line 2) uses a complex filtergraph with one input and two outputs
+to scale the video to 1920x1080 and duplicate the result to both
+outputs;
+
+@item
+(line 3) encodes one scaled output with @code{libx264} and writes the result to
+@file{output.mkv};
+
+@item
+(line 4) decodes this encoded stream with a loopback decoder;
+
+@item
+(line 5) places the output of the loopback decoder (i.e. the
+@code{libx264}-encoded video) side by side with the scaled original input;
+
+@item
+(line 6) combined video is then losslessly encoded and written into
+@file{comparison.mkv}.
+
+@end itemize
+
+Note that the two filtergraphs cannot be combined into one, because then there
+would be a cycle in the transcoding pipeline (filtergraph output goes to
+encoding, from there to decoding, then back to the same graph), and such cycles
+are not allowed.
+
+@end itemize
+
 An unlabeled input will be connected to the first unused input stream of the
 matching type.
 
diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index 1e14962f41..f108f8daf9 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -902,6 +902,63 @@ int ofilter_bind_ost(OutputFilter *ofilter, OutputStream *ost,
     return 0;
 }
 
+static int ofilter_bind_ifilter(OutputFilter *ofilter, InputFilterPriv *ifp,
+                                const OutputFilterOptions *opts)
+{
+    OutputFilterPriv *ofp = ofp_from_ofilter(ofilter);
+
+    av_assert0(!ofilter->bound);
+    av_assert0(ofilter->type == ifp->type);
+
+    ofilter->bound = 1;
+    av_freep(&ofilter->linklabel);
+
+    ofp->name = av_strdup(opts->name);
+    if (!ofp->name)
+        return AVERROR(EINVAL);
+
+    av_strlcatf(ofp->log_name, sizeof(ofp->log_name), "->%s", ofp->name);
+
+    return 0;
+}
+
+static int ifilter_bind_fg(InputFilterPriv *ifp, FilterGraph *fg_src, int out_idx)
+{
+    FilterGraphPriv      *fgp = fgp_from_fg(ifp->ifilter.graph);
+    OutputFilter *ofilter_src = fg_src->outputs[out_idx];
+    OutputFilterOptions opts;
+    char name[32];
+    int ret;
+
+    av_assert0(!ifp->bound);
+    ifp->bound = 1;
+
+    if (ifp->type != ofilter_src->type) {
+        av_log(fgp, AV_LOG_ERROR, "Tried to connect %s output to %s input\n",
+               av_get_media_type_string(ofilter_src->type),
+               av_get_media_type_string(ifp->type));
+        return AVERROR(EINVAL);
+    }
+
+    ifp->type_src = ifp->type;
+
+    memset(&opts, 0, sizeof(opts));
+
+    snprintf(name, sizeof(name), "fg:%d:%d", fgp->fg.index, ifp->index);
+    opts.name = name;
+
+    ret = ofilter_bind_ifilter(ofilter_src, ifp, &opts);
+    if (ret < 0)
+        return ret;
+
+    ret = sch_connect(fgp->sch, SCH_FILTER_OUT(fg_src->index, out_idx),
+                                SCH_FILTER_IN(fgp->sch_idx, ifp->index));
+    if (ret < 0)
+        return ret;
+
+    return 0;
+}
+
 static InputFilter *ifilter_alloc(FilterGraph *fg)
 {
     InputFilterPriv *ifp;
@@ -1213,12 +1270,38 @@ static int fg_complex_bind_input(FilterGraph *fg, InputFilter *ifilter)
                    ifilter->name);
         return ret;
     } else if (ifp->linklabel) {
-        // bind to an explicitly specified demuxer stream
         AVFormatContext *s;
         AVStream       *st = NULL;
         char *p;
-        int file_idx = strtol(ifp->linklabel, &p, 0);
+        int file_idx;
 
+        // try finding an unbound filtergraph output with this label
+        for (int i = 0; i < nb_filtergraphs; i++) {
+            FilterGraph *fg_src = filtergraphs[i];
+
+            if (fg == fg_src)
+                continue;
+
+            for (int j = 0; j < fg_src->nb_outputs; j++) {
+                OutputFilter *ofilter = fg_src->outputs[j];
+
+                if (!ofilter->bound && ofilter->linklabel &&
+                    !strcmp(ofilter->linklabel, ifp->linklabel)) {
+                    av_log(fg, AV_LOG_VERBOSE,
+                           "Binding input with label '%s' to filtergraph output %d:%d\n",
+                           ifp->linklabel, i, j);
+
+                    ret = ifilter_bind_fg(ifp, fg_src, j);
+                    if (ret < 0)
+                        av_log(fg, AV_LOG_ERROR, "Error binding filtergraph input %s\n",
+                               ifp->linklabel);
+                    return ret;
+                }
+            }
+        }
+
+        // bind to an explicitly specified demuxer stream
+        file_idx = strtol(ifp->linklabel, &p, 0);
         if (file_idx < 0 || file_idx >= nb_input_files) {
             av_log(fg, AV_LOG_FATAL, "Invalid file index %d in filtergraph description %s.\n",
                    file_idx, fgp->graph_desc);
@@ -1274,7 +1357,7 @@ static int fg_complex_bind_input(FilterGraph *fg, InputFilter *ifilter)
 
 static int bind_inputs(FilterGraph *fg)
 {
-    // bind filtergraph inputs to input streams
+    // bind filtergraph inputs to input streams or other filtergraphs
     for (int i = 0; i < fg->nb_inputs; i++) {
         InputFilterPriv *ifp = ifp_from_ifilter(fg->inputs[i]);
         int ret;
-- 
2.43.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".

  parent reply	other threads:[~2024-04-05 16:20 UTC|newest]

Thread overview: 35+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-04-05 16:11 [FFmpeg-devel] [PATCH 01/31] lavfi/vf_scale: fix AVOption flags for "size"/"s" Anton Khirnov
2024-04-05 16:11 ` [FFmpeg-devel] [PATCH 02/31] lavfi/avfilter: add an "auto" constant to the threads option Anton Khirnov
2024-04-05 16:11 ` [FFmpeg-devel] [PATCH 03/31] fftools/ffmpeg_filter: do not pass OutputStream to set_channel_layout() Anton Khirnov
2024-04-05 16:11 ` [FFmpeg-devel] [PATCH 04/31] fftools/ffmpeg_filter: stop accessing AVCodecContext.codec Anton Khirnov
2024-04-05 16:11 ` [FFmpeg-devel] [PATCH 05/31] fftools/ffmpeg_filter: check that filter type matches output stream type Anton Khirnov
2024-04-05 16:11 ` [FFmpeg-devel] [PATCH 06/31] fftools/ffmpeg_filter: pass ts offset through OutputFilterOptions Anton Khirnov
2024-04-05 16:11 ` [FFmpeg-devel] [PATCH 07/31] fftools/ffmpeg_filter: pass keep_pix_fmt " Anton Khirnov
2024-04-05 16:11 ` [FFmpeg-devel] [PATCH 08/31] fftools/ffmpeg: warn about ignored -enc_time_base for subtitles earlier Anton Khirnov
2024-04-05 16:11 ` [FFmpeg-devel] [PATCH 09/31] fftools/ffmpeg_filter: pass enc_timebase through OutputFilterOptions Anton Khirnov
2024-04-05 16:11 ` [FFmpeg-devel] [PATCH 10/31] fftools/ffmpeg_filter: move the MJPEG format selection hack to muxer setup Anton Khirnov
2024-04-05 16:11 ` [FFmpeg-devel] [PATCH 11/31] fftools/ffmpeg_filter: stop accessing encoder AVCodecContext Anton Khirnov
2024-04-05 16:50   ` Dennis Mungai
2024-04-05 16:54     ` Gyan Doshi
2024-04-05 17:07       ` Dennis Mungai
2024-04-05 17:09         ` Anton Khirnov
2024-04-05 16:11 ` [FFmpeg-devel] [PATCH 12/31] fftools/ffmpeg_filter: pass vsync method through OutputFilterOptions Anton Khirnov
2024-04-05 16:11 ` [FFmpeg-devel] [PATCH 13/31] fftools/ffmpeg: drop OutputStream.is_cfr Anton Khirnov
2024-04-05 16:11 ` [FFmpeg-devel] [PATCH 14/31] fftools/ffmpeg_filter: accept a caller-provided output name Anton Khirnov
2024-04-05 16:11 ` [FFmpeg-devel] [PATCH 15/31] fftools/ffmpeg_filter: drop a redundant check Anton Khirnov
2024-04-05 16:11 ` [FFmpeg-devel] [PATCH 16/31] fftools/ffmpeg_filter: simplify retrieving filter type Anton Khirnov
2024-04-05 16:11 ` [FFmpeg-devel] [PATCH 17/31] fftools/ffmpeg_filter: add an AVClass to OutputFilter Anton Khirnov
2024-04-05 16:11 ` [FFmpeg-devel] [PATCH 18/31] fftools/ffmpeg_filter: drop an unnecessary use of OutputStream Anton Khirnov
2024-04-05 16:12 ` [FFmpeg-devel] [PATCH 19/31] fftools/ffmpeg_filter: pass sws/swr opts through OutputFilterOptions Anton Khirnov
2024-04-05 16:12 ` [FFmpeg-devel] [PATCH 20/31] fftools/ffmpeg_filter: pass autoscale " Anton Khirnov
2024-04-05 16:12 ` [FFmpeg-devel] [PATCH 21/31] fftools/ffmpeg_filter: pass trim parameters " Anton Khirnov
2024-04-05 16:12 ` [FFmpeg-devel] [PATCH 22/31] fftools/ffmpeg_filter: move most of -apad logic to the muxer Anton Khirnov
2024-04-05 16:12 ` [FFmpeg-devel] [PATCH 23/31] fftools/ffmpeg_mux: drop OutputFile.shortest Anton Khirnov
2024-04-05 16:12 ` [FFmpeg-devel] [PATCH 24/31] fftools/ffmpeg_mux: drop OutputFile.format Anton Khirnov
2024-04-05 16:12 ` [FFmpeg-devel] [PATCH 25/31] fftools/ffmpeg_filter: accept encoder thread count through OutputFilterOptions Anton Khirnov
2024-04-05 16:12 ` [FFmpeg-devel] [PATCH 26/31] fftools/ffmpeg_filter: drop OutputFilter.ost Anton Khirnov
2024-04-05 16:12 ` [FFmpeg-devel] [PATCH 27/31] fftools/ffmpeg_filter: only store complex filtergraphs in global array Anton Khirnov
2024-04-05 16:12 ` [FFmpeg-devel] [PATCH 28/31] fftools/ffmpeg_filter: change processing order in fg_finalise_bindings() Anton Khirnov
2024-04-05 16:12 ` [FFmpeg-devel] [PATCH 29/31] fftools/ffmpeg_sched: allow filtergraphs to send to filtergraphs Anton Khirnov
2024-04-05 16:12 ` Anton Khirnov [this message]
2024-04-05 16:12 ` [FFmpeg-devel] [PATCH 31/31] doc/ffmpeg: document that there can be multiple complex filtergraphs Anton Khirnov

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20240405161212.26167-30-anton@khirnov.net \
    --to=anton@khirnov.net \
    --cc=ffmpeg-devel@ffmpeg.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
		ffmpegdev@gitmailbox.com
	public-inbox-index ffmpegdev

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git