avfilter/af_adynamicequalizer: do gain calculations in log domain
This commit is contained in:
parent
afb967b81e
commit
82be1e5c0d
@ -26,8 +26,10 @@
|
||||
#undef FMIN
|
||||
#undef CLIP
|
||||
#undef SAMPLE_FORMAT
|
||||
#undef EPSILON
|
||||
#undef FABS
|
||||
#undef FLOG10
|
||||
#undef FEXP10
|
||||
#undef EPSILON
|
||||
#if DEPTH == 32
|
||||
#define SAMPLE_FORMAT float
|
||||
#define SQRT sqrtf
|
||||
@ -39,8 +41,10 @@
|
||||
#define FMAX fmaxf
|
||||
#define CLIP av_clipf
|
||||
#define FABS fabsf
|
||||
#define FLOG10 log10f
|
||||
#define FEXP10 ff_exp10f
|
||||
#define EPSILON (1.f / (1 << 23))
|
||||
#define ftype float
|
||||
#define EPSILON (1.f / (1 << 22))
|
||||
#else
|
||||
#define SAMPLE_FORMAT double
|
||||
#define SQRT sqrt
|
||||
@ -52,10 +56,15 @@
|
||||
#define FMAX fmax
|
||||
#define CLIP av_clipd
|
||||
#define FABS fabs
|
||||
#define FLOG10 log10
|
||||
#define FEXP10 ff_exp10
|
||||
#define EPSILON (1.0 / (1LL << 53))
|
||||
#define ftype double
|
||||
#define EPSILON (1.0 / (1LL << 51))
|
||||
#endif
|
||||
|
||||
#define LIN2LOG(x) (20.0 * FLOG10(x))
|
||||
#define LOG2LIN(x) (FEXP10(x / 20.0))
|
||||
|
||||
#define fn3(a,b) a##_##b
|
||||
#define fn2(a,b) fn3(a,b)
|
||||
#define fn(a) fn2(a, SAMPLE_FORMAT)
|
||||
@ -85,8 +94,11 @@ static int fn(filter_prepare)(AVFilterContext *ctx)
|
||||
ftype *dm = fn(s->dm);
|
||||
ftype k;
|
||||
|
||||
s->attack_coef = get_coef(s->attack, sample_rate);
|
||||
s->release_coef = get_coef(s->release, sample_rate);
|
||||
s->threshold_log = LIN2LOG(s->threshold);
|
||||
s->dattack_coef = get_coef(s->dattack, sample_rate);
|
||||
s->drelease_coef = get_coef(s->drelease, sample_rate);
|
||||
s->gattack_coef = s->dattack_coef * 0.25;
|
||||
s->grelease_coef = s->drelease_coef * 0.25;
|
||||
|
||||
switch (dftype) {
|
||||
case 0:
|
||||
@ -150,9 +162,11 @@ static int fn(filter_channels)(AVFilterContext *ctx, void *arg, int jobnr, int n
|
||||
const ftype range = s->range;
|
||||
const ftype tfrequency = FMIN(s->tfrequency, sample_rate * 0.5);
|
||||
const int mode = s->mode;
|
||||
const int power = (mode == CUT_BELOW || mode == CUT_ABOVE) ? -1 : 1;
|
||||
const ftype release = s->release_coef;
|
||||
const ftype attack = s->attack_coef;
|
||||
const ftype power = (mode == CUT_BELOW || mode == CUT_ABOVE) ? -ONE : ONE;
|
||||
const ftype grelease = s->grelease_coef;
|
||||
const ftype gattack = s->gattack_coef;
|
||||
const ftype drelease = s->drelease_coef;
|
||||
const ftype dattack = s->dattack_coef;
|
||||
const ftype tqfactor = s->tqfactor;
|
||||
const ftype itqfactor = ONE / tqfactor;
|
||||
const ftype fg = TAN(M_PI * tfrequency / sample_rate);
|
||||
@ -164,21 +178,39 @@ static int fn(filter_channels)(AVFilterContext *ctx, void *arg, int jobnr, int n
|
||||
const ftype *da = fn(s->da);
|
||||
const ftype *dm = fn(s->dm);
|
||||
|
||||
if (detection > 0) {
|
||||
if (detection == DET_ON) {
|
||||
for (int ch = start; ch < end; ch++) {
|
||||
const ftype *src = (const ftype *)in->extended_data[ch];
|
||||
ChannelContext *cc = &s->cc[ch];
|
||||
ftype *tstate = fn(cc->tstate);
|
||||
ftype new_threshold = ZERO;
|
||||
|
||||
if (cc->detection != detection) {
|
||||
cc->detection = detection;
|
||||
fn(cc->new_threshold_log) = LIN2LOG(EPSILON);
|
||||
}
|
||||
|
||||
for (int n = 0; n < in->nb_samples; n++) {
|
||||
ftype detect = fn(get_svf)(src[n], dm, da, tstate);
|
||||
fn(cc->threshold) = FMAX(fn(cc->threshold), detect);
|
||||
ftype detect = FABS(fn(get_svf)(src[n], dm, da, tstate));
|
||||
new_threshold = FMAX(new_threshold, detect);
|
||||
}
|
||||
|
||||
fn(cc->new_threshold_log) = FMAX(fn(cc->new_threshold_log), LIN2LOG(new_threshold));
|
||||
}
|
||||
} else if (detection < 0) {
|
||||
} else if (detection == DET_DISABLED) {
|
||||
for (int ch = start; ch < end; ch++) {
|
||||
ChannelContext *cc = &s->cc[ch];
|
||||
fn(cc->threshold) = s->threshold;
|
||||
fn(cc->threshold_log) = s->threshold_log;
|
||||
cc->detection = detection;
|
||||
}
|
||||
} else if (detection == DET_OFF) {
|
||||
for (int ch = start; ch < end; ch++) {
|
||||
ChannelContext *cc = &s->cc[ch];
|
||||
if (cc->detection == DET_ON)
|
||||
fn(cc->threshold_log) = fn(cc->new_threshold_log);
|
||||
else if (cc->detection == DET_UNSET)
|
||||
fn(cc->threshold_log) = s->threshold_log;
|
||||
cc->detection = detection;
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,95 +218,99 @@ static int fn(filter_channels)(AVFilterContext *ctx, void *arg, int jobnr, int n
|
||||
const ftype *src = (const ftype *)in->extended_data[ch];
|
||||
ftype *dst = (ftype *)out->extended_data[ch];
|
||||
ChannelContext *cc = &s->cc[ch];
|
||||
const ftype threshold = fn(cc->threshold);
|
||||
const ftype threshold_log = fn(cc->threshold_log);
|
||||
ftype *fa = fn(cc->fa), *fm = fn(cc->fm);
|
||||
ftype *fstate = fn(cc->fstate);
|
||||
ftype *dstate = fn(cc->dstate);
|
||||
ftype gain = fn(cc->gain);
|
||||
const int init = cc->init;
|
||||
ftype detect = fn(cc->detect);
|
||||
ftype lin_gain = fn(cc->lin_gain);
|
||||
int init = cc->init;
|
||||
|
||||
for (int n = 0; n < out->nb_samples; n++) {
|
||||
ftype detect, v, listen, new_gain = ONE;
|
||||
ftype k, g;
|
||||
ftype new_detect, new_lin_gain = ONE;
|
||||
ftype f, v, listen, k, g, ld;
|
||||
|
||||
detect = listen = fn(get_svf)(src[n], dm, da, dstate);
|
||||
detect = FABS(detect);
|
||||
listen = fn(get_svf)(src[n], dm, da, dstate);
|
||||
if (mode > LISTEN) {
|
||||
new_detect = FABS(listen);
|
||||
f = (new_detect > detect) * dattack + (new_detect <= detect) * drelease;
|
||||
detect = f * new_detect + (ONE - f) * detect;
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case LISTEN:
|
||||
break;
|
||||
case CUT_BELOW:
|
||||
case BOOST_BELOW:
|
||||
if (detect < threshold)
|
||||
new_gain = CLIP(ONE + makeup + (threshold - detect) * ratio, ONE, range);
|
||||
ld = LIN2LOG(detect);
|
||||
if (ld < threshold_log) {
|
||||
ftype new_log_gain = CLIP(makeup + (threshold_log - ld) * ratio, ZERO, range) * power;
|
||||
new_lin_gain = LOG2LIN(new_log_gain);
|
||||
}
|
||||
break;
|
||||
case CUT_ABOVE:
|
||||
case BOOST_ABOVE:
|
||||
if (detect > threshold)
|
||||
new_gain = CLIP(ONE + makeup + (detect - threshold) * ratio, ONE, range);
|
||||
ld = LIN2LOG(detect);
|
||||
if (ld > threshold_log) {
|
||||
ftype new_log_gain = CLIP(makeup + (ld - threshold_log) * ratio, ZERO, range) * power;
|
||||
new_lin_gain = LOG2LIN(new_log_gain);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (power < 0)
|
||||
new_gain = ONE / new_gain;
|
||||
f = (new_lin_gain > lin_gain) * gattack + (new_lin_gain <= lin_gain) * grelease;
|
||||
new_lin_gain = f * new_lin_gain + (ONE - f) * lin_gain;
|
||||
|
||||
if (mode > LISTEN) {
|
||||
ftype delta = new_gain - gain;
|
||||
|
||||
if (delta > EPSILON)
|
||||
new_gain = gain + attack * delta;
|
||||
else if (delta < -EPSILON)
|
||||
new_gain = gain + release * delta;
|
||||
}
|
||||
|
||||
if (gain != new_gain || !init) {
|
||||
gain = new_gain;
|
||||
if (lin_gain != new_lin_gain || !init) {
|
||||
init = 1;
|
||||
lin_gain = new_lin_gain;
|
||||
|
||||
switch (tftype) {
|
||||
case 0:
|
||||
k = itqfactor / gain;
|
||||
k = itqfactor / lin_gain;
|
||||
|
||||
fa[0] = ONE / (ONE + fg * (fg + k));
|
||||
fa[1] = fg * fa[0];
|
||||
fa[2] = fg * fa[1];
|
||||
|
||||
fm[0] = ONE;
|
||||
fm[1] = k * (gain * gain - ONE);
|
||||
fm[1] = k * (lin_gain * lin_gain - ONE);
|
||||
fm[2] = ZERO;
|
||||
break;
|
||||
case 1:
|
||||
k = itqfactor;
|
||||
g = fg / SQRT(gain);
|
||||
g = fg / SQRT(lin_gain);
|
||||
|
||||
fa[0] = ONE / (ONE + g * (g + k));
|
||||
fa[1] = g * fa[0];
|
||||
fa[2] = g * fa[1];
|
||||
|
||||
fm[0] = ONE;
|
||||
fm[1] = k * (gain - ONE);
|
||||
fm[2] = gain * gain - ONE;
|
||||
fm[1] = k * (lin_gain - ONE);
|
||||
fm[2] = lin_gain * lin_gain - ONE;
|
||||
break;
|
||||
case 2:
|
||||
k = itqfactor;
|
||||
g = fg * SQRT(gain);
|
||||
g = fg * SQRT(lin_gain);
|
||||
|
||||
fa[0] = ONE / (ONE + g * (g + k));
|
||||
fa[1] = g * fa[0];
|
||||
fa[2] = g * fa[1];
|
||||
|
||||
fm[0] = gain * gain;
|
||||
fm[1] = k * (ONE - gain) * gain;
|
||||
fm[2] = ONE - gain * gain;
|
||||
fm[0] = lin_gain * lin_gain;
|
||||
fm[1] = k * (ONE - lin_gain) * lin_gain;
|
||||
fm[2] = ONE - lin_gain * lin_gain;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
v = fn(get_svf)(src[n], fm, fa, fstate);
|
||||
v = mode == -1 ? listen : v;
|
||||
v = mode == LISTEN ? listen : v;
|
||||
dst[n] = is_disabled ? src[n] : v;
|
||||
}
|
||||
|
||||
fn(cc->gain) = gain;
|
||||
fn(cc->detect) = detect;
|
||||
fn(cc->lin_gain) = lin_gain;
|
||||
cc->init = 1;
|
||||
}
|
||||
|
||||
|
@ -18,18 +18,27 @@
|
||||
|
||||
#include <float.h>
|
||||
|
||||
#include "libavutil/ffmath.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "avfilter.h"
|
||||
#include "audio.h"
|
||||
#include "formats.h"
|
||||
|
||||
enum DetectionModes {
|
||||
DET_UNSET = 0,
|
||||
DET_DISABLED,
|
||||
DET_OFF,
|
||||
DET_ON,
|
||||
NB_DMODES,
|
||||
};
|
||||
|
||||
enum FilterModes {
|
||||
LISTEN = -1,
|
||||
CUT_BELOW,
|
||||
CUT_ABOVE,
|
||||
BOOST_BELOW,
|
||||
BOOST_ABOVE,
|
||||
NB_MODES,
|
||||
NB_FMODES,
|
||||
};
|
||||
|
||||
typedef struct ChannelContext {
|
||||
@ -37,14 +46,19 @@ typedef struct ChannelContext {
|
||||
double dstate_double[2];
|
||||
double fstate_double[2];
|
||||
double tstate_double[2];
|
||||
double gain_double;
|
||||
double threshold_double;
|
||||
double lin_gain_double;
|
||||
double detect_double;
|
||||
double threshold_log_double;
|
||||
double new_threshold_log_double;
|
||||
float fa_float[3], fm_float[3];
|
||||
float dstate_float[2];
|
||||
float fstate_float[2];
|
||||
float tstate_float[2];
|
||||
float gain_float;
|
||||
float threshold_float;
|
||||
float lin_gain_float;
|
||||
float detect_float;
|
||||
float threshold_log_float;
|
||||
float new_threshold_log_float;
|
||||
int detection;
|
||||
int init;
|
||||
} ChannelContext;
|
||||
|
||||
@ -52,6 +66,7 @@ typedef struct AudioDynamicEqualizerContext {
|
||||
const AVClass *class;
|
||||
|
||||
double threshold;
|
||||
double threshold_log;
|
||||
double dfrequency;
|
||||
double dqfactor;
|
||||
double tfrequency;
|
||||
@ -59,10 +74,12 @@ typedef struct AudioDynamicEqualizerContext {
|
||||
double ratio;
|
||||
double range;
|
||||
double makeup;
|
||||
double attack;
|
||||
double release;
|
||||
double attack_coef;
|
||||
double release_coef;
|
||||
double dattack;
|
||||
double drelease;
|
||||
double dattack_coef;
|
||||
double drelease_coef;
|
||||
double gattack_coef;
|
||||
double grelease_coef;
|
||||
int mode;
|
||||
int detection;
|
||||
int tftype;
|
||||
@ -100,7 +117,7 @@ static int query_formats(AVFilterContext *ctx)
|
||||
|
||||
static double get_coef(double x, double sr)
|
||||
{
|
||||
return 1.0 - exp(-1000. / (x * sr));
|
||||
return 1.0 - exp(-1.0 / (0.001 * x * sr));
|
||||
}
|
||||
|
||||
typedef struct ThreadData {
|
||||
@ -135,12 +152,6 @@ static int config_input(AVFilterLink *inlink)
|
||||
break;
|
||||
}
|
||||
|
||||
for (int ch = 0; ch < inlink->ch_layout.nb_channels; ch++) {
|
||||
ChannelContext *cc = &s->cc[ch];
|
||||
cc->gain_float = 1.f;
|
||||
cc->gain_double = 1.0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -191,12 +202,12 @@ static const AVOption adynamicequalizer_options[] = {
|
||||
{ "dqfactor", "set detection Q factor", OFFSET(dqfactor), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0.001, 1000, FLAGS },
|
||||
{ "tfrequency", "set target frequency", OFFSET(tfrequency), AV_OPT_TYPE_DOUBLE, {.dbl=1000}, 2, 1000000, FLAGS },
|
||||
{ "tqfactor", "set target Q factor", OFFSET(tqfactor), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0.001, 1000, FLAGS },
|
||||
{ "attack", "set attack duration", OFFSET(attack), AV_OPT_TYPE_DOUBLE, {.dbl=20}, 1, 2000, FLAGS },
|
||||
{ "release", "set release duration", OFFSET(release), AV_OPT_TYPE_DOUBLE, {.dbl=200}, 1, 2000, FLAGS },
|
||||
{ "attack", "set detection attack duration", OFFSET(dattack), AV_OPT_TYPE_DOUBLE, {.dbl=20}, 0.01, 2000, FLAGS },
|
||||
{ "release","set detection release duration",OFFSET(drelease), AV_OPT_TYPE_DOUBLE, {.dbl=200}, 0.01, 2000, FLAGS },
|
||||
{ "ratio", "set ratio factor", OFFSET(ratio), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0, 30, FLAGS },
|
||||
{ "makeup", "set makeup gain", OFFSET(makeup), AV_OPT_TYPE_DOUBLE, {.dbl=0}, 0, 100, FLAGS },
|
||||
{ "range", "set max gain", OFFSET(range), AV_OPT_TYPE_DOUBLE, {.dbl=50}, 1, 200, FLAGS },
|
||||
{ "mode", "set mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=0}, LISTEN,NB_MODES-1,FLAGS, "mode" },
|
||||
{ "makeup", "set makeup gain", OFFSET(makeup), AV_OPT_TYPE_DOUBLE, {.dbl=0}, 0, 1000, FLAGS },
|
||||
{ "range", "set max gain", OFFSET(range), AV_OPT_TYPE_DOUBLE, {.dbl=50}, 1, 2000, FLAGS },
|
||||
{ "mode", "set mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=0}, LISTEN,NB_FMODES-1,FLAGS, "mode" },
|
||||
{ "listen", 0, 0, AV_OPT_TYPE_CONST, {.i64=LISTEN}, 0, 0, FLAGS, "mode" },
|
||||
{ "cutbelow", 0, 0, AV_OPT_TYPE_CONST, {.i64=CUT_BELOW},0, 0, FLAGS, "mode" },
|
||||
{ "cutabove", 0, 0, AV_OPT_TYPE_CONST, {.i64=CUT_ABOVE},0, 0, FLAGS, "mode" },
|
||||
@ -211,10 +222,10 @@ static const AVOption adynamicequalizer_options[] = {
|
||||
{ "bell", 0, 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, FLAGS, "tftype" },
|
||||
{ "lowshelf", 0, 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, FLAGS, "tftype" },
|
||||
{ "highshelf",0, 0, AV_OPT_TYPE_CONST, {.i64=2}, 0, 0, FLAGS, "tftype" },
|
||||
{ "auto", "set auto threshold", OFFSET(detection), AV_OPT_TYPE_INT, {.i64=-1}, -1, 1, FLAGS, "auto" },
|
||||
{ "disabled", 0, 0, AV_OPT_TYPE_CONST, {.i64=-1}, 0, 0, FLAGS, "auto" },
|
||||
{ "off", 0, 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, FLAGS, "auto" },
|
||||
{ "on", 0, 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, FLAGS, "auto" },
|
||||
{ "auto", "set auto threshold", OFFSET(detection), AV_OPT_TYPE_INT, {.i64=DET_OFF},DET_DISABLED,NB_DMODES-1,FLAGS, "auto" },
|
||||
{ "disabled", 0, 0, AV_OPT_TYPE_CONST, {.i64=DET_DISABLED}, 0, 0, FLAGS, "auto" },
|
||||
{ "off", 0, 0, AV_OPT_TYPE_CONST, {.i64=DET_OFF}, 0, 0, FLAGS, "auto" },
|
||||
{ "on", 0, 0, AV_OPT_TYPE_CONST, {.i64=DET_ON}, 0, 0, FLAGS, "auto" },
|
||||
{ "precision", "set processing precision", OFFSET(precision), AV_OPT_TYPE_INT, {.i64=0}, 0, 2, AF, "precision" },
|
||||
{ "auto", "set auto processing precision", 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, AF, "precision" },
|
||||
{ "float", "set single-floating point processing precision", 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, AF, "precision" },
|
||||
|
Loading…
x
Reference in New Issue
Block a user