lavfi: add timeline support.
Flag added in a few simple filters. A bunch of other filters can likely use the feature as well.
This commit is contained in:
parent
b8a5c76131
commit
fdd93eabfb
@ -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,
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user