Commit e424abc3 authored by Stefano Sabatini's avatar Stefano Sabatini

lavfi: add aeval filter

parent 405b3eb5
...@@ -12,6 +12,7 @@ version <next> ...@@ -12,6 +12,7 @@ version <next>
- complete Voxware MetaSound decoder - complete Voxware MetaSound decoder
- remove mp3_header_compress bitstream filters - remove mp3_header_compress bitstream filters
- Windows resource files for shared libraries - Windows resource files for shared libraries
- aeval filter
version 2.1: version 2.1:
......
...@@ -435,6 +435,70 @@ aecho=0.8:0.9:1000|1800:0.3|0.25 ...@@ -435,6 +435,70 @@ aecho=0.8:0.9:1000|1800:0.3|0.25
@end example @end example
@end itemize @end itemize
@section aeval
Modify an audio signal according to the specified expressions.
This filter accepts one or more expressions (one for each channel),
which are evaluated and used to modify a corresponding audio signal.
This filter accepts the following options:
@table @option
@item exprs
Set the '|'-separated expressions list for each separate channel. If
the number of input channels is greater than the number of
expressions, the last specified expression is used for the remaining
output channels.
@item channel_layout, c
Set output channel layout. If not specified, the channel layout is
specified by the number of expressions. If set to @samp{same}, it will
use by default the same input channel layout.
@end table
Each expression in @var{exprs} can contain the following constants and functions:
@table @option
@item ch
channel number of the current expression
@item n
number of the evaluated sample, starting from 0
@item s
sample rate
@item t
time of the evaluated sample expressed in seconds
@item nb_in_channels
@item nb_out_channels
input and output number of channels
@item val(CH)
the value of input channel with number @var{CH}
@end table
Note: this filter is slow. For faster processing you should use a
dedicated filter.
@subsection Examples
@itemize
@item
Half volume:
@example
aeval=val(ch)/2:c=same
@end example
@item
Invert phase of the second channel:
@example
eval=val(0)|-val(1)
@end example
@end itemize
@section afade @section afade
Apply fade-in/out effect to input audio. Apply fade-in/out effect to input audio.
......
...@@ -53,6 +53,7 @@ OBJS-$(CONFIG_AVCODEC) += avcodec.o ...@@ -53,6 +53,7 @@ OBJS-$(CONFIG_AVCODEC) += avcodec.o
OBJS-$(CONFIG_ACONVERT_FILTER) += af_aconvert.o OBJS-$(CONFIG_ACONVERT_FILTER) += af_aconvert.o
OBJS-$(CONFIG_ADELAY_FILTER) += af_adelay.o OBJS-$(CONFIG_ADELAY_FILTER) += af_adelay.o
OBJS-$(CONFIG_AECHO_FILTER) += af_aecho.o OBJS-$(CONFIG_AECHO_FILTER) += af_aecho.o
OBJS-$(CONFIG_AEVAL_FILTER) += asrc_aevalsrc.o
OBJS-$(CONFIG_AFADE_FILTER) += af_afade.o OBJS-$(CONFIG_AFADE_FILTER) += af_afade.o
OBJS-$(CONFIG_AFORMAT_FILTER) += af_aformat.o OBJS-$(CONFIG_AFORMAT_FILTER) += af_aformat.o
OBJS-$(CONFIG_AINTERLEAVE_FILTER) += f_interleave.o OBJS-$(CONFIG_AINTERLEAVE_FILTER) += f_interleave.o
......
...@@ -50,6 +50,7 @@ void avfilter_register_all(void) ...@@ -50,6 +50,7 @@ void avfilter_register_all(void)
#endif #endif
REGISTER_FILTER(ADELAY, adelay, af); REGISTER_FILTER(ADELAY, adelay, af);
REGISTER_FILTER(AECHO, aecho, af); REGISTER_FILTER(AECHO, aecho, af);
REGISTER_FILTER(AEVAL, aeval, af);
REGISTER_FILTER(AFADE, afade, af); REGISTER_FILTER(AFADE, afade, af);
REGISTER_FILTER(AFORMAT, aformat, af); REGISTER_FILTER(AFORMAT, aformat, af);
REGISTER_FILTER(AINTERLEAVE, ainterleave, af); REGISTER_FILTER(AINTERLEAVE, ainterleave, af);
......
...@@ -34,14 +34,20 @@ ...@@ -34,14 +34,20 @@
#include "internal.h" #include "internal.h"
static const char * const var_names[] = { static const char * const var_names[] = {
"ch", ///< the value of the current channel
"n", ///< number of frame "n", ///< number of frame
"nb_in_channels",
"nb_out_channels",
"t", ///< timestamp expressed in seconds "t", ///< timestamp expressed in seconds
"s", ///< sample rate "s", ///< sample rate
NULL NULL
}; };
enum var_name { enum var_name {
VAR_CH,
VAR_N, VAR_N,
VAR_NB_IN_CHANNELS,
VAR_NB_OUT_CHANNELS,
VAR_T, VAR_T,
VAR_S, VAR_S,
VAR_VARS_NB VAR_VARS_NB
...@@ -53,7 +59,9 @@ typedef struct { ...@@ -53,7 +59,9 @@ typedef struct {
int sample_rate; int sample_rate;
int64_t chlayout; int64_t chlayout;
char *chlayout_str; char *chlayout_str;
int nb_channels; int nb_channels; ///< number of output channels
int nb_in_channels; ///< number of input channels
int same_chlayout; ///< set output as input channel layout
int64_t pts; int64_t pts;
AVExpr **expr; AVExpr **expr;
char *exprs; char *exprs;
...@@ -61,8 +69,19 @@ typedef struct { ...@@ -61,8 +69,19 @@ typedef struct {
int64_t duration; int64_t duration;
uint64_t n; uint64_t n;
double var_values[VAR_VARS_NB]; double var_values[VAR_VARS_NB];
double *channel_values;
int64_t out_channel_layout;
} EvalContext; } EvalContext;
static double val(void *priv, double ch)
{
EvalContext *eval = priv;
return eval->channel_values[FFMIN((int)ch, eval->nb_in_channels-1)];
}
static double (* const aeval_func1[])(void *, double) = { val, NULL };
static const char * const aeval_func1_names[] = { "val", NULL };
#define OFFSET(x) offsetof(EvalContext, x) #define OFFSET(x) offsetof(EvalContext, x)
#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM #define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
...@@ -81,17 +100,27 @@ static const AVOption aevalsrc_options[]= { ...@@ -81,17 +100,27 @@ static const AVOption aevalsrc_options[]= {
AVFILTER_DEFINE_CLASS(aevalsrc); AVFILTER_DEFINE_CLASS(aevalsrc);
static av_cold int init(AVFilterContext *ctx) static int parse_channel_expressions(AVFilterContext *ctx,
int expected_nb_channels)
{ {
EvalContext *eval = ctx->priv; EvalContext *eval = ctx->priv;
char *args1 = av_strdup(eval->exprs); char *args1 = av_strdup(eval->exprs);
char *expr, *last_expr, *buf; char *expr, *last_expr, *buf;
int ret; double (* const *func1)(void *, double) = NULL;
const char * const *func1_names = NULL;
int i, ret = 0;
if (!args1)
return AVERROR(ENOMEM);
if (!args1) { if (!eval->exprs) {
av_log(ctx, AV_LOG_ERROR, "Channels expressions list is empty\n"); av_log(ctx, AV_LOG_ERROR, "Channels expressions list is empty\n");
ret = eval->exprs ? AVERROR(ENOMEM) : AVERROR(EINVAL); return AVERROR(EINVAL);
goto end; }
if (!strcmp(ctx->filter->name, "aeval")) {
func1 = aeval_func1;
func1_names = aeval_func1_names;
} }
#define ADD_EXPRESSION(expr_) do { \ #define ADD_EXPRESSION(expr_) do { \
...@@ -102,56 +131,79 @@ static av_cold int init(AVFilterContext *ctx) ...@@ -102,56 +131,79 @@ static av_cold int init(AVFilterContext *ctx)
} \ } \
eval->expr[eval->nb_channels-1] = NULL; \ eval->expr[eval->nb_channels-1] = NULL; \
ret = av_expr_parse(&eval->expr[eval->nb_channels - 1], expr_, \ ret = av_expr_parse(&eval->expr[eval->nb_channels - 1], expr_, \
var_names, NULL, NULL, \ var_names, func1_names, func1, \
NULL, NULL, 0, ctx); \ NULL, NULL, 0, ctx); \
if (ret < 0) \ if (ret < 0) \
goto end; \ goto end; \
} while (0) } while (0)
/* parse expressions */ /* reset expressions */
for (i = 0; i < eval->nb_channels; i++) {
av_expr_free(eval->expr[i]);
eval->expr[i] = NULL;
}
av_freep(&eval->expr);
eval->nb_channels = 0;
buf = args1; buf = args1;
while (expr = av_strtok(buf, "|", &buf)) { while (expr = av_strtok(buf, "|", &buf)) {
ADD_EXPRESSION(expr); ADD_EXPRESSION(expr);
last_expr = expr; last_expr = expr;
} }
if (eval->chlayout_str) { if (expected_nb_channels > eval->nb_channels)
int i, n; for (i = eval->nb_channels; i < expected_nb_channels; i++)
ret = ff_parse_channel_layout(&eval->chlayout, NULL, eval->chlayout_str, ctx); ADD_EXPRESSION(last_expr);
if (ret < 0)
goto end; if (expected_nb_channels > 0 && eval->nb_channels != expected_nb_channels) {
av_log(ctx, AV_LOG_ERROR,
n = av_get_channel_layout_nb_channels(eval->chlayout); "Mismatch between the specified number of channel expressions '%d' "
if (n > eval->nb_channels) { "and the number of expected output channels '%d' for the specified channel layout\n",
for (i = eval->nb_channels; i < n; i++) eval->nb_channels, expected_nb_channels);
ADD_EXPRESSION(last_expr); ret = AVERROR(EINVAL);
} goto end;
}
end:
av_free(args1);
return ret;
}
if (n != eval->nb_channels) { static av_cold int init(AVFilterContext *ctx)
av_log(ctx, AV_LOG_ERROR, {
"Mismatch between the specified number of channels '%d' " EvalContext *eval = ctx->priv;
"and the number of channels '%d' in the specified channel layout '%s'\n", int ret;
eval->nb_channels, n, eval->chlayout_str);
ret = AVERROR(EINVAL); if (eval->chlayout_str) {
goto end; if (!strcmp(eval->chlayout_str, "same") && !strcmp(ctx->filter->name, "aeval")) {
eval->same_chlayout = 1;
} else {
ret = ff_parse_channel_layout(&eval->chlayout, NULL, eval->chlayout_str, ctx);
if (ret < 0)
return ret;
ret = parse_channel_expressions(ctx, av_get_channel_layout_nb_channels(eval->chlayout));
if (ret < 0)
return ret;
} }
} else { } else {
/* guess channel layout from nb expressions/channels */ /* guess channel layout from nb expressions/channels */
if ((ret = parse_channel_expressions(ctx, -1)) < 0)
return ret;
eval->chlayout = av_get_default_channel_layout(eval->nb_channels); eval->chlayout = av_get_default_channel_layout(eval->nb_channels);
if (!eval->chlayout && eval->nb_channels <= 0) { if (!eval->chlayout && eval->nb_channels <= 0) {
av_log(ctx, AV_LOG_ERROR, "Invalid number of channels '%d' provided\n", av_log(ctx, AV_LOG_ERROR, "Invalid number of channels '%d' provided\n",
eval->nb_channels); eval->nb_channels);
ret = AVERROR(EINVAL); return AVERROR(EINVAL);
goto end;
} }
} }
if ((ret = ff_parse_sample_rate(&eval->sample_rate, eval->sample_rate_str, ctx))) if (eval->sample_rate_str)
goto end; if ((ret = ff_parse_sample_rate(&eval->sample_rate, eval->sample_rate_str, ctx)))
return ret;
eval->n = 0; eval->n = 0;
end:
av_free(args1);
return ret; return ret;
} }
...@@ -176,6 +228,8 @@ static int config_props(AVFilterLink *outlink) ...@@ -176,6 +228,8 @@ static int config_props(AVFilterLink *outlink)
outlink->sample_rate = eval->sample_rate; outlink->sample_rate = eval->sample_rate;
eval->var_values[VAR_S] = eval->sample_rate; eval->var_values[VAR_S] = eval->sample_rate;
eval->var_values[VAR_NB_IN_CHANNELS] = NAN;
eval->var_values[VAR_NB_OUT_CHANNELS] = outlink->channels;
av_get_channel_layout_string(buf, sizeof(buf), 0, eval->chlayout); av_get_channel_layout_string(buf, sizeof(buf), 0, eval->chlayout);
...@@ -232,6 +286,7 @@ static int request_frame(AVFilterLink *outlink) ...@@ -232,6 +286,7 @@ static int request_frame(AVFilterLink *outlink)
return ff_filter_frame(outlink, samplesref); return ff_filter_frame(outlink, samplesref);
} }
#if CONFIG_AEVALSRC_FILTER
static const AVFilterPad aevalsrc_outputs[] = { static const AVFilterPad aevalsrc_outputs[] = {
{ {
.name = "default", .name = "default",
...@@ -253,3 +308,159 @@ AVFilter ff_asrc_aevalsrc = { ...@@ -253,3 +308,159 @@ AVFilter ff_asrc_aevalsrc = {
.outputs = aevalsrc_outputs, .outputs = aevalsrc_outputs,
.priv_class = &aevalsrc_class, .priv_class = &aevalsrc_class,
}; };
#endif /* CONFIG_AEVALSRC_FILTER */
#define OFFSET(x) offsetof(EvalContext, x)
#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
static const AVOption aeval_options[]= {
{ "exprs", "set the '|'-separated list of channels expressions", OFFSET(exprs), AV_OPT_TYPE_STRING, {.str = NULL}, .flags = FLAGS },
{ "channel_layout", "set channel layout", OFFSET(chlayout_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
{ "c", "set channel layout", OFFSET(chlayout_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
{ NULL }
};
AVFILTER_DEFINE_CLASS(aeval);
static int aeval_query_formats(AVFilterContext *ctx)
{
AVFilterFormats *formats = NULL;
AVFilterChannelLayouts *layouts;
AVFilterLink *inlink = ctx->inputs[0];
AVFilterLink *outlink = ctx->outputs[0];
EvalContext *eval = ctx->priv;
static const enum AVSampleFormat sample_fmts[] = {
AV_SAMPLE_FMT_DBLP, AV_SAMPLE_FMT_NONE
};
// inlink supports any channel layout
layouts = ff_all_channel_counts();
ff_channel_layouts_ref(layouts, &inlink->out_channel_layouts);
if (eval->same_chlayout) {
layouts = ff_all_channel_counts();
if (!layouts)
return AVERROR(ENOMEM);
ff_set_common_channel_layouts(ctx, layouts);
} else {
// outlink supports only requested output channel layout
layouts = NULL;
ff_add_channel_layout(&layouts,
eval->out_channel_layout ? eval->out_channel_layout :
FF_COUNT2LAYOUT(eval->nb_channels));
ff_channel_layouts_ref(layouts, &outlink->in_channel_layouts);
}
formats = ff_make_format_list(sample_fmts);
if (!formats)
return AVERROR(ENOMEM);
ff_set_common_formats(ctx, formats);
formats = ff_all_samplerates();
if (!formats)
return AVERROR(ENOMEM);
ff_set_common_samplerates(ctx, formats);
return 0;
}
static int aeval_config_output(AVFilterLink *outlink)
{
AVFilterContext *ctx = outlink->src;
EvalContext *eval = ctx->priv;
AVFilterLink *inlink = ctx->inputs[0];
int ret;
if (eval->same_chlayout) {
eval->chlayout = inlink->channel_layout;
if ((ret = parse_channel_expressions(ctx, inlink->channels)) < 0)
return ret;
}
eval->n = 0;
eval->nb_in_channels = eval->var_values[VAR_NB_IN_CHANNELS] = inlink->channels;
eval->var_values[VAR_NB_OUT_CHANNELS] = outlink->channels;
eval->var_values[VAR_S] = inlink->sample_rate;
eval->var_values[VAR_T] = NAN;
eval->channel_values = av_realloc_f(eval->channel_values,
inlink->channels, sizeof(*eval->channel_values));
if (!eval->channel_values)
return AVERROR(ENOMEM);
return 0;
}
#define TS2T(ts, tb) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts)*av_q2d(tb))
static int filter_frame(AVFilterLink *inlink, AVFrame *in)
{
EvalContext *eval = inlink->dst->priv;
AVFilterLink *outlink = inlink->dst->outputs[0];
int nb_samples = in->nb_samples;
AVFrame *out;
double t0;
int i, j;
/* do volume scaling in-place if input buffer is writable */
out = ff_get_audio_buffer(outlink, nb_samples);
if (!out)
return AVERROR(ENOMEM);
av_frame_copy_props(out, in);
t0 = TS2T(in->pts, inlink->time_base);
/* evaluate expression for each single sample and for each channel */
for (i = 0; i < nb_samples; i++, eval->n++) {
eval->var_values[VAR_N] = eval->n;
eval->var_values[VAR_T] = t0 + i * (double)1/inlink->sample_rate;
for (j = 0; j < inlink->channels; j++)
eval->channel_values[j] = *((double *) in->extended_data[j] + i);
for (j = 0; j < outlink->channels; j++) {
eval->var_values[VAR_CH] = j;
*((double *) out->extended_data[j] + i) =
av_expr_eval(eval->expr[j], eval->var_values, eval);
}
}
av_frame_free(&in);
return ff_filter_frame(outlink, out);
}
#if CONFIG_AEVAL_FILTER
static const AVFilterPad aeval_inputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_AUDIO,
.filter_frame = filter_frame,
},
{ NULL }
};
static const AVFilterPad aeval_outputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_AUDIO,
.config_props = aeval_config_output,
},
{ NULL }
};
AVFilter ff_af_aeval = {
.name = "aeval",
.description = NULL_IF_CONFIG_SMALL("Filter audio signal according to a specified expression."),
.query_formats = aeval_query_formats,
.init = init,
.uninit = uninit,
.priv_size = sizeof(EvalContext),
.inputs = aeval_inputs,
.outputs = aeval_outputs,
.priv_class = &aeval_class,
};
#endif /* CONFIG_AEVAL_FILTER */
...@@ -30,8 +30,8 @@ ...@@ -30,8 +30,8 @@
#include "libavutil/avutil.h" #include "libavutil/avutil.h"
#define LIBAVFILTER_VERSION_MAJOR 3 #define LIBAVFILTER_VERSION_MAJOR 3
#define LIBAVFILTER_VERSION_MINOR 91 #define LIBAVFILTER_VERSION_MINOR 92
#define LIBAVFILTER_VERSION_MICRO 101 #define LIBAVFILTER_VERSION_MICRO 100
#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