lavfi/volume: support volume expression and per-frame expression evaluation

The eval mode allows to evaluate the expression per-frame or just at
init.

In particular, address ticket #3234.
This commit is contained in:
Stefano Sabatini 2013-02-23 00:17:17 +01:00
parent 239a0543a6
commit 7619a87cc8
4 changed files with 208 additions and 21 deletions

View File

@ -1796,7 +1796,7 @@ The filter accepts the following options:
@table @option @table @option
@item volume @item volume
Expresses how the audio volume will be increased or decreased. Set audio volume expression.
Output values are clipped to the maximum value. Output values are clipped to the maximum value.
@ -1805,7 +1805,7 @@ The output audio volume is given by the relation:
@var{output_volume} = @var{volume} * @var{input_volume} @var{output_volume} = @var{volume} * @var{input_volume}
@end example @end example
Default value for @var{volume} is 1.0. Default value for @var{volume} is "1.0".
@item precision @item precision
Set the mathematical precision. Set the mathematical precision.
@ -1821,8 +1821,55 @@ precision of the volume scaling.
@item double @item double
64-bit floating-point; limits input sample format to DBL. 64-bit floating-point; limits input sample format to DBL.
@end table @end table
@item eval
Set when the volume expression is evaluated.
It accepts the following values:
@table @samp
@item once
only evaluate expression once during the filter initialization
@item frame
evaluate expression for each incoming frame
@end table @end table
Default value is @samp{once}.
@end table
The volume expression can contain the following parameters.
@table @option
@item n
frame number (starting at zero)
@item nb_channels
number of channels
@item nb_consumed_samples
number of samples consumed by the filter
@item nb_samples
number of samples in the current frame
@item pos
original frame position in the file
@item pts
frame PTS
@item sample_rate
sample rate
@item startpts
PTS at start of stream
@item startt
time at start of stream
@item t
frame time
@item tb
timestamp timebase
@item volume
last set volume value
@end table
Note that when @option{eval} is set to @samp{once} only the
@var{sample_rate} and @var{tb} variables are available, all other
variables will evaluate to NAN.
@subsection Examples @subsection Examples
@itemize @itemize
@ -1845,6 +1892,12 @@ Increase input audio power by 6 decibels using fixed-point precision:
@example @example
volume=volume=6dB:precision=fixed volume=volume=6dB:precision=fixed
@end example @end example
@item
Fade volume after time 10 with an annihilation period of 5 seconds:
@example
volume='if(lt(t,10),1,max(1-(t-10)/5,0))':eval=frame
@end example
@end itemize @end itemize
@section volumedetect @section volumedetect

View File

@ -39,39 +39,73 @@ static const char *precision_str[] = {
"fixed", "float", "double" "fixed", "float", "double"
}; };
static const char *const var_names[] = {
"n", ///< frame number (starting at zero)
"nb_channels", ///< number of channels
"nb_consumed_samples", ///< number of samples consumed by the filter
"nb_samples", ///< number of samples in the current frame
"pos", ///< position in the file of the frame
"pts", ///< frame presentation timestamp
"sample_rate", ///< sample rate
"startpts", ///< PTS at start of stream
"startt", ///< time at start of stream
"t", ///< time in the file of the frame
"tb", ///< timebase
"volume", ///< last set value
NULL
};
#define OFFSET(x) offsetof(VolumeContext, x) #define OFFSET(x) offsetof(VolumeContext, x)
#define A AV_OPT_FLAG_AUDIO_PARAM #define A AV_OPT_FLAG_AUDIO_PARAM
#define F AV_OPT_FLAG_FILTERING_PARAM #define F AV_OPT_FLAG_FILTERING_PARAM
static const AVOption volume_options[] = { static const AVOption volume_options[] = {
{ "volume", "set volume adjustment", { "volume", "set volume adjustment expression",
OFFSET(volume), AV_OPT_TYPE_DOUBLE, { .dbl = 1.0 }, 0, 0x7fffff, A|F }, OFFSET(volume_expr), AV_OPT_TYPE_STRING, { .str = "1.0" }, .flags = A|F },
{ "precision", "select mathematical precision", { "precision", "select mathematical precision",
OFFSET(precision), AV_OPT_TYPE_INT, { .i64 = PRECISION_FLOAT }, PRECISION_FIXED, PRECISION_DOUBLE, A|F, "precision" }, OFFSET(precision), AV_OPT_TYPE_INT, { .i64 = PRECISION_FLOAT }, PRECISION_FIXED, PRECISION_DOUBLE, A|F, "precision" },
{ "fixed", "select 8-bit fixed-point", 0, AV_OPT_TYPE_CONST, { .i64 = PRECISION_FIXED }, INT_MIN, INT_MAX, A|F, "precision" }, { "fixed", "select 8-bit fixed-point", 0, AV_OPT_TYPE_CONST, { .i64 = PRECISION_FIXED }, INT_MIN, INT_MAX, A|F, "precision" },
{ "float", "select 32-bit floating-point", 0, AV_OPT_TYPE_CONST, { .i64 = PRECISION_FLOAT }, INT_MIN, INT_MAX, A|F, "precision" }, { "float", "select 32-bit floating-point", 0, AV_OPT_TYPE_CONST, { .i64 = PRECISION_FLOAT }, INT_MIN, INT_MAX, A|F, "precision" },
{ "double", "select 64-bit floating-point", 0, AV_OPT_TYPE_CONST, { .i64 = PRECISION_DOUBLE }, INT_MIN, INT_MAX, A|F, "precision" }, { "double", "select 64-bit floating-point", 0, AV_OPT_TYPE_CONST, { .i64 = PRECISION_DOUBLE }, INT_MIN, INT_MAX, A|F, "precision" },
{ "eval", "specify when to evaluate expressions", OFFSET(eval_mode), AV_OPT_TYPE_INT, {.i64 = EVAL_MODE_ONCE}, 0, EVAL_MODE_NB-1, .flags = A|F, "eval" },
{ "once", "eval volume expression once", 0, AV_OPT_TYPE_CONST, {.i64=EVAL_MODE_ONCE}, .flags = A|F, .unit = "eval" },
{ "frame", "eval volume expression per-frame", 0, AV_OPT_TYPE_CONST, {.i64=EVAL_MODE_FRAME}, .flags = A|F, .unit = "eval" },
{ NULL } { NULL }
}; };
AVFILTER_DEFINE_CLASS(volume); AVFILTER_DEFINE_CLASS(volume);
static int set_expr(AVExpr **pexpr, const char *expr, void *log_ctx)
{
int ret;
AVExpr *old = NULL;
if (*pexpr)
old = *pexpr;
ret = av_expr_parse(pexpr, expr, var_names,
NULL, NULL, NULL, NULL, 0, log_ctx);
if (ret < 0) {
av_log(log_ctx, AV_LOG_ERROR,
"Error when evaluating the volume expression '%s'\n", expr);
*pexpr = old;
return ret;
}
av_expr_free(old);
return 0;
}
static av_cold int init(AVFilterContext *ctx) static av_cold int init(AVFilterContext *ctx)
{ {
VolumeContext *vol = ctx->priv; VolumeContext *vol = ctx->priv;
return set_expr(&vol->volume_pexpr, vol->volume_expr, ctx);
}
if (vol->precision == PRECISION_FIXED) { static av_cold void uninit(AVFilterContext *ctx)
vol->volume_i = (int)(vol->volume * 256 + 0.5); {
vol->volume = vol->volume_i / 256.0; VolumeContext *vol = ctx->priv;
av_log(ctx, AV_LOG_VERBOSE, "volume:(%d/256)(%f)(%1.2fdB) precision:fixed\n", av_expr_free(vol->volume_pexpr);
vol->volume_i, vol->volume, 20.0*log(vol->volume)/M_LN10); av_opt_free(vol);
} else {
av_log(ctx, AV_LOG_VERBOSE, "volume:(%f)(%1.2fdB) precision:%s\n",
vol->volume, 20.0*log(vol->volume)/M_LN10,
precision_str[vol->precision]);
}
return 0;
} }
static int query_formats(AVFilterContext *ctx) static int query_formats(AVFilterContext *ctx)
@ -199,6 +233,37 @@ static av_cold void volume_init(VolumeContext *vol)
ff_volume_init_x86(vol); ff_volume_init_x86(vol);
} }
static int set_volume(AVFilterContext *ctx)
{
VolumeContext *vol = ctx->priv;
vol->volume = av_expr_eval(vol->volume_pexpr, vol->var_values, NULL);
if (isnan(vol->volume)) {
if (vol->eval_mode == EVAL_MODE_ONCE) {
av_log(ctx, AV_LOG_ERROR, "Invalid value NaN for volume\n");
return AVERROR(EINVAL);
} else {
av_log(ctx, AV_LOG_WARNING, "Invalid value NaN for volume, setting to 0\n");
vol->volume = 0;
}
}
vol->var_values[VAR_VOLUME] = vol->volume;
if (vol->precision == PRECISION_FIXED) {
vol->volume_i = (int)(vol->volume * 256 + 0.5);
vol->volume = vol->volume_i / 256.0;
av_log(ctx, AV_LOG_VERBOSE, "volume:(%d/256)(%f)(%1.2fdB) precision:fixed\n",
vol->volume_i, vol->volume, 20.0*log(vol->volume)/M_LN10);
} else {
av_log(ctx, AV_LOG_VERBOSE, "volume:(%f)(%1.2fdB) precision:%s\n",
vol->volume, 20.0*log(vol->volume)/M_LN10,
precision_str[vol->precision]);
}
volume_init(vol);
return 0;
}
static int config_output(AVFilterLink *outlink) static int config_output(AVFilterLink *outlink)
{ {
AVFilterContext *ctx = outlink->src; AVFilterContext *ctx = outlink->src;
@ -209,20 +274,58 @@ static int config_output(AVFilterLink *outlink)
vol->channels = inlink->channels; vol->channels = inlink->channels;
vol->planes = av_sample_fmt_is_planar(inlink->format) ? vol->channels : 1; vol->planes = av_sample_fmt_is_planar(inlink->format) ? vol->channels : 1;
volume_init(vol); vol->var_values[VAR_N] =
vol->var_values[VAR_NB_CONSUMED_SAMPLES] =
vol->var_values[VAR_NB_SAMPLES] =
vol->var_values[VAR_POS] =
vol->var_values[VAR_PTS] =
vol->var_values[VAR_STARTPTS] =
vol->var_values[VAR_STARTT] =
vol->var_values[VAR_T] =
vol->var_values[VAR_VOLUME] = NAN;
return 0; vol->var_values[VAR_NB_CHANNELS] = inlink->channels;
vol->var_values[VAR_TB] = av_q2d(inlink->time_base);
vol->var_values[VAR_SAMPLE_RATE] = inlink->sample_rate;
av_log(inlink->src, AV_LOG_VERBOSE, "tb:%f sample_rate:%f nb_channels:%f\n",
vol->var_values[VAR_TB],
vol->var_values[VAR_SAMPLE_RATE],
vol->var_values[VAR_NB_CHANNELS]);
return set_volume(ctx);
} }
#define D2TS(d) (isnan(d) ? AV_NOPTS_VALUE : (int64_t)(d))
#define TS2D(ts) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts))
#define TS2T(ts, tb) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts)*av_q2d(tb))
static int filter_frame(AVFilterLink *inlink, AVFrame *buf) static int filter_frame(AVFilterLink *inlink, AVFrame *buf)
{ {
AVFilterContext *ctx = inlink->dst;
VolumeContext *vol = inlink->dst->priv; VolumeContext *vol = inlink->dst->priv;
AVFilterLink *outlink = inlink->dst->outputs[0]; AVFilterLink *outlink = inlink->dst->outputs[0];
int nb_samples = buf->nb_samples; int nb_samples = buf->nb_samples;
AVFrame *out_buf; AVFrame *out_buf;
int64_t pos;
if (vol->volume == 1.0 || vol->volume_i == 256) if (isnan(vol->var_values[VAR_STARTPTS])) {
return ff_filter_frame(outlink, buf); vol->var_values[VAR_STARTPTS] = TS2D(buf->pts);
vol->var_values[VAR_STARTT ] = TS2T(buf->pts, inlink->time_base);
}
vol->var_values[VAR_PTS] = TS2D(buf->pts);
vol->var_values[VAR_T ] = TS2T(buf->pts, inlink->time_base);
vol->var_values[VAR_N ] = inlink->frame_count;
pos = av_frame_get_pkt_pos(buf);
vol->var_values[VAR_POS] = pos == -1 ? NAN : pos;
if (vol->eval_mode == EVAL_MODE_FRAME)
set_volume(ctx);
if (vol->volume == 1.0 || vol->volume_i == 256) {
out_buf = buf;
goto end;
}
/* do volume scaling in-place if input buffer is writable */ /* do volume scaling in-place if input buffer is writable */
if (av_frame_is_writable(buf)) { if (av_frame_is_writable(buf)) {
@ -266,6 +369,8 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *buf)
if (buf != out_buf) if (buf != out_buf)
av_frame_free(&buf); av_frame_free(&buf);
end:
vol->var_values[VAR_NB_CONSUMED_SAMPLES] += buf->nb_samples;
return ff_filter_frame(outlink, out_buf); return ff_filter_frame(outlink, out_buf);
} }
@ -294,6 +399,7 @@ AVFilter ff_af_volume = {
.priv_size = sizeof(VolumeContext), .priv_size = sizeof(VolumeContext),
.priv_class = &volume_class, .priv_class = &volume_class,
.init = init, .init = init,
.uninit = uninit,
.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_GENERIC, .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,

View File

@ -25,6 +25,7 @@
#define AVFILTER_AF_VOLUME_H #define AVFILTER_AF_VOLUME_H
#include "libavutil/common.h" #include "libavutil/common.h"
#include "libavutil/eval.h"
#include "libavutil/float_dsp.h" #include "libavutil/float_dsp.h"
#include "libavutil/opt.h" #include "libavutil/opt.h"
#include "libavutil/samplefmt.h" #include "libavutil/samplefmt.h"
@ -35,10 +36,37 @@ enum PrecisionType {
PRECISION_DOUBLE, PRECISION_DOUBLE,
}; };
enum EvalMode {
EVAL_MODE_ONCE,
EVAL_MODE_FRAME,
EVAL_MODE_NB
};
enum VolumeVarName {
VAR_N,
VAR_NB_CHANNELS,
VAR_NB_CONSUMED_SAMPLES,
VAR_NB_SAMPLES,
VAR_POS,
VAR_PTS,
VAR_SAMPLE_RATE,
VAR_STARTPTS,
VAR_STARTT,
VAR_T,
VAR_TB,
VAR_VOLUME,
VAR_VARS_NB
};
typedef struct VolumeContext { typedef struct VolumeContext {
const AVClass *class; const AVClass *class;
AVFloatDSPContext fdsp; AVFloatDSPContext fdsp;
enum PrecisionType precision; enum PrecisionType precision;
enum EvalMode eval_mode;
const char *volume_expr;
AVExpr *volume_pexpr;
double var_values[VAR_VARS_NB];
double volume; double volume;
int volume_i; int volume_i;
int channels; int channels;

View File

@ -31,7 +31,7 @@
#define LIBAVFILTER_VERSION_MAJOR 4 #define LIBAVFILTER_VERSION_MAJOR 4
#define LIBAVFILTER_VERSION_MINOR 0 #define LIBAVFILTER_VERSION_MINOR 0
#define LIBAVFILTER_VERSION_MICRO 100 #define LIBAVFILTER_VERSION_MICRO 101
#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, \