FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavfilter/af_acrusher.c
Date: 2024-04-19 17:50:32
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 "internal.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 AVFilter ff_af_acrusher = {
330 .name = "acrusher",
331 .description = NULL_IF_CONFIG_SMALL("Reduce audio bit resolution."),
332 .priv_size = sizeof(ACrusherContext),
333 .priv_class = &acrusher_class,
334 .uninit = uninit,
335 FILTER_INPUTS(avfilter_af_acrusher_inputs),
336 FILTER_OUTPUTS(ff_audio_default_filterpad),
337 FILTER_SINGLE_SAMPLEFMT(AV_SAMPLE_FMT_DBL),
338 .process_command = process_command,
339 .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL,
340 };
341