Skip to content

Commit

Permalink
fftools/ffmpeg: add loopback decoding
Browse files Browse the repository at this point in the history
This allows to send an encoder's output back to decoding and feed the
result into a complex filtergraph.
  • Loading branch information
elenril committed Mar 13, 2024
1 parent b98af44 commit a9193f7
Show file tree
Hide file tree
Showing 10 changed files with 336 additions and 14 deletions.
1 change: 1 addition & 0 deletions Changelog
Expand Up @@ -33,6 +33,7 @@ version <next>:
- ffprobe -show_stream_groups option
- ffprobe (with -export_side_data film_grain) now prints film grain metadata
- AEA muxer
- ffmpeg CLI loopback decoders


version 6.1:
Expand Down
49 changes: 44 additions & 5 deletions doc/ffmpeg.texi
Expand Up @@ -219,6 +219,40 @@ Since there is no decoding or encoding, it is very fast and there is no quality
loss. However, it might not work in some cases because of many factors. Applying
filters is obviously also impossible, since filters work on uncompressed data.

@section Loopback decoders
While decoders are normally associated with demuxer streams, it is also possible
to create "loopback" decoders that decode the output from some encoder and allow
it to be fed back to complex filtergraphs. This is done with the @code{-dec}
directive, which takes as a parameter the index of the output stream that should
be decoded. Every such directive creates a new loopback decoder, indexed with
successive integers starting at zero. These indices should then be used to refer
to loopback decoders in complex filtergraph link labels, as described in the
documentation for @option{-filter_complex}.

E.g. the following example:

@example
ffmpeg -i INPUT \
-map 0:v:0 -c:v libx264 -crf 45 -f null - \
-dec 0:0 -filter_complex '[0:v][dec:0]hstack[stack]' \
-map '[stack]' -c:v ffv1 OUTPUT
@end example

reads an input video and
@itemize
@item
(line 2) encodes it with @code{libx264} at low quality;

@item
(line 3) decodes this encoded stream and places it side by side with the
original input video;

@item
(line 4) combined video is then losslessly encoded and written into
@file{OUTPUT}.

@end itemize

@c man end DETAILED DESCRIPTION

@chapter Stream selection
Expand Down Expand Up @@ -2105,11 +2139,16 @@ 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 input streams using 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. An unlabeled input will be connected to the first unused input stream of
the matching type.
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.

For decoders, the link label must be [dec:@var{dec_idx}], where @var{dec_idx} is
the index of the loopback decoder to be connected to given input.

An unlabeled input will be connected to the first unused input stream of the
matching type.

Output link labels are referred to with @option{-map}. Unlabeled outputs are
added to the first output file.
Expand Down
2 changes: 1 addition & 1 deletion fftools/cmdutils.c
Expand Up @@ -528,7 +528,7 @@ static void check_options(const OptionDef *po)
{
while (po->name) {
if (po->flags & OPT_PERFILE)
av_assert0(po->flags & (OPT_INPUT | OPT_OUTPUT));
av_assert0(po->flags & (OPT_INPUT | OPT_OUTPUT | OPT_DECODER));

if (po->type == OPT_TYPE_FUNC)
av_assert0(!(po->flags & (OPT_FLAG_OFFSET | OPT_FLAG_SPEC)));
Expand Down
7 changes: 5 additions & 2 deletions fftools/cmdutils.h
Expand Up @@ -144,8 +144,8 @@ typedef struct OptionDef {
#define OPT_AUDIO (1 << 4)
#define OPT_SUBTITLE (1 << 5)
#define OPT_DATA (1 << 6)
/* The option is per-file (currently ffmpeg-only). At least one of OPT_INPUT or
* OPT_OUTPUT must be set when this flag is in use.
/* The option is per-file (currently ffmpeg-only). At least one of OPT_INPUT,
* OPT_OUTPUT, OPT_DECODER must be set when this flag is in use.
*/
#define OPT_PERFILE (1 << 7)

Expand Down Expand Up @@ -175,6 +175,9 @@ typedef struct OptionDef {
* name is stored in u1.name_canon */
#define OPT_HAS_CANON (1 << 14)

/* ffmpeg-only - OPT_PERFILE may apply to standalone decoders */
#define OPT_DECODER (1 << 15)

union {
void *dst_ptr;
int (*func_arg)(void *, const char *, const char *);
Expand Down
26 changes: 26 additions & 0 deletions fftools/ffmpeg.c
Expand Up @@ -131,6 +131,9 @@ int nb_output_files = 0;
FilterGraph **filtergraphs;
int nb_filtergraphs;

Decoder **decoders;
int nb_decoders;

#if HAVE_TERMIOS_H

/* init terminal so that we can grab keys */
Expand Down Expand Up @@ -340,6 +343,10 @@ static void ffmpeg_cleanup(int ret)
for (int i = 0; i < nb_input_files; i++)
ifile_close(&input_files[i]);

for (int i = 0; i < nb_decoders; i++)
dec_free(&decoders[i]);
av_freep(&decoders);

if (vstats_file) {
if (fclose(vstats_file))
av_log(NULL, AV_LOG_ERROR,
Expand Down Expand Up @@ -404,6 +411,10 @@ InputStream *ist_iter(InputStream *prev)

static void frame_data_free(void *opaque, uint8_t *data)
{
FrameData *fd = (FrameData *)data;

avcodec_parameters_free(&fd->par_enc);

av_free(data);
}

Expand All @@ -430,6 +441,21 @@ static int frame_data_ensure(AVBufferRef **dst, int writable)
const FrameData *fd_src = (const FrameData *)src->data;

memcpy(fd, fd_src, sizeof(*fd));
fd->par_enc = NULL;

if (fd_src->par_enc) {
int ret = 0;

fd->par_enc = avcodec_parameters_alloc();
ret = fd->par_enc ?
avcodec_parameters_copy(fd->par_enc, fd_src->par_enc) :
AVERROR(ENOMEM);
if (ret < 0) {
av_buffer_unref(dst);
av_buffer_unref(&src);
return ret;
}
}

av_buffer_unref(&src);
} else {
Expand Down
22 changes: 22 additions & 0 deletions fftools/ffmpeg.h
Expand Up @@ -331,6 +331,8 @@ typedef struct DecoderOpts {
typedef struct Decoder {
const AVClass *class;

enum AVMediaType type;

const uint8_t *subtitle_header;
int subtitle_header_size;

Expand Down Expand Up @@ -606,6 +608,8 @@ typedef struct FrameData {
int bits_per_raw_sample;

int64_t wallclock[LATENCY_PROBE_NB];

AVCodecParameters *par_enc;
} FrameData;

extern InputFile **input_files;
Expand All @@ -617,6 +621,10 @@ extern int nb_output_files;
extern FilterGraph **filtergraphs;
extern int nb_filtergraphs;

// standalone decoders (not tied to demuxed streams)
extern Decoder **decoders;
extern int nb_decoders;

extern char *vstats_filename;

extern float dts_delta_threshold;
Expand Down Expand Up @@ -734,6 +742,11 @@ void hw_device_free_all(void);
*/
AVBufferRef *hw_device_for_filter(void);

/**
* Create a standalone decoder.
*/
int dec_create(const OptionsContext *o, const char *arg, Scheduler *sch);

/**
* @param dec_opts Dictionary filled with decoder options. Its ownership
* is transferred to the decoder.
Expand All @@ -748,12 +761,21 @@ int dec_init(Decoder **pdec, Scheduler *sch,
AVFrame *param_out);
void dec_free(Decoder **pdec);

/*
* Called by filters to connect decoder's output to given filtergraph input.
*
* @param opts filtergraph input options, to be filled by this function
*/
int dec_filter_add(Decoder *dec, InputFilter *ifilter, InputFilterOptions *opts);

int enc_alloc(Encoder **penc, const AVCodec *codec,
Scheduler *sch, unsigned sch_idx);
void enc_free(Encoder **penc);

int enc_open(void *opaque, const AVFrame *frame);

int enc_loopback(Encoder *enc);

/*
* Initialize muxing state for the given stream, should be called
* after the codec/streamcopy setup has been done.
Expand Down

0 comments on commit a9193f7

Please sign in to comment.