[PATCH] lavfi/volume: add dynamic expression evaluation
TODO: update docs, bump micro --- libavfilter/af_volume.c | 120 ++++++++++++++++++++++++++++++++++++----------- libavfilter/af_volume.h | 20 ++++++++ 2 files changed, 113 insertions(+), 27 deletions(-) diff --git a/libavfilter/af_volume.c b/libavfilter/af_volume.c index 5ffa1fe..87898c8 100644 --- a/libavfilter/af_volume.c +++ b/libavfilter/af_volume.c @@ -29,6 +29,7 @@ #include "libavutil/eval.h" #include "libavutil/float_dsp.h" #include "libavutil/opt.h" +#include "libavutil/time.h" #include "audio.h" #include "avfilter.h" #include "formats.h" @@ -39,13 +40,28 @@ static const char *precision_str[] = { "fixed", "float", "double" }; +static const char *const var_names[] = { + "N", ///< frame number (starting at zero) + "NB_CONSUMED_SAMPLES", ///< number of samples consumed by the filter (only audio) + "NB_SAMPLES", ///< number of samples in the current frame (only audio) + "POS", ///< original position in the file of the frame + "PTS", ///< original pts in the file of the frame + "RTC_STARTT", ///< start wallclock (RTC) time in micro seconds + "SAMPLE_RATE", ///< sample rate (only audio) + "STARTPTS", ///< PTS at start of stream + "STARTT", ///< time at start of stream + "T", ///< original time in the file of the frame + "TB", ///< timebase + NULL +}; + #define OFFSET(x) offsetof(VolumeContext, x) #define A AV_OPT_FLAG_AUDIO_PARAM #define F AV_OPT_FLAG_FILTERING_PARAM static const AVOption volume_options[] = { - { "volume", "set volume adjustment", - OFFSET(volume), AV_OPT_TYPE_DOUBLE, { .dbl = 1.0 }, 0, 0x7fffff, A|F }, + { "volume", "set volume adjustment expression", + OFFSET(volume_expr), AV_OPT_TYPE_STRING, { .str = "1.0" }, .flags = A|F }, { "precision", "select mathematical 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" }, @@ -68,21 +84,25 @@ static av_cold int init(AVFilterContext *ctx, const char *args) if ((ret = av_opt_set_from_string(vol, args, shorthand, "=", ":")) < 0) return ret; - 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]); + if ((ret = av_expr_parse(&vol->volume_pexpr,vol->volume_expr, + var_names, NULL, NULL, NULL, NULL, 0, ctx)) < 0) { + av_log(ctx, AV_LOG_ERROR, "Error while parsing expression '%s'\n", vol->volume_expr); + return ret; } - av_opt_free(vol); + vol->var_values[VAR_N ] = 0.0; + vol->var_values[VAR_STARTPTS] = vol->var_values[VAR_STARTT] = NAN; + return ret; } +static av_cold void uninit(AVFilterContext *ctx) +{ + VolumeContext *vol = ctx->priv; + av_expr_free(vol->volume_pexpr); + av_opt_free(vol); +} + static int query_formats(AVFilterContext *ctx) { VolumeContext *vol = ctx->priv; @@ -182,18 +202,6 @@ static void volume_init(VolumeContext *vol) vol->samples_align = 1; switch (av_get_packed_sample_fmt(vol->sample_fmt)) { - case AV_SAMPLE_FMT_U8: - if (vol->volume_i < 0x1000000) - vol->scale_samples = scale_samples_u8_small; - else - vol->scale_samples = scale_samples_u8; - break; - case AV_SAMPLE_FMT_S16: - if (vol->volume_i < 0x10000) - vol->scale_samples = scale_samples_s16_small; - else - vol->scale_samples = scale_samples_s16; - break; case AV_SAMPLE_FMT_S32: vol->scale_samples = scale_samples_s32; break; @@ -221,20 +229,73 @@ static int config_output(AVFilterLink *outlink) vol->channels = av_get_channel_layout_nb_channels(inlink->channel_layout); vol->planes = av_sample_fmt_is_planar(inlink->format) ? vol->channels : 1; - volume_init(vol); + vol->var_values[VAR_TB] = av_q2d(inlink->time_base); + vol->var_values[VAR_RTC_STARTT] = av_gettime(); + vol->var_values[VAR_SAMPLE_RATE] = inlink->sample_rate; + + av_log(inlink->src, AV_LOG_VERBOSE, "TB:%f SAMPLE_RATE:%f RTC_STARTT:%f\n", + vol->var_values[VAR_TB], + vol->var_values[VAR_SAMPLE_RATE], + vol->var_values[VAR_RTC_STARTT]); + volume_init(vol); return 0; } +#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, AVFilterBufferRef *buf) { + AVFilterContext *ctx = inlink->dst; VolumeContext *vol = inlink->dst->priv; AVFilterLink *outlink = inlink->dst->outputs[0]; int nb_samples = buf->audio->nb_samples; AVFilterBufferRef *out_buf; - if (vol->volume == 1.0 || vol->volume_i == 256) - return ff_filter_frame(outlink, buf); + if (isnan(vol->var_values[VAR_STARTPTS])) { + 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_POS] = buf->pos == -1 ? NAN : buf->pos; + vol->volume = av_expr_eval(vol->volume_pexpr, vol->var_values, NULL); + if (isnan(vol->volume)) { + av_log(ctx, AV_LOG_WARNING, "Invalid value NaN for volume, setting to 0\n"); + vol->volume = 0; + } + + 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_DEBUG, "volume_i:%d/256 volume:%f volume_db:%1.2f\n", + vol->volume_i, vol->volume, 20.0*log(vol->volume)/M_LN10); + } else { + av_log(ctx, AV_LOG_DEBUG, "volume:%f volume_db:%1.2f\n", + vol->volume, 20.0*log(vol->volume)/M_LN10); + } + + if (vol->volume == 1.0 || vol->volume_i == 256) { + out_buf = buf; + goto end; + } + + switch (av_get_packed_sample_fmt(vol->sample_fmt)) { + case AV_SAMPLE_FMT_U8: + if (vol->volume_i < 0x1000000) + vol->scale_samples = scale_samples_u8_small; + else + vol->scale_samples = scale_samples_u8; + break; + case AV_SAMPLE_FMT_S16: + if (vol->volume_i < 0x10000) + vol->scale_samples = scale_samples_s16_small; + else + vol->scale_samples = scale_samples_s16; + break; + } /* do volume scaling in-place if input buffer is writable */ if (buf->perms & AV_PERM_WRITE) { @@ -278,6 +339,10 @@ static int filter_frame(AVFilterLink *inlink, AVFilterBufferRef *buf) if (buf != out_buf) avfilter_unref_buffer(buf); +end: + vol->var_values[VAR_N] += 1.0; + vol->var_values[VAR_NB_CONSUMED_SAMPLES] += buf->audio->nb_samples; + return ff_filter_frame(outlink, out_buf); } @@ -305,6 +370,7 @@ AVFilter avfilter_af_volume = { .query_formats = query_formats, .priv_size = sizeof(VolumeContext), .init = init, + .uninit = uninit, .inputs = avfilter_af_volume_inputs, .outputs = avfilter_af_volume_outputs, .priv_class = &volume_class, diff --git a/libavfilter/af_volume.h b/libavfilter/af_volume.h index bd7932e..38e43a8 100644 --- a/libavfilter/af_volume.h +++ b/libavfilter/af_volume.h @@ -25,6 +25,7 @@ #define AVFILTER_AF_VOLUME_H #include "libavutil/common.h" +#include "libavutil/eval.h" #include "libavutil/float_dsp.h" #include "libavutil/opt.h" #include "libavutil/samplefmt.h" @@ -35,10 +36,29 @@ enum PrecisionType { PRECISION_DOUBLE, }; +enum VolumeVarName { + VAR_N, + VAR_NB_CONSUMED_SAMPLES, + VAR_NB_SAMPLES, + VAR_POS, + VAR_PTS, + VAR_RTC_STARTT, + VAR_SAMPLE_RATE, + VAR_STARTPTS, + VAR_STARTT, + VAR_T, + VAR_TB, + VAR_VARS_NB +}; + typedef struct VolumeContext { const AVClass *class; AVFloatDSPContext fdsp; enum PrecisionType precision; + const char *volume_expr; + AVExpr *volume_pexpr; + double var_values[VAR_VARS_NB]; + double volume; int volume_i; int channels; -- 1.7.9.5
On 2/23/13, Stefano Sabatini <stefasab@gmail.com> wrote:
TODO: update docs, bump micro --- libavfilter/af_volume.c | 120 ++++++++++++++++++++++++++++++++++++----------- libavfilter/af_volume.h | 20 ++++++++ 2 files changed, 113 insertions(+), 27 deletions(-)
diff --git a/libavfilter/af_volume.c b/libavfilter/af_volume.c index 5ffa1fe..87898c8 100644 --- a/libavfilter/af_volume.c +++ b/libavfilter/af_volume.c @@ -29,6 +29,7 @@ #include "libavutil/eval.h" #include "libavutil/float_dsp.h" #include "libavutil/opt.h" +#include "libavutil/time.h" #include "audio.h" #include "avfilter.h" #include "formats.h" @@ -39,13 +40,28 @@ static const char *precision_str[] = { "fixed", "float", "double" };
+static const char *const var_names[] = { + "N", ///< frame number (starting at zero) + "NB_CONSUMED_SAMPLES", ///< number of samples consumed by the filter (only audio) + "NB_SAMPLES", ///< number of samples in the current frame (only audio) + "POS", ///< original position in the file of the frame + "PTS", ///< original pts in the file of the frame + "RTC_STARTT", ///< start wallclock (RTC) time in micro seconds + "SAMPLE_RATE", ///< sample rate (only audio) + "STARTPTS", ///< PTS at start of stream + "STARTT", ///< time at start of stream + "T", ///< original time in the file of the frame + "TB", ///< timebase + NULL +}; + #define OFFSET(x) offsetof(VolumeContext, x) #define A AV_OPT_FLAG_AUDIO_PARAM #define F AV_OPT_FLAG_FILTERING_PARAM
static const AVOption volume_options[] = { - { "volume", "set volume adjustment", - OFFSET(volume), AV_OPT_TYPE_DOUBLE, { .dbl = 1.0 }, 0, 0x7fffff, A|F }, + { "volume", "set volume adjustment expression", + OFFSET(volume_expr), AV_OPT_TYPE_STRING, { .str = "1.0" }, .flags = A|F }, { "precision", "select mathematical 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" }, @@ -68,21 +84,25 @@ static av_cold int init(AVFilterContext *ctx, const char *args) if ((ret = av_opt_set_from_string(vol, args, shorthand, "=", ":")) < 0) return ret;
- 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]); + if ((ret = av_expr_parse(&vol->volume_pexpr,vol->volume_expr, + var_names, NULL, NULL, NULL, NULL, 0, ctx)) < 0) { + av_log(ctx, AV_LOG_ERROR, "Error while parsing expression '%s'\n", vol->volume_expr); + return ret; }
- av_opt_free(vol); + vol->var_values[VAR_N ] = 0.0; + vol->var_values[VAR_STARTPTS] = vol->var_values[VAR_STARTT] = NAN; + return ret; }
+static av_cold void uninit(AVFilterContext *ctx) +{ + VolumeContext *vol = ctx->priv; + av_expr_free(vol->volume_pexpr); + av_opt_free(vol); +} + static int query_formats(AVFilterContext *ctx) { VolumeContext *vol = ctx->priv; @@ -182,18 +202,6 @@ static void volume_init(VolumeContext *vol) vol->samples_align = 1;
switch (av_get_packed_sample_fmt(vol->sample_fmt)) { - case AV_SAMPLE_FMT_U8: - if (vol->volume_i < 0x1000000) - vol->scale_samples = scale_samples_u8_small; - else - vol->scale_samples = scale_samples_u8; - break; - case AV_SAMPLE_FMT_S16: - if (vol->volume_i < 0x10000) - vol->scale_samples = scale_samples_s16_small; - else - vol->scale_samples = scale_samples_s16; - break; case AV_SAMPLE_FMT_S32: vol->scale_samples = scale_samples_s32; break; @@ -221,20 +229,73 @@ static int config_output(AVFilterLink *outlink) vol->channels = av_get_channel_layout_nb_channels(inlink->channel_layout); vol->planes = av_sample_fmt_is_planar(inlink->format) ? vol->channels : 1;
- volume_init(vol); + vol->var_values[VAR_TB] = av_q2d(inlink->time_base); + vol->var_values[VAR_RTC_STARTT] = av_gettime(); + vol->var_values[VAR_SAMPLE_RATE] = inlink->sample_rate; + + av_log(inlink->src, AV_LOG_VERBOSE, "TB:%f SAMPLE_RATE:%f RTC_STARTT:%f\n", + vol->var_values[VAR_TB], + vol->var_values[VAR_SAMPLE_RATE], + vol->var_values[VAR_RTC_STARTT]);
+ volume_init(vol); return 0; }
+#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, AVFilterBufferRef *buf) { + AVFilterContext *ctx = inlink->dst; VolumeContext *vol = inlink->dst->priv; AVFilterLink *outlink = inlink->dst->outputs[0]; int nb_samples = buf->audio->nb_samples; AVFilterBufferRef *out_buf;
- if (vol->volume == 1.0 || vol->volume_i == 256) - return ff_filter_frame(outlink, buf); + if (isnan(vol->var_values[VAR_STARTPTS])) { + 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_POS] = buf->pos == -1 ? NAN : buf->pos; + vol->volume = av_expr_eval(vol->volume_pexpr, vol->var_values, NULL); + if (isnan(vol->volume)) { + av_log(ctx, AV_LOG_WARNING, "Invalid value NaN for volume, setting to 0\n"); + vol->volume = 0; + } + + 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_DEBUG, "volume_i:%d/256 volume:%f volume_db:%1.2f\n", + vol->volume_i, vol->volume, 20.0*log(vol->volume)/M_LN10); + } else { + av_log(ctx, AV_LOG_DEBUG, "volume:%f volume_db:%1.2f\n", + vol->volume, 20.0*log(vol->volume)/M_LN10); + } + + if (vol->volume == 1.0 || vol->volume_i == 256) { + out_buf = buf; + goto end; + } + + switch (av_get_packed_sample_fmt(vol->sample_fmt)) { + case AV_SAMPLE_FMT_U8: + if (vol->volume_i < 0x1000000) + vol->scale_samples = scale_samples_u8_small; + else + vol->scale_samples = scale_samples_u8; + break; + case AV_SAMPLE_FMT_S16: + if (vol->volume_i < 0x10000) + vol->scale_samples = scale_samples_s16_small; + else + vol->scale_samples = scale_samples_s16; + break; + }
What about other sample formats?
/* do volume scaling in-place if input buffer is writable */ if (buf->perms & AV_PERM_WRITE) { @@ -278,6 +339,10 @@ static int filter_frame(AVFilterLink *inlink, AVFilterBufferRef *buf) if (buf != out_buf) avfilter_unref_buffer(buf);
+end: + vol->var_values[VAR_N] += 1.0; + vol->var_values[VAR_NB_CONSUMED_SAMPLES] += buf->audio->nb_samples; + return ff_filter_frame(outlink, out_buf); }
@@ -305,6 +370,7 @@ AVFilter avfilter_af_volume = { .query_formats = query_formats, .priv_size = sizeof(VolumeContext), .init = init, + .uninit = uninit, .inputs = avfilter_af_volume_inputs, .outputs = avfilter_af_volume_outputs, .priv_class = &volume_class, diff --git a/libavfilter/af_volume.h b/libavfilter/af_volume.h index bd7932e..38e43a8 100644 --- a/libavfilter/af_volume.h +++ b/libavfilter/af_volume.h @@ -25,6 +25,7 @@ #define AVFILTER_AF_VOLUME_H
#include "libavutil/common.h" +#include "libavutil/eval.h" #include "libavutil/float_dsp.h" #include "libavutil/opt.h" #include "libavutil/samplefmt.h" @@ -35,10 +36,29 @@ enum PrecisionType { PRECISION_DOUBLE, };
+enum VolumeVarName { + VAR_N, + VAR_NB_CONSUMED_SAMPLES, + VAR_NB_SAMPLES, + VAR_POS, + VAR_PTS, + VAR_RTC_STARTT, + VAR_SAMPLE_RATE, + VAR_STARTPTS, + VAR_STARTT, + VAR_T, + VAR_TB, + VAR_VARS_NB +}; + typedef struct VolumeContext { const AVClass *class; AVFloatDSPContext fdsp; enum PrecisionType precision; + const char *volume_expr; + AVExpr *volume_pexpr; + double var_values[VAR_VARS_NB]; + double volume; int volume_i; int channels; -- 1.7.9.5
_______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
Le quintidi 5 ventôse, an CCXXI, Stefano Sabatini a écrit :
TODO: update docs, bump micro --- libavfilter/af_volume.c | 120 ++++++++++++++++++++++++++++++++++++----------- libavfilter/af_volume.h | 20 ++++++++ 2 files changed, 113 insertions(+), 27 deletions(-)
Did you check how it affects speed when the expression is constant and the frames are very small?
diff --git a/libavfilter/af_volume.c b/libavfilter/af_volume.c index 5ffa1fe..87898c8 100644 --- a/libavfilter/af_volume.c +++ b/libavfilter/af_volume.c @@ -29,6 +29,7 @@ #include "libavutil/eval.h" #include "libavutil/float_dsp.h" #include "libavutil/opt.h" +#include "libavutil/time.h" #include "audio.h" #include "avfilter.h" #include "formats.h" @@ -39,13 +40,28 @@ static const char *precision_str[] = { "fixed", "float", "double" };
+static const char *const var_names[] = { + "N", ///< frame number (starting at zero) + "NB_CONSUMED_SAMPLES", ///< number of samples consumed by the filter (only audio) + "NB_SAMPLES", ///< number of samples in the current frame (only audio) + "POS", ///< original position in the file of the frame + "PTS", ///< original pts in the file of the frame + "RTC_STARTT", ///< start wallclock (RTC) time in micro seconds + "SAMPLE_RATE", ///< sample rate (only audio) + "STARTPTS", ///< PTS at start of stream + "STARTT", ///< time at start of stream + "T", ///< original time in the file of the frame + "TB", ///< timebase + NULL +}; + #define OFFSET(x) offsetof(VolumeContext, x) #define A AV_OPT_FLAG_AUDIO_PARAM #define F AV_OPT_FLAG_FILTERING_PARAM
static const AVOption volume_options[] = { - { "volume", "set volume adjustment", - OFFSET(volume), AV_OPT_TYPE_DOUBLE, { .dbl = 1.0 }, 0, 0x7fffff, A|F }, + { "volume", "set volume adjustment expression", + OFFSET(volume_expr), AV_OPT_TYPE_STRING, { .str = "1.0" }, .flags = A|F }, { "precision", "select mathematical 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" }, @@ -68,21 +84,25 @@ static av_cold int init(AVFilterContext *ctx, const char *args) if ((ret = av_opt_set_from_string(vol, args, shorthand, "=", ":")) < 0) return ret;
- 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]); + if ((ret = av_expr_parse(&vol->volume_pexpr,vol->volume_expr, + var_names, NULL, NULL, NULL, NULL, 0, ctx)) < 0) { + av_log(ctx, AV_LOG_ERROR, "Error while parsing expression '%s'\n", vol->volume_expr); + return ret; }
- av_opt_free(vol); + vol->var_values[VAR_N ] = 0.0; + vol->var_values[VAR_STARTPTS] = vol->var_values[VAR_STARTT] = NAN; + return ret; }
+static av_cold void uninit(AVFilterContext *ctx) +{ + VolumeContext *vol = ctx->priv; + av_expr_free(vol->volume_pexpr); + av_opt_free(vol); +} + static int query_formats(AVFilterContext *ctx) { VolumeContext *vol = ctx->priv; @@ -182,18 +202,6 @@ static void volume_init(VolumeContext *vol) vol->samples_align = 1;
switch (av_get_packed_sample_fmt(vol->sample_fmt)) { - case AV_SAMPLE_FMT_U8: - if (vol->volume_i < 0x1000000) - vol->scale_samples = scale_samples_u8_small; - else - vol->scale_samples = scale_samples_u8; - break; - case AV_SAMPLE_FMT_S16: - if (vol->volume_i < 0x10000) - vol->scale_samples = scale_samples_s16_small; - else - vol->scale_samples = scale_samples_s16; - break; case AV_SAMPLE_FMT_S32: vol->scale_samples = scale_samples_s32; break; @@ -221,20 +229,73 @@ static int config_output(AVFilterLink *outlink) vol->channels = av_get_channel_layout_nb_channels(inlink->channel_layout); vol->planes = av_sample_fmt_is_planar(inlink->format) ? vol->channels : 1;
- volume_init(vol); + vol->var_values[VAR_TB] = av_q2d(inlink->time_base); + vol->var_values[VAR_RTC_STARTT] = av_gettime(); + vol->var_values[VAR_SAMPLE_RATE] = inlink->sample_rate; + + av_log(inlink->src, AV_LOG_VERBOSE, "TB:%f SAMPLE_RATE:%f RTC_STARTT:%f\n", + vol->var_values[VAR_TB], + vol->var_values[VAR_SAMPLE_RATE], + vol->var_values[VAR_RTC_STARTT]);
+ volume_init(vol); return 0; }
+#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, AVFilterBufferRef *buf) { + AVFilterContext *ctx = inlink->dst; VolumeContext *vol = inlink->dst->priv; AVFilterLink *outlink = inlink->dst->outputs[0]; int nb_samples = buf->audio->nb_samples; AVFilterBufferRef *out_buf;
- if (vol->volume == 1.0 || vol->volume_i == 256) - return ff_filter_frame(outlink, buf); + if (isnan(vol->var_values[VAR_STARTPTS])) { + 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_POS] = buf->pos == -1 ? NAN : buf->pos; + vol->volume = av_expr_eval(vol->volume_pexpr, vol->var_values, NULL); + if (isnan(vol->volume)) { + av_log(ctx, AV_LOG_WARNING, "Invalid value NaN for volume, setting to 0\n"); + vol->volume = 0; + } + + 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_DEBUG, "volume_i:%d/256 volume:%f volume_db:%1.2f\n", + vol->volume_i, vol->volume, 20.0*log(vol->volume)/M_LN10); + } else { + av_log(ctx, AV_LOG_DEBUG, "volume:%f volume_db:%1.2f\n", + vol->volume, 20.0*log(vol->volume)/M_LN10); + } + + if (vol->volume == 1.0 || vol->volume_i == 256) { + out_buf = buf; + goto end; + } +
+ switch (av_get_packed_sample_fmt(vol->sample_fmt)) { + case AV_SAMPLE_FMT_U8: + if (vol->volume_i < 0x1000000) + vol->scale_samples = scale_samples_u8_small; + else + vol->scale_samples = scale_samples_u8; + break; + case AV_SAMPLE_FMT_S16: + if (vol->volume_i < 0x10000) + vol->scale_samples = scale_samples_s16_small; + else + vol->scale_samples = scale_samples_s16; + break; + }
This part could do with a comment explaining what is going on. The problem is already present in the original code, but with this change, I am a little afraid that it is killing the assembly optimizations.
/* do volume scaling in-place if input buffer is writable */ if (buf->perms & AV_PERM_WRITE) { @@ -278,6 +339,10 @@ static int filter_frame(AVFilterLink *inlink, AVFilterBufferRef *buf) if (buf != out_buf) avfilter_unref_buffer(buf);
+end: + vol->var_values[VAR_N] += 1.0; + vol->var_values[VAR_NB_CONSUMED_SAMPLES] += buf->audio->nb_samples; + return ff_filter_frame(outlink, out_buf); }
@@ -305,6 +370,7 @@ AVFilter avfilter_af_volume = { .query_formats = query_formats, .priv_size = sizeof(VolumeContext), .init = init, + .uninit = uninit, .inputs = avfilter_af_volume_inputs, .outputs = avfilter_af_volume_outputs, .priv_class = &volume_class, diff --git a/libavfilter/af_volume.h b/libavfilter/af_volume.h index bd7932e..38e43a8 100644 --- a/libavfilter/af_volume.h +++ b/libavfilter/af_volume.h @@ -25,6 +25,7 @@ #define AVFILTER_AF_VOLUME_H
#include "libavutil/common.h" +#include "libavutil/eval.h" #include "libavutil/float_dsp.h" #include "libavutil/opt.h" #include "libavutil/samplefmt.h" @@ -35,10 +36,29 @@ enum PrecisionType { PRECISION_DOUBLE, };
+enum VolumeVarName { + VAR_N, + VAR_NB_CONSUMED_SAMPLES, + VAR_NB_SAMPLES, + VAR_POS, + VAR_PTS, + VAR_RTC_STARTT, + VAR_SAMPLE_RATE, + VAR_STARTPTS, + VAR_STARTT, + VAR_T, + VAR_TB, + VAR_VARS_NB +}; + typedef struct VolumeContext { const AVClass *class; AVFloatDSPContext fdsp; enum PrecisionType precision; + const char *volume_expr; + AVExpr *volume_pexpr; + double var_values[VAR_VARS_NB]; + double volume; int volume_i; int channels;
Regards, -- Nicolas George
On Sat, Feb 23, 2013 at 01:00:40AM +0100, Stefano Sabatini wrote:
TODO: update docs, bump micro --- libavfilter/af_volume.c | 120 ++++++++++++++++++++++++++++++++++++----------- libavfilter/af_volume.h | 20 ++++++++ 2 files changed, 113 insertions(+), 27 deletions(-)
diff --git a/libavfilter/af_volume.c b/libavfilter/af_volume.c index 5ffa1fe..87898c8 100644 --- a/libavfilter/af_volume.c +++ b/libavfilter/af_volume.c @@ -29,6 +29,7 @@ #include "libavutil/eval.h" #include "libavutil/float_dsp.h" #include "libavutil/opt.h" +#include "libavutil/time.h" #include "audio.h" #include "avfilter.h" #include "formats.h" @@ -39,13 +40,28 @@ static const char *precision_str[] = { "fixed", "float", "double" };
+static const char *const var_names[] = { + "N", ///< frame number (starting at zero) + "NB_CONSUMED_SAMPLES", ///< number of samples consumed by the filter (only audio) + "NB_SAMPLES", ///< number of samples in the current frame (only audio) + "POS", ///< original position in the file of the frame + "PTS", ///< original pts in the file of the frame + "RTC_STARTT", ///< start wallclock (RTC) time in micro seconds + "SAMPLE_RATE", ///< sample rate (only audio) + "STARTPTS", ///< PTS at start of stream + "STARTT", ///< time at start of stream + "T", ///< original time in the file of the frame + "TB", ///< timebase + NULL +}; + #define OFFSET(x) offsetof(VolumeContext, x) #define A AV_OPT_FLAG_AUDIO_PARAM #define F AV_OPT_FLAG_FILTERING_PARAM
static const AVOption volume_options[] = { - { "volume", "set volume adjustment", - OFFSET(volume), AV_OPT_TYPE_DOUBLE, { .dbl = 1.0 }, 0, 0x7fffff, A|F }, + { "volume", "set volume adjustment expression", + OFFSET(volume_expr), AV_OPT_TYPE_STRING, { .str = "1.0" }, .flags = A|F }, { "precision", "select mathematical 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" }, @@ -68,21 +84,25 @@ static av_cold int init(AVFilterContext *ctx, const char *args) if ((ret = av_opt_set_from_string(vol, args, shorthand, "=", ":")) < 0) return ret;
- 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]); + if ((ret = av_expr_parse(&vol->volume_pexpr,vol->volume_expr, + var_names, NULL, NULL, NULL, NULL, 0, ctx)) < 0) { + av_log(ctx, AV_LOG_ERROR, "Error while parsing expression '%s'\n", vol->volume_expr); + return ret;
Will that still support the "12dB" syntax? Also, if it's a problem from a performance PoV (and the feature being relatively marginal), would it make sense to instead add a "dyn_volume" option? [...] -- Clément B.
On date Wednesday 2013-02-27 15:21:49 +0100, Clément Bœsch encoded:
On Sat, Feb 23, 2013 at 01:00:40AM +0100, Stefano Sabatini wrote:
TODO: update docs, bump micro --- libavfilter/af_volume.c | 120 ++++++++++++++++++++++++++++++++++++----------- libavfilter/af_volume.h | 20 ++++++++ 2 files changed, 113 insertions(+), 27 deletions(-)
diff --git a/libavfilter/af_volume.c b/libavfilter/af_volume.c index 5ffa1fe..87898c8 100644 --- a/libavfilter/af_volume.c +++ b/libavfilter/af_volume.c @@ -29,6 +29,7 @@ #include "libavutil/eval.h" #include "libavutil/float_dsp.h" #include "libavutil/opt.h" +#include "libavutil/time.h" #include "audio.h" #include "avfilter.h" #include "formats.h" @@ -39,13 +40,28 @@ static const char *precision_str[] = { "fixed", "float", "double" };
+static const char *const var_names[] = { + "N", ///< frame number (starting at zero) + "NB_CONSUMED_SAMPLES", ///< number of samples consumed by the filter (only audio) + "NB_SAMPLES", ///< number of samples in the current frame (only audio) + "POS", ///< original position in the file of the frame + "PTS", ///< original pts in the file of the frame + "RTC_STARTT", ///< start wallclock (RTC) time in micro seconds + "SAMPLE_RATE", ///< sample rate (only audio) + "STARTPTS", ///< PTS at start of stream + "STARTT", ///< time at start of stream + "T", ///< original time in the file of the frame + "TB", ///< timebase + NULL +}; + #define OFFSET(x) offsetof(VolumeContext, x) #define A AV_OPT_FLAG_AUDIO_PARAM #define F AV_OPT_FLAG_FILTERING_PARAM
static const AVOption volume_options[] = { - { "volume", "set volume adjustment", - OFFSET(volume), AV_OPT_TYPE_DOUBLE, { .dbl = 1.0 }, 0, 0x7fffff, A|F }, + { "volume", "set volume adjustment expression", + OFFSET(volume_expr), AV_OPT_TYPE_STRING, { .str = "1.0" }, .flags = A|F }, { "precision", "select mathematical 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" }, @@ -68,21 +84,25 @@ static av_cold int init(AVFilterContext *ctx, const char *args) if ((ret = av_opt_set_from_string(vol, args, shorthand, "=", ":")) < 0) return ret;
- 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]); + if ((ret = av_expr_parse(&vol->volume_pexpr,vol->volume_expr, + var_names, NULL, NULL, NULL, NULL, 0, ctx)) < 0) { + av_log(ctx, AV_LOG_ERROR, "Error while parsing expression '%s'\n", vol->volume_expr); + return ret;
Will that still support the "12dB" syntax?
yes
Also, if it's a problem from a performance PoV (and the feature being relatively marginal), would it make sense to instead add a "dyn_volume" option?
That's a solution (or add a filter variant). The other solution would be to make the filter behaves in a different way if the expression is constant (CONST OP CONST is detected as constant), which would need some hack to the eval code (of course this can't detect things like EXPR-EXPR). Please tell what you prefer (and feel free to pick the patch and improve it yourself if you're in hurry). Next in the plan is to add support to a command. -- FFmpeg = Furious and Fierce MultiPurpose Explosive Goblin
Le quartidi 14 ventôse, an CCXXI, Stefano Sabatini a écrit :
That's a solution (or add a filter variant). The other solution would be to make the filter behaves in a different way if the expression is constant (CONST OP CONST is detected as constant), which would need some hack to the eval code (of course this can't detect things like EXPR-EXPR).
Detecting what variables / functions are used in the expression is not impossible at all, and it would allow to know whether the expression is constant or not even in complex cases.
Please tell what you prefer (and feel free to pick the patch and improve it yourself if you're in hurry).
Could you make a quick benchmark, using a small frame size (asetnsamples=16 for example): that would greatly help knowing whether it is worth optimizing that or not. Regards, -- Nicolas George
On date Tuesday 2013-03-05 16:08:54 +0100, Nicolas George encoded:
Le quartidi 14 ventôse, an CCXXI, Stefano Sabatini a écrit :
That's a solution (or add a filter variant). The other solution would be to make the filter behaves in a different way if the expression is constant (CONST OP CONST is detected as constant), which would need some hack to the eval code (of course this can't detect things like EXPR-EXPR).
Detecting what variables / functions are used in the expression is not impossible at all, and it would allow to know whether the expression is constant or not even in complex cases.
Please tell what you prefer (and feel free to pick the patch and improve it yourself if you're in hurry).
Could you make a quick benchmark, using a small frame size (asetnsamples=16 for example): that would greatly help knowing whether it is worth optimizing that or not.
I'm trying to implement av_expr_is_const() (and trying to clean up eval code a bit at the same time). -- FFmpeg = Frightening Formidable Meaningless Power Erudite Gymnast
On date Tuesday 2013-03-05 18:10:45 +0100, Stefano Sabatini encoded:
On date Tuesday 2013-03-05 16:08:54 +0100, Nicolas George encoded:
Le quartidi 14 ventôse, an CCXXI, Stefano Sabatini a écrit :
That's a solution (or add a filter variant). The other solution would be to make the filter behaves in a different way if the expression is constant (CONST OP CONST is detected as constant), which would need some hack to the eval code (of course this can't detect things like EXPR-EXPR).
Detecting what variables / functions are used in the expression is not impossible at all, and it would allow to know whether the expression is constant or not even in complex cases.
Please tell what you prefer (and feel free to pick the patch and improve it yourself if you're in hurry).
Could you make a quick benchmark, using a small frame size (asetnsamples=16 for example): that would greatly help knowing whether it is worth optimizing that or not.
I'm trying to implement av_expr_is_const() (and trying to clean up eval code a bit at the same time).
And here there is a problem. In case I have something like this: foo(100); assuming the function is constant (and there is no way to know in case it is an user function), then the evaluation is obviously constant. OTOH if we have: foo(W+X) we don't know which parameters we assume are going to change (which really depends on the implementation, if the parameters are reset after one evaluation and the next one). So av_expr_is_const() doesn't seem like a viable choice (unless I'm missing something), because it helps only in a few trivial cases, when no custom functions and no variables are used in the expression. In this specific case the best solutions seem to implement a mode which forces re-evaluation for each frame. -- FFmpeg = Forgiving & Furious Minimal Philosofic Elegant Gem
On Tue, Mar 05, 2013 at 10:51:33PM +0100, Stefano Sabatini wrote:
On date Tuesday 2013-03-05 18:10:45 +0100, Stefano Sabatini encoded:
On date Tuesday 2013-03-05 16:08:54 +0100, Nicolas George encoded:
Le quartidi 14 ventôse, an CCXXI, Stefano Sabatini a écrit :
That's a solution (or add a filter variant). The other solution would be to make the filter behaves in a different way if the expression is constant (CONST OP CONST is detected as constant), which would need some hack to the eval code (of course this can't detect things like EXPR-EXPR).
Detecting what variables / functions are used in the expression is not impossible at all, and it would allow to know whether the expression is constant or not even in complex cases.
Please tell what you prefer (and feel free to pick the patch and improve it yourself if you're in hurry).
Could you make a quick benchmark, using a small frame size (asetnsamples=16 for example): that would greatly help knowing whether it is worth optimizing that or not.
I'm trying to implement av_expr_is_const() (and trying to clean up eval code a bit at the same time).
And here there is a problem. In case I have something like this: foo(100);
assuming the function is constant (and there is no way to know in case it is an user function), then the evaluation is obviously constant.
OTOH if we have: foo(W+X)
we don't know which parameters we assume are going to change (which really depends on the implementation, if the parameters are reset after one evaluation and the next one).
So av_expr_is_const() doesn't seem like a viable choice (unless I'm missing something), because it helps only in a few trivial cases, when no custom functions and no variables are used in the expression.
Having a av_expr_is_const() not very efficient (aka returning "it's not const" even when it actually is) is not really a problem. The main thing we are interested in is to not slow down the obvious cases such as volume=-3dB. When the user starts to use expressions, there might be a risk of slow down, but that's not really problematic in most cases IMO. Everytime custom functions or custom var_values are used, just return 0. You should be able to detect the usage of the eval local/general constants and builtin constant functions to detect as const stuff like sin(3*PI). That's in my opinion more than enough for a first version since it will prevent ANY slowdown with what the users are currently doing. -- Clément B.
Le quintidi 15 ventôse, an CCXXI, Stefano Sabatini a écrit :
And here there is a problem. In case I have something like this: foo(100);
assuming the function is constant (and there is no way to know in case it is an user function), then the evaluation is obviously constant.
OTOH if we have: foo(W+X)
we don't know which parameters we assume are going to change (which really depends on the implementation, if the parameters are reset after one evaluation and the next one).
IMHO, the code that calls av_expr_is_const() is responsible for providing the information: for each user variable, is it considered constant or not; for each user function, is it pure or not. OTOH, I agree with Clément that av_expr_is_const() returning false even when the expression is actually constant in certain cases is acceptable. (In fact I believe that determining if an expression is constant or not is undecidable. So the return value of av_expr_is_const() is actually whether the expression is obviously constant, and the only question is how much effort to put in this "obviously".) Regards, -- Nicolas George
On date Tuesday 2013-03-05 22:51:33 +0100, Stefano Sabatini encoded:
On date Tuesday 2013-03-05 18:10:45 +0100, Stefano Sabatini encoded: [...]
I'm trying to implement av_expr_is_const() (and trying to clean up eval code a bit at the same time).
And here there is a problem. In case I have something like this: foo(100);
assuming the function is constant (and there is no way to know in case it is an user function), then the evaluation is obviously constant.
OTOH if we have: foo(W+X)
we don't know which parameters we assume are going to change (which really depends on the implementation, if the parameters are reset after one evaluation and the next one).
So av_expr_is_const() doesn't seem like a viable choice (unless I'm missing something), because it helps only in a few trivial cases, when no custom functions and no variables are used in the expression.
In this specific case the best solutions seem to implement a mode which forces re-evaluation for each frame.
Updated with a solution similar to that of overlay. I don't plan to implement the av_expr_is_const() thing, and will probably push this variant unless there are objections. Comments are welcome. -- FFmpeg = Free & Funny Magnificient Proud Elected Geisha
Le tridi 3 nivôse, an CCXXII, Stefano Sabatini a écrit :
Updated with a solution similar to that of overlay. I don't plan to implement the av_expr_is_const() thing, and will probably push this variant unless there are objections.
Comments are welcome. -- FFmpeg = Free & Funny Magnificient Proud Elected Geisha
From 9afa55b01eb84e6615fff877dc718d192305abf3 Mon Sep 17 00:00:00 2001 From: Stefano Sabatini <stefasab@gmail.com> Date: Sat, 23 Feb 2013 00:17:17 +0100 Subject: [PATCH] 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.
TODO: bump micro --- doc/filters.texi | 55 ++++++++++++++++++- libavfilter/af_volume.c | 142 ++++++++++++++++++++++++++++++++++++++++++++++-- libavfilter/af_volume.h | 27 +++++++++ 3 files changed, 217 insertions(+), 7 deletions(-)
diff --git a/doc/filters.texi b/doc/filters.texi index 8bcfd82..d29bedc 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -1796,7 +1796,7 @@ The filter accepts the following options: @table @option
@item volume -Expresses how the audio volume will be increased or decreased. +Set audio volume expression.
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} @end example
-Default value for @var{volume} is 1.0. +Default value for @var{volume} is "1.0".
@item precision Set the mathematical precision. @@ -1821,8 +1821,53 @@ precision of the volume scaling. @item double 64-bit floating-point; limits input sample format to DBL. @end table + +@item eval +Set when the volume expression is evaluated. + +It accepts the following values: +@table @samp +@item init +only evaluate expression once during the filter initialization + +@item frame +evaluate expression for each incoming frame +@end table + +Default value is @samp{init}. +@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 @end table
+Note that when @option{eval} is set to @samp{init} only the +@var{sample_rate} and @var{tb} variables are available, all other +variables will evaluate to NAN. + @subsection Examples
@itemize @@ -1845,6 +1890,12 @@ Increase input audio power by 6 decibels using fixed-point precision: @example volume=volume=6dB:precision=fixed @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
@section volumedetect diff --git a/libavfilter/af_volume.c b/libavfilter/af_volume.c index 21fe9a1..9bfd840 100644 --- a/libavfilter/af_volume.c +++ b/libavfilter/af_volume.c @@ -29,6 +29,7 @@ #include "libavutil/eval.h" #include "libavutil/float_dsp.h" #include "libavutil/opt.h" +#include "libavutil/time.h" #include "audio.h" #include "avfilter.h" #include "formats.h" @@ -39,24 +40,42 @@ static const char *precision_str[] = { "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 + NULL +}; + #define OFFSET(x) offsetof(VolumeContext, x) #define A AV_OPT_FLAG_AUDIO_PARAM #define F AV_OPT_FLAG_FILTERING_PARAM
static const AVOption volume_options[] = { - { "volume", "set volume adjustment", - OFFSET(volume), AV_OPT_TYPE_DOUBLE, { .dbl = 1.0 }, 0, 0x7fffff, A|F }, + { "volume", "set volume adjustment expression", + OFFSET(volume_expr), AV_OPT_TYPE_STRING, { .str = "1.0" }, .flags = A|F }, { "precision", "select mathematical 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" }, { "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" }, + { "eval", "specify when to evaluate expressions", OFFSET(eval_mode), AV_OPT_TYPE_INT, {.i64 = EVAL_MODE_INIT}, 0, EVAL_MODE_NB-1, .flags = A|F, "eval" }, + { "init", "eval expressions once during initialization", 0, AV_OPT_TYPE_CONST, {.i64=EVAL_MODE_INIT}, .flags = A|F, .unit = "eval" }, + { "frame", "eval expressions per-frame", 0, AV_OPT_TYPE_CONST, {.i64=EVAL_MODE_FRAME}, .flags = A|F, .unit = "eval" }, { NULL } };
AVFILTER_DEFINE_CLASS(volume);
-static av_cold int init(AVFilterContext *ctx) +static void set_volume(AVFilterContext *ctx) { VolumeContext *vol = ctx->priv;
@@ -70,10 +89,41 @@ static av_cold int init(AVFilterContext *ctx) vol->volume, 20.0*log(vol->volume)/M_LN10, precision_str[vol->precision]); } +} + +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) +{ + VolumeContext *vol = ctx->priv; + return set_expr(&vol->volume_pexpr, vol->volume_expr, ctx); +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + VolumeContext *vol = ctx->priv; + av_expr_free(vol->volume_pexpr); + av_opt_free(vol); +} + static int query_formats(AVFilterContext *ctx) { VolumeContext *vol = ctx->priv; @@ -204,25 +254,102 @@ static int config_output(AVFilterLink *outlink) AVFilterContext *ctx = outlink->src; VolumeContext *vol = ctx->priv; AVFilterLink *inlink = ctx->inputs[0]; + int ret;
vol->sample_fmt = inlink->format; vol->channels = inlink->channels; vol->planes = av_sample_fmt_is_planar(inlink->format) ? vol->channels : 1;
+ 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] = NAN; + + 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\n", + vol->var_values[VAR_TB], vol->var_values[VAR_SAMPLE_RATE]); + volume_init(vol);
+ if (vol->eval_mode == EVAL_MODE_INIT) {
+ if ((ret = av_expr_parse(&vol->volume_pexpr, vol->volume_expr, + var_names, NULL, NULL, NULL, NULL, 0, ctx)) < 0) { + av_log(ctx, AV_LOG_ERROR, "Error while parsing expression '%s'\n", + vol->volume_expr); + return ret; + }
It looks like duplicated code with set_expr().
+ + vol->volume = av_expr_eval(vol->volume_pexpr, vol->var_values, NULL); + if (isnan(vol->volume)) { + av_log(ctx, AV_LOG_ERROR, "Invalid value NaN for volume\n"); + return AVERROR(EINVAL); + } + + set_volume(ctx);
This bit could probably be factored out, that would make the patch with command processing slightly shorter.
+ } + return 0; }
+#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) { + AVFilterContext *ctx = inlink->dst; VolumeContext *vol = inlink->dst->priv; AVFilterLink *outlink = inlink->dst->outputs[0]; int nb_samples = buf->nb_samples; AVFrame *out_buf; + int64_t pos; + + if (vol->eval_mode == EVAL_MODE_FRAME) { + if (isnan(vol->var_values[VAR_STARTPTS])) { + 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; + + vol->volume = av_expr_eval(vol->volume_pexpr, vol->var_values, NULL); + if (isnan(vol->volume)) { + av_log(ctx, AV_LOG_WARNING, "Invalid value NaN for volume, setting to 0\n"); + vol->volume = 0; + }
- if (vol->volume == 1.0 || vol->volume_i == 256) - return ff_filter_frame(outlink, buf); + set_volume(ctx); + if (vol->volume == 1.0 || vol->volume_i == 256) { + out_buf = buf; + goto end; + } + } + + switch (av_get_packed_sample_fmt(vol->sample_fmt)) { + case AV_SAMPLE_FMT_U8: + if (vol->volume_i < 0x1000000) + vol->scale_samples = scale_samples_u8_small; + else + vol->scale_samples = scale_samples_u8; + break; + case AV_SAMPLE_FMT_S16: + if (vol->volume_i < 0x10000) + vol->scale_samples = scale_samples_s16_small; + else + vol->scale_samples = scale_samples_s16; + break; + }
/* do volume scaling in-place if input buffer is writable */ if (av_frame_is_writable(buf)) { @@ -266,6 +393,10 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *buf) if (buf != out_buf) av_frame_free(&buf);
+end: + if (vol->eval_mode == EVAL_MODE_FRAME) + vol->var_values[VAR_NB_CONSUMED_SAMPLES] += buf->nb_samples; + return ff_filter_frame(outlink, out_buf); }
@@ -294,6 +425,7 @@ AVFilter ff_af_volume = { .priv_size = sizeof(VolumeContext), .priv_class = &volume_class, .init = init, + .uninit = uninit, .inputs = avfilter_af_volume_inputs, .outputs = avfilter_af_volume_outputs, .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, diff --git a/libavfilter/af_volume.h b/libavfilter/af_volume.h index bd7932e..70b313f 100644 --- a/libavfilter/af_volume.h +++ b/libavfilter/af_volume.h @@ -25,6 +25,7 @@ #define AVFILTER_AF_VOLUME_H
#include "libavutil/common.h" +#include "libavutil/eval.h" #include "libavutil/float_dsp.h" #include "libavutil/opt.h" #include "libavutil/samplefmt.h" @@ -35,10 +36,36 @@ enum PrecisionType { PRECISION_DOUBLE, };
+enum EvalMode { + EVAL_MODE_INIT, + 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_VARS_NB +}; + typedef struct VolumeContext { const AVClass *class; AVFloatDSPContext fdsp; enum PrecisionType precision; + enum EvalMode eval_mode; + const char *volume_expr; + AVExpr *volume_pexpr; + double var_values[VAR_VARS_NB]; + double volume; int volume_i; int channels;
Regards, -- Nicolas George
On date Monday 2013-12-23 19:48:25 +0100, Nicolas George encoded:
Le tridi 3 nivôse, an CCXXII, Stefano Sabatini a écrit :
Updated with a solution similar to that of overlay. I don't plan to implement the av_expr_is_const() thing, and will probably push this variant unless there are objections.
Comments are welcome. -- FFmpeg = Free & Funny Magnificient Proud Elected Geisha
From 9afa55b01eb84e6615fff877dc718d192305abf3 Mon Sep 17 00:00:00 2001 From: Stefano Sabatini <stefasab@gmail.com> Date: Sat, 23 Feb 2013 00:17:17 +0100 Subject: [PATCH] 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.
TODO: bump micro --- doc/filters.texi | 55 ++++++++++++++++++- libavfilter/af_volume.c | 142 ++++++++++++++++++++++++++++++++++++++++++++++-- libavfilter/af_volume.h | 27 +++++++++ 3 files changed, 217 insertions(+), 7 deletions(-)
diff --git a/doc/filters.texi b/doc/filters.texi index 8bcfd82..d29bedc 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -1796,7 +1796,7 @@ The filter accepts the following options: @table @option
@item volume -Expresses how the audio volume will be increased or decreased. +Set audio volume expression.
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} @end example
-Default value for @var{volume} is 1.0. +Default value for @var{volume} is "1.0".
@item precision Set the mathematical precision. @@ -1821,8 +1821,53 @@ precision of the volume scaling. @item double 64-bit floating-point; limits input sample format to DBL. @end table + +@item eval +Set when the volume expression is evaluated. + +It accepts the following values: +@table @samp +@item init +only evaluate expression once during the filter initialization + +@item frame +evaluate expression for each incoming frame +@end table + +Default value is @samp{init}. +@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 @end table
+Note that when @option{eval} is set to @samp{init} only the +@var{sample_rate} and @var{tb} variables are available, all other +variables will evaluate to NAN. + @subsection Examples
@itemize @@ -1845,6 +1890,12 @@ Increase input audio power by 6 decibels using fixed-point precision: @example volume=volume=6dB:precision=fixed @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
@section volumedetect diff --git a/libavfilter/af_volume.c b/libavfilter/af_volume.c index 21fe9a1..9bfd840 100644 --- a/libavfilter/af_volume.c +++ b/libavfilter/af_volume.c @@ -29,6 +29,7 @@ #include "libavutil/eval.h" #include "libavutil/float_dsp.h" #include "libavutil/opt.h" +#include "libavutil/time.h" #include "audio.h" #include "avfilter.h" #include "formats.h" @@ -39,24 +40,42 @@ static const char *precision_str[] = { "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 + NULL +}; + #define OFFSET(x) offsetof(VolumeContext, x) #define A AV_OPT_FLAG_AUDIO_PARAM #define F AV_OPT_FLAG_FILTERING_PARAM
static const AVOption volume_options[] = { - { "volume", "set volume adjustment", - OFFSET(volume), AV_OPT_TYPE_DOUBLE, { .dbl = 1.0 }, 0, 0x7fffff, A|F }, + { "volume", "set volume adjustment expression", + OFFSET(volume_expr), AV_OPT_TYPE_STRING, { .str = "1.0" }, .flags = A|F }, { "precision", "select mathematical 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" }, { "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" }, + { "eval", "specify when to evaluate expressions", OFFSET(eval_mode), AV_OPT_TYPE_INT, {.i64 = EVAL_MODE_INIT}, 0, EVAL_MODE_NB-1, .flags = A|F, "eval" }, + { "init", "eval expressions once during initialization", 0, AV_OPT_TYPE_CONST, {.i64=EVAL_MODE_INIT}, .flags = A|F, .unit = "eval" }, + { "frame", "eval expressions per-frame", 0, AV_OPT_TYPE_CONST, {.i64=EVAL_MODE_FRAME}, .flags = A|F, .unit = "eval" }, { NULL } };
AVFILTER_DEFINE_CLASS(volume);
-static av_cold int init(AVFilterContext *ctx) +static void set_volume(AVFilterContext *ctx) { VolumeContext *vol = ctx->priv;
@@ -70,10 +89,41 @@ static av_cold int init(AVFilterContext *ctx) vol->volume, 20.0*log(vol->volume)/M_LN10, precision_str[vol->precision]); } +} + +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) +{ + VolumeContext *vol = ctx->priv; + return set_expr(&vol->volume_pexpr, vol->volume_expr, ctx); +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + VolumeContext *vol = ctx->priv; + av_expr_free(vol->volume_pexpr); + av_opt_free(vol); +} + static int query_formats(AVFilterContext *ctx) { VolumeContext *vol = ctx->priv; @@ -204,25 +254,102 @@ static int config_output(AVFilterLink *outlink) AVFilterContext *ctx = outlink->src; VolumeContext *vol = ctx->priv; AVFilterLink *inlink = ctx->inputs[0]; + int ret;
vol->sample_fmt = inlink->format; vol->channels = inlink->channels; vol->planes = av_sample_fmt_is_planar(inlink->format) ? vol->channels : 1;
+ 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] = NAN; + + 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\n", + vol->var_values[VAR_TB], vol->var_values[VAR_SAMPLE_RATE]); + volume_init(vol);
+ if (vol->eval_mode == EVAL_MODE_INIT) {
+ if ((ret = av_expr_parse(&vol->volume_pexpr, vol->volume_expr, + var_names, NULL, NULL, NULL, NULL, 0, ctx)) < 0) { + av_log(ctx, AV_LOG_ERROR, "Error while parsing expression '%s'\n", + vol->volume_expr); + return ret; + }
It looks like duplicated code with set_expr().
+ + vol->volume = av_expr_eval(vol->volume_pexpr, vol->var_values, NULL); + if (isnan(vol->volume)) { + av_log(ctx, AV_LOG_ERROR, "Invalid value NaN for volume\n"); + return AVERROR(EINVAL); + } + + set_volume(ctx);
This bit could probably be factored out, that would make the patch with command processing slightly shorter.
Correct. I also added the vol variable, which may be useful for example for doing interactive volume = vol/2. -- FFmpeg = Friendly and Fabulous Magical Pacific Emblematic Geek
On date Monday 2013-12-23 20:15:25 +0100, Stefano Sabatini encoded:
On date Monday 2013-12-23 19:48:25 +0100, Nicolas George encoded: [...]
+ if (vol->eval_mode == EVAL_MODE_INIT) {
+ if ((ret = av_expr_parse(&vol->volume_pexpr, vol->volume_expr, + var_names, NULL, NULL, NULL, NULL, 0, ctx)) < 0) { + av_log(ctx, AV_LOG_ERROR, "Error while parsing expression '%s'\n", + vol->volume_expr); + return ret; + }
It looks like duplicated code with set_expr().
+ + vol->volume = av_expr_eval(vol->volume_pexpr, vol->var_values, NULL); + if (isnan(vol->volume)) { + av_log(ctx, AV_LOG_ERROR, "Invalid value NaN for volume\n"); + return AVERROR(EINVAL); + } + + set_volume(ctx);
This bit could probably be factored out, that would make the patch with command processing slightly shorter.
Correct. I also added the vol variable, which may be useful for example for doing interactive volume = vol/2. -- FFmpeg = Friendly and Fabulous Magical Pacific Emblematic Geek
From e5e9e86b131b9dd272457b311c0d745d3131752e Mon Sep 17 00:00:00 2001 From: Stefano Sabatini <stefasab@gmail.com> Date: Sat, 23 Feb 2013 00:17:17 +0100 Subject: [PATCH] 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.
TODO: bump micro --- doc/filters.texi | 57 +++++++++++++++- libavfilter/af_volume.c | 169 +++++++++++++++++++++++++++++++++++++++++++----- libavfilter/af_volume.h | 28 ++++++++ 3 files changed, 235 insertions(+), 19 deletions(-)
Updated. Renamed eval=init -> eval=once, and vol variable to volume. -- FFmpeg = Fanciful & Forgiving Mind-dumbing Pitiless Eretic Guru
On date Monday 2013-12-23 20:21:50 +0100, Stefano Sabatini encoded:
On date Monday 2013-12-23 20:15:25 +0100, Stefano Sabatini encoded: [...] From 9431036a70b9653d8d2748b7a453358309faf85b Mon Sep 17 00:00:00 2001 From: Stefano Sabatini <stefasab@gmail.com> Date: Sat, 23 Feb 2013 00:17:17 +0100 Subject: [PATCH] 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.
TODO: bump micro --- doc/filters.texi | 57 +++++++++++++++- libavfilter/af_volume.c | 169 +++++++++++++++++++++++++++++++++++++++++++----- libavfilter/af_volume.h | 28 ++++++++ 3 files changed, 235 insertions(+), 19 deletions(-)
Pushed after more fixes. -- FFmpeg = Frightening & Fanciful Multimedia Political Encoding/decoding Guru
participants (5)
-
Clément Bœsch -
Nicolas George -
Nicolas George -
Paul B Mahol -
Stefano Sabatini