Flag added in a few simple filters. A bunch of other filters can likely use the feature as well.tags/n2.0
| @@ -30,6 +30,7 @@ version <next>: | |||||
| - decent native animated GIF encoding | - decent native animated GIF encoding | ||||
| - asetrate filter | - asetrate filter | ||||
| - interleave filter | - interleave filter | ||||
| - timeline editing with filters | |||||
| version 1.2: | version 1.2: | ||||
| @@ -1683,6 +1683,8 @@ static void show_help_filter(const char *name) | |||||
| if (f->priv_class) | if (f->priv_class) | ||||
| show_help_children(f->priv_class, AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM | | show_help_children(f->priv_class, AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM | | ||||
| AV_OPT_FLAG_AUDIO_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"); | |||||
| #else | #else | ||||
| av_log(NULL, AV_LOG_ERROR, "Build without libavfilter; " | av_log(NULL, AV_LOG_ERROR, "Build without libavfilter; " | ||||
| "can not to satisfy request\n"); | "can not to satisfy request\n"); | ||||
| @@ -267,6 +267,36 @@ See the ``Quoting and escaping'' section in the ffmpeg-utils manual | |||||
| for more information about the escaping and quoting rules adopted by | for more information about the escaping and quoting rules adopted by | ||||
| FFmpeg. | FFmpeg. | ||||
| @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 non-zero, | |||||
| the filter will be enabled, otherwise the frame will be sent unchanged to the | |||||
| next filter in the filtergraph. | |||||
| The expression accepts the following values: | |||||
| @table @samp | |||||
| @item t | |||||
| timestamp expressed in seconds, NAN if the input timestamp is unknown | |||||
| @item n | |||||
| sequential number of the input frame, starting from 0 | |||||
| @item pos | |||||
| the position in the file of the input frame, NAN if unknown | |||||
| @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: | |||||
| @example | |||||
| hqdn3d = enable='between(t,10,3*60)', | |||||
| curves = enable='gte(t,3)' : preset=cross_process | |||||
| @end example | |||||
| @c man end FILTERGRAPH DESCRIPTION | @c man end FILTERGRAPH DESCRIPTION | ||||
| @chapter Audio Filters | @chapter Audio Filters | ||||
| @@ -2409,6 +2439,7 @@ indicates never reset and return the largest area encountered during | |||||
| playback. | playback. | ||||
| @end table | @end table | ||||
| @anchor{curves} | |||||
| @section curves | @section curves | ||||
| Apply color adjustments using curves. | Apply color adjustments using curves. | ||||
| @@ -4013,6 +4044,7 @@ ffplay -i input -vf histogram | |||||
| @end itemize | @end itemize | ||||
| @anchor{hqdn3d} | |||||
| @section hqdn3d | @section hqdn3d | ||||
| High precision/quality 3d denoise filter. This filter aims to reduce | High precision/quality 3d denoise filter. This filter aims to reduce | ||||
| @@ -296,4 +296,5 @@ AVFilter avfilter_af_volume = { | |||||
| .init = init, | .init = init, | ||||
| .inputs = avfilter_af_volume_inputs, | .inputs = avfilter_af_volume_inputs, | ||||
| .outputs = avfilter_af_volume_outputs, | .outputs = avfilter_af_volume_outputs, | ||||
| .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |||||
| }; | }; | ||||
| @@ -23,6 +23,7 @@ | |||||
| #include "libavutil/avstring.h" | #include "libavutil/avstring.h" | ||||
| #include "libavutil/channel_layout.h" | #include "libavutil/channel_layout.h" | ||||
| #include "libavutil/common.h" | #include "libavutil/common.h" | ||||
| #include "libavutil/eval.h" | |||||
| #include "libavutil/imgutils.h" | #include "libavutil/imgutils.h" | ||||
| #include "libavutil/opt.h" | #include "libavutil/opt.h" | ||||
| #include "libavutil/pixdesc.h" | #include "libavutil/pixdesc.h" | ||||
| @@ -483,12 +484,20 @@ static const AVClass *filter_child_class_next(const AVClass *prev) | |||||
| return NULL; | 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 } | |||||
| }; | |||||
| static const AVClass avfilter_class = { | static const AVClass avfilter_class = { | ||||
| .class_name = "AVFilter", | .class_name = "AVFilter", | ||||
| .item_name = default_filter_name, | .item_name = default_filter_name, | ||||
| .version = LIBAVUTIL_VERSION_INT, | .version = LIBAVUTIL_VERSION_INT, | ||||
| .category = AV_CLASS_CATEGORY_FILTER, | .category = AV_CLASS_CATEGORY_FILTER, | ||||
| .child_next = filter_child_next, | .child_next = filter_child_next, | ||||
| .option = filters_common_options, | |||||
| .child_class_next = filter_child_class_next, | .child_class_next = filter_child_class_next, | ||||
| }; | }; | ||||
| @@ -618,9 +627,16 @@ void avfilter_free(AVFilterContext *filter) | |||||
| while(filter->command_queue){ | while(filter->command_queue){ | ||||
| ff_command_queue_pop(filter); | 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); | av_free(filter); | ||||
| } | } | ||||
| static const char *const var_names[] = { "t", "n", "pos", NULL }; | |||||
| enum { VAR_T, VAR_N, VAR_POS, VAR_VARS_NB }; | |||||
| static int process_options(AVFilterContext *ctx, AVDictionary **options, | static int process_options(AVFilterContext *ctx, AVDictionary **options, | ||||
| const char *args) | const char *args) | ||||
| { | { | ||||
| @@ -630,6 +646,8 @@ static int process_options(AVFilterContext *ctx, AVDictionary **options, | |||||
| const char *key; | const char *key; | ||||
| int offset= -1; | int offset= -1; | ||||
| av_opt_set_defaults(ctx); | |||||
| if (!args) | if (!args) | ||||
| return 0; | 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); | 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); | av_dict_set(options, key, value, 0); | ||||
| if ((ret = av_opt_set(ctx->priv, key, value, 0)) < 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)) { | 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; | return ret; | ||||
| } | } | ||||
| } | } | ||||
| } | |||||
| av_free(value); | av_free(value); | ||||
| av_free(parsed_key); | av_free(parsed_key); | ||||
| count++; | 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; | |||||
| } | |||||
| return count; | 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) | static int ff_filter_frame_framed(AVFilterLink *link, AVFrame *frame) | ||||
| { | { | ||||
| int (*filter_frame)(AVFilterLink *, AVFrame *); | int (*filter_frame)(AVFilterLink *, AVFrame *); | ||||
| AVFilterContext *dstctx = link->dst; | |||||
| AVFilterPad *dst = link->dstpad; | AVFilterPad *dst = link->dstpad; | ||||
| AVFrame *out; | AVFrame *out; | ||||
| int ret; | int ret; | ||||
| @@ -914,6 +955,15 @@ static int ff_filter_frame_framed(AVFilterLink *link, AVFrame *frame) | |||||
| } | } | ||||
| pts = out->pts; | pts = out->pts; | ||||
| if (dstctx->enable_str) { | |||||
| int64_t pos = av_frame_get_pkt_pos(out); | |||||
| dstctx->var_values[VAR_N] = link->frame_count; | |||||
| dstctx->var_values[VAR_T] = pts == AV_NOPTS_VALUE ? NAN : pts * av_q2d(link->time_base); | |||||
| dstctx->var_values[VAR_POS] = pos == -1 ? NAN : pos; | |||||
| if (!av_expr_eval(dstctx->enable, dstctx->var_values, NULL)) | |||||
| filter_frame = dst->passthrough_filter_frame ? dst->passthrough_filter_frame | |||||
| : default_filter_frame; | |||||
| } | |||||
| ret = filter_frame(link, out); | ret = filter_frame(link, out); | ||||
| link->frame_count++; | link->frame_count++; | ||||
| link->frame_requested = 0; | link->frame_requested = 0; | ||||
| @@ -385,6 +385,19 @@ struct AVFilterPad { | |||||
| int needs_fifo; | int needs_fifo; | ||||
| int needs_writable; | int needs_writable; | ||||
| /** | |||||
| * Passthrough filtering callback. | |||||
| * | |||||
| * If a filter supports timeline editing (in case | |||||
| * AVFILTER_FLAG_SUPPORT_TIMELINE is enabled) then it can implement a | |||||
| * custom passthrough callback to update its local context (for example to | |||||
| * keep a frame reference, or simply send the filter to a custom outlink). | |||||
| * The filter must not do any change to the frame in this callback. | |||||
| * | |||||
| * Input pads only. | |||||
| */ | |||||
| int (*passthrough_filter_frame)(AVFilterLink *link, AVFrame *frame); | |||||
| }; | }; | ||||
| #endif | #endif | ||||
| @@ -428,6 +441,12 @@ enum AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int pad_idx); | |||||
| * the options supplied to it. | * the options supplied to it. | ||||
| */ | */ | ||||
| #define AVFILTER_FLAG_DYNAMIC_OUTPUTS (1 << 1) | #define AVFILTER_FLAG_DYNAMIC_OUTPUTS (1 << 1) | ||||
| /** | |||||
| * Some filters support 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 set. | |||||
| */ | |||||
| #define AVFILTER_FLAG_SUPPORT_TIMELINE (1 << 16) | |||||
| /** | /** | ||||
| * Filter definition. This defines the pads a filter contains, and all the | * Filter definition. This defines the pads a filter contains, and all the | ||||
| @@ -522,7 +541,7 @@ typedef struct AVFilter { | |||||
| /** An instance of a filter */ | /** An instance of a filter */ | ||||
| struct AVFilterContext { | 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 | const AVFilter *filter; ///< the AVFilter of which this is an instance | ||||
| @@ -547,6 +566,10 @@ struct AVFilterContext { | |||||
| struct AVFilterGraph *graph; ///< filtergraph this filter belongs to | struct AVFilterGraph *graph; ///< filtergraph this filter belongs to | ||||
| struct AVFilterCommand *command_queue; | struct AVFilterCommand *command_queue; | ||||
| char *enable_str; ///< enable expression string | |||||
| void *enable; ///< parsed expression (AVExpr*) | |||||
| double *var_values; ///< variable values for the enable expression | |||||
| }; | }; | ||||
| /** | /** | ||||
| @@ -383,4 +383,5 @@ AVFilter avfilter_vf_boxblur = { | |||||
| .inputs = avfilter_vf_boxblur_inputs, | .inputs = avfilter_vf_boxblur_inputs, | ||||
| .outputs = avfilter_vf_boxblur_outputs, | .outputs = avfilter_vf_boxblur_outputs, | ||||
| .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |||||
| }; | }; | ||||
| @@ -385,4 +385,5 @@ AVFilter avfilter_vf_colormatrix = { | |||||
| .inputs = colormatrix_inputs, | .inputs = colormatrix_inputs, | ||||
| .outputs = colormatrix_outputs, | .outputs = colormatrix_outputs, | ||||
| .priv_class = &colormatrix_class, | .priv_class = &colormatrix_class, | ||||
| .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |||||
| }; | }; | ||||
| @@ -233,4 +233,5 @@ AVFilter avfilter_vf_cropdetect = { | |||||
| .query_formats = query_formats, | .query_formats = query_formats, | ||||
| .inputs = avfilter_vf_cropdetect_inputs, | .inputs = avfilter_vf_cropdetect_inputs, | ||||
| .outputs = avfilter_vf_cropdetect_outputs, | .outputs = avfilter_vf_cropdetect_outputs, | ||||
| .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |||||
| }; | }; | ||||
| @@ -514,4 +514,5 @@ AVFilter avfilter_vf_curves = { | |||||
| .inputs = curves_inputs, | .inputs = curves_inputs, | ||||
| .outputs = curves_outputs, | .outputs = curves_outputs, | ||||
| .priv_class = &curves_class, | .priv_class = &curves_class, | ||||
| .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |||||
| }; | }; | ||||
| @@ -181,4 +181,5 @@ AVFilter avfilter_vf_drawbox = { | |||||
| .query_formats = query_formats, | .query_formats = query_formats, | ||||
| .inputs = avfilter_vf_drawbox_inputs, | .inputs = avfilter_vf_drawbox_inputs, | ||||
| .outputs = avfilter_vf_drawbox_outputs, | .outputs = avfilter_vf_drawbox_outputs, | ||||
| .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |||||
| }; | }; | ||||
| @@ -327,4 +327,5 @@ AVFilter avfilter_vf_edgedetect = { | |||||
| .inputs = edgedetect_inputs, | .inputs = edgedetect_inputs, | ||||
| .outputs = edgedetect_outputs, | .outputs = edgedetect_outputs, | ||||
| .priv_class = &edgedetect_class, | .priv_class = &edgedetect_class, | ||||
| .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |||||
| }; | }; | ||||
| @@ -260,4 +260,5 @@ AVFilter avfilter_vf_gradfun = { | |||||
| .query_formats = query_formats, | .query_formats = query_formats, | ||||
| .inputs = avfilter_vf_gradfun_inputs, | .inputs = avfilter_vf_gradfun_inputs, | ||||
| .outputs = avfilter_vf_gradfun_outputs, | .outputs = avfilter_vf_gradfun_outputs, | ||||
| .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |||||
| }; | }; | ||||
| @@ -279,4 +279,5 @@ AVFilter avfilter_vf_histeq = { | |||||
| .inputs = histeq_inputs, | .inputs = histeq_inputs, | ||||
| .outputs = histeq_outputs, | .outputs = histeq_outputs, | ||||
| .priv_class = &histeq_class, | .priv_class = &histeq_class, | ||||
| .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |||||
| }; | }; | ||||
| @@ -355,6 +355,6 @@ AVFilter avfilter_vf_hqdn3d = { | |||||
| .query_formats = query_formats, | .query_formats = query_formats, | ||||
| .inputs = avfilter_vf_hqdn3d_inputs, | .inputs = avfilter_vf_hqdn3d_inputs, | ||||
| .outputs = avfilter_vf_hqdn3d_outputs, | .outputs = avfilter_vf_hqdn3d_outputs, | ||||
| .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |||||
| }; | }; | ||||
| @@ -349,4 +349,5 @@ AVFilter avfilter_vf_hue = { | |||||
| .inputs = hue_inputs, | .inputs = hue_inputs, | ||||
| .outputs = hue_outputs, | .outputs = hue_outputs, | ||||
| .priv_class = &hue_class, | .priv_class = &hue_class, | ||||
| .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |||||
| }; | }; | ||||
| @@ -350,6 +350,7 @@ static const AVFilterPad outputs[] = { | |||||
| \ | \ | ||||
| .inputs = inputs, \ | .inputs = inputs, \ | ||||
| .outputs = outputs, \ | .outputs = outputs, \ | ||||
| .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, \ | |||||
| } | } | ||||
| #if CONFIG_LUT_FILTER | #if CONFIG_LUT_FILTER | ||||
| @@ -471,4 +471,5 @@ AVFilter avfilter_vf_noise = { | |||||
| .inputs = noise_inputs, | .inputs = noise_inputs, | ||||
| .outputs = noise_outputs, | .outputs = noise_outputs, | ||||
| .priv_class = &noise_class, | .priv_class = &noise_class, | ||||
| .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |||||
| }; | }; | ||||
| @@ -180,5 +180,5 @@ AVFilter avfilter_vf_pp = { | |||||
| .outputs = pp_outputs, | .outputs = pp_outputs, | ||||
| .process_command = pp_process_command, | .process_command = pp_process_command, | ||||
| .priv_class = &pp_class, | .priv_class = &pp_class, | ||||
| .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |||||
| }; | }; | ||||
| @@ -301,4 +301,5 @@ AVFilter avfilter_vf_smartblur = { | |||||
| .inputs = smartblur_inputs, | .inputs = smartblur_inputs, | ||||
| .outputs = smartblur_outputs, | .outputs = smartblur_outputs, | ||||
| .priv_class = &smartblur_class, | .priv_class = &smartblur_class, | ||||
| .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |||||
| }; | }; | ||||
| @@ -299,6 +299,6 @@ AVFilter avfilter_vf_unsharp = { | |||||
| .query_formats = query_formats, | .query_formats = query_formats, | ||||
| .inputs = avfilter_vf_unsharp_inputs, | .inputs = avfilter_vf_unsharp_inputs, | ||||
| .outputs = avfilter_vf_unsharp_outputs, | .outputs = avfilter_vf_unsharp_outputs, | ||||
| .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |||||
| }; | }; | ||||