Skip to content

Commit

Permalink
fftools/ffmpeg_filter: implement filtergraph chaining
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
elenril committed Apr 9, 2024
1 parent 255ae03 commit 3bd7c57
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 8 deletions.
1 change: 1 addition & 0 deletions Changelog
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
58 changes: 53 additions & 5 deletions doc/ffmpeg.texi
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
89 changes: 86 additions & 3 deletions fftools/ffmpeg_filter.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 3bd7c57

Please sign in to comment.