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