[FFmpeg-devel] [PATCH 2/4] lavfi: timeline support.

Stefano Sabatini stefasab at gmail.com
Mon Apr 15 23:33:13 CEST 2013


On date Monday 2013-04-15 18:56:36 +0200, Clément Bœsch encoded:
> Flag added in a few simple filters. A bunch of other filters can likely
> use the feature as well.
> ---
>  Changelog                    |  1 +
>  cmdutils.c                   |  2 ++
>  doc/filters.texi             | 29 +++++++++++++++++++++++++++
>  libavfilter/af_volume.c      |  1 +
>  libavfilter/avfilter.c       | 47 ++++++++++++++++++++++++++++++++++++++++++++
>  libavfilter/avfilter.h       | 12 ++++++++++-
>  libavfilter/vf_boxblur.c     |  1 +
>  libavfilter/vf_colormatrix.c |  1 +
>  libavfilter/vf_cropdetect.c  |  1 +
>  libavfilter/vf_curves.c      |  1 +
>  libavfilter/vf_drawbox.c     |  1 +
>  libavfilter/vf_edgedetect.c  |  1 +
>  libavfilter/vf_gradfun.c     |  1 +
>  libavfilter/vf_histeq.c      |  1 +
>  libavfilter/vf_hqdn3d.c      |  2 +-
>  libavfilter/vf_hue.c         |  1 +
>  libavfilter/vf_lut.c         |  1 +
>  libavfilter/vf_noise.c       |  1 +
>  libavfilter/vf_pp.c          |  2 +-
>  libavfilter/vf_smartblur.c   |  1 +
>  libavfilter/vf_unsharp.c     |  2 +-
>  21 files changed, 106 insertions(+), 4 deletions(-)
> 
> diff --git a/Changelog b/Changelog
> index c5383ff..d0e0234 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -23,6 +23,7 @@ version <next>:
>  - new interlace filter
>  - smptehdbars source
>  - inverse telecine filters (fieldmatch and decimate)
> +- timeline editing with filters
>  
>  
>  version 1.2:
> diff --git a/cmdutils.c b/cmdutils.c
> index 4b625fd..c5aa000 100644
> --- a/cmdutils.c
> +++ b/cmdutils.c
> @@ -1677,6 +1677,8 @@ static void show_help_filter(const char *name)
>      if (f->priv_class)
>          show_help_children(f->priv_class, AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM |
>                                            AV_OPT_FLAG_AUDIO_PARAM);
> +    if (f->flags & AVFILTER_FLAG_SUPPORT_TIMELINE)
> +        printf("This filter has support for timeline through the 'enable' option.\n");

I'd rather prefer a flags line like for codecs (could be useful also
for other flags like dynamic outputs).

>  #else
>      av_log(NULL, AV_LOG_ERROR, "Build without libavfilter; "
>             "can not to satisfy request\n");
> diff --git a/doc/filters.texi b/doc/filters.texi
> index 23d777d..d73d1ba 100644
> --- a/doc/filters.texi
> +++ b/doc/filters.texi
> @@ -267,6 +267,33 @@ See the ``Quoting and escaping'' section in the ffmpeg-utils manual
>  for more information about the escaping and quoting rules adopted by
>  FFmpeg.
>  
> + at chapter Timeline editing
> +
> +Some filters support a generic @option{enable} option. For the filters
> +supporting timeline editing, this option can be set to an expression which is
> +evaluated before sending a frame to the filter. If the evaluation is different
> +from 0, the filter will be enabled, otherwise the frame will be send to the

sent

also "the frame will be sent ro the next..." looks confusing, since it
implies that the frame is not sent otherwise. Maybe you want to say
"will be sent unchanged".

> +next filter in the filtergraph.
> +
> +The expression accepts the following values:
> + at table @samp
> + at item t
> +timestamp expressed in seconds, NAN if the input timestamp is unknown
> +
> + at item n
> +sequential number of the input frame, starting from 0
> + at end table
> +
> +Like any other filtering option, the @option{enable} option follows the same
> +rules.
> +
> +For example, to enable a denoiser filter (@ref{hqdn3d}) from 10 seconds to 3
> +minutes, and a @ref{curves} filter starting at 3 seconds:

> + at example
> +-vf 'hqdn3d = enable=between(t\,10\,3*60),
> +     curves = enable=gte(t\,3) : preset=cross_process'
> + at end example

Nit: please avoid ff* tools syntax in generic docs.

> +
>  @c man end FILTERGRAPH DESCRIPTION
>  
>  @chapter Audio Filters
> @@ -2283,6 +2310,7 @@ indicates never reset and return the largest area encountered during
>  playback.
>  @end table
>  
> + at anchor{curves}
>  @section curves
>  
>  Apply color adjustments using curves.
> @@ -3863,6 +3891,7 @@ ffplay -i input -vf histogram
>  
>  @end itemize
>  
> + at anchor{hqdn3d}
>  @section hqdn3d
>  
>  High precision/quality 3d denoise filter. This filter aims to reduce
> diff --git a/libavfilter/af_volume.c b/libavfilter/af_volume.c
> index 7d2b9ba..a55e1a3 100644
> --- a/libavfilter/af_volume.c
> +++ b/libavfilter/af_volume.c
> @@ -296,4 +296,5 @@ AVFilter avfilter_af_volume = {
>      .init           = init,
>      .inputs         = avfilter_af_volume_inputs,
>      .outputs        = avfilter_af_volume_outputs,
> +    .flags          = AVFILTER_FLAG_SUPPORT_TIMELINE,
>  };
> diff --git a/libavfilter/avfilter.c b/libavfilter/avfilter.c
> index 43340d1..8a5292d 100644
> --- a/libavfilter/avfilter.c
> +++ b/libavfilter/avfilter.c
> @@ -23,6 +23,7 @@
>  #include "libavutil/avstring.h"
>  #include "libavutil/channel_layout.h"
>  #include "libavutil/common.h"
> +#include "libavutil/eval.h"
>  #include "libavutil/imgutils.h"
>  #include "libavutil/opt.h"
>  #include "libavutil/pixdesc.h"
> @@ -483,12 +484,20 @@ static const AVClass *filter_child_class_next(const AVClass *prev)
>      return NULL;
>  }
>  
> +#define OFFSET(x) offsetof(AVFilterContext, x)
> +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM
> +static const AVOption filters_common_options[] = {
> +    { "enable", "set enable expression", OFFSET(enable_str), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
> +    { NULL }
> +};

Note: is this shown in regular filter output help?

> +
>  static const AVClass avfilter_class = {
>      .class_name = "AVFilter",
>      .item_name  = default_filter_name,
>      .version    = LIBAVUTIL_VERSION_INT,
>      .category   = AV_CLASS_CATEGORY_FILTER,
>      .child_next = filter_child_next,
> +    .option     = filters_common_options,
>      .child_class_next = filter_child_class_next,
>  };
>  
> @@ -618,9 +627,16 @@ void avfilter_free(AVFilterContext *filter)
>      while(filter->command_queue){
>          ff_command_queue_pop(filter);
>      }
> +    av_opt_free(filter);
> +    av_expr_free(filter->enable);
> +    filter->enable = NULL;
> +    av_freep(&filter->var_values);
>      av_free(filter);
>  }
>  
> +static const char *const var_names[] = {   "t",   "n",        NULL };
> +enum                                   { VAR_T, VAR_N, VAR_VARS_NB };
> +
>  static int process_options(AVFilterContext *ctx, AVDictionary **options,
>                             const char *args)
>  {
> @@ -630,6 +646,8 @@ static int process_options(AVFilterContext *ctx, AVDictionary **options,
>      const char *key;
>      int offset= -1;
>  
> +    av_opt_set_defaults(ctx);
> +
>      if (!args)
>          return 0;
>  
> @@ -665,6 +683,12 @@ static int process_options(AVFilterContext *ctx, AVDictionary **options,
>          }
>  
>          av_log(ctx, AV_LOG_DEBUG, "Setting '%s' to value '%s'\n", key, value);
> +
> +        if (av_opt_find(ctx, key, NULL, 0, 0)) {
> +            ret = av_opt_set(ctx, key, value, 0);
> +            if (ret < 0)
> +                return ret;
> +        } else {
>          av_dict_set(options, key, value, 0);
>          if ((ret = av_opt_set(ctx->priv, key, value, 0)) < 0) {
>              if (!av_opt_find(ctx->priv, key, NULL, 0, AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ)) {
> @@ -675,11 +699,27 @@ static int process_options(AVFilterContext *ctx, AVDictionary **options,
>              return ret;
>              }
>          }
> +        }

Can't you just set the option in the parent, and automatically look in
the children contexts?

>  
>          av_free(value);
>          av_free(parsed_key);
>          count++;
>      }
> +
> +    if (ctx->enable_str) {
> +        if (!(ctx->filter->flags & AVFILTER_FLAG_SUPPORT_TIMELINE)) {
> +            av_log(ctx, AV_LOG_ERROR, "Timeline ('enable' option) not supported "
> +                   "with filter '%s'\n", ctx->filter->name);
> +            return AVERROR_PATCHWELCOME;
> +        }
> +        ctx->var_values = av_calloc(VAR_VARS_NB, sizeof(*ctx->var_values));
> +        if (!ctx->var_values)
> +            return AVERROR(ENOMEM);
> +        ret = av_expr_parse((AVExpr**)&ctx->enable, ctx->enable_str, var_names,
> +                            NULL, NULL, NULL, NULL, 0, ctx->priv);
> +        if (ret < 0)
> +            return ret;

make sure this returns a meaningful error message (e.g. it should be
clear that the parsing error occurred in the enable option)

> +    }
>      return count;
>  }
>  
> @@ -852,6 +892,7 @@ static int default_filter_frame(AVFilterLink *link, AVFrame *frame)
>  static int ff_filter_frame_framed(AVFilterLink *link, AVFrame *frame)
>  {
>      int (*filter_frame)(AVFilterLink *, AVFrame *);
> +    AVFilterContext *dstctx = link->dst;
>      AVFilterPad *dst = link->dstpad;
>      AVFrame *out;
>      int ret;
> @@ -914,6 +955,12 @@ static int ff_filter_frame_framed(AVFilterLink *link, AVFrame *frame)
>      }
>  
>      pts = out->pts;
> +    if (dstctx->enable_str) {
> +        dstctx->var_values[VAR_N] = link->frame_count;
> +        dstctx->var_values[VAR_T] = pts == AV_NOPTS_VALUE ? NAN : pts * av_q2d(link->time_base);

> +        if (!((int)av_expr_eval(dstctx->enable, dstctx->var_values, NULL)))

uhm no you're doing my same mistake in select(), double->int is lossy

> +            filter_frame = default_filter_frame;

> +    }
>      ret = filter_frame(link, out);
>      link->frame_count++;
>      link->frame_requested = 0;
> diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h
> index 38bc5ee..a5518f7 100644
> --- a/libavfilter/avfilter.h
> +++ b/libavfilter/avfilter.h
> @@ -428,6 +428,12 @@ enum AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int pad_idx);
>   * the options supplied to it.
>   */
>  #define AVFILTER_FLAG_DYNAMIC_OUTPUTS       (1 << 1)
> +/**

> + * Some filter supports a generic "enable" expression option that can be used
> + * to enable or disable a filter in the timeline. Filters supporting this
> + * option have this flag.

this flag set?

> + */
> +#define AVFILTER_FLAG_SUPPORT_TIMELINE      (1 << 16)
>  
>  /**
>   * Filter definition. This defines the pads a filter contains, and all the
> @@ -522,7 +528,7 @@ typedef struct AVFilter {
>  
>  /** An instance of a filter */
>  struct AVFilterContext {
> -    const AVClass *av_class;        ///< needed for av_log()
> +    const AVClass *av_class;        ///< needed for av_log() and filters common options
>  
>      const AVFilter *filter;         ///< the AVFilter of which this is an instance
>  
> @@ -547,6 +553,10 @@ struct AVFilterContext {
>      struct AVFilterGraph *graph;    ///< filtergraph this filter belongs to
>  
>      struct AVFilterCommand *command_queue;
> +

> +    char *enable_str;
> +    void *enable;

Nit+: doxy would be nice

> +    double *var_values;
>  };
>  
>  /**
> diff --git a/libavfilter/vf_boxblur.c b/libavfilter/vf_boxblur.c
> index 7589644..b010670 100644
> --- a/libavfilter/vf_boxblur.c
> +++ b/libavfilter/vf_boxblur.c
> @@ -383,4 +383,5 @@ AVFilter avfilter_vf_boxblur = {
>  
>      .inputs    = avfilter_vf_boxblur_inputs,
>      .outputs   = avfilter_vf_boxblur_outputs,
> +    .flags     = AVFILTER_FLAG_SUPPORT_TIMELINE,
>  };
> diff --git a/libavfilter/vf_colormatrix.c b/libavfilter/vf_colormatrix.c
> index d25ce60..8db5fcf 100644
> --- a/libavfilter/vf_colormatrix.c
> +++ b/libavfilter/vf_colormatrix.c
> @@ -385,4 +385,5 @@ AVFilter avfilter_vf_colormatrix = {
>      .inputs        = colormatrix_inputs,
>      .outputs       = colormatrix_outputs,
>      .priv_class    = &colormatrix_class,
> +    .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE,
>  };
> diff --git a/libavfilter/vf_cropdetect.c b/libavfilter/vf_cropdetect.c
> index 3d109f8..cb170d0 100644
> --- a/libavfilter/vf_cropdetect.c
> +++ b/libavfilter/vf_cropdetect.c
> @@ -233,4 +233,5 @@ AVFilter avfilter_vf_cropdetect = {
>      .query_formats = query_formats,
>      .inputs    = avfilter_vf_cropdetect_inputs,
>      .outputs   = avfilter_vf_cropdetect_outputs,
> +    .flags     = AVFILTER_FLAG_SUPPORT_TIMELINE,
>  };
> diff --git a/libavfilter/vf_curves.c b/libavfilter/vf_curves.c
> index 9f5d8bd..626b4ea 100644
> --- a/libavfilter/vf_curves.c
> +++ b/libavfilter/vf_curves.c
> @@ -514,4 +514,5 @@ AVFilter avfilter_vf_curves = {
>      .inputs        = curves_inputs,
>      .outputs       = curves_outputs,
>      .priv_class    = &curves_class,
> +    .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE,
>  };
> diff --git a/libavfilter/vf_drawbox.c b/libavfilter/vf_drawbox.c
> index edb77cc..c2ffbf3 100644
> --- a/libavfilter/vf_drawbox.c
> +++ b/libavfilter/vf_drawbox.c
> @@ -181,4 +181,5 @@ AVFilter avfilter_vf_drawbox = {
>      .query_formats   = query_formats,
>      .inputs    = avfilter_vf_drawbox_inputs,
>      .outputs   = avfilter_vf_drawbox_outputs,
> +    .flags     = AVFILTER_FLAG_SUPPORT_TIMELINE,
>  };
> diff --git a/libavfilter/vf_edgedetect.c b/libavfilter/vf_edgedetect.c
> index f29c7ee..b4698a8 100644
> --- a/libavfilter/vf_edgedetect.c
> +++ b/libavfilter/vf_edgedetect.c
> @@ -327,4 +327,5 @@ AVFilter avfilter_vf_edgedetect = {
>      .inputs        = edgedetect_inputs,
>      .outputs       = edgedetect_outputs,
>      .priv_class    = &edgedetect_class,
> +    .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE,
>  };
> diff --git a/libavfilter/vf_gradfun.c b/libavfilter/vf_gradfun.c
> index 9e21560..48ac378 100644
> --- a/libavfilter/vf_gradfun.c
> +++ b/libavfilter/vf_gradfun.c
> @@ -260,4 +260,5 @@ AVFilter avfilter_vf_gradfun = {
>      .query_formats = query_formats,
>      .inputs        = avfilter_vf_gradfun_inputs,
>      .outputs       = avfilter_vf_gradfun_outputs,
> +    .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE,
>  };
> diff --git a/libavfilter/vf_histeq.c b/libavfilter/vf_histeq.c
> index 7d68be7..69f3b64 100644
> --- a/libavfilter/vf_histeq.c
> +++ b/libavfilter/vf_histeq.c
> @@ -279,4 +279,5 @@ AVFilter avfilter_vf_histeq = {
>      .inputs        = histeq_inputs,
>      .outputs       = histeq_outputs,
>      .priv_class    = &histeq_class,
> +    .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE,
>  };
> diff --git a/libavfilter/vf_hqdn3d.c b/libavfilter/vf_hqdn3d.c
> index a722d8f..003f175 100644
> --- a/libavfilter/vf_hqdn3d.c
> +++ b/libavfilter/vf_hqdn3d.c
> @@ -355,6 +355,6 @@ AVFilter avfilter_vf_hqdn3d = {
>      .query_formats = query_formats,
>  
>      .inputs    = avfilter_vf_hqdn3d_inputs,
> -
>      .outputs   = avfilter_vf_hqdn3d_outputs,
> +    .flags     = AVFILTER_FLAG_SUPPORT_TIMELINE,
>  };
> diff --git a/libavfilter/vf_hue.c b/libavfilter/vf_hue.c
> index a1280be..9f63240 100644
> --- a/libavfilter/vf_hue.c
> +++ b/libavfilter/vf_hue.c
> @@ -349,4 +349,5 @@ AVFilter avfilter_vf_hue = {
>      .inputs          = hue_inputs,
>      .outputs         = hue_outputs,
>      .priv_class      = &hue_class,
> +    .flags           = AVFILTER_FLAG_SUPPORT_TIMELINE,
>  };
> diff --git a/libavfilter/vf_lut.c b/libavfilter/vf_lut.c
> index 76db0f3..7eeaacd 100644
> --- a/libavfilter/vf_lut.c
> +++ b/libavfilter/vf_lut.c
> @@ -347,6 +347,7 @@ static const AVFilterPad outputs[] = {
>                                                                          \
>          .inputs        = inputs,                                        \
>          .outputs       = outputs,                                       \
> +        .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE,                \
>      }
>  
>  #if CONFIG_LUT_FILTER
> diff --git a/libavfilter/vf_noise.c b/libavfilter/vf_noise.c
> index bdab9fa..226dfa2 100644
> --- a/libavfilter/vf_noise.c
> +++ b/libavfilter/vf_noise.c
> @@ -472,4 +472,5 @@ AVFilter avfilter_vf_noise = {
>      .inputs        = noise_inputs,
>      .outputs       = noise_outputs,
>      .priv_class    = &noise_class,
> +    .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE,
>  };
> diff --git a/libavfilter/vf_pp.c b/libavfilter/vf_pp.c
> index d2c1965..0571cfc 100644
> --- a/libavfilter/vf_pp.c
> +++ b/libavfilter/vf_pp.c
> @@ -180,5 +180,5 @@ AVFilter avfilter_vf_pp = {
>      .outputs         = pp_outputs,
>      .process_command = pp_process_command,
>      .priv_class      = &pp_class,
> -
> +    .flags           = AVFILTER_FLAG_SUPPORT_TIMELINE,
>  };
> diff --git a/libavfilter/vf_smartblur.c b/libavfilter/vf_smartblur.c
> index aed3fa6..4dd1664 100644
> --- a/libavfilter/vf_smartblur.c
> +++ b/libavfilter/vf_smartblur.c
> @@ -301,4 +301,5 @@ AVFilter avfilter_vf_smartblur = {
>      .inputs        = smartblur_inputs,
>      .outputs       = smartblur_outputs,
>      .priv_class    = &smartblur_class,
> +    .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE,
>  };
> diff --git a/libavfilter/vf_unsharp.c b/libavfilter/vf_unsharp.c
> index 038ba4b..2d7aab2 100644
> --- a/libavfilter/vf_unsharp.c
> +++ b/libavfilter/vf_unsharp.c
> @@ -299,6 +299,6 @@ AVFilter avfilter_vf_unsharp = {
>      .query_formats = query_formats,
>  
>      .inputs    = avfilter_vf_unsharp_inputs,
> -
>      .outputs   = avfilter_vf_unsharp_outputs,
> +    .flags     = AVFILTER_FLAG_SUPPORT_TIMELINE,

Should be good otherwise if reasonably tested.
-- 
FFmpeg = Foolish and Faithless Mysterious Pacific Ecstatic Game


More information about the ffmpeg-devel mailing list