blob: ed191a3e34b9654e9bd526d5f62a5dc7eb070cda
1 | /* |
2 | * Copyright (c) 2016 Paul B Mahol |
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 | /** |
22 | * @file |
23 | * SpectrumSynth filter |
24 | * @todo support float pixel format |
25 | */ |
26 | |
27 | #include "libavcodec/avfft.h" |
28 | #include "libavutil/avassert.h" |
29 | #include "libavutil/channel_layout.h" |
30 | #include "libavutil/ffmath.h" |
31 | #include "libavutil/opt.h" |
32 | #include "libavutil/parseutils.h" |
33 | #include "avfilter.h" |
34 | #include "formats.h" |
35 | #include "audio.h" |
36 | #include "video.h" |
37 | #include "internal.h" |
38 | #include "window_func.h" |
39 | |
40 | enum MagnitudeScale { LINEAR, LOG, NB_SCALES }; |
41 | enum SlideMode { REPLACE, SCROLL, FULLFRAME, RSCROLL, NB_SLIDES }; |
42 | enum Orientation { VERTICAL, HORIZONTAL, NB_ORIENTATIONS }; |
43 | |
44 | typedef struct SpectrumSynthContext { |
45 | const AVClass *class; |
46 | int sample_rate; |
47 | int channels; |
48 | int scale; |
49 | int sliding; |
50 | int win_func; |
51 | float overlap; |
52 | int orientation; |
53 | |
54 | AVFrame *magnitude, *phase; |
55 | FFTContext *fft; ///< Fast Fourier Transform context |
56 | int fft_bits; ///< number of bits (FFT window size = 1<<fft_bits) |
57 | FFTComplex **fft_data; ///< bins holder for each (displayed) channels |
58 | int win_size; |
59 | int size; |
60 | int nb_freq; |
61 | int hop_size; |
62 | int start, end; |
63 | int xpos; |
64 | int xend; |
65 | int64_t pts; |
66 | float factor; |
67 | AVFrame *buffer; |
68 | float *window_func_lut; ///< Window function LUT |
69 | } SpectrumSynthContext; |
70 | |
71 | #define OFFSET(x) offsetof(SpectrumSynthContext, x) |
72 | #define A AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_AUDIO_PARAM |
73 | #define V AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM |
74 | |
75 | static const AVOption spectrumsynth_options[] = { |
76 | { "sample_rate", "set sample rate", OFFSET(sample_rate), AV_OPT_TYPE_INT, {.i64 = 44100}, 15, INT_MAX, A }, |
77 | { "channels", "set channels", OFFSET(channels), AV_OPT_TYPE_INT, {.i64 = 1}, 1, 8, A }, |
78 | { "scale", "set input amplitude scale", OFFSET(scale), AV_OPT_TYPE_INT, {.i64 = LOG}, 0, NB_SCALES-1, V, "scale" }, |
79 | { "lin", "linear", 0, AV_OPT_TYPE_CONST, {.i64=LINEAR}, 0, 0, V, "scale" }, |
80 | { "log", "logarithmic", 0, AV_OPT_TYPE_CONST, {.i64=LOG}, 0, 0, V, "scale" }, |
81 | { "slide", "set input sliding mode", OFFSET(sliding), AV_OPT_TYPE_INT, {.i64 = FULLFRAME}, 0, NB_SLIDES-1, V, "slide" }, |
82 | { "replace", "consume old columns with new", 0, AV_OPT_TYPE_CONST, {.i64=REPLACE}, 0, 0, V, "slide" }, |
83 | { "scroll", "consume only most right column", 0, AV_OPT_TYPE_CONST, {.i64=SCROLL}, 0, 0, V, "slide" }, |
84 | { "fullframe", "consume full frames", 0, AV_OPT_TYPE_CONST, {.i64=FULLFRAME}, 0, 0, V, "slide" }, |
85 | { "rscroll", "consume only most left column", 0, AV_OPT_TYPE_CONST, {.i64=RSCROLL}, 0, 0, V, "slide" }, |
86 | { "win_func", "set window function", OFFSET(win_func), AV_OPT_TYPE_INT, {.i64 = 0}, 0, NB_WFUNC-1, A, "win_func" }, |
87 | { "rect", "Rectangular", 0, AV_OPT_TYPE_CONST, {.i64=WFUNC_RECT}, 0, 0, A, "win_func" }, |
88 | { "bartlett", "Bartlett", 0, AV_OPT_TYPE_CONST, {.i64=WFUNC_BARTLETT}, 0, 0, A, "win_func" }, |
89 | { "hann", "Hann", 0, AV_OPT_TYPE_CONST, {.i64=WFUNC_HANNING}, 0, 0, A, "win_func" }, |
90 | { "hanning", "Hanning", 0, AV_OPT_TYPE_CONST, {.i64=WFUNC_HANNING}, 0, 0, A, "win_func" }, |
91 | { "hamming", "Hamming", 0, AV_OPT_TYPE_CONST, {.i64=WFUNC_HAMMING}, 0, 0, A, "win_func" }, |
92 | { "sine", "Sine", 0, AV_OPT_TYPE_CONST, {.i64=WFUNC_SINE}, 0, 0, A, "win_func" }, |
93 | { "overlap", "set window overlap", OFFSET(overlap), AV_OPT_TYPE_FLOAT, {.dbl=1}, 0, 1, A }, |
94 | { "orientation", "set orientation", OFFSET(orientation), AV_OPT_TYPE_INT, {.i64=VERTICAL}, 0, NB_ORIENTATIONS-1, V, "orientation" }, |
95 | { "vertical", NULL, 0, AV_OPT_TYPE_CONST, {.i64=VERTICAL}, 0, 0, V, "orientation" }, |
96 | { "horizontal", NULL, 0, AV_OPT_TYPE_CONST, {.i64=HORIZONTAL}, 0, 0, V, "orientation" }, |
97 | { NULL } |
98 | }; |
99 | |
100 | AVFILTER_DEFINE_CLASS(spectrumsynth); |
101 | |
102 | static int query_formats(AVFilterContext *ctx) |
103 | { |
104 | SpectrumSynthContext *s = ctx->priv; |
105 | AVFilterFormats *formats = NULL; |
106 | AVFilterChannelLayouts *layout = NULL; |
107 | AVFilterLink *magnitude = ctx->inputs[0]; |
108 | AVFilterLink *phase = ctx->inputs[1]; |
109 | AVFilterLink *outlink = ctx->outputs[0]; |
110 | static const enum AVSampleFormat sample_fmts[] = { AV_SAMPLE_FMT_FLTP, AV_SAMPLE_FMT_NONE }; |
111 | static const enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY16, |
112 | AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUVJ444P, |
113 | AV_PIX_FMT_YUV444P16, AV_PIX_FMT_NONE }; |
114 | int ret, sample_rates[] = { 48000, -1 }; |
115 | |
116 | formats = ff_make_format_list(sample_fmts); |
117 | if ((ret = ff_formats_ref (formats, &outlink->in_formats )) < 0 || |
118 | (ret = ff_add_channel_layout (&layout, FF_COUNT2LAYOUT(s->channels))) < 0 || |
119 | (ret = ff_channel_layouts_ref (layout , &outlink->in_channel_layouts)) < 0) |
120 | return ret; |
121 | |
122 | sample_rates[0] = s->sample_rate; |
123 | formats = ff_make_format_list(sample_rates); |
124 | if (!formats) |
125 | return AVERROR(ENOMEM); |
126 | if ((ret = ff_formats_ref(formats, &outlink->in_samplerates)) < 0) |
127 | return ret; |
128 | |
129 | formats = ff_make_format_list(pix_fmts); |
130 | if (!formats) |
131 | return AVERROR(ENOMEM); |
132 | if ((ret = ff_formats_ref(formats, &magnitude->out_formats)) < 0) |
133 | return ret; |
134 | |
135 | formats = ff_make_format_list(pix_fmts); |
136 | if (!formats) |
137 | return AVERROR(ENOMEM); |
138 | if ((ret = ff_formats_ref(formats, &phase->out_formats)) < 0) |
139 | return ret; |
140 | |
141 | return 0; |
142 | } |
143 | |
144 | static int config_output(AVFilterLink *outlink) |
145 | { |
146 | AVFilterContext *ctx = outlink->src; |
147 | SpectrumSynthContext *s = ctx->priv; |
148 | int width = ctx->inputs[0]->w; |
149 | int height = ctx->inputs[0]->h; |
150 | AVRational time_base = ctx->inputs[0]->time_base; |
151 | AVRational frame_rate = ctx->inputs[0]->frame_rate; |
152 | int i, ch, fft_bits; |
153 | float factor, overlap; |
154 | |
155 | outlink->sample_rate = s->sample_rate; |
156 | outlink->time_base = (AVRational){1, s->sample_rate}; |
157 | |
158 | if (width != ctx->inputs[1]->w || |
159 | height != ctx->inputs[1]->h) { |
160 | av_log(ctx, AV_LOG_ERROR, |
161 | "Magnitude and Phase sizes differ (%dx%d vs %dx%d).\n", |
162 | width, height, |
163 | ctx->inputs[1]->w, ctx->inputs[1]->h); |
164 | return AVERROR_INVALIDDATA; |
165 | } else if (av_cmp_q(time_base, ctx->inputs[1]->time_base) != 0) { |
166 | av_log(ctx, AV_LOG_ERROR, |
167 | "Magnitude and Phase time bases differ (%d/%d vs %d/%d).\n", |
168 | time_base.num, time_base.den, |
169 | ctx->inputs[1]->time_base.num, |
170 | ctx->inputs[1]->time_base.den); |
171 | return AVERROR_INVALIDDATA; |
172 | } else if (av_cmp_q(frame_rate, ctx->inputs[1]->frame_rate) != 0) { |
173 | av_log(ctx, AV_LOG_ERROR, |
174 | "Magnitude and Phase framerates differ (%d/%d vs %d/%d).\n", |
175 | frame_rate.num, frame_rate.den, |
176 | ctx->inputs[1]->frame_rate.num, |
177 | ctx->inputs[1]->frame_rate.den); |
178 | return AVERROR_INVALIDDATA; |
179 | } |
180 | |
181 | s->size = s->orientation == VERTICAL ? height / s->channels : width / s->channels; |
182 | s->xend = s->orientation == VERTICAL ? width : height; |
183 | |
184 | for (fft_bits = 1; 1 << fft_bits < 2 * s->size; fft_bits++); |
185 | |
186 | s->win_size = 1 << fft_bits; |
187 | s->nb_freq = 1 << (fft_bits - 1); |
188 | |
189 | s->fft = av_fft_init(fft_bits, 1); |
190 | if (!s->fft) { |
191 | av_log(ctx, AV_LOG_ERROR, "Unable to create FFT context. " |
192 | "The window size might be too high.\n"); |
193 | return AVERROR(EINVAL); |
194 | } |
195 | s->fft_data = av_calloc(s->channels, sizeof(*s->fft_data)); |
196 | if (!s->fft_data) |
197 | return AVERROR(ENOMEM); |
198 | for (ch = 0; ch < s->channels; ch++) { |
199 | s->fft_data[ch] = av_calloc(s->win_size, sizeof(**s->fft_data)); |
200 | if (!s->fft_data[ch]) |
201 | return AVERROR(ENOMEM); |
202 | } |
203 | |
204 | s->buffer = ff_get_audio_buffer(outlink, s->win_size * 2); |
205 | if (!s->buffer) |
206 | return AVERROR(ENOMEM); |
207 | |
208 | /* pre-calc windowing function */ |
209 | s->window_func_lut = av_realloc_f(s->window_func_lut, s->win_size, |
210 | sizeof(*s->window_func_lut)); |
211 | if (!s->window_func_lut) |
212 | return AVERROR(ENOMEM); |
213 | ff_generate_window_func(s->window_func_lut, s->win_size, s->win_func, &overlap); |
214 | if (s->overlap == 1) |
215 | s->overlap = overlap; |
216 | s->hop_size = (1 - s->overlap) * s->win_size; |
217 | for (factor = 0, i = 0; i < s->win_size; i++) { |
218 | factor += s->window_func_lut[i] * s->window_func_lut[i]; |
219 | } |
220 | s->factor = (factor / s->win_size) / FFMAX(1 / (1 - s->overlap) - 1, 1); |
221 | |
222 | return 0; |
223 | } |
224 | |
225 | static int request_frame(AVFilterLink *outlink) |
226 | { |
227 | AVFilterContext *ctx = outlink->src; |
228 | SpectrumSynthContext *s = ctx->priv; |
229 | int ret; |
230 | |
231 | if (!s->magnitude) { |
232 | ret = ff_request_frame(ctx->inputs[0]); |
233 | if (ret < 0) |
234 | return ret; |
235 | } |
236 | if (!s->phase) { |
237 | ret = ff_request_frame(ctx->inputs[1]); |
238 | if (ret < 0) |
239 | return ret; |
240 | } |
241 | return 0; |
242 | } |
243 | |
244 | static void read16_fft_bin(SpectrumSynthContext *s, |
245 | int x, int y, int f, int ch) |
246 | { |
247 | const int m_linesize = s->magnitude->linesize[0]; |
248 | const int p_linesize = s->phase->linesize[0]; |
249 | const uint16_t *m = (uint16_t *)(s->magnitude->data[0] + y * m_linesize); |
250 | const uint16_t *p = (uint16_t *)(s->phase->data[0] + y * p_linesize); |
251 | float magnitude, phase; |
252 | |
253 | switch (s->scale) { |
254 | case LINEAR: |
255 | magnitude = m[x] / (double)UINT16_MAX; |
256 | break; |
257 | case LOG: |
258 | magnitude = ff_exp10(((m[x] / (double)UINT16_MAX) - 1.) * 6.); |
259 | break; |
260 | default: |
261 | av_assert0(0); |
262 | } |
263 | phase = ((p[x] / (double)UINT16_MAX) * 2. - 1.) * M_PI; |
264 | |
265 | s->fft_data[ch][f].re = magnitude * cos(phase); |
266 | s->fft_data[ch][f].im = magnitude * sin(phase); |
267 | } |
268 | |
269 | static void read8_fft_bin(SpectrumSynthContext *s, |
270 | int x, int y, int f, int ch) |
271 | { |
272 | const int m_linesize = s->magnitude->linesize[0]; |
273 | const int p_linesize = s->phase->linesize[0]; |
274 | const uint8_t *m = (uint8_t *)(s->magnitude->data[0] + y * m_linesize); |
275 | const uint8_t *p = (uint8_t *)(s->phase->data[0] + y * p_linesize); |
276 | float magnitude, phase; |
277 | |
278 | switch (s->scale) { |
279 | case LINEAR: |
280 | magnitude = m[x] / (double)UINT8_MAX; |
281 | break; |
282 | case LOG: |
283 | magnitude = ff_exp10(((m[x] / (double)UINT8_MAX) - 1.) * 6.); |
284 | break; |
285 | default: |
286 | av_assert0(0); |
287 | } |
288 | phase = ((p[x] / (double)UINT8_MAX) * 2. - 1.) * M_PI; |
289 | |
290 | s->fft_data[ch][f].re = magnitude * cos(phase); |
291 | s->fft_data[ch][f].im = magnitude * sin(phase); |
292 | } |
293 | |
294 | static void read_fft_data(AVFilterContext *ctx, int x, int h, int ch) |
295 | { |
296 | SpectrumSynthContext *s = ctx->priv; |
297 | AVFilterLink *inlink = ctx->inputs[0]; |
298 | int start = h * (s->channels - ch) - 1; |
299 | int end = h * (s->channels - ch - 1); |
300 | int y, f; |
301 | |
302 | switch (s->orientation) { |
303 | case VERTICAL: |
304 | switch (inlink->format) { |
305 | case AV_PIX_FMT_YUV444P16: |
306 | case AV_PIX_FMT_GRAY16: |
307 | for (y = start, f = 0; y >= end; y--, f++) { |
308 | read16_fft_bin(s, x, y, f, ch); |
309 | } |
310 | break; |
311 | case AV_PIX_FMT_YUVJ444P: |
312 | case AV_PIX_FMT_YUV444P: |
313 | case AV_PIX_FMT_GRAY8: |
314 | for (y = start, f = 0; y >= end; y--, f++) { |
315 | read8_fft_bin(s, x, y, f, ch); |
316 | } |
317 | break; |
318 | } |
319 | break; |
320 | case HORIZONTAL: |
321 | switch (inlink->format) { |
322 | case AV_PIX_FMT_YUV444P16: |
323 | case AV_PIX_FMT_GRAY16: |
324 | for (y = end, f = 0; y <= start; y++, f++) { |
325 | read16_fft_bin(s, y, x, f, ch); |
326 | } |
327 | break; |
328 | case AV_PIX_FMT_YUVJ444P: |
329 | case AV_PIX_FMT_YUV444P: |
330 | case AV_PIX_FMT_GRAY8: |
331 | for (y = end, f = 0; y <= start; y++, f++) { |
332 | read8_fft_bin(s, y, x, f, ch); |
333 | } |
334 | break; |
335 | } |
336 | break; |
337 | } |
338 | } |
339 | |
340 | static void synth_window(AVFilterContext *ctx, int x) |
341 | { |
342 | SpectrumSynthContext *s = ctx->priv; |
343 | const int h = s->size; |
344 | int nb = s->win_size; |
345 | int y, f, ch; |
346 | |
347 | for (ch = 0; ch < s->channels; ch++) { |
348 | read_fft_data(ctx, x, h, ch); |
349 | |
350 | for (y = h; y <= s->nb_freq; y++) { |
351 | s->fft_data[ch][y].re = 0; |
352 | s->fft_data[ch][y].im = 0; |
353 | } |
354 | |
355 | for (y = s->nb_freq + 1, f = s->nb_freq - 1; y < nb; y++, f--) { |
356 | s->fft_data[ch][y].re = s->fft_data[ch][f].re; |
357 | s->fft_data[ch][y].im = -s->fft_data[ch][f].im; |
358 | } |
359 | |
360 | av_fft_permute(s->fft, s->fft_data[ch]); |
361 | av_fft_calc(s->fft, s->fft_data[ch]); |
362 | } |
363 | } |
364 | |
365 | static int try_push_frame(AVFilterContext *ctx, int x) |
366 | { |
367 | SpectrumSynthContext *s = ctx->priv; |
368 | AVFilterLink *outlink = ctx->outputs[0]; |
369 | const float factor = s->factor; |
370 | int ch, n, i, ret; |
371 | int start, end; |
372 | AVFrame *out; |
373 | |
374 | synth_window(ctx, x); |
375 | |
376 | for (ch = 0; ch < s->channels; ch++) { |
377 | float *buf = (float *)s->buffer->extended_data[ch]; |
378 | int j, k; |
379 | |
380 | start = s->start; |
381 | end = s->end; |
382 | k = end; |
383 | for (i = 0, j = start; j < k && i < s->win_size; i++, j++) { |
384 | buf[j] += s->fft_data[ch][i].re; |
385 | } |
386 | |
387 | for (; i < s->win_size; i++, j++) { |
388 | buf[j] = s->fft_data[ch][i].re; |
389 | } |
390 | |
391 | start += s->hop_size; |
392 | end = j; |
393 | |
394 | if (start >= s->win_size) { |
395 | start -= s->win_size; |
396 | end -= s->win_size; |
397 | |
398 | if (ch == s->channels - 1) { |
399 | float *dst; |
400 | int c; |
401 | |
402 | out = ff_get_audio_buffer(outlink, s->win_size); |
403 | if (!out) { |
404 | av_frame_free(&s->magnitude); |
405 | av_frame_free(&s->phase); |
406 | return AVERROR(ENOMEM); |
407 | } |
408 | |
409 | out->pts = s->pts; |
410 | s->pts += s->win_size; |
411 | for (c = 0; c < s->channels; c++) { |
412 | dst = (float *)out->extended_data[c]; |
413 | buf = (float *)s->buffer->extended_data[c]; |
414 | |
415 | for (n = 0; n < s->win_size; n++) { |
416 | dst[n] = buf[n] * factor; |
417 | } |
418 | memmove(buf, buf + s->win_size, s->win_size * 4); |
419 | } |
420 | |
421 | ret = ff_filter_frame(outlink, out); |
422 | if (ret < 0) |
423 | return ret; |
424 | } |
425 | } |
426 | } |
427 | |
428 | s->start = start; |
429 | s->end = end; |
430 | |
431 | return 0; |
432 | } |
433 | |
434 | static int try_push_frames(AVFilterContext *ctx) |
435 | { |
436 | SpectrumSynthContext *s = ctx->priv; |
437 | int ret, x; |
438 | |
439 | if (!(s->magnitude && s->phase)) |
440 | return 0; |
441 | |
442 | switch (s->sliding) { |
443 | case REPLACE: |
444 | ret = try_push_frame(ctx, s->xpos); |
445 | s->xpos++; |
446 | if (s->xpos >= s->xend) |
447 | s->xpos = 0; |
448 | break; |
449 | case SCROLL: |
450 | s->xpos = s->xend - 1; |
451 | ret = try_push_frame(ctx, s->xpos); |
452 | break; |
453 | case RSCROLL: |
454 | s->xpos = 0; |
455 | ret = try_push_frame(ctx, s->xpos); |
456 | break; |
457 | case FULLFRAME: |
458 | for (x = 0; x < s->xend; x++) { |
459 | ret = try_push_frame(ctx, x); |
460 | if (ret < 0) |
461 | break; |
462 | } |
463 | break; |
464 | default: |
465 | av_assert0(0); |
466 | } |
467 | |
468 | av_frame_free(&s->magnitude); |
469 | av_frame_free(&s->phase); |
470 | return ret; |
471 | } |
472 | |
473 | static int filter_frame_magnitude(AVFilterLink *inlink, AVFrame *magnitude) |
474 | { |
475 | AVFilterContext *ctx = inlink->dst; |
476 | SpectrumSynthContext *s = ctx->priv; |
477 | |
478 | s->magnitude = magnitude; |
479 | return try_push_frames(ctx); |
480 | } |
481 | |
482 | static int filter_frame_phase(AVFilterLink *inlink, AVFrame *phase) |
483 | { |
484 | AVFilterContext *ctx = inlink->dst; |
485 | SpectrumSynthContext *s = ctx->priv; |
486 | |
487 | s->phase = phase; |
488 | return try_push_frames(ctx); |
489 | } |
490 | |
491 | static av_cold void uninit(AVFilterContext *ctx) |
492 | { |
493 | SpectrumSynthContext *s = ctx->priv; |
494 | int i; |
495 | |
496 | av_frame_free(&s->magnitude); |
497 | av_frame_free(&s->phase); |
498 | av_frame_free(&s->buffer); |
499 | av_fft_end(s->fft); |
500 | if (s->fft_data) { |
501 | for (i = 0; i < s->channels; i++) |
502 | av_freep(&s->fft_data[i]); |
503 | } |
504 | av_freep(&s->fft_data); |
505 | av_freep(&s->window_func_lut); |
506 | } |
507 | |
508 | static const AVFilterPad spectrumsynth_inputs[] = { |
509 | { |
510 | .name = "magnitude", |
511 | .type = AVMEDIA_TYPE_VIDEO, |
512 | .filter_frame = filter_frame_magnitude, |
513 | .needs_fifo = 1, |
514 | }, |
515 | { |
516 | .name = "phase", |
517 | .type = AVMEDIA_TYPE_VIDEO, |
518 | .filter_frame = filter_frame_phase, |
519 | .needs_fifo = 1, |
520 | }, |
521 | { NULL } |
522 | }; |
523 | |
524 | static const AVFilterPad spectrumsynth_outputs[] = { |
525 | { |
526 | .name = "default", |
527 | .type = AVMEDIA_TYPE_AUDIO, |
528 | .config_props = config_output, |
529 | .request_frame = request_frame, |
530 | }, |
531 | { NULL } |
532 | }; |
533 | |
534 | AVFilter ff_vaf_spectrumsynth = { |
535 | .name = "spectrumsynth", |
536 | .description = NULL_IF_CONFIG_SMALL("Convert input spectrum videos to audio output."), |
537 | .uninit = uninit, |
538 | .query_formats = query_formats, |
539 | .priv_size = sizeof(SpectrumSynthContext), |
540 | .inputs = spectrumsynth_inputs, |
541 | .outputs = spectrumsynth_outputs, |
542 | .priv_class = &spectrumsynth_class, |
543 | }; |
544 |