fftools/ffmpeg_filter: implement filtergraph chaining
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.
This commit is contained in:
parent
255ae03601
commit
3bd7c57125
|
@ -4,6 +4,7 @@ releases are sorted from youngest to oldest.
|
||||||
version <next>:
|
version <next>:
|
||||||
- Raw Captions with Time (RCWT) closed caption demuxer
|
- Raw Captions with Time (RCWT) closed caption demuxer
|
||||||
- LC3/LC3plus decoding/encoding using external library liblc3
|
- LC3/LC3plus decoding/encoding using external library liblc3
|
||||||
|
- ffmpeg CLI filtergraph chaining
|
||||||
|
|
||||||
|
|
||||||
version 7.0:
|
version 7.0:
|
||||||
|
|
|
@ -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
|
the filtergraph, as described in the ``Filtergraph syntax'' section of the
|
||||||
ffmpeg-filters manual.
|
ffmpeg-filters manual.
|
||||||
|
|
||||||
Input link labels must refer to either input streams or loopback decoders. For
|
Inputs to a complex filtergraph may come from different source types,
|
||||||
input streams, use the @code{[file_index:stream_specifier]} syntax (i.e. the
|
distinguished by the format of the corresponding link label:
|
||||||
same as @option{-map} uses). If @var{stream_specifier} matches multiple streams,
|
@itemize
|
||||||
the first one will be used.
|
@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.
|
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
|
An unlabeled input will be connected to the first unused input stream of the
|
||||||
matching type.
|
matching type.
|
||||||
|
|
||||||
|
|
|
@ -902,6 +902,63 @@ int ofilter_bind_ost(OutputFilter *ofilter, OutputStream *ost,
|
||||||
return 0;
|
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)
|
static InputFilter *ifilter_alloc(FilterGraph *fg)
|
||||||
{
|
{
|
||||||
InputFilterPriv *ifp;
|
InputFilterPriv *ifp;
|
||||||
|
@ -1213,12 +1270,38 @@ static int fg_complex_bind_input(FilterGraph *fg, InputFilter *ifilter)
|
||||||
ifilter->name);
|
ifilter->name);
|
||||||
return ret;
|
return ret;
|
||||||
} else if (ifp->linklabel) {
|
} else if (ifp->linklabel) {
|
||||||
// bind to an explicitly specified demuxer stream
|
|
||||||
AVFormatContext *s;
|
AVFormatContext *s;
|
||||||
AVStream *st = NULL;
|
AVStream *st = NULL;
|
||||||
char *p;
|
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) {
|
if (file_idx < 0 || file_idx >= nb_input_files) {
|
||||||
av_log(fg, AV_LOG_FATAL, "Invalid file index %d in filtergraph description %s.\n",
|
av_log(fg, AV_LOG_FATAL, "Invalid file index %d in filtergraph description %s.\n",
|
||||||
file_idx, fgp->graph_desc);
|
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)
|
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++) {
|
for (int i = 0; i < fg->nb_inputs; i++) {
|
||||||
InputFilterPriv *ifp = ifp_from_ifilter(fg->inputs[i]);
|
InputFilterPriv *ifp = ifp_from_ifilter(fg->inputs[i]);
|
||||||
int ret;
|
int ret;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user