From 8e9631967427106468cfaf1d258ecddc06db39ff Mon Sep 17 00:00:00 2001 From: Paul B Mahol Date: Tue, 8 Nov 2022 15:17:50 +0100 Subject: [PATCH] avfilter/af_dynaudnorm: add curve option --- doc/filters.texi | 27 +++++++++++++++ libavfilter/af_dynaudnorm.c | 66 ++++++++++++++++++++++++++++++++++--- 2 files changed, 88 insertions(+), 5 deletions(-) diff --git a/doc/filters.texi b/doc/filters.texi index 910fc1fe79..5b49687645 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -4587,6 +4587,33 @@ Using >0 and <1 values will make less conservative gain adjustments, like when framelen option is set to smaller value, if framelen option value is compensated for non-zero overlap then gain adjustments will be smoother across time compared to zero overlap case. + +@item curve, v +Specify the peak mapping curve expression which is going to be used when calculating +gain applied to frames. The max output frame gain will still be limited by other +options mentioned previously for this filter. + +The expression can contain the following constants: + +@table @option +@item ch +current channel number + +@item sn +current sample number + +@item nb_channels +number of channels + +@item t +timestamp expressed in seconds + +@item sr +sample rate + +@item p +current frame peak value +@end table @end table @subsection Commands diff --git a/libavfilter/af_dynaudnorm.c b/libavfilter/af_dynaudnorm.c index 88d1b382f3..e9d8ad8ec8 100644 --- a/libavfilter/af_dynaudnorm.c +++ b/libavfilter/af_dynaudnorm.c @@ -28,6 +28,7 @@ #include "libavutil/avassert.h" #include "libavutil/channel_layout.h" +#include "libavutil/eval.h" #include "libavutil/opt.h" #define MIN_FILTER_SIZE 3 @@ -41,6 +42,26 @@ #include "filters.h" #include "internal.h" +static const char * const var_names[] = { + "ch", ///< the value of the current channel + "sn", ///< number of samples + "nb_channels", + "t", ///< timestamp expressed in seconds + "sr", ///< sample rate + "p", ///< peak value + NULL +}; + +enum var_name { + VAR_CH, + VAR_SN, + VAR_NB_CHANNELS, + VAR_T, + VAR_SR, + VAR_P, + VAR_VARS_NB +}; + typedef struct local_gain { double max_gain; double threshold; @@ -65,6 +86,7 @@ typedef struct DynamicAudioNormalizerContext { int channels_coupled; int alt_boundary_mode; double overlap; + char *expr_str; double peak_value; double max_amplification; @@ -91,6 +113,9 @@ typedef struct DynamicAudioNormalizerContext { cqueue *is_enabled; AVFrame *window; + + AVExpr *expr; + double var_values[VAR_VARS_NB]; } DynamicAudioNormalizerContext; typedef struct ThreadData { @@ -122,10 +147,12 @@ static const AVOption dynaudnorm_options[] = { { "s", "set the compress factor", OFFSET(compress_factor), AV_OPT_TYPE_DOUBLE, {.dbl = 0.0}, 0.0, 30.0, FLAGS }, { "threshold", "set the threshold value", OFFSET(threshold), AV_OPT_TYPE_DOUBLE, {.dbl = 0.0}, 0.0, 1.0, FLAGS }, { "t", "set the threshold value", OFFSET(threshold), AV_OPT_TYPE_DOUBLE, {.dbl = 0.0}, 0.0, 1.0, FLAGS }, - { "channels", "set channels to filter", OFFSET(channels_to_filter),AV_OPT_TYPE_STRING, {.str="all"}, 0, 0, FLAGS }, - { "h", "set channels to filter", OFFSET(channels_to_filter),AV_OPT_TYPE_STRING, {.str="all"}, 0, 0, FLAGS }, + { "channels", "set channels to filter", OFFSET(channels_to_filter),AV_OPT_TYPE_STRING, {.str="all"}, 0, 0, FLAGS }, + { "h", "set channels to filter", OFFSET(channels_to_filter),AV_OPT_TYPE_STRING, {.str="all"}, 0, 0, FLAGS }, { "overlap", "set the frame overlap", OFFSET(overlap), AV_OPT_TYPE_DOUBLE, {.dbl=.0}, 0.0, 1.0, FLAGS }, { "o", "set the frame overlap", OFFSET(overlap), AV_OPT_TYPE_DOUBLE, {.dbl=.0}, 0.0, 1.0, FLAGS }, + { "curve", "set the custom peak mapping curve",OFFSET(expr_str), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, + { "v", "set the custom peak mapping curve",OFFSET(expr_str), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, { NULL } }; @@ -309,12 +336,15 @@ static av_cold void uninit(AVFilterContext *ctx) ff_bufqueue_discard_all(&s->queue); av_frame_free(&s->window); + av_expr_free(s->expr); + s->expr = NULL; } static int config_input(AVFilterLink *inlink) { AVFilterContext *ctx = inlink->dst; DynamicAudioNormalizerContext *s = ctx->priv; + int ret = 0; uninit(ctx); @@ -358,7 +388,13 @@ static int config_input(AVFilterLink *inlink) return AVERROR(ENOMEM); s->sample_advance = FFMAX(1, lrint(s->frame_len * (1. - s->overlap))); - return 0; + s->var_values[VAR_SR] = inlink->sample_rate; + s->var_values[VAR_NB_CHANNELS] = s->channels; + + if (s->expr_str) + ret = av_expr_parse(&s->expr, s->expr_str, var_names, NULL, NULL, + NULL, NULL, 0, ctx); + return ret; } static inline double fade(double prev, double next, int pos, int length) @@ -433,10 +469,22 @@ static local_gain get_max_local_gain(DynamicAudioNormalizerContext *s, AVFrame * const double peak_magnitude = find_peak_magnitude(frame, channel); const double maximum_gain = s->peak_value / peak_magnitude; const double rms_gain = s->target_rms > DBL_EPSILON ? (s->target_rms / compute_frame_rms(frame, channel)) : DBL_MAX; + double target_gain = DBL_MAX; local_gain gain; + if (s->expr_str) { + double var_values[VAR_VARS_NB]; + + memcpy(var_values, s->var_values, sizeof(var_values)); + + var_values[VAR_CH] = channel; + var_values[VAR_P] = peak_magnitude; + + target_gain = av_expr_eval(s->expr, var_values, s) / peak_magnitude; + } + gain.threshold = peak_magnitude > s->threshold; - gain.max_gain = bound(s->max_amplification, fmin(maximum_gain, rms_gain)); + gain.max_gain = bound(s->max_amplification, fmin(target_gain, fmin(maximum_gain, rms_gain))); return gain; } @@ -731,6 +779,9 @@ static int analyze_frame(AVFilterContext *ctx, AVFilterLink *outlink, AVFrame ** analyze_frame = *frame; } + s->var_values[VAR_SN] = outlink->sample_count_in; + s->var_values[VAR_T] = s->var_values[VAR_SN] * (double)1/outlink->sample_rate; + if (s->channels_coupled) { const local_gain gain = get_max_local_gain(s, analyze_frame, -1); for (int c = 0; c < s->channels; c++) @@ -951,7 +1002,12 @@ static int process_command(AVFilterContext *ctx, const char *cmd, const char *ar s->frame_len = frame_size(inlink->sample_rate, s->frame_len_msec); s->sample_advance = FFMAX(1, lrint(s->frame_len * (1. - s->overlap))); - + if (s->expr_str) { + ret = av_expr_parse(&s->expr, s->expr_str, var_names, NULL, NULL, + NULL, NULL, 0, ctx); + if (ret < 0) + return ret; + } return 0; }