diff options
Diffstat (limited to 'libavfilter/af_acrusher.c')
-rw-r--r-- | libavfilter/af_acrusher.c | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/libavfilter/af_acrusher.c b/libavfilter/af_acrusher.c new file mode 100644 index 0000000000..ddce74465d --- /dev/null +++ b/libavfilter/af_acrusher.c @@ -0,0 +1,362 @@ +/* + * Copyright (c) Markus Schmidt and Christian Holschuh + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/opt.h" +#include "avfilter.h" +#include "internal.h" +#include "audio.h" + +typedef struct LFOContext { + double freq; + double offset; + int srate; + double amount; + double pwidth; + double phase; +} LFOContext; + +typedef struct SRContext { + double target; + double real; + double samples; + double last; +} SRContext; + +typedef struct ACrusherContext { + const AVClass *class; + + double level_in; + double level_out; + double bits; + double mix; + int mode; + double dc; + double idc; + double aa; + double samples; + int is_lfo; + double lforange; + double lforate; + + double sqr; + double aa1; + double coeff; + int round; + double sov; + double smin; + double sdiff; + + LFOContext lfo; + SRContext *sr; +} ACrusherContext; + +#define OFFSET(x) offsetof(ACrusherContext, x) +#define A AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM + +static const AVOption acrusher_options[] = { + { "level_in", "set level in", OFFSET(level_in), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0.015625, 64, A }, + { "level_out","set level out", OFFSET(level_out), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0.015625, 64, A }, + { "bits", "set bit reduction", OFFSET(bits), AV_OPT_TYPE_DOUBLE, {.dbl=8}, 1, 64, A }, + { "mix", "set mix", OFFSET(mix), AV_OPT_TYPE_DOUBLE, {.dbl=.5}, 0, 1, A }, + { "mode", "set mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, A, "mode" }, + { "lin", "linear", 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, A, "mode" }, + { "log", "logarithmic", 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, A, "mode" }, + { "dc", "set DC", OFFSET(dc), AV_OPT_TYPE_DOUBLE, {.dbl=1}, .25, 4, A }, + { "aa", "set anti-aliasing", OFFSET(aa), AV_OPT_TYPE_DOUBLE, {.dbl=.5}, 0, 1, A }, + { "samples", "set sample reduction", OFFSET(samples), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 1, 250, A }, + { "lfo", "enable LFO", OFFSET(is_lfo), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, A }, + { "lforange", "set LFO depth", OFFSET(lforange), AV_OPT_TYPE_DOUBLE, {.dbl=20}, 1, 250, A }, + { "lforate", "set LFO rate", OFFSET(lforate), AV_OPT_TYPE_DOUBLE, {.dbl=.3}, .01, 200, A }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(acrusher); + +static double samplereduction(ACrusherContext *s, SRContext *sr, double in) +{ + sr->samples++; + if (sr->samples >= s->round) { + sr->target += s->samples; + sr->real += s->round; + if (sr->target + s->samples >= sr->real + 1) { + sr->last = in; + sr->target = 0; + sr->real = 0; + } + sr->samples = 0; + } + return sr->last; +} + +static double add_dc(double s, double dc, double idc) +{ + return s > 0 ? s * dc : s * idc; +} + +static double remove_dc(double s, double dc, double idc) +{ + return s > 0 ? s * idc : s * dc; +} + +static inline double factor(double y, double k, double aa1, double aa) +{ + return 0.5 * (sin(M_PI * (fabs(y - k) - aa1) / aa - M_PI_2) + 1); +} + +static double bitreduction(ACrusherContext *s, double in) +{ + const double sqr = s->sqr; + const double coeff = s->coeff; + const double aa = s->aa; + const double aa1 = s->aa1; + double y, k; + + // add dc + in = add_dc(in, s->dc, s->idc); + + // main rounding calculation depending on mode + + // the idea for anti-aliasing: + // you need a function f which brings you to the scale, where + // you want to round and the function f_b (with f(f_b)=id) which + // brings you back to your original scale. + // + // then you can use the logic below in the following way: + // y = f(in) and k = roundf(y) + // if (y > k + aa1) + // k = f_b(k) + ( f_b(k+1) - f_b(k) ) * 0.5 * (sin(x - PI/2) + 1) + // if (y < k + aa1) + // k = f_b(k) - ( f_b(k+1) - f_b(k) ) * 0.5 * (sin(x - PI/2) + 1) + // + // whereas x = (fabs(f(in) - k) - aa1) * PI / aa + // for both cases. + + switch (s->mode) { + case 0: + default: + // linear + y = in * coeff; + k = roundf(y); + if (k - aa1 <= y && y <= k + aa1) { + k /= coeff; + } else if (y > k + aa1) { + k = k / coeff + ((k + 1) / coeff - k / coeff) * + factor(y, k, aa1, aa); + } else { + k = k / coeff - (k / coeff - (k - 1) / coeff) * + factor(y, k, aa1, aa); + } + break; + case 1: + // logarithmic + y = sqr * log(fabs(in)) + sqr * sqr; + k = roundf(y); + if(!in) { + k = 0; + } else if (k - aa1 <= y && y <= k + aa1) { + k = in / fabs(in) * exp(k / sqr - sqr); + } else if (y > k + aa1) { + double x = exp(k / sqr - sqr); + k = FFSIGN(in) * (x + (exp((k + 1) / sqr - sqr) - x) * + factor(y, k, aa1, aa)); + } else { + double x = exp(k / sqr - sqr); + k = in / fabs(in) * (x - (x - exp((k - 1) / sqr - sqr)) * + factor(y, k, aa1, aa)); + } + break; + } + + // mix between dry and wet signal + k += (in - k) * s->mix; + + // remove dc + k = remove_dc(k, s->dc, s->idc); + + return k; +} + +static double lfo_get(LFOContext *lfo) +{ + double phs = FFMIN(100., lfo->phase / FFMIN(1.99, FFMAX(0.01, lfo->pwidth)) + lfo->offset); + double val; + + if (phs > 1) + phs = fmod(phs, 1.); + + val = sin((phs * 360.) * M_PI / 180); + + return val * lfo->amount; +} + +static void lfo_advance(LFOContext *lfo, unsigned count) +{ + lfo->phase = fabs(lfo->phase + count * lfo->freq * (1. / lfo->srate)); + if (lfo->phase >= 1.) + lfo->phase = fmod(lfo->phase, 1.); +} + +static int filter_frame(AVFilterLink *inlink, AVFrame *in) +{ + AVFilterContext *ctx = inlink->dst; + ACrusherContext *s = ctx->priv; + AVFilterLink *outlink = ctx->outputs[0]; + AVFrame *out; + const double *src = (const double *)in->data[0]; + double *dst; + const double level_in = s->level_in; + const double level_out = s->level_out; + const double mix = s->mix; + int n, c; + + if (av_frame_is_writable(in)) { + out = in; + } else { + out = ff_get_audio_buffer(inlink, in->nb_samples); + if (!out) { + av_frame_free(&in); + return AVERROR(ENOMEM); + } + av_frame_copy_props(out, in); + } + + dst = (double *)out->data[0]; + for (n = 0; n < in->nb_samples; n++) { + if (s->is_lfo) { + s->samples = s->smin + s->sdiff * (lfo_get(&s->lfo) + 0.5); + s->round = round(s->samples); + } + + for (c = 0; c < inlink->channels; c++) { + double sample = src[c] * level_in; + + sample = mix * samplereduction(s, &s->sr[c], sample) + src[c] * (1. - mix) * level_in; + dst[c] = bitreduction(s, sample) * level_out; + } + src += c; + dst += c; + + if (s->is_lfo) + lfo_advance(&s->lfo, 1); + } + + if (in != out) + av_frame_free(&in); + + return ff_filter_frame(outlink, out); +} + +static int query_formats(AVFilterContext *ctx) +{ + AVFilterFormats *formats; + AVFilterChannelLayouts *layouts; + static const enum AVSampleFormat sample_fmts[] = { + AV_SAMPLE_FMT_DBL, + AV_SAMPLE_FMT_NONE + }; + int ret; + + layouts = ff_all_channel_counts(); + if (!layouts) + return AVERROR(ENOMEM); + ret = ff_set_common_channel_layouts(ctx, layouts); + if (ret < 0) + return ret; + + formats = ff_make_format_list(sample_fmts); + if (!formats) + return AVERROR(ENOMEM); + ret = ff_set_common_formats(ctx, formats); + if (ret < 0) + return ret; + + formats = ff_all_samplerates(); + if (!formats) + return AVERROR(ENOMEM); + return ff_set_common_samplerates(ctx, formats); +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + ACrusherContext *s = ctx->priv; + + av_freep(&s->sr); +} + +static int config_input(AVFilterLink *inlink) +{ + AVFilterContext *ctx = inlink->dst; + ACrusherContext *s = ctx->priv; + double rad, sunder, smax, sover; + + s->idc = 1. / s->dc; + s->coeff = exp2(s->bits) - 1; + s->sqr = sqrt(s->coeff / 2); + s->aa1 = (1. - s->aa) / 2.; + s->round = round(s->samples); + rad = s->lforange / 2.; + s->smin = FFMAX(s->samples - rad, 1.); + sunder = s->samples - rad - s->smin; + smax = FFMIN(s->samples + rad, 250.); + sover = s->samples + rad - smax; + smax -= sunder; + s->smin -= sover; + s->sdiff = smax - s->smin; + + s->lfo.freq = s->lforate; + s->lfo.pwidth = 1.; + s->lfo.srate = inlink->sample_rate; + s->lfo.amount = .5; + + s->sr = av_calloc(inlink->channels, sizeof(*s->sr)); + if (!s->sr) + return AVERROR(ENOMEM); + + return 0; +} + +static const AVFilterPad avfilter_af_acrusher_inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_AUDIO, + .config_props = config_input, + .filter_frame = filter_frame, + }, + { NULL } +}; + +static const AVFilterPad avfilter_af_acrusher_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_AUDIO, + }, + { NULL } +}; + +AVFilter ff_af_acrusher = { + .name = "acrusher", + .description = NULL_IF_CONFIG_SMALL("Reduce audio bit resolution."), + .priv_size = sizeof(ACrusherContext), + .priv_class = &acrusher_class, + .uninit = uninit, + .query_formats = query_formats, + .inputs = avfilter_af_acrusher_inputs, + .outputs = avfilter_af_acrusher_outputs, +}; |