diff options
author | Paul B Mahol <onemda@gmail.com> | 2016-08-10 16:11:37 +0200 |
---|---|---|
committer | Paul B Mahol <onemda@gmail.com> | 2016-08-11 15:02:16 +0200 |
commit | 7f1b14bc5730bd5603dda57302d4adad94ccdd60 (patch) | |
tree | bb98e471cc6cde00dbcc964e9df72e73350a0f92 | |
parent | cc6a59d2b9116a4084275bbb8634862ddd14ec56 (diff) | |
download | ffmpeg-7f1b14bc5730bd5603dda57302d4adad94ccdd60.tar.gz |
avfilter: add acrusher filter
-rw-r--r-- | Changelog | 1 | ||||
-rw-r--r-- | doc/filters.texi | 58 | ||||
-rw-r--r-- | libavfilter/Makefile | 1 | ||||
-rw-r--r-- | libavfilter/af_acrusher.c | 362 | ||||
-rw-r--r-- | libavfilter/allfilters.c | 1 | ||||
-rw-r--r-- | libavfilter/version.h | 2 |
6 files changed, 424 insertions, 1 deletions
@@ -14,6 +14,7 @@ version <next>: - MediaCodec hwaccel - True Audio (TTA) muxer - crystalizer audio filter +- acrusher audio filter version 3.1: diff --git a/doc/filters.texi b/doc/filters.texi index 9dab959bdb..8bb0ca00ca 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -441,6 +441,64 @@ ffmpeg -i first.flac -i second.flac -filter_complex acrossfade=d=10:o=0:c1=exp:c @end example @end itemize +@section acrusher + +Reduce audio bit resolution. + +This filter is bit crusher with enhanced funcionality. A bit crusher +is used to audibly reduce number of bits an audio signal is sampled +with. This doesn't change the bit depth at all, it just produces the +effect. Material reduced in bit depth sounds more harsh and "digital". +This filter is able to even round to continous values instead of discrete +bit depths. +Additionally it has a D/C offset which results in different crushing of +the lower and the upper half of the signal. +An Anti-Aliasing setting is able to produce "softer" crushing sounds. + +Another feature of this filter is the logarithmic mode. +This setting switches from linear distances between bits to logarithmic ones. +The result is a much more "natural" sounding crusher which doesn't gate low +signals for example. The human ear has a logarithmic perception, too +so this kind of crushing is much more pleasant. +Logarithmic crushing is also able to get anti-aliased. + +The filter accepts the following options: + +@table @option +@item level_in +Set level in. + +@item level_out +Set level out. + +@item bits +Set bit reduction. + +@item mix +Set mixing ammount. + +@item mode +Can be linear: @code{lin} or logarithmic: @code{log}. + +@item dc +Set DC. + +@item aa +Set anti-aliasing. + +@item samples +Set sample reduction. + +@item lfo +Enable LFO. By default disabled. + +@item lforange +Set LFO range. + +@item lforate +Set LFO rate. +@end table + @section adelay Delay one or more audio channels. diff --git a/libavfilter/Makefile b/libavfilter/Makefile index cd62fd563d..0d94f84b45 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -30,6 +30,7 @@ OBJS-$(HAVE_THREADS) += pthread.o OBJS-$(CONFIG_ABENCH_FILTER) += f_bench.o OBJS-$(CONFIG_ACOMPRESSOR_FILTER) += af_sidechaincompress.o OBJS-$(CONFIG_ACROSSFADE_FILTER) += af_afade.o +OBJS-$(CONFIG_ACRUSHER_FILTER) += af_acrusher.o OBJS-$(CONFIG_ADELAY_FILTER) += af_adelay.o OBJS-$(CONFIG_AECHO_FILTER) += af_aecho.o OBJS-$(CONFIG_AEMPHASIS_FILTER) += af_aemphasis.o diff --git a/libavfilter/af_acrusher.c b/libavfilter/af_acrusher.c new file mode 100644 index 0000000000..66d299d406 --- /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, sun, smax, sov; + + 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.); + sun = s->samples - rad - s->smin; + smax = FFMIN(s->samples + rad, 250.); + sov = s->samples + rad - smax; + smax -= sun; + s->smin -= sov; + 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, +}; diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 7f78c6506f..feed4f8930 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -48,6 +48,7 @@ void avfilter_register_all(void) REGISTER_FILTER(ABENCH, abench, af); REGISTER_FILTER(ACOMPRESSOR, acompressor, af); REGISTER_FILTER(ACROSSFADE, acrossfade, af); + REGISTER_FILTER(ACRUSHER, acrusher, af); REGISTER_FILTER(ADELAY, adelay, af); REGISTER_FILTER(AECHO, aecho, af); REGISTER_FILTER(AEMPHASIS, aemphasis, af); diff --git a/libavfilter/version.h b/libavfilter/version.h index f44edf8dae..ac66c08b06 100644 --- a/libavfilter/version.h +++ b/libavfilter/version.h @@ -30,7 +30,7 @@ #include "libavutil/version.h" #define LIBAVFILTER_VERSION_MAJOR 6 -#define LIBAVFILTER_VERSION_MINOR 50 +#define LIBAVFILTER_VERSION_MINOR 51 #define LIBAVFILTER_VERSION_MICRO 100 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ |