You've already forked FFmpeg
mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2025-08-15 14:13:16 +02:00
lavfi/select: add support for dynamic number of outputs
This commit is contained in:
@@ -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
|
|||||||
|
|
||||||
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 @@
|
|||||||
* 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 {
|
|||||||
#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)
|
|||||||
}
|
}
|
||||||
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)
|
|||||||
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)
|
|||||||
|
|
||||||
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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
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[] = {
|
|||||||
{ 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 = {
|
|||||||
.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 = {
|
|||||||
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[] = {
|
|||||||
{ 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 = {
|
|||||||
.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 @@
|
|||||||
|
|
||||||
#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, \
|
||||||
|
Reference in New Issue
Block a user