FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavfilter/vf_datascope.c
Date: 2025-04-25 22:50:00
Exec Total Coverage
Lines: 0 555 0.0%
Functions: 0 25 0.0%
Branches: 0 282 0.0%

Line Branch Exec Source
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 #include "libavutil/intreadwrite.h"
22 #include "libavutil/mem.h"
23 #include "libavutil/opt.h"
24 #include "libavutil/pixdesc.h"
25 #include "libavutil/xga_font_data.h"
26 #include "avfilter.h"
27 #include "drawutils.h"
28 #include "filters.h"
29 #include "formats.h"
30 #include "video.h"
31
32 typedef struct DatascopeContext {
33 const AVClass *class;
34 int ow, oh;
35 int x, y;
36 int mode;
37 int dformat;
38 int axis;
39 int components;
40 float opacity;
41
42 int nb_planes;
43 int nb_comps;
44 int chars;
45 FFDrawContext draw;
46 FFDrawColor yellow;
47 FFDrawColor white;
48 FFDrawColor black;
49 FFDrawColor gray;
50
51 void (*pick_color)(FFDrawContext *draw, FFDrawColor *color, AVFrame *in, int x, int y, int *value);
52 void (*reverse_color)(FFDrawContext *draw, FFDrawColor *color, FFDrawColor *reverse);
53 int (*filter)(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs);
54 } DatascopeContext;
55
56 #define OFFSET(x) offsetof(DatascopeContext, x)
57 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
58 #define FLAGSR AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_RUNTIME_PARAM
59
60 static const AVOption datascope_options[] = {
61 { "size", "set output size", OFFSET(ow), AV_OPT_TYPE_IMAGE_SIZE, {.str="hd720"}, 0, 0, FLAGS },
62 { "s", "set output size", OFFSET(ow), AV_OPT_TYPE_IMAGE_SIZE, {.str="hd720"}, 0, 0, FLAGS },
63 { "x", "set x offset", OFFSET(x), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGSR },
64 { "y", "set y offset", OFFSET(y), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGSR },
65 { "mode", "set scope mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=0}, 0, 2, FLAGSR, .unit = "mode" },
66 { "mono", NULL, 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, FLAGSR, .unit = "mode" },
67 { "color", NULL, 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, FLAGSR, .unit = "mode" },
68 { "color2", NULL, 0, AV_OPT_TYPE_CONST, {.i64=2}, 0, 0, FLAGSR, .unit = "mode" },
69 { "axis", "draw column/row numbers", OFFSET(axis), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGSR },
70 { "opacity", "set background opacity", OFFSET(opacity), AV_OPT_TYPE_FLOAT, {.dbl=0.75}, 0, 1, FLAGSR },
71 { "format", "set display number format", OFFSET(dformat), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGSR, .unit = "format" },
72 { "hex", NULL, 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, FLAGSR, .unit = "format" },
73 { "dec", NULL, 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, FLAGSR, .unit = "format" },
74 { "components", "set components to display", OFFSET(components), AV_OPT_TYPE_INT, {.i64=15}, 1, 15, FLAGSR },
75 { NULL }
76 };
77
78 AVFILTER_DEFINE_CLASS(datascope);
79
80 static int query_formats(const AVFilterContext *ctx,
81 AVFilterFormatsConfig **cfg_in,
82 AVFilterFormatsConfig **cfg_out)
83 {
84 return ff_set_common_formats2(ctx, cfg_in, cfg_out,
85 ff_draw_supported_pixel_formats(0));
86 }
87
88 static void draw_text(FFDrawContext *draw, AVFrame *frame, FFDrawColor *color,
89 int x0, int y0, const uint8_t *text, int vertical)
90 {
91 int x = x0;
92
93 for (; *text; text++) {
94 if (*text == '\n') {
95 x = x0;
96 y0 += 8;
97 continue;
98 }
99 ff_blend_mask(draw, color, frame->data, frame->linesize,
100 frame->width, frame->height,
101 avpriv_cga_font + *text * 8, 1, 8, 8, 0, 0, x, y0);
102 if (vertical) {
103 x = x0;
104 y0 += 8;
105 } else {
106 x += 8;
107 }
108 }
109 }
110
111 static void pick_color8(FFDrawContext *draw, FFDrawColor *color, AVFrame *in, int x, int y, int *value)
112 {
113 int p, i;
114
115 color->rgba[3] = 255;
116 for (p = 0; p < draw->nb_planes; p++) {
117 if (draw->nb_planes == 1) {
118 for (i = 0; i < 4; i++) {
119 value[i] = in->data[0][y * in->linesize[0] + x * draw->pixelstep[0] + i];
120 color->comp[0].u8[i] = value[i];
121 }
122 } else {
123 value[p] = in->data[p][(y >> draw->vsub[p]) * in->linesize[p] + (x >> draw->hsub[p])];
124 color->comp[p].u8[0] = value[p];
125 }
126 }
127 }
128
129 static void pick_color16(FFDrawContext *draw, FFDrawColor *color, AVFrame *in, int x, int y, int *value)
130 {
131 int p, i;
132
133 color->rgba[3] = 255;
134 for (p = 0; p < draw->nb_planes; p++) {
135 if (draw->nb_planes == 1) {
136 for (i = 0; i < 4; i++) {
137 value[i] = AV_RL16(in->data[0] + y * in->linesize[0] + x * draw->pixelstep[0] + i * 2);
138 color->comp[0].u16[i] = value[i];
139 }
140 } else {
141 value[p] = AV_RL16(in->data[p] + (y >> draw->vsub[p]) * in->linesize[p] + (x >> draw->hsub[p]) * 2);
142 color->comp[p].u16[0] = value[p];
143 }
144 }
145 }
146
147 static void reverse_color8(FFDrawContext *draw, FFDrawColor *color, FFDrawColor *reverse)
148 {
149 int p;
150
151 reverse->rgba[3] = 255;
152 for (p = 0; p < draw->nb_planes; p++) {
153 reverse->comp[p].u8[0] = color->comp[p].u8[0] > 127 ? 0 : 255;
154 reverse->comp[p].u8[1] = color->comp[p].u8[1] > 127 ? 0 : 255;
155 reverse->comp[p].u8[2] = color->comp[p].u8[2] > 127 ? 0 : 255;
156 }
157 }
158
159 static void reverse_color16(FFDrawContext *draw, FFDrawColor *color, FFDrawColor *reverse)
160 {
161 int p;
162
163 reverse->rgba[3] = 255;
164 for (p = 0; p < draw->nb_planes; p++) {
165 const unsigned max = (1 << draw->desc->comp[p].depth) - 1;
166 const unsigned mid = (max + 1) / 2;
167
168 reverse->comp[p].u16[0] = color->comp[p].u16[0] > mid ? 0 : max;
169 reverse->comp[p].u16[1] = color->comp[p].u16[1] > mid ? 0 : max;
170 reverse->comp[p].u16[2] = color->comp[p].u16[2] > mid ? 0 : max;
171 }
172 }
173
174 typedef struct ThreadData {
175 AVFrame *in, *out;
176 int xoff, yoff, PP;
177 } ThreadData;
178
179 static int filter_color2(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
180 {
181 DatascopeContext *s = ctx->priv;
182 AVFilterLink *outlink = ctx->outputs[0];
183 AVFilterLink *inlink = ctx->inputs[0];
184 ThreadData *td = arg;
185 AVFrame *in = td->in;
186 AVFrame *out = td->out;
187 const int PP = td->PP;
188 const int xoff = td->xoff;
189 const int yoff = td->yoff;
190 const int P = FFMAX(s->nb_planes, s->nb_comps);
191 const int C = s->chars;
192 const int D = ((s->chars - s->dformat) >> 2) + s->dformat * 2;
193 const int W = (outlink->w - xoff) / (C * 10);
194 const int H = (outlink->h - yoff) / (PP * 12);
195 const char *format[4] = {"%02X\n", "%04X\n", "%03d\n", "%05d\n"};
196 const int slice_start = (W * jobnr) / nb_jobs;
197 const int slice_end = (W * (jobnr+1)) / nb_jobs;
198 int x, y, p;
199
200 for (y = 0; y < H && (y + s->y < inlink->h); y++) {
201 for (x = slice_start; x < slice_end && (x + s->x < inlink->w); x++) {
202 FFDrawColor color = { { 0 } };
203 FFDrawColor reverse = { { 0 } };
204 int value[4] = { 0 }, pp = 0;
205
206 s->pick_color(&s->draw, &color, in, x + s->x, y + s->y, value);
207 s->reverse_color(&s->draw, &color, &reverse);
208 ff_fill_rectangle(&s->draw, &color, out->data, out->linesize,
209 xoff + x * C * 10, yoff + y * PP * 12, C * 10, PP * 12);
210
211 for (p = 0; p < P; p++) {
212 char text[256];
213
214 if (!(s->components & (1 << p)))
215 continue;
216 snprintf(text, sizeof(text), format[D], value[p]);
217 draw_text(&s->draw, out, &reverse, xoff + x * C * 10 + 2, yoff + y * PP * 12 + pp * 10 + 2, text, 0);
218 pp++;
219 }
220 }
221 }
222
223 return 0;
224 }
225
226 static int filter_color(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
227 {
228 DatascopeContext *s = ctx->priv;
229 AVFilterLink *outlink = ctx->outputs[0];
230 AVFilterLink *inlink = ctx->inputs[0];
231 ThreadData *td = arg;
232 AVFrame *in = td->in;
233 AVFrame *out = td->out;
234 const int PP = td->PP;
235 const int xoff = td->xoff;
236 const int yoff = td->yoff;
237 const int P = FFMAX(s->nb_planes, s->nb_comps);
238 const int C = s->chars;
239 const int D = ((s->chars - s->dformat) >> 2) + s->dformat * 2;
240 const int W = (outlink->w - xoff) / (C * 10);
241 const int H = (outlink->h - yoff) / (PP * 12);
242 const char *format[4] = {"%02X\n", "%04X\n", "%03d\n", "%05d\n"};
243 const int slice_start = (W * jobnr) / nb_jobs;
244 const int slice_end = (W * (jobnr+1)) / nb_jobs;
245 int x, y, p;
246
247 for (y = 0; y < H && (y + s->y < inlink->h); y++) {
248 for (x = slice_start; x < slice_end && (x + s->x < inlink->w); x++) {
249 FFDrawColor color = { { 0 } };
250 int value[4] = { 0 }, pp = 0;
251
252 s->pick_color(&s->draw, &color, in, x + s->x, y + s->y, value);
253
254 for (p = 0; p < P; p++) {
255 char text[256];
256
257 if (!(s->components & (1 << p)))
258 continue;
259 snprintf(text, sizeof(text), format[D], value[p]);
260 draw_text(&s->draw, out, &color, xoff + x * C * 10 + 2, yoff + y * PP * 12 + pp * 10 + 2, text, 0);
261 pp++;
262 }
263 }
264 }
265
266 return 0;
267 }
268
269 static int filter_mono(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
270 {
271 DatascopeContext *s = ctx->priv;
272 AVFilterLink *outlink = ctx->outputs[0];
273 AVFilterLink *inlink = ctx->inputs[0];
274 ThreadData *td = arg;
275 AVFrame *in = td->in;
276 AVFrame *out = td->out;
277 const int PP = td->PP;
278 const int xoff = td->xoff;
279 const int yoff = td->yoff;
280 const int P = FFMAX(s->nb_planes, s->nb_comps);
281 const int C = s->chars;
282 const int D = ((s->chars - s->dformat) >> 2) + s->dformat * 2;
283 const int W = (outlink->w - xoff) / (C * 10);
284 const int H = (outlink->h - yoff) / (PP * 12);
285 const char *format[4] = {"%02X\n", "%04X\n", "%03d\n", "%05d\n"};
286 const int slice_start = (W * jobnr) / nb_jobs;
287 const int slice_end = (W * (jobnr+1)) / nb_jobs;
288 int x, y, p;
289
290 for (y = 0; y < H && (y + s->y < inlink->h); y++) {
291 for (x = slice_start; x < slice_end && (x + s->x < inlink->w); x++) {
292 FFDrawColor color = { { 0 } };
293 int value[4] = { 0 }, pp = 0;
294
295 s->pick_color(&s->draw, &color, in, x + s->x, y + s->y, value);
296 for (p = 0; p < P; p++) {
297 char text[256];
298
299 if (!(s->components & (1 << p)))
300 continue;
301 snprintf(text, sizeof(text), format[D], value[p]);
302 draw_text(&s->draw, out, &s->white, xoff + x * C * 10 + 2, yoff + y * PP * 12 + pp * 10 + 2, text, 0);
303 pp++;
304 }
305 }
306 }
307
308 return 0;
309 }
310
311 static int filter_frame(AVFilterLink *inlink, AVFrame *in)
312 {
313 AVFilterContext *ctx = inlink->dst;
314 DatascopeContext *s = ctx->priv;
315 AVFilterLink *outlink = ctx->outputs[0];
316 const int P = FFMAX(s->nb_planes, s->nb_comps);
317 ThreadData td = { 0 };
318 int ymaxlen = 0;
319 int xmaxlen = 0;
320 int PP = 0;
321 AVFrame *out;
322
323 out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
324 if (!out) {
325 av_frame_free(&in);
326 return AVERROR(ENOMEM);
327 }
328 av_frame_copy_props(out, in);
329
330 ff_fill_rectangle(&s->draw, &s->black, out->data, out->linesize,
331 0, 0, outlink->w, outlink->h);
332
333 for (int p = 0; p < P; p++) {
334 if (s->components & (1 << p))
335 PP++;
336 }
337 PP = FFMAX(PP, 1);
338
339 if (s->axis) {
340 const int C = s->chars;
341 int Y = outlink->h / (PP * 12);
342 int X = outlink->w / (C * 10);
343 char text[256] = { 0 };
344 int x, y;
345
346 snprintf(text, sizeof(text), "%d", s->y + Y);
347 ymaxlen = strlen(text);
348 ymaxlen *= 10;
349 snprintf(text, sizeof(text), "%d", s->x + X);
350 xmaxlen = strlen(text);
351 xmaxlen *= 10;
352
353 Y = (outlink->h - xmaxlen) / (PP * 12);
354 X = (outlink->w - ymaxlen) / (C * 10);
355
356 for (y = 0; y < Y; y++) {
357 snprintf(text, sizeof(text), "%d", s->y + y);
358
359 ff_fill_rectangle(&s->draw, &s->gray, out->data, out->linesize,
360 0, xmaxlen + y * PP * 12 + (PP + 1) * PP - 2, ymaxlen, 10);
361
362 draw_text(&s->draw, out, &s->yellow, 2, xmaxlen + y * PP * 12 + (PP + 1) * PP, text, 0);
363 }
364
365 for (x = 0; x < X; x++) {
366 snprintf(text, sizeof(text), "%d", s->x + x);
367
368 ff_fill_rectangle(&s->draw, &s->gray, out->data, out->linesize,
369 ymaxlen + x * C * 10 + 2 * C - 2, 0, 10, xmaxlen);
370
371 draw_text(&s->draw, out, &s->yellow, ymaxlen + x * C * 10 + 2 * C, 2, text, 1);
372 }
373 }
374
375 td.in = in; td.out = out, td.yoff = xmaxlen, td.xoff = ymaxlen, td.PP = PP;
376 ff_filter_execute(ctx, s->filter, &td, NULL,
377 FFMIN(ff_filter_get_nb_threads(ctx), FFMAX(outlink->w / 20, 1)));
378
379 av_frame_free(&in);
380 return ff_filter_frame(outlink, out);
381 }
382
383 static int config_input(AVFilterLink *inlink)
384 {
385 AVFilterContext *ctx = inlink->dst;
386 DatascopeContext *s = ctx->priv;
387
388 uint8_t alpha = s->opacity * 255;
389 int ret;
390
391 s->nb_planes = av_pix_fmt_count_planes(inlink->format);
392 ret = ff_draw_init2(&s->draw, inlink->format, inlink->colorspace, inlink->color_range, 0);
393 if (ret < 0) {
394 av_log(ctx, AV_LOG_ERROR, "Failed to initialize FFDrawContext\n");
395 return ret;
396 }
397 ff_draw_color(&s->draw, &s->white, (uint8_t[]){ 255, 255, 255, 255} );
398 ff_draw_color(&s->draw, &s->black, (uint8_t[]){ 0, 0, 0, alpha} );
399 ff_draw_color(&s->draw, &s->yellow, (uint8_t[]){ 255, 255, 0, 255} );
400 ff_draw_color(&s->draw, &s->gray, (uint8_t[]){ 77, 77, 77, 255} );
401 s->chars = (s->draw.desc->comp[0].depth + 7) / 8 * 2 + s->dformat;
402 s->nb_comps = s->draw.desc->nb_components;
403
404 switch (s->mode) {
405 case 0: s->filter = filter_mono; break;
406 case 1: s->filter = filter_color; break;
407 case 2: s->filter = filter_color2; break;
408 }
409
410 if (s->draw.desc->comp[0].depth <= 8) {
411 s->pick_color = pick_color8;
412 s->reverse_color = reverse_color8;
413 } else {
414 s->pick_color = pick_color16;
415 s->reverse_color = reverse_color16;
416 }
417
418 return 0;
419 }
420
421 static int config_output(AVFilterLink *outlink)
422 {
423 DatascopeContext *s = outlink->src->priv;
424
425 outlink->h = s->oh;
426 outlink->w = s->ow;
427 outlink->sample_aspect_ratio = (AVRational){1,1};
428
429 return 0;
430 }
431
432 static int process_command(AVFilterContext *ctx, const char *cmd, const char *args,
433 char *res, int res_len, int flags)
434 {
435 int ret;
436
437 ret = ff_filter_process_command(ctx, cmd, args, res, res_len, flags);
438 if (ret < 0)
439 return ret;
440
441 return config_input(ctx->inputs[0]);
442 }
443
444 static const AVFilterPad inputs[] = {
445 {
446 .name = "default",
447 .type = AVMEDIA_TYPE_VIDEO,
448 .filter_frame = filter_frame,
449 .config_props = config_input,
450 },
451 };
452
453 static const AVFilterPad outputs[] = {
454 {
455 .name = "default",
456 .type = AVMEDIA_TYPE_VIDEO,
457 .config_props = config_output,
458 },
459 };
460
461 const FFFilter ff_vf_datascope = {
462 .p.name = "datascope",
463 .p.description = NULL_IF_CONFIG_SMALL("Video data analysis."),
464 .p.priv_class = &datascope_class,
465 .p.flags = AVFILTER_FLAG_SLICE_THREADS,
466 .priv_size = sizeof(DatascopeContext),
467 FILTER_INPUTS(inputs),
468 FILTER_OUTPUTS(outputs),
469 FILTER_QUERY_FUNC2(query_formats),
470 .process_command = process_command,
471 };
472
473 typedef struct PixscopeContext {
474 const AVClass *class;
475
476 float xpos, ypos;
477 float wx, wy;
478 int w, h;
479 float o;
480
481 int x, y;
482 int ww, wh;
483
484 int nb_planes;
485 int nb_comps;
486 int is_rgb;
487 uint8_t rgba_map[4];
488 FFDrawContext draw;
489 FFDrawColor dark;
490 FFDrawColor black;
491 FFDrawColor white;
492 FFDrawColor green;
493 FFDrawColor blue;
494 FFDrawColor red;
495 FFDrawColor *colors[4];
496
497 uint16_t values[4][80][80];
498
499 void (*pick_color)(FFDrawContext *draw, FFDrawColor *color, AVFrame *in, int x, int y, int *value);
500 } PixscopeContext;
501
502 #define POFFSET(x) offsetof(PixscopeContext, x)
503
504 static const AVOption pixscope_options[] = {
505 { "x", "set scope x offset", POFFSET(xpos), AV_OPT_TYPE_FLOAT, {.dbl=0.5}, 0, 1, FLAGSR },
506 { "y", "set scope y offset", POFFSET(ypos), AV_OPT_TYPE_FLOAT, {.dbl=0.5}, 0, 1, FLAGSR },
507 { "w", "set scope width", POFFSET(w), AV_OPT_TYPE_INT, {.i64=7}, 1, 80, FLAGSR },
508 { "h", "set scope height", POFFSET(h), AV_OPT_TYPE_INT, {.i64=7}, 1, 80, FLAGSR },
509 { "o", "set window opacity", POFFSET(o), AV_OPT_TYPE_FLOAT, {.dbl=0.5}, 0, 1, FLAGSR },
510 { "wx", "set window x offset", POFFSET(wx), AV_OPT_TYPE_FLOAT, {.dbl=-1}, -1, 1, FLAGSR },
511 { "wy", "set window y offset", POFFSET(wy), AV_OPT_TYPE_FLOAT, {.dbl=-1}, -1, 1, FLAGSR },
512 { NULL }
513 };
514
515 AVFILTER_DEFINE_CLASS(pixscope);
516
517 static int pixscope_config_input(AVFilterLink *inlink)
518 {
519 AVFilterContext *ctx = inlink->dst;
520 PixscopeContext *s = ctx->priv;
521 int ret;
522
523 s->nb_planes = av_pix_fmt_count_planes(inlink->format);
524 ret = ff_draw_init(&s->draw, inlink->format, 0);
525 if (ret < 0) {
526 av_log(ctx, AV_LOG_ERROR, "Failed to initialize FFDrawContext\n");
527 return ret;
528 }
529 ff_draw_color(&s->draw, &s->dark, (uint8_t[]){ 0, 0, 0, s->o * 255} );
530 ff_draw_color(&s->draw, &s->black, (uint8_t[]){ 0, 0, 0, 255} );
531 ff_draw_color(&s->draw, &s->white, (uint8_t[]){ 255, 255, 255, 255} );
532 ff_draw_color(&s->draw, &s->green, (uint8_t[]){ 0, 255, 0, 255} );
533 ff_draw_color(&s->draw, &s->blue, (uint8_t[]){ 0, 0, 255, 255} );
534 ff_draw_color(&s->draw, &s->red, (uint8_t[]){ 255, 0, 0, 255} );
535 s->nb_comps = s->draw.desc->nb_components;
536 s->is_rgb = s->draw.desc->flags & AV_PIX_FMT_FLAG_RGB;
537
538 if (s->is_rgb) {
539 s->colors[0] = &s->red;
540 s->colors[1] = &s->green;
541 s->colors[2] = &s->blue;
542 s->colors[3] = &s->white;
543 ff_fill_rgba_map(s->rgba_map, inlink->format);
544 } else {
545 s->colors[0] = &s->white;
546 s->colors[1] = &s->blue;
547 s->colors[2] = &s->red;
548 s->colors[3] = &s->white;
549 s->rgba_map[0] = 0;
550 s->rgba_map[1] = 1;
551 s->rgba_map[2] = 2;
552 s->rgba_map[3] = 3;
553 }
554
555 if (s->draw.desc->comp[0].depth <= 8) {
556 s->pick_color = pick_color8;
557 } else {
558 s->pick_color = pick_color16;
559 }
560
561 if (inlink->w < 640 || inlink->h < 480) {
562 av_log(inlink->dst, AV_LOG_ERROR, "min supported resolution is 640x480\n");
563 return AVERROR(EINVAL);
564 }
565
566 s->ww = 300;
567 s->wh = 300 * 1.6;
568 s->x = s->xpos * (inlink->w - 1);
569 s->y = s->ypos * (inlink->h - 1);
570 if (s->x + s->w >= inlink->w || s->y + s->h >= inlink->h) {
571 av_log(inlink->dst, AV_LOG_WARNING, "scope position is out of range, clipping\n");
572 s->x = FFMIN(s->x, inlink->w - s->w);
573 s->y = FFMIN(s->y, inlink->h - s->h);
574 }
575
576 return 0;
577 }
578
579 #define SQR(x) ((x)*(x))
580
581 static int pixscope_filter_frame(AVFilterLink *inlink, AVFrame *in)
582 {
583 AVFilterContext *ctx = inlink->dst;
584 PixscopeContext *s = ctx->priv;
585 AVFilterLink *outlink = ctx->outputs[0];
586 AVFrame *out = ff_get_video_buffer(outlink, in->width, in->height);
587 int max[4] = { 0 }, min[4] = { INT_MAX, INT_MAX, INT_MAX, INT_MAX };
588 float average[4] = { 0 };
589 double std[4] = { 0 }, rms[4] = { 0 };
590 const char rgba[4] = { 'R', 'G', 'B', 'A' };
591 const char yuva[4] = { 'Y', 'U', 'V', 'A' };
592 int x, y, X, Y, i, w, h;
593 char text[128];
594
595 if (!out) {
596 av_frame_free(&in);
597 return AVERROR(ENOMEM);
598 }
599 av_frame_copy_props(out, in);
600 av_frame_copy(out, in);
601
602 w = s->ww / s->w;
603 h = s->ww / s->h;
604
605 if (s->wx >= 0) {
606 X = (in->width - s->ww) * s->wx;
607 } else {
608 X = (in->width - s->ww) * -s->wx;
609 }
610 if (s->wy >= 0) {
611 Y = (in->height - s->wh) * s->wy;
612 } else {
613 Y = (in->height - s->wh) * -s->wy;
614 }
615
616 if (s->wx < 0) {
617 if (s->x + s->w >= X && (s->x + s->w <= X + s->ww) &&
618 s->y + s->h >= Y && (s->y + s->h <= Y + s->wh)) {
619 X = (in->width - s->ww) * (1 + s->wx);
620 }
621 }
622
623 if (s->wy < 0) {
624 if (s->x + s->w >= X && (s->x + s->w <= X + s->ww) &&
625 s->y + s->h >= Y && (s->y + s->h <= Y + s->wh)) {
626 Y = (in->height - s->wh) * (1 + s->wy);
627 }
628 }
629
630 ff_blend_rectangle(&s->draw, &s->dark, out->data, out->linesize,
631 out->width, out->height,
632 X,
633 Y,
634 s->ww,
635 s->wh);
636
637 for (y = 0; y < s->h; y++) {
638 for (x = 0; x < s->w; x++) {
639 FFDrawColor color = { { 0 } };
640 int value[4] = { 0 };
641
642 s->pick_color(&s->draw, &color, in, x + s->x, y + s->y, value);
643 ff_fill_rectangle(&s->draw, &color, out->data, out->linesize,
644 x * w + (s->ww - 4 - (s->w * w)) / 2 + X, y * h + 2 + Y, w, h);
645 for (i = 0; i < 4; i++) {
646 s->values[i][x][y] = value[i];
647 rms[i] += (double)value[i] * (double)value[i];
648 average[i] += value[i];
649 min[i] = FFMIN(min[i], value[i]);
650 max[i] = FFMAX(max[i], value[i]);
651 }
652 }
653 }
654
655 ff_blend_rectangle(&s->draw, &s->black, out->data, out->linesize,
656 out->width, out->height,
657 s->x - 2, s->y - 2, s->w + 4, 1);
658
659 ff_blend_rectangle(&s->draw, &s->white, out->data, out->linesize,
660 out->width, out->height,
661 s->x - 1, s->y - 1, s->w + 2, 1);
662
663 ff_blend_rectangle(&s->draw, &s->white, out->data, out->linesize,
664 out->width, out->height,
665 s->x - 1, s->y - 1, 1, s->h + 2);
666
667 ff_blend_rectangle(&s->draw, &s->black, out->data, out->linesize,
668 out->width, out->height,
669 s->x - 2, s->y - 2, 1, s->h + 4);
670
671 ff_blend_rectangle(&s->draw, &s->white, out->data, out->linesize,
672 out->width, out->height,
673 s->x - 1, s->y + 1 + s->h, s->w + 3, 1);
674
675 ff_blend_rectangle(&s->draw, &s->black, out->data, out->linesize,
676 out->width, out->height,
677 s->x - 2, s->y + 2 + s->h, s->w + 4, 1);
678
679 ff_blend_rectangle(&s->draw, &s->white, out->data, out->linesize,
680 out->width, out->height,
681 s->x + 1 + s->w, s->y - 1, 1, s->h + 2);
682
683 ff_blend_rectangle(&s->draw, &s->black, out->data, out->linesize,
684 out->width, out->height,
685 s->x + 2 + s->w, s->y - 2, 1, s->h + 5);
686
687 for (i = 0; i < 4; i++) {
688 rms[i] /= s->w * s->h;
689 rms[i] = sqrt(rms[i]);
690 average[i] /= s->w * s->h;
691 }
692
693 for (y = 0; y < s->h; y++) {
694 for (x = 0; x < s->w; x++) {
695 for (i = 0; i < 4; i++)
696 std[i] += SQR(s->values[i][x][y] - average[i]);
697 }
698 }
699
700 for (i = 0; i < 4; i++) {
701 std[i] /= s->w * s->h;
702 std[i] = sqrt(std[i]);
703 }
704
705 snprintf(text, sizeof(text), "CH AVG MIN MAX RMS\n");
706 draw_text(&s->draw, out, &s->white, X + 28, Y + s->ww + 5, text, 0);
707 for (i = 0; i < s->nb_comps; i++) {
708 int c = s->rgba_map[i];
709
710 snprintf(text, sizeof(text), "%c %07.1f %05d %05d %07.1f\n", s->is_rgb ? rgba[i] : yuva[i], average[c], min[c], max[c], rms[c]);
711 draw_text(&s->draw, out, s->colors[i], X + 28, Y + s->ww + 15 * (i + 1), text, 0);
712 }
713 snprintf(text, sizeof(text), "CH STD\n");
714 draw_text(&s->draw, out, &s->white, X + 28, Y + s->ww + 15 * (0 + 5), text, 0);
715 for (i = 0; i < s->nb_comps; i++) {
716 int c = s->rgba_map[i];
717
718 snprintf(text, sizeof(text), "%c %07.2f\n", s->is_rgb ? rgba[i] : yuva[i], std[c]);
719 draw_text(&s->draw, out, s->colors[i], X + 28, Y + s->ww + 15 * (i + 6), text, 0);
720 }
721
722 av_frame_free(&in);
723 return ff_filter_frame(outlink, out);
724 }
725
726 static int pixscope_process_command(AVFilterContext *ctx, const char *cmd, const char *args,
727 char *res, int res_len, int flags)
728 {
729 int ret;
730
731 ret = ff_filter_process_command(ctx, cmd, args, res, res_len, flags);
732 if (ret < 0)
733 return ret;
734
735 return pixscope_config_input(ctx->inputs[0]);
736 }
737
738 static const AVFilterPad pixscope_inputs[] = {
739 {
740 .name = "default",
741 .type = AVMEDIA_TYPE_VIDEO,
742 .filter_frame = pixscope_filter_frame,
743 .config_props = pixscope_config_input,
744 },
745 };
746
747 const FFFilter ff_vf_pixscope = {
748 .p.name = "pixscope",
749 .p.description = NULL_IF_CONFIG_SMALL("Pixel data analysis."),
750 .p.priv_class = &pixscope_class,
751 .p.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
752 .priv_size = sizeof(PixscopeContext),
753 FILTER_INPUTS(pixscope_inputs),
754 FILTER_OUTPUTS(ff_video_default_filterpad),
755 FILTER_QUERY_FUNC2(query_formats),
756 .process_command = pixscope_process_command,
757 };
758
759 typedef struct PixelValues {
760 uint16_t p[4];
761 } PixelValues;
762
763 typedef struct OscilloscopeContext {
764 const AVClass *class;
765
766 float xpos, ypos;
767 float tx, ty;
768 float size;
769 float tilt;
770 float theight, twidth;
771 float o;
772 int components;
773 int grid;
774 int statistics;
775 int scope;
776
777 int x1, y1, x2, y2;
778 int ox, oy;
779 int height, width;
780
781 int max;
782 int nb_planes;
783 int nb_comps;
784 int is_rgb;
785 uint8_t rgba_map[4];
786 FFDrawContext draw;
787 FFDrawColor dark;
788 FFDrawColor black;
789 FFDrawColor white;
790 FFDrawColor green;
791 FFDrawColor blue;
792 FFDrawColor red;
793 FFDrawColor cyan;
794 FFDrawColor magenta;
795 FFDrawColor gray;
796 FFDrawColor *colors[4];
797
798 int nb_values;
799 PixelValues *values;
800
801 void (*pick_color)(FFDrawContext *draw, FFDrawColor *color, AVFrame *in, int x, int y, int *value);
802 void (*draw_trace)(struct OscilloscopeContext *s, AVFrame *frame);
803 } OscilloscopeContext;
804
805 #define OOFFSET(x) offsetof(OscilloscopeContext, x)
806
807 static const AVOption oscilloscope_options[] = {
808 { "x", "set scope x position", OOFFSET(xpos), AV_OPT_TYPE_FLOAT, {.dbl=0.5}, 0, 1, FLAGSR },
809 { "y", "set scope y position", OOFFSET(ypos), AV_OPT_TYPE_FLOAT, {.dbl=0.5}, 0, 1, FLAGSR },
810 { "s", "set scope size", OOFFSET(size), AV_OPT_TYPE_FLOAT, {.dbl=0.8}, 0, 1, FLAGSR },
811 { "t", "set scope tilt", OOFFSET(tilt), AV_OPT_TYPE_FLOAT, {.dbl=0.5}, 0, 1, FLAGSR },
812 { "o", "set trace opacity", OOFFSET(o), AV_OPT_TYPE_FLOAT, {.dbl=0.8}, 0, 1, FLAGSR },
813 { "tx", "set trace x position", OOFFSET(tx), AV_OPT_TYPE_FLOAT, {.dbl=0.5}, 0, 1, FLAGSR },
814 { "ty", "set trace y position", OOFFSET(ty), AV_OPT_TYPE_FLOAT, {.dbl=0.9}, 0, 1, FLAGSR },
815 { "tw", "set trace width", OOFFSET(twidth), AV_OPT_TYPE_FLOAT, {.dbl=0.8},.1, 1, FLAGSR },
816 { "th", "set trace height", OOFFSET(theight), AV_OPT_TYPE_FLOAT, {.dbl=0.3},.1, 1, FLAGSR },
817 { "c", "set components to trace", OOFFSET(components), AV_OPT_TYPE_INT, {.i64=7}, 0, 15, FLAGSR },
818 { "g", "draw trace grid", OOFFSET(grid), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1, FLAGSR },
819 { "st", "draw statistics", OOFFSET(statistics), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1, FLAGSR },
820 { "sc", "draw scope", OOFFSET(scope), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1, FLAGSR },
821 { NULL }
822 };
823
824 AVFILTER_DEFINE_CLASS(oscilloscope);
825
826 static void oscilloscope_uninit(AVFilterContext *ctx)
827 {
828 OscilloscopeContext *s = ctx->priv;
829
830 av_freep(&s->values);
831 }
832
833 static void draw_line(FFDrawContext *draw, int x0, int y0, int x1, int y1,
834 AVFrame *out, FFDrawColor *color)
835 {
836 int dx = FFABS(x1 - x0), sx = x0 < x1 ? 1 : -1;
837 int dy = FFABS(y1 - y0), sy = y0 < y1 ? 1 : -1;
838 int err = (dx > dy ? dx : -dy) / 2, e2;
839 int p, i;
840
841 for (;;) {
842 if (x0 >= 0 && y0 >= 0 && x0 < out->width && y0 < out->height) {
843 for (p = 0; p < draw->nb_planes; p++) {
844 if (draw->desc->comp[p].depth == 8) {
845 if (draw->nb_planes == 1) {
846 for (i = 0; i < draw->desc->nb_components; i++) {
847 out->data[0][y0 * out->linesize[0] + x0 * draw->pixelstep[0] + i] = color->comp[0].u8[i];
848 }
849 } else {
850 out->data[p][out->linesize[p] * (y0 >> draw->vsub[p]) + (x0 >> draw->hsub[p])] = color->comp[p].u8[0];
851 }
852 } else {
853 if (draw->nb_planes == 1) {
854 for (i = 0; i < draw->desc->nb_components; i++) {
855 AV_WN16(out->data[0] + y0 * out->linesize[0] + (x0 * draw->pixelstep[0] + i), color->comp[0].u16[i]);
856 }
857 } else {
858 AV_WN16(out->data[p] + out->linesize[p] * (y0 >> draw->vsub[p]) + (x0 >> draw->hsub[p]) * 2, color->comp[p].u16[0]);
859 }
860 }
861 }
862 }
863
864 if (x0 == x1 && y0 == y1)
865 break;
866
867 e2 = err;
868
869 if (e2 >-dx) {
870 err -= dy;
871 x0 += sx;
872 }
873
874 if (e2 < dy) {
875 err += dx;
876 y0 += sy;
877 }
878 }
879 }
880
881 static void draw_trace8(OscilloscopeContext *s, AVFrame *frame)
882 {
883 int i, c;
884
885 for (i = 1; i < s->nb_values; i++) {
886 for (c = 0; c < s->nb_comps; c++) {
887 if ((1 << c) & s->components) {
888 int x = i * s->width / s->nb_values;
889 int px = (i - 1) * s->width / s->nb_values;
890 int py = s->height - s->values[i-1].p[s->rgba_map[c]] * s->height / 256;
891 int y = s->height - s->values[i].p[s->rgba_map[c]] * s->height / 256;
892
893 draw_line(&s->draw, s->ox + x, s->oy + y, s->ox + px, s->oy + py, frame, s->colors[c]);
894 }
895 }
896 }
897 }
898
899
900 static void draw_trace16(OscilloscopeContext *s, AVFrame *frame)
901 {
902 int i, c;
903
904 for (i = 1; i < s->nb_values; i++) {
905 for (c = 0; c < s->nb_comps; c++) {
906 if ((1 << c) & s->components) {
907 int x = i * s->width / s->nb_values;
908 int px = (i - 1) * s->width / s->nb_values;
909 int py = s->height - s->values[i-1].p[s->rgba_map[c]] * s->height / s->max;
910 int y = s->height - s->values[i].p[s->rgba_map[c]] * s->height / s->max;
911
912 draw_line(&s->draw, s->ox + x, s->oy + y, s->ox + px, s->oy + py, frame, s->colors[c]);
913 }
914 }
915 }
916 }
917
918 static void update_oscilloscope(AVFilterContext *ctx)
919 {
920 OscilloscopeContext *s = ctx->priv;
921 AVFilterLink *inlink = ctx->inputs[0];
922 int cx, cy, size;
923 double tilt;
924
925 ff_draw_color(&s->draw, &s->dark, (uint8_t[]){ 0, 0, 0, s->o * 255} );
926 s->height = s->theight * inlink->h;
927 s->width = s->twidth * inlink->w;
928 size = hypot(inlink->w, inlink->h);
929 size *= s->size;
930 tilt = (s->tilt - 0.5) * M_PI;
931 cx = s->xpos * (inlink->w - 1);
932 cy = s->ypos * (inlink->h - 1);
933 s->x1 = cx - size / 2.0 * cos(tilt);
934 s->x2 = cx + size / 2.0 * cos(tilt);
935 s->y1 = cy - size / 2.0 * sin(tilt);
936 s->y2 = cy + size / 2.0 * sin(tilt);
937 s->ox = (inlink->w - s->width) * s->tx;
938 s->oy = (inlink->h - s->height) * s->ty;
939 }
940
941 static int oscilloscope_config_input(AVFilterLink *inlink)
942 {
943 AVFilterContext *ctx = inlink->dst;
944 OscilloscopeContext *s = ctx->priv;
945 int size;
946 int ret;
947
948 s->nb_planes = av_pix_fmt_count_planes(inlink->format);
949 ret = ff_draw_init(&s->draw, inlink->format, 0);
950 if (ret < 0) {
951 av_log(ctx, AV_LOG_ERROR, "Failed to initialize FFDrawContext\n");
952 return ret;
953 }
954 ff_draw_color(&s->draw, &s->black, (uint8_t[]){ 0, 0, 0, 255} );
955 ff_draw_color(&s->draw, &s->white, (uint8_t[]){ 255, 255, 255, 255} );
956 ff_draw_color(&s->draw, &s->green, (uint8_t[]){ 0, 255, 0, 255} );
957 ff_draw_color(&s->draw, &s->blue, (uint8_t[]){ 0, 0, 255, 255} );
958 ff_draw_color(&s->draw, &s->red, (uint8_t[]){ 255, 0, 0, 255} );
959 ff_draw_color(&s->draw, &s->cyan, (uint8_t[]){ 0, 255, 255, 255} );
960 ff_draw_color(&s->draw, &s->magenta, (uint8_t[]){ 255, 0, 255, 255} );
961 ff_draw_color(&s->draw, &s->gray, (uint8_t[]){ 128, 128, 128, 255} );
962 s->nb_comps = s->draw.desc->nb_components;
963 s->is_rgb = s->draw.desc->flags & AV_PIX_FMT_FLAG_RGB;
964
965 if (s->is_rgb) {
966 s->colors[0] = &s->red;
967 s->colors[1] = &s->green;
968 s->colors[2] = &s->blue;
969 s->colors[3] = &s->white;
970 ff_fill_rgba_map(s->rgba_map, inlink->format);
971 } else {
972 s->colors[0] = &s->white;
973 s->colors[1] = &s->cyan;
974 s->colors[2] = &s->magenta;
975 s->colors[3] = &s->white;
976 s->rgba_map[0] = 0;
977 s->rgba_map[1] = 1;
978 s->rgba_map[2] = 2;
979 s->rgba_map[3] = 3;
980 }
981
982 if (s->draw.desc->comp[0].depth <= 8) {
983 s->pick_color = pick_color8;
984 s->draw_trace = draw_trace8;
985 } else {
986 s->pick_color = pick_color16;
987 s->draw_trace = draw_trace16;
988 }
989
990 s->max = (1 << s->draw.desc->comp[0].depth);
991 size = hypot(inlink->w, inlink->h);
992
993 s->values = av_calloc(size, sizeof(*s->values));
994 if (!s->values)
995 return AVERROR(ENOMEM);
996
997 update_oscilloscope(inlink->dst);
998
999 return 0;
1000 }
1001
1002 static void draw_scope(OscilloscopeContext *s, int x0, int y0, int x1, int y1,
1003 AVFrame *out, PixelValues *p, int state)
1004 {
1005 int dx = FFABS(x1 - x0), sx = x0 < x1 ? 1 : -1;
1006 int dy = FFABS(y1 - y0), sy = y0 < y1 ? 1 : -1;
1007 int err = (dx > dy ? dx : -dy) / 2, e2;
1008
1009 for (;;) {
1010 if (x0 >= 0 && y0 >= 0 && x0 < out->width && y0 < out->height) {
1011 FFDrawColor color = { { 0 } };
1012 int value[4] = { 0 };
1013
1014 s->pick_color(&s->draw, &color, out, x0, y0, value);
1015 s->values[s->nb_values].p[0] = value[0];
1016 s->values[s->nb_values].p[1] = value[1];
1017 s->values[s->nb_values].p[2] = value[2];
1018 s->values[s->nb_values].p[3] = value[3];
1019 s->nb_values++;
1020
1021 if (s->scope) {
1022 if (s->draw.desc->comp[0].depth == 8) {
1023 if (s->draw.nb_planes == 1) {
1024 int i;
1025
1026 for (i = 0; i < s->nb_comps; i++)
1027 out->data[0][out->linesize[0] * y0 + x0 * s->draw.pixelstep[0] + i] = 255 * ((s->nb_values + state) & 1);
1028 } else {
1029 out->data[0][out->linesize[0] * y0 + x0] = 255 * ((s->nb_values + state) & 1);
1030 }
1031 } else {
1032 if (s->draw.nb_planes == 1) {
1033 int i;
1034
1035 for (i = 0; i < s->nb_comps; i++)
1036 AV_WN16(out->data[0] + out->linesize[0] * y0 + x0 * s->draw.pixelstep[0] + i, (s->max - 1) * ((s->nb_values + state) & 1));
1037 } else {
1038 AV_WN16(out->data[0] + out->linesize[0] * y0 + 2 * x0, (s->max - 1) * ((s->nb_values + state) & 1));
1039 }
1040 }
1041 }
1042 }
1043
1044 if (x0 == x1 && y0 == y1)
1045 break;
1046
1047 e2 = err;
1048
1049 if (e2 >-dx) {
1050 err -= dy;
1051 x0 += sx;
1052 }
1053
1054 if (e2 < dy) {
1055 err += dx;
1056 y0 += sy;
1057 }
1058 }
1059 }
1060
1061 static int oscilloscope_filter_frame(AVFilterLink *inlink, AVFrame *frame)
1062 {
1063 FilterLink *inl = ff_filter_link(inlink);
1064 AVFilterContext *ctx = inlink->dst;
1065 OscilloscopeContext *s = ctx->priv;
1066 AVFilterLink *outlink = ctx->outputs[0];
1067 float average[4] = { 0 };
1068 int max[4] = { 0 };
1069 int min[4] = { INT_MAX, INT_MAX, INT_MAX, INT_MAX };
1070 int i, c;
1071
1072 s->nb_values = 0;
1073 draw_scope(s, s->x1, s->y1, s->x2, s->y2, frame, s->values, inl->frame_count_in & 1);
1074 ff_blend_rectangle(&s->draw, &s->dark, frame->data, frame->linesize,
1075 frame->width, frame->height,
1076 s->ox, s->oy, s->width, s->height + 20 * s->statistics);
1077
1078 if (s->grid && outlink->h >= 10) {
1079 ff_fill_rectangle(&s->draw, &s->gray, frame->data, frame->linesize,
1080 s->ox, s->oy, s->width - 1, 1);
1081
1082 for (i = 1; i < 5; i++) {
1083 ff_fill_rectangle(&s->draw, &s->gray, frame->data, frame->linesize,
1084 s->ox, s->oy + i * (s->height - 1) / 4, s->width, 1);
1085 }
1086
1087 for (i = 0; i < 10; i++) {
1088 ff_fill_rectangle(&s->draw, &s->gray, frame->data, frame->linesize,
1089 s->ox + i * (s->width - 1) / 10, s->oy, 1, s->height);
1090 }
1091
1092 ff_fill_rectangle(&s->draw, &s->gray, frame->data, frame->linesize,
1093 s->ox + s->width - 1, s->oy, 1, s->height);
1094 }
1095
1096 s->draw_trace(s, frame);
1097
1098 for (i = 0; i < s->nb_values; i++) {
1099 for (c = 0; c < s->nb_comps; c++) {
1100 if ((1 << c) & s->components) {
1101 max[c] = FFMAX(max[c], s->values[i].p[s->rgba_map[c]]);
1102 min[c] = FFMIN(min[c], s->values[i].p[s->rgba_map[c]]);
1103 average[c] += s->values[i].p[s->rgba_map[c]];
1104 }
1105 }
1106 }
1107 for (c = 0; c < s->nb_comps; c++) {
1108 average[c] /= s->nb_values;
1109 }
1110
1111 if (s->statistics && s->height > 10 && s->width > 280 * av_popcount(s->components)) {
1112 for (c = 0, i = 0; c < s->nb_comps; c++) {
1113 if ((1 << c) & s->components) {
1114 const char rgba[4] = { 'R', 'G', 'B', 'A' };
1115 const char yuva[4] = { 'Y', 'U', 'V', 'A' };
1116 char text[128];
1117
1118 snprintf(text, sizeof(text), "%c avg:%.1f min:%d max:%d\n", s->is_rgb ? rgba[c] : yuva[c], average[c], min[c], max[c]);
1119 draw_text(&s->draw, frame, &s->white, s->ox + 2 + 280 * i++, s->oy + s->height + 4, text, 0);
1120 }
1121 }
1122 }
1123
1124 return ff_filter_frame(outlink, frame);
1125 }
1126
1127 static int oscilloscope_process_command(AVFilterContext *ctx, const char *cmd, const char *args,
1128 char *res, int res_len, int flags)
1129 {
1130 int ret;
1131
1132 ret = ff_filter_process_command(ctx, cmd, args, res, res_len, flags);
1133 if (ret < 0)
1134 return ret;
1135
1136 update_oscilloscope(ctx);
1137
1138 return 0;
1139 }
1140
1141 static const AVFilterPad oscilloscope_inputs[] = {
1142 {
1143 .name = "default",
1144 .type = AVMEDIA_TYPE_VIDEO,
1145 .flags = AVFILTERPAD_FLAG_NEEDS_WRITABLE,
1146 .filter_frame = oscilloscope_filter_frame,
1147 .config_props = oscilloscope_config_input,
1148 },
1149 };
1150
1151 const FFFilter ff_vf_oscilloscope = {
1152 .p.name = "oscilloscope",
1153 .p.description = NULL_IF_CONFIG_SMALL("2D Video Oscilloscope."),
1154 .p.priv_class = &oscilloscope_class,
1155 .p.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
1156 .priv_size = sizeof(OscilloscopeContext),
1157 .uninit = oscilloscope_uninit,
1158 FILTER_INPUTS(oscilloscope_inputs),
1159 FILTER_OUTPUTS(ff_video_default_filterpad),
1160 FILTER_QUERY_FUNC2(query_formats),
1161 .process_command = oscilloscope_process_command,
1162 };
1163