Commit dcc1b323 authored by Stefano Sabatini's avatar Stefano Sabatini

lavfi/select: add support for dynamic number of outputs

parent 565c50ac
...@@ -6721,10 +6721,20 @@ This filter accepts the following options: ...@@ -6721,10 +6721,20 @@ This filter accepts the following options:
@table @option @table @option
@item expr, e @item expr, e
An expression, which is evaluated for each input frame. If the expression is Set expression, which is evaluated for each input frame.
evaluated to a non-zero value, the frame is selected and passed to the output,
otherwise it is discarded.
If the expression is evaluated to zero, the frame is discarded.
If the evaluation result is negative or NaN, the frame is sent to the
first output; otherwise it is sent to the output with index
@code{ceil(val)-1}, assuming that the input index starts from 0.
For example a value of @code{1.2} corresponds to the output with index
@code{ceil(1.2)-1 = 2-1 = 1}, that is the second output.
@item outputs, n
Set the number of outputs. The output to which to send the selected
frame is based on the result of the evaluation. Default value is 1.
@end table @end table
The expression can contain the following constants: The expression can contain the following constants:
...@@ -6878,6 +6888,12 @@ ffmpeg -i video.avi -vf select='gt(scene\,0.4)',scale=160:120,tile -frames:v 1 p ...@@ -6878,6 +6888,12 @@ ffmpeg -i video.avi -vf select='gt(scene\,0.4)',scale=160:120,tile -frames:v 1 p
Comparing @var{scene} against a value between 0.3 and 0.5 is generally a sane Comparing @var{scene} against a value between 0.3 and 0.5 is generally a sane
choice. choice.
@item
Send even and odd frames to separate outputs, and compose them:
@example
select=n=2:e='mod(n, 2)+1' [odd][even]; [odd] pad=h=2*ih [tmp]; [tmp][even] overlay=y=h
@end example
@end itemize @end itemize
@section asendcmd, sendcmd @section asendcmd, sendcmd
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
* filter for selecting which frame passes in the filterchain * filter for selecting which frame passes in the filterchain
*/ */
#include "libavutil/avstring.h"
#include "libavutil/eval.h" #include "libavutil/eval.h"
#include "libavutil/fifo.h" #include "libavutil/fifo.h"
#include "libavutil/internal.h" #include "libavutil/internal.h"
...@@ -136,13 +137,16 @@ typedef struct { ...@@ -136,13 +137,16 @@ typedef struct {
#endif #endif
AVFrame *prev_picref; ///< previous frame (scene detect only) AVFrame *prev_picref; ///< previous frame (scene detect only)
double select; double select;
int select_out; ///< mark the selected output pad index
int nb_outputs;
} SelectContext; } SelectContext;
static int request_frame(AVFilterLink *outlink);
static av_cold int init(AVFilterContext *ctx) static av_cold int init(AVFilterContext *ctx)
{ {
SelectContext *select = ctx->priv; SelectContext *select = ctx->priv;
int ret; int i, ret;
if ((ret = av_expr_parse(&select->expr, select->expr_str, if ((ret = av_expr_parse(&select->expr, select->expr_str,
var_names, NULL, NULL, NULL, NULL, 0, ctx)) < 0) { var_names, NULL, NULL, NULL, NULL, 0, ctx)) < 0) {
...@@ -152,6 +156,17 @@ static av_cold int init(AVFilterContext *ctx) ...@@ -152,6 +156,17 @@ static av_cold int init(AVFilterContext *ctx)
} }
select->do_scene_detect = !!strstr(select->expr_str, "scene"); select->do_scene_detect = !!strstr(select->expr_str, "scene");
for (i = 0; i < select->nb_outputs; i++) {
AVFilterPad pad = { 0 };
pad.name = av_asprintf("output%d", i);
if (!pad.name)
return AVERROR(ENOMEM);
pad.type = ctx->filter->inputs[0].type;
pad.request_frame = request_frame;
ff_insert_outpad(ctx, i, &pad);
}
return 0; return 0;
} }
...@@ -308,7 +323,15 @@ static void select_frame(AVFilterContext *ctx, AVFrame *frame) ...@@ -308,7 +323,15 @@ static void select_frame(AVFilterContext *ctx, AVFrame *frame)
break; break;
} }
av_log(inlink->dst, AV_LOG_DEBUG, " -> select:%f\n", res); if (res == 0) {
select->select_out = -1; /* drop */
} else if (isnan(res) || res < 0) {
select->select_out = 0; /* first output */
} else {
select->select_out = FFMIN(ceilf(res)-1, select->nb_outputs-1); /* other outputs */
}
av_log(inlink->dst, AV_LOG_DEBUG, " -> select:%f select_out:%d\n", res, select->select_out);
if (res) { if (res) {
select->var_values[VAR_PREV_SELECTED_N] = select->var_values[VAR_N]; select->var_values[VAR_PREV_SELECTED_N] = select->var_values[VAR_N];
...@@ -326,11 +349,12 @@ static void select_frame(AVFilterContext *ctx, AVFrame *frame) ...@@ -326,11 +349,12 @@ static void select_frame(AVFilterContext *ctx, AVFrame *frame)
static int filter_frame(AVFilterLink *inlink, AVFrame *frame) static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
{ {
SelectContext *select = inlink->dst->priv; AVFilterContext *ctx = inlink->dst;
SelectContext *select = ctx->priv;
select_frame(inlink->dst, frame); select_frame(ctx, frame);
if (select->select) if (select->select)
return ff_filter_frame(inlink->dst->outputs[0], frame); return ff_filter_frame(ctx->outputs[select->select_out], frame);
av_frame_free(&frame); av_frame_free(&frame);
return 0; return 0;
...@@ -341,13 +365,13 @@ static int request_frame(AVFilterLink *outlink) ...@@ -341,13 +365,13 @@ static int request_frame(AVFilterLink *outlink)
AVFilterContext *ctx = outlink->src; AVFilterContext *ctx = outlink->src;
SelectContext *select = ctx->priv; SelectContext *select = ctx->priv;
AVFilterLink *inlink = outlink->src->inputs[0]; AVFilterLink *inlink = outlink->src->inputs[0];
select->select = 0; int out_no = FF_OUTLINK_IDX(outlink);
do { do {
int ret = ff_request_frame(inlink); int ret = ff_request_frame(inlink);
if (ret < 0) if (ret < 0)
return ret; return ret;
} while (!select->select); } while (select->select_out != out_no);
return 0; return 0;
} }
...@@ -355,10 +379,14 @@ static int request_frame(AVFilterLink *outlink) ...@@ -355,10 +379,14 @@ static int request_frame(AVFilterLink *outlink)
static av_cold void uninit(AVFilterContext *ctx) static av_cold void uninit(AVFilterContext *ctx)
{ {
SelectContext *select = ctx->priv; SelectContext *select = ctx->priv;
int i;
av_expr_free(select->expr); av_expr_free(select->expr);
select->expr = NULL; select->expr = NULL;
for (i = 0; i < ctx->nb_outputs; i++)
av_freep(&ctx->output_pads[i].name);
#if CONFIG_AVCODEC #if CONFIG_AVCODEC
if (select->do_scene_detect) { if (select->do_scene_detect) {
av_frame_free(&select->prev_picref); av_frame_free(&select->prev_picref);
...@@ -393,6 +421,8 @@ static int query_formats(AVFilterContext *ctx) ...@@ -393,6 +421,8 @@ static int query_formats(AVFilterContext *ctx)
static const AVOption aselect_options[] = { static const AVOption aselect_options[] = {
{ "expr", "An expression to use for selecting frames", OFFSET(expr_str), AV_OPT_TYPE_STRING, { .str = "1" }, .flags = AFLAGS }, { "expr", "An expression to use for selecting frames", OFFSET(expr_str), AV_OPT_TYPE_STRING, { .str = "1" }, .flags = AFLAGS },
{ "e", "An expression to use for selecting frames", OFFSET(expr_str), AV_OPT_TYPE_STRING, { .str = "1" }, .flags = AFLAGS }, { "e", "An expression to use for selecting frames", OFFSET(expr_str), AV_OPT_TYPE_STRING, { .str = "1" }, .flags = AFLAGS },
{ "outputs", "set the number of outputs", OFFSET(nb_outputs), AV_OPT_TYPE_INT, {.i64 = 1}, 1, INT_MAX, AFLAGS },
{ "n", "set the number of outputs", OFFSET(nb_outputs), AV_OPT_TYPE_INT, {.i64 = 1}, 1, INT_MAX, AFLAGS },
{ NULL }, { NULL },
}; };
AVFILTER_DEFINE_CLASS(aselect); AVFILTER_DEFINE_CLASS(aselect);
...@@ -424,14 +454,6 @@ static const AVFilterPad avfilter_af_aselect_inputs[] = { ...@@ -424,14 +454,6 @@ static const AVFilterPad avfilter_af_aselect_inputs[] = {
{ NULL } { NULL }
}; };
static const AVFilterPad avfilter_af_aselect_outputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_AUDIO,
},
{ NULL }
};
AVFilter avfilter_af_aselect = { AVFilter avfilter_af_aselect = {
.name = "aselect", .name = "aselect",
.description = NULL_IF_CONFIG_SMALL("Select audio frames to pass in output."), .description = NULL_IF_CONFIG_SMALL("Select audio frames to pass in output."),
...@@ -439,8 +461,8 @@ AVFilter avfilter_af_aselect = { ...@@ -439,8 +461,8 @@ AVFilter avfilter_af_aselect = {
.uninit = uninit, .uninit = uninit,
.priv_size = sizeof(SelectContext), .priv_size = sizeof(SelectContext),
.inputs = avfilter_af_aselect_inputs, .inputs = avfilter_af_aselect_inputs,
.outputs = avfilter_af_aselect_outputs,
.priv_class = &aselect_class, .priv_class = &aselect_class,
.flags = AVFILTER_FLAG_DYNAMIC_OUTPUTS,
}; };
#endif /* CONFIG_ASELECT_FILTER */ #endif /* CONFIG_ASELECT_FILTER */
...@@ -451,6 +473,8 @@ AVFilter avfilter_af_aselect = { ...@@ -451,6 +473,8 @@ AVFilter avfilter_af_aselect = {
static const AVOption select_options[] = { static const AVOption select_options[] = {
{ "expr", "An expression to use for selecting frames", OFFSET(expr_str), AV_OPT_TYPE_STRING, { .str = "1" }, .flags = FLAGS }, { "expr", "An expression to use for selecting frames", OFFSET(expr_str), AV_OPT_TYPE_STRING, { .str = "1" }, .flags = FLAGS },
{ "e", "An expression to use for selecting frames", OFFSET(expr_str), AV_OPT_TYPE_STRING, { .str = "1" }, .flags = FLAGS }, { "e", "An expression to use for selecting frames", OFFSET(expr_str), AV_OPT_TYPE_STRING, { .str = "1" }, .flags = FLAGS },
{ "outputs", "set the number of outputs", OFFSET(nb_outputs), AV_OPT_TYPE_INT, {.i64 = 1}, 1, INT_MAX, FLAGS },
{ "n", "set the number of outputs", OFFSET(nb_outputs), AV_OPT_TYPE_INT, {.i64 = 1}, 1, INT_MAX, FLAGS },
{ NULL }, { NULL },
}; };
...@@ -483,15 +507,6 @@ static const AVFilterPad avfilter_vf_select_inputs[] = { ...@@ -483,15 +507,6 @@ static const AVFilterPad avfilter_vf_select_inputs[] = {
{ NULL } { NULL }
}; };
static const AVFilterPad avfilter_vf_select_outputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
.request_frame = request_frame,
},
{ NULL }
};
AVFilter avfilter_vf_select = { AVFilter avfilter_vf_select = {
.name = "select", .name = "select",
.description = NULL_IF_CONFIG_SMALL("Select video frames to pass in output."), .description = NULL_IF_CONFIG_SMALL("Select video frames to pass in output."),
...@@ -503,6 +518,6 @@ AVFilter avfilter_vf_select = { ...@@ -503,6 +518,6 @@ AVFilter avfilter_vf_select = {
.priv_class = &select_class, .priv_class = &select_class,
.inputs = avfilter_vf_select_inputs, .inputs = avfilter_vf_select_inputs,
.outputs = avfilter_vf_select_outputs, .flags = AVFILTER_FLAG_DYNAMIC_OUTPUTS,
}; };
#endif /* CONFIG_SELECT_FILTER */ #endif /* CONFIG_SELECT_FILTER */
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
#define LIBAVFILTER_VERSION_MAJOR 3 #define LIBAVFILTER_VERSION_MAJOR 3
#define LIBAVFILTER_VERSION_MINOR 56 #define LIBAVFILTER_VERSION_MINOR 56
#define LIBAVFILTER_VERSION_MICRO 102 #define LIBAVFILTER_VERSION_MICRO 103
#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
LIBAVFILTER_VERSION_MINOR, \ LIBAVFILTER_VERSION_MINOR, \
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment