FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavfilter/af_acrusher.c
Date: 2026-09-24 20:08:24
Exec Total Coverage
Lines: 0 130 0.0%
Functions: 0 11 0.0%
Branches: 0 70 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (c) Markus Schmidt and Christian Holschuh
3 *
4 * This file is part of FFmpeg.
5 *
6 * FFmpeg is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * FFmpeg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with FFmpeg; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include "libavutil/mem.h"
22 #include "libavutil/opt.h"
23 #include "avfilter.h"
24 #include "filters.h"
25 #include "audio.h"
26
27 typedef struct LFOContext {
28 double freq;
29 double offset;
30 int srate;
31 double amount;
32 double pwidth;
33 double phase;
34 } LFOContext;
35
36 typedef struct SRContext {
37 double target;
38 double real;
39 double samples;
40 double last;
41 } SRContext;
42
43 typedef struct ACrusherContext {
44 const AVClass *class;
45
46 double level_in;
47 double level_out;
48 double bits;
49 double mix;
50 int mode;
51 double dc;
52 double idc;
53 double aa;
54 double samples;
55 int is_lfo;
56 double lforange;
57 double lforate;
58
59 double sqr;
60 double aa1;
61 double coeff;
62 int round;
63 double sov;
64 double smin;
65 double sdiff;
66
67 LFOContext lfo;
68 SRContext *sr;
69 } ACrusherContext;
70
71 #define OFFSET(x) offsetof(ACrusherContext, x)
72 #define A AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_RUNTIME_PARAM
73
74 static const AVOption acrusher_options[] = {
75 { "level_in", "set level in", OFFSET(level_in), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0.015625, 64, A },
76 { "level_out","set level out", OFFSET(level_out), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0.015625, 64, A },
77 { "bits", "set bit reduction", OFFSET(bits), AV_OPT_TYPE_DOUBLE, {.dbl=8}, 1, 64, A },
78 { "mix", "set mix", OFFSET(mix), AV_OPT_TYPE_DOUBLE, {.dbl=.5}, 0, 1, A },
79 { "mode", "set mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, A, .unit = "mode" },
80 { "lin", "linear", 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, A, .unit = "mode" },
81 { "log", "logarithmic", 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, A, .unit = "mode" },
82 { "dc", "set DC", OFFSET(dc), AV_OPT_TYPE_DOUBLE, {.dbl=1}, .25, 4, A },
83 { "aa", "set anti-aliasing", OFFSET(aa), AV_OPT_TYPE_DOUBLE, {.dbl=.5}, 0, 1, A },
84 { "samples", "set sample reduction", OFFSET(samples), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 1, 250, A },
85 { "lfo", "enable LFO", OFFSET(is_lfo), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, A },
86 { "lforange", "set LFO depth", OFFSET(lforange), AV_OPT_TYPE_DOUBLE, {.dbl=20}, 1, 250, A },
87 { "lforate", "set LFO rate", OFFSET(lforate), AV_OPT_TYPE_DOUBLE, {.dbl=.3}, .01, 200, A },
88 { NULL }
89 };
90
91 AVFILTER_DEFINE_CLASS(acrusher);
92
93 ✗ static double samplereduction(ACrusherContext *s, SRContext *sr, double in)
94 {
95 ✗ sr->samples++;
96 ✗ if (sr->samples >= s->round) {
97 ✗ sr->target += s->samples;
98 ✗ sr->real += s->round;
99 ✗ if (sr->target + s->samples >= sr->real + 1) {
100 ✗ sr->last = in;
101 ✗ sr->target = 0;
102 ✗ sr->real = 0;
103 }
104 ✗ sr->samples = 0;
105 }
106 ✗ return sr->last;
107 }
108
109 ✗ static double add_dc(double s, double dc, double idc)
110 {
111 ✗ return s > 0 ? s * dc : s * idc;
112 }
113
114 ✗ static double remove_dc(double s, double dc, double idc)
115 {
116 ✗ return s > 0 ? s * idc : s * dc;
117 }
118
119 ✗ static inline double factor(double y, double k, double aa1, double aa)
120 {
121 ✗ return 0.5 * (sin(M_PI * (fabs(y - k) - aa1) / aa - M_PI_2) + 1);
122 }
123
124 ✗ static double bitreduction(ACrusherContext *s, double in)
125 {
126 ✗ const double sqr = s->sqr;
127 ✗ const double coeff = s->coeff;
128 ✗ const double aa = s->aa;
129 ✗ const double aa1 = s->aa1;
130 double y, k;
131
132 // add dc
133 ✗ in = add_dc(in, s->dc, s->idc);
134
135 // main rounding calculation depending on mode
136
137 // the idea for anti-aliasing:
138 // you need a function f which brings you to the scale, where
139 // you want to round and the function f_b (with f(f_b)=id) which
140 // brings you back to your original scale.
141 //
142 // then you can use the logic below in the following way:
143 // y = f(in) and k = roundf(y)
144 // if (y > k + aa1)
145 // k = f_b(k) + ( f_b(k+1) - f_b(k) ) * 0.5 * (sin(x - PI/2) + 1)
146 // if (y < k + aa1)
147 // k = f_b(k) - ( f_b(k+1) - f_b(k) ) * 0.5 * (sin(x - PI/2) + 1)
148 //
149 // whereas x = (fabs(f(in) - k) - aa1) * PI / aa
150 // for both cases.
151
152 ✗ switch (s->mode) {
153 ✗ case 0:
154 default:
155 // linear
156 ✗ y = in * coeff;
157 ✗ k = roundf(y);
158 ✗ if (k - aa1 <= y && y <= k + aa1) {
159 ✗ k /= coeff;
160 ✗ } else if (y > k + aa1) {
161 ✗ k = k / coeff + ((k + 1) / coeff - k / coeff) *
162 ✗ factor(y, k, aa1, aa);
163 } else {
164 ✗ k = k / coeff - (k / coeff - (k - 1) / coeff) *
165 ✗ factor(y, k, aa1, aa);
166 }
167 ✗ break;
168 ✗ case 1:
169 // logarithmic
170 ✗ y = sqr * log(fabs(in)) + sqr * sqr;
171 ✗ k = roundf(y);
172 ✗ if(!in) {
173 ✗ k = 0;
174 ✗ } else if (k - aa1 <= y && y <= k + aa1) {
175 ✗ k = in / fabs(in) * exp(k / sqr - sqr);
176 ✗ } else if (y > k + aa1) {
177 ✗ double x = exp(k / sqr - sqr);
178 ✗ k = FFSIGN(in) * (x + (exp((k + 1) / sqr - sqr) - x) *
179 ✗ factor(y, k, aa1, aa));
180 } else {
181 ✗ double x = exp(k / sqr - sqr);
182 ✗ k = in / fabs(in) * (x - (x - exp((k - 1) / sqr - sqr)) *
183 ✗ factor(y, k, aa1, aa));
184 }
185 ✗ break;
186 }
187
188 // mix between dry and wet signal
189 ✗ k += (in - k) * s->mix;
190
191 // remove dc
192 ✗ k = remove_dc(k, s->dc, s->idc);
193
194 ✗ return k;
195 }
196
197 ✗ static double lfo_get(LFOContext *lfo)
198 {
199 ✗ double phs = FFMIN(100., lfo->phase / FFMIN(1.99, FFMAX(0.01, lfo->pwidth)) + lfo->offset);
200 double val;
201
202 ✗ if (phs > 1)
203 ✗ phs = fmod(phs, 1.);
204
205 ✗ val = sin((phs * 360.) * M_PI / 180);
206
207 ✗ return val * lfo->amount;
208 }
209
210 ✗ static void lfo_advance(LFOContext *lfo, unsigned count)
211 {
212 ✗ lfo->phase = fabs(lfo->phase + count * lfo->freq * (1. / lfo->srate));
213 ✗ if (lfo->phase >= 1.)
214 ✗ lfo->phase = fmod(lfo->phase, 1.);
215 ✗ }
216
217 ✗ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
218 {
219 ✗ AVFilterContext *ctx = inlink->dst;
220 ✗ ACrusherContext *s = ctx->priv;
221 ✗ AVFilterLink *outlink = ctx->outputs[0];
222 AVFrame *out;
223 ✗ const double *src = (const double *)in->data[0];
224 double *dst;
225 ✗ const double level_in = s->level_in;
226 ✗ const double level_out = s->level_out;
227 ✗ const double mix = s->mix;
228 int n, c;
229
230 ✗ if (av_frame_is_writable(in)) {
231 ✗ out = in;
232 } else {
233 ✗ out = ff_get_audio_buffer(inlink, in->nb_samples);
234 ✗ if (!out) {
235 ✗ av_frame_free(&in);
236 ✗ return AVERROR(ENOMEM);
237 }
238 ✗ av_frame_copy_props(out, in);
239 }
240
241 ✗ dst = (double *)out->data[0];
242 ✗ for (n = 0; n < in->nb_samples; n++) {
243 ✗ if (s->is_lfo) {
244 ✗ s->samples = s->smin + s->sdiff * (lfo_get(&s->lfo) + 0.5);
245 ✗ s->round = round(s->samples);
246 }
247
248 ✗ for (c = 0; c < inlink->ch_layout.nb_channels; c++) {
249 ✗ double sample = src[c] * level_in;
250
251 ✗ sample = mix * samplereduction(s, &s->sr[c], sample) + src[c] * (1. - mix) * level_in;
252 ✗ dst[c] = ctx->is_disabled ? src[c] : bitreduction(s, sample) * level_out;
253 }
254 ✗ src += c;
255 ✗ dst += c;
256
257 ✗ if (s->is_lfo)
258 ✗ lfo_advance(&s->lfo, 1);
259 }
260
261 ✗ if (in != out)
262 ✗ av_frame_free(&in);
263
264 ✗ return ff_filter_frame(outlink, out);
265 }
266
267 ✗ static av_cold void uninit(AVFilterContext *ctx)
268 {
269 ✗ ACrusherContext *s = ctx->priv;
270
271 ✗ av_freep(&s->sr);
272 ✗ }
273
274 ✗ static int config_input(AVFilterLink *inlink)
275 {
276 ✗ AVFilterContext *ctx = inlink->dst;
277 ✗ ACrusherContext *s = ctx->priv;
278 double rad, sunder, smax, sover;
279
280 ✗ s->idc = 1. / s->dc;
281 ✗ s->coeff = exp2(s->bits) - 1;
282 ✗ s->sqr = sqrt(s->coeff / 2);
283 ✗ s->aa1 = (1. - s->aa) / 2.;
284 ✗ s->round = round(s->samples);
285 ✗ rad = s->lforange / 2.;
286 ✗ s->smin = FFMAX(s->samples - rad, 1.);
287 ✗ sunder = s->samples - rad - s->smin;
288 ✗ smax = FFMIN(s->samples + rad, 250.);
289 ✗ sover = s->samples + rad - smax;
290 ✗ smax -= sunder;
291 ✗ s->smin -= sover;
292 ✗ s->sdiff = smax - s->smin;
293
294 ✗ s->lfo.freq = s->lforate;
295 ✗ s->lfo.pwidth = 1.;
296 ✗ s->lfo.srate = inlink->sample_rate;
297 ✗ s->lfo.amount = .5;
298
299 ✗ if (!s->sr)
300 ✗ s->sr = av_calloc(inlink->ch_layout.nb_channels, sizeof(*s->sr));
301 ✗ if (!s->sr)
302 ✗ return AVERROR(ENOMEM);
303
304 ✗ return 0;
305 }
306
307 ✗ static int process_command(AVFilterContext *ctx, const char *cmd, const char *args,
308 char *res, int res_len, int flags)
309 {
310 ✗ AVFilterLink *inlink = ctx->inputs[0];
311 int ret;
312
313 ✗ ret = ff_filter_process_command(ctx, cmd, args, res, res_len, flags);
314 ✗ if (ret < 0)
315 ✗ return ret;
316
317 ✗ return config_input(inlink);
318 }
319
320 static const AVFilterPad avfilter_af_acrusher_inputs[] = {
321 {
322 .name = "default",
323 .type = AVMEDIA_TYPE_AUDIO,
324 .config_props = config_input,
325 .filter_frame = filter_frame,
326 },
327 };
328
329 const FFFilter ff_af_acrusher = {
330 .p.name = "acrusher",
331 .p.description = NULL_IF_CONFIG_SMALL("Reduce audio bit resolution."),
332 .p.priv_class = &acrusher_class,
333 .p.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL,
334 .priv_size = sizeof(ACrusherContext),
335 .uninit = uninit,
336 FILTER_INPUTS(avfilter_af_acrusher_inputs),
337 FILTER_OUTPUTS(ff_audio_default_filterpad),
338 FILTER_SINGLE_SAMPLEFMT(AV_SAMPLE_FMT_DBL),
339 .process_command = process_command,
340 };
341