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 : - 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;