blob: 3063283efb5ebeabfa28f59ac92cb78e7db0ac74
1 | /* |
2 | * Copyright (c) 2013 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 | * audio to video multimedia vectorscope filter |
24 | */ |
25 | |
26 | #include "libavutil/avassert.h" |
27 | #include "libavutil/channel_layout.h" |
28 | #include "libavutil/opt.h" |
29 | #include "libavutil/parseutils.h" |
30 | #include "avfilter.h" |
31 | #include "formats.h" |
32 | #include "audio.h" |
33 | #include "video.h" |
34 | #include "internal.h" |
35 | |
36 | enum VectorScopeMode { |
37 | LISSAJOUS, |
38 | LISSAJOUS_XY, |
39 | POLAR, |
40 | MODE_NB, |
41 | }; |
42 | |
43 | enum VectorScopeDraw { |
44 | DOT, |
45 | LINE, |
46 | DRAW_NB, |
47 | }; |
48 | |
49 | enum VectorScopeScale { |
50 | LIN, |
51 | SQRT, |
52 | CBRT, |
53 | LOG, |
54 | SCALE_NB, |
55 | }; |
56 | |
57 | typedef struct AudioVectorScopeContext { |
58 | const AVClass *class; |
59 | AVFrame *outpicref; |
60 | int w, h; |
61 | int hw, hh; |
62 | int mode; |
63 | int draw; |
64 | int scale; |
65 | int contrast[4]; |
66 | int fade[4]; |
67 | double zoom; |
68 | unsigned prev_x, prev_y; |
69 | AVRational frame_rate; |
70 | } AudioVectorScopeContext; |
71 | |
72 | #define OFFSET(x) offsetof(AudioVectorScopeContext, x) |
73 | #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM |
74 | |
75 | static const AVOption avectorscope_options[] = { |
76 | { "mode", "set mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=LISSAJOUS}, 0, MODE_NB-1, FLAGS, "mode" }, |
77 | { "m", "set mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=LISSAJOUS}, 0, MODE_NB-1, FLAGS, "mode" }, |
78 | { "lissajous", "", 0, AV_OPT_TYPE_CONST, {.i64=LISSAJOUS}, 0, 0, FLAGS, "mode" }, |
79 | { "lissajous_xy", "", 0, AV_OPT_TYPE_CONST, {.i64=LISSAJOUS_XY}, 0, 0, FLAGS, "mode" }, |
80 | { "polar", "", 0, AV_OPT_TYPE_CONST, {.i64=POLAR}, 0, 0, FLAGS, "mode" }, |
81 | { "rate", "set video rate", OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str="25"}, 0, INT_MAX, FLAGS }, |
82 | { "r", "set video rate", OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str="25"}, 0, INT_MAX, FLAGS }, |
83 | { "size", "set video size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str="400x400"}, 0, 0, FLAGS }, |
84 | { "s", "set video size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str="400x400"}, 0, 0, FLAGS }, |
85 | { "rc", "set red contrast", OFFSET(contrast[0]), AV_OPT_TYPE_INT, {.i64=40}, 0, 255, FLAGS }, |
86 | { "gc", "set green contrast", OFFSET(contrast[1]), AV_OPT_TYPE_INT, {.i64=160}, 0, 255, FLAGS }, |
87 | { "bc", "set blue contrast", OFFSET(contrast[2]), AV_OPT_TYPE_INT, {.i64=80}, 0, 255, FLAGS }, |
88 | { "ac", "set alpha contrast", OFFSET(contrast[3]), AV_OPT_TYPE_INT, {.i64=255}, 0, 255, FLAGS }, |
89 | { "rf", "set red fade", OFFSET(fade[0]), AV_OPT_TYPE_INT, {.i64=15}, 0, 255, FLAGS }, |
90 | { "gf", "set green fade", OFFSET(fade[1]), AV_OPT_TYPE_INT, {.i64=10}, 0, 255, FLAGS }, |
91 | { "bf", "set blue fade", OFFSET(fade[2]), AV_OPT_TYPE_INT, {.i64=5}, 0, 255, FLAGS }, |
92 | { "af", "set alpha fade", OFFSET(fade[3]), AV_OPT_TYPE_INT, {.i64=5}, 0, 255, FLAGS }, |
93 | { "zoom", "set zoom factor", OFFSET(zoom), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 1, 10, FLAGS }, |
94 | { "draw", "set draw mode", OFFSET(draw), AV_OPT_TYPE_INT, {.i64=DOT}, 0, DRAW_NB-1, FLAGS, "draw" }, |
95 | { "dot", "", 0, AV_OPT_TYPE_CONST, {.i64=DOT} , 0, 0, FLAGS, "draw" }, |
96 | { "line", "", 0, AV_OPT_TYPE_CONST, {.i64=LINE}, 0, 0, FLAGS, "draw" }, |
97 | { "scale", "set amplitude scale mode", OFFSET(scale), AV_OPT_TYPE_INT, {.i64=LIN}, 0, SCALE_NB-1, FLAGS, "scale" }, |
98 | { "lin", "linear", 0, AV_OPT_TYPE_CONST, {.i64=LIN}, 0, 0, FLAGS, "scale" }, |
99 | { "sqrt", "square root", 0, AV_OPT_TYPE_CONST, {.i64=SQRT}, 0, 0, FLAGS, "scale" }, |
100 | { "cbrt", "cube root", 0, AV_OPT_TYPE_CONST, {.i64=CBRT}, 0, 0, FLAGS, "scale" }, |
101 | { "log", "logarithmic", 0, AV_OPT_TYPE_CONST, {.i64=LOG}, 0, 0, FLAGS, "scale" }, |
102 | { NULL } |
103 | }; |
104 | |
105 | AVFILTER_DEFINE_CLASS(avectorscope); |
106 | |
107 | static void draw_dot(AudioVectorScopeContext *s, unsigned x, unsigned y) |
108 | { |
109 | const int linesize = s->outpicref->linesize[0]; |
110 | uint8_t *dst; |
111 | |
112 | if (s->zoom > 1) { |
113 | if (y >= s->h || x >= s->w) |
114 | return; |
115 | } else { |
116 | y = FFMIN(y, s->h - 1); |
117 | x = FFMIN(x, s->w - 1); |
118 | } |
119 | |
120 | dst = &s->outpicref->data[0][y * linesize + x * 4]; |
121 | dst[0] = FFMIN(dst[0] + s->contrast[0], 255); |
122 | dst[1] = FFMIN(dst[1] + s->contrast[1], 255); |
123 | dst[2] = FFMIN(dst[2] + s->contrast[2], 255); |
124 | dst[3] = FFMIN(dst[3] + s->contrast[3], 255); |
125 | } |
126 | |
127 | static void draw_line(AudioVectorScopeContext *s, int x0, int y0, int x1, int y1) |
128 | { |
129 | int dx = FFABS(x1-x0), sx = x0 < x1 ? 1 : -1; |
130 | int dy = FFABS(y1-y0), sy = y0 < y1 ? 1 : -1; |
131 | int err = (dx>dy ? dx : -dy) / 2, e2; |
132 | |
133 | for (;;) { |
134 | draw_dot(s, x0, y0); |
135 | |
136 | if (x0 == x1 && y0 == y1) |
137 | break; |
138 | |
139 | e2 = err; |
140 | |
141 | if (e2 >-dx) { |
142 | err -= dy; |
143 | x0 += sx; |
144 | } |
145 | |
146 | if (e2 < dy) { |
147 | err += dx; |
148 | y0 += sy; |
149 | } |
150 | } |
151 | } |
152 | |
153 | static void fade(AudioVectorScopeContext *s) |
154 | { |
155 | const int linesize = s->outpicref->linesize[0]; |
156 | int i, j; |
157 | |
158 | if (s->fade[0] || s->fade[1] || s->fade[2]) { |
159 | uint8_t *d = s->outpicref->data[0]; |
160 | for (i = 0; i < s->h; i++) { |
161 | for (j = 0; j < s->w*4; j+=4) { |
162 | d[j+0] = FFMAX(d[j+0] - s->fade[0], 0); |
163 | d[j+1] = FFMAX(d[j+1] - s->fade[1], 0); |
164 | d[j+2] = FFMAX(d[j+2] - s->fade[2], 0); |
165 | d[j+3] = FFMAX(d[j+3] - s->fade[3], 0); |
166 | } |
167 | d += linesize; |
168 | } |
169 | } |
170 | } |
171 | |
172 | static int query_formats(AVFilterContext *ctx) |
173 | { |
174 | AVFilterFormats *formats = NULL; |
175 | AVFilterChannelLayouts *layout = NULL; |
176 | AVFilterLink *inlink = ctx->inputs[0]; |
177 | AVFilterLink *outlink = ctx->outputs[0]; |
178 | static const enum AVSampleFormat sample_fmts[] = { AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_FLT, AV_SAMPLE_FMT_NONE }; |
179 | static const enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_RGBA, AV_PIX_FMT_NONE }; |
180 | int ret; |
181 | |
182 | formats = ff_make_format_list(sample_fmts); |
183 | if ((ret = ff_formats_ref (formats, &inlink->out_formats )) < 0 || |
184 | (ret = ff_add_channel_layout (&layout, AV_CH_LAYOUT_STEREO )) < 0 || |
185 | (ret = ff_channel_layouts_ref (layout , &inlink->out_channel_layouts)) < 0) |
186 | return ret; |
187 | |
188 | formats = ff_all_samplerates(); |
189 | if ((ret = ff_formats_ref(formats, &inlink->out_samplerates)) < 0) |
190 | return ret; |
191 | |
192 | formats = ff_make_format_list(pix_fmts); |
193 | if ((ret = ff_formats_ref(formats, &outlink->in_formats)) < 0) |
194 | return ret; |
195 | |
196 | return 0; |
197 | } |
198 | |
199 | static int config_input(AVFilterLink *inlink) |
200 | { |
201 | AVFilterContext *ctx = inlink->dst; |
202 | AudioVectorScopeContext *s = ctx->priv; |
203 | int nb_samples; |
204 | |
205 | nb_samples = FFMAX(1024, ((double)inlink->sample_rate / av_q2d(s->frame_rate)) + 0.5); |
206 | inlink->partial_buf_size = |
207 | inlink->min_samples = |
208 | inlink->max_samples = nb_samples; |
209 | |
210 | return 0; |
211 | } |
212 | |
213 | static int config_output(AVFilterLink *outlink) |
214 | { |
215 | AudioVectorScopeContext *s = outlink->src->priv; |
216 | |
217 | outlink->w = s->w; |
218 | outlink->h = s->h; |
219 | outlink->sample_aspect_ratio = (AVRational){1,1}; |
220 | outlink->frame_rate = s->frame_rate; |
221 | |
222 | s->prev_x = s->hw = s->w / 2; |
223 | s->prev_y = s->hh = s->mode == POLAR ? s->h - 1 : s->h / 2; |
224 | |
225 | return 0; |
226 | } |
227 | |
228 | static int filter_frame(AVFilterLink *inlink, AVFrame *insamples) |
229 | { |
230 | AVFilterContext *ctx = inlink->dst; |
231 | AVFilterLink *outlink = ctx->outputs[0]; |
232 | AudioVectorScopeContext *s = ctx->priv; |
233 | const int hw = s->hw; |
234 | const int hh = s->hh; |
235 | unsigned x, y; |
236 | unsigned prev_x = s->prev_x, prev_y = s->prev_y; |
237 | const double zoom = s->zoom; |
238 | int i; |
239 | |
240 | if (!s->outpicref || s->outpicref->width != outlink->w || |
241 | s->outpicref->height != outlink->h) { |
242 | av_frame_free(&s->outpicref); |
243 | s->outpicref = ff_get_video_buffer(outlink, outlink->w, outlink->h); |
244 | if (!s->outpicref) { |
245 | av_frame_free(&insamples); |
246 | return AVERROR(ENOMEM); |
247 | } |
248 | |
249 | for (i = 0; i < outlink->h; i++) |
250 | memset(s->outpicref->data[0] + i * s->outpicref->linesize[0], 0, outlink->w * 4); |
251 | } |
252 | s->outpicref->pts = insamples->pts; |
253 | |
254 | fade(s); |
255 | |
256 | for (i = 0; i < insamples->nb_samples; i++) { |
257 | int16_t *samples = (int16_t *)insamples->data[0] + i * 2; |
258 | float *samplesf = (float *)insamples->data[0] + i * 2; |
259 | float src[2]; |
260 | |
261 | switch (insamples->format) { |
262 | case AV_SAMPLE_FMT_S16: |
263 | src[0] = samples[0] / (float)INT16_MAX; |
264 | src[1] = samples[1] / (float)INT16_MAX; |
265 | break; |
266 | case AV_SAMPLE_FMT_FLT: |
267 | src[0] = samplesf[0]; |
268 | src[1] = samplesf[1]; |
269 | break; |
270 | } |
271 | |
272 | switch (s->scale) { |
273 | case SQRT: |
274 | src[0] = FFSIGN(src[0]) * sqrtf(FFABS(src[0])); |
275 | src[1] = FFSIGN(src[1]) * sqrtf(FFABS(src[1])); |
276 | break; |
277 | case CBRT: |
278 | src[0] = FFSIGN(src[0]) * cbrtf(FFABS(src[0])); |
279 | src[1] = FFSIGN(src[1]) * cbrtf(FFABS(src[1])); |
280 | break; |
281 | case LOG: |
282 | src[0] = FFSIGN(src[0]) * logf(1 + FFABS(src[0])) / logf(2); |
283 | src[1] = FFSIGN(src[1]) * logf(1 + FFABS(src[1])) / logf(2); |
284 | break; |
285 | } |
286 | |
287 | if (s->mode == LISSAJOUS) { |
288 | x = ((src[1] - src[0]) * zoom / 2 + 1) * hw; |
289 | y = (1.0 - (src[0] + src[1]) * zoom / 2) * hh; |
290 | } else if (s->mode == LISSAJOUS_XY) { |
291 | x = (src[1] * zoom + 1) * hw; |
292 | y = (src[0] * zoom + 1) * hh; |
293 | } else { |
294 | float sx, sy, cx, cy; |
295 | |
296 | sx = src[1] * zoom; |
297 | sy = src[0] * zoom; |
298 | cx = sx * sqrtf(1 - 0.5 * sy * sy); |
299 | cy = sy * sqrtf(1 - 0.5 * sx * sx); |
300 | x = hw + hw * FFSIGN(cx + cy) * (cx - cy) * .7; |
301 | y = s->h - s->h * fabsf(cx + cy) * .7; |
302 | } |
303 | |
304 | if (s->draw == DOT) { |
305 | draw_dot(s, x, y); |
306 | } else { |
307 | draw_line(s, x, y, prev_x, prev_y); |
308 | } |
309 | prev_x = x; |
310 | prev_y = y; |
311 | } |
312 | |
313 | s->prev_x = x, s->prev_y = y; |
314 | av_frame_free(&insamples); |
315 | |
316 | return ff_filter_frame(outlink, av_frame_clone(s->outpicref)); |
317 | } |
318 | |
319 | static av_cold void uninit(AVFilterContext *ctx) |
320 | { |
321 | AudioVectorScopeContext *s = ctx->priv; |
322 | |
323 | av_frame_free(&s->outpicref); |
324 | } |
325 | |
326 | static const AVFilterPad audiovectorscope_inputs[] = { |
327 | { |
328 | .name = "default", |
329 | .type = AVMEDIA_TYPE_AUDIO, |
330 | .config_props = config_input, |
331 | .filter_frame = filter_frame, |
332 | }, |
333 | { NULL } |
334 | }; |
335 | |
336 | static const AVFilterPad audiovectorscope_outputs[] = { |
337 | { |
338 | .name = "default", |
339 | .type = AVMEDIA_TYPE_VIDEO, |
340 | .config_props = config_output, |
341 | }, |
342 | { NULL } |
343 | }; |
344 | |
345 | AVFilter ff_avf_avectorscope = { |
346 | .name = "avectorscope", |
347 | .description = NULL_IF_CONFIG_SMALL("Convert input audio to vectorscope video output."), |
348 | .uninit = uninit, |
349 | .query_formats = query_formats, |
350 | .priv_size = sizeof(AudioVectorScopeContext), |
351 | .inputs = audiovectorscope_inputs, |
352 | .outputs = audiovectorscope_outputs, |
353 | .priv_class = &avectorscope_class, |
354 | }; |
355 |