blob: 4c705fe85119614ab4d51500d19066291bfba17b
1 | /* |
2 | * Copyright (c) 2015 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 "float.h" |
22 | |
23 | #include "libavutil/eval.h" |
24 | #include "libavutil/intreadwrite.h" |
25 | #include "libavutil/opt.h" |
26 | #include "avfilter.h" |
27 | #include "formats.h" |
28 | #include "internal.h" |
29 | #include "video.h" |
30 | |
31 | typedef struct DrawGraphContext { |
32 | const AVClass *class; |
33 | |
34 | char *key[4]; |
35 | float min, max; |
36 | char *fg_str[4]; |
37 | AVExpr *fg_expr[4]; |
38 | uint8_t bg[4]; |
39 | int mode; |
40 | int slide; |
41 | int w, h; |
42 | |
43 | AVFrame *out; |
44 | int x; |
45 | int prev_y[4]; |
46 | int first; |
47 | float *values[4]; |
48 | int values_size[4]; |
49 | int nb_values; |
50 | } DrawGraphContext; |
51 | |
52 | #define OFFSET(x) offsetof(DrawGraphContext, x) |
53 | #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM |
54 | |
55 | static const AVOption drawgraph_options[] = { |
56 | { "m1", "set 1st metadata key", OFFSET(key[0]), AV_OPT_TYPE_STRING, {.str=""}, CHAR_MIN, CHAR_MAX, FLAGS }, |
57 | { "fg1", "set 1st foreground color expression", OFFSET(fg_str[0]), AV_OPT_TYPE_STRING, {.str="0xffff0000"}, CHAR_MIN, CHAR_MAX, FLAGS }, |
58 | { "m2", "set 2nd metadata key", OFFSET(key[1]), AV_OPT_TYPE_STRING, {.str=""}, CHAR_MIN, CHAR_MAX, FLAGS }, |
59 | { "fg2", "set 2nd foreground color expression", OFFSET(fg_str[1]), AV_OPT_TYPE_STRING, {.str="0xff00ff00"}, CHAR_MIN, CHAR_MAX, FLAGS }, |
60 | { "m3", "set 3rd metadata key", OFFSET(key[2]), AV_OPT_TYPE_STRING, {.str=""}, CHAR_MIN, CHAR_MAX, FLAGS }, |
61 | { "fg3", "set 3rd foreground color expression", OFFSET(fg_str[2]), AV_OPT_TYPE_STRING, {.str="0xffff00ff"}, CHAR_MIN, CHAR_MAX, FLAGS }, |
62 | { "m4", "set 4th metadata key", OFFSET(key[3]), AV_OPT_TYPE_STRING, {.str=""}, CHAR_MIN, CHAR_MAX, FLAGS }, |
63 | { "fg4", "set 4th foreground color expression", OFFSET(fg_str[3]), AV_OPT_TYPE_STRING, {.str="0xffffff00"}, CHAR_MIN, CHAR_MAX, FLAGS }, |
64 | { "bg", "set background color", OFFSET(bg), AV_OPT_TYPE_COLOR, {.str="white"}, CHAR_MIN, CHAR_MAX, FLAGS }, |
65 | { "min", "set minimal value", OFFSET(min), AV_OPT_TYPE_FLOAT, {.dbl=-1.}, INT_MIN, INT_MAX, FLAGS }, |
66 | { "max", "set maximal value", OFFSET(max), AV_OPT_TYPE_FLOAT, {.dbl=1.}, INT_MIN, INT_MAX, FLAGS }, |
67 | { "mode", "set graph mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=2}, 0, 2, FLAGS, "mode" }, |
68 | {"bar", "draw bars", OFFSET(mode), AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, FLAGS, "mode"}, |
69 | {"dot", "draw dots", OFFSET(mode), AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, FLAGS, "mode"}, |
70 | {"line", "draw lines", OFFSET(mode), AV_OPT_TYPE_CONST, {.i64=2}, 0, 0, FLAGS, "mode"}, |
71 | { "slide", "set slide mode", OFFSET(slide), AV_OPT_TYPE_INT, {.i64=0}, 0, 4, FLAGS, "slide" }, |
72 | {"frame", "draw new frames", OFFSET(slide), AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, FLAGS, "slide"}, |
73 | {"replace", "replace old columns with new", OFFSET(slide), AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, FLAGS, "slide"}, |
74 | {"scroll", "scroll from right to left", OFFSET(slide), AV_OPT_TYPE_CONST, {.i64=2}, 0, 0, FLAGS, "slide"}, |
75 | {"rscroll", "scroll from left to right", OFFSET(slide), AV_OPT_TYPE_CONST, {.i64=3}, 0, 0, FLAGS, "slide"}, |
76 | {"picture", "display graph in single frame", OFFSET(slide), AV_OPT_TYPE_CONST, {.i64=4}, 0, 0, FLAGS, "slide"}, |
77 | { "size", "set graph size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str="900x256"}, 0, 0, FLAGS }, |
78 | { "s", "set graph size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str="900x256"}, 0, 0, FLAGS }, |
79 | { NULL } |
80 | }; |
81 | |
82 | static const char *const var_names[] = { "MAX", "MIN", "VAL", NULL }; |
83 | enum { VAR_MAX, VAR_MIN, VAR_VAL, VAR_VARS_NB }; |
84 | |
85 | static av_cold int init(AVFilterContext *ctx) |
86 | { |
87 | DrawGraphContext *s = ctx->priv; |
88 | int ret, i; |
89 | |
90 | if (s->max <= s->min) { |
91 | av_log(ctx, AV_LOG_ERROR, "max is same or lower than min\n"); |
92 | return AVERROR(EINVAL); |
93 | } |
94 | |
95 | for (i = 0; i < 4; i++) { |
96 | if (s->fg_str[i]) { |
97 | ret = av_expr_parse(&s->fg_expr[i], s->fg_str[i], var_names, |
98 | NULL, NULL, NULL, NULL, 0, ctx); |
99 | |
100 | if (ret < 0) |
101 | return ret; |
102 | } |
103 | } |
104 | |
105 | s->first = 1; |
106 | |
107 | if (s->slide == 4) { |
108 | s->values[0] = av_fast_realloc(NULL, &s->values_size[0], 2000); |
109 | s->values[1] = av_fast_realloc(NULL, &s->values_size[1], 2000); |
110 | s->values[2] = av_fast_realloc(NULL, &s->values_size[2], 2000); |
111 | s->values[3] = av_fast_realloc(NULL, &s->values_size[3], 2000); |
112 | |
113 | if (!s->values[0] || !s->values[1] || |
114 | !s->values[2] || !s->values[3]) { |
115 | return AVERROR(ENOMEM); |
116 | } |
117 | } |
118 | |
119 | return 0; |
120 | } |
121 | |
122 | static int query_formats(AVFilterContext *ctx) |
123 | { |
124 | AVFilterLink *outlink = ctx->outputs[0]; |
125 | static const enum AVPixelFormat pix_fmts[] = { |
126 | AV_PIX_FMT_RGBA, |
127 | AV_PIX_FMT_NONE |
128 | }; |
129 | int ret; |
130 | |
131 | AVFilterFormats *fmts_list = ff_make_format_list(pix_fmts); |
132 | if ((ret = ff_formats_ref(fmts_list, &outlink->in_formats)) < 0) |
133 | return ret; |
134 | |
135 | return 0; |
136 | } |
137 | |
138 | static void clear_image(DrawGraphContext *s, AVFrame *out, AVFilterLink *outlink) |
139 | { |
140 | int i, j; |
141 | int bg = AV_RN32(s->bg); |
142 | |
143 | for (i = 0; i < out->height; i++) |
144 | for (j = 0; j < out->width; j++) |
145 | AV_WN32(out->data[0] + i * out->linesize[0] + j * 4, bg); |
146 | } |
147 | |
148 | static inline void draw_dot(int fg, int x, int y, AVFrame *out) |
149 | { |
150 | AV_WN32(out->data[0] + y * out->linesize[0] + x * 4, fg); |
151 | } |
152 | |
153 | static int filter_frame(AVFilterLink *inlink, AVFrame *in) |
154 | { |
155 | AVFilterContext *ctx = inlink->dst; |
156 | DrawGraphContext *s = ctx->priv; |
157 | AVFilterLink *outlink = ctx->outputs[0]; |
158 | AVDictionary *metadata; |
159 | AVDictionaryEntry *e; |
160 | AVFrame *out = s->out; |
161 | int i; |
162 | |
163 | if (s->slide == 4 && s->nb_values >= s->values_size[0] / sizeof(float)) { |
164 | float *ptr; |
165 | |
166 | ptr = av_fast_realloc(s->values[0], &s->values_size[0], s->values_size[0] * 2); |
167 | if (!ptr) |
168 | return AVERROR(ENOMEM); |
169 | s->values[0] = ptr; |
170 | |
171 | ptr = av_fast_realloc(s->values[1], &s->values_size[1], s->values_size[1] * 2); |
172 | if (!ptr) |
173 | return AVERROR(ENOMEM); |
174 | s->values[1] = ptr; |
175 | |
176 | ptr = av_fast_realloc(s->values[2], &s->values_size[2], s->values_size[2] * 2); |
177 | if (!ptr) |
178 | return AVERROR(ENOMEM); |
179 | s->values[2] = ptr; |
180 | |
181 | ptr = av_fast_realloc(s->values[3], &s->values_size[3], s->values_size[3] * 2); |
182 | if (!ptr) |
183 | return AVERROR(ENOMEM); |
184 | s->values[3] = ptr; |
185 | } |
186 | |
187 | if (s->slide != 4 || s->nb_values == 0) { |
188 | if (!s->out || s->out->width != outlink->w || |
189 | s->out->height != outlink->h) { |
190 | av_frame_free(&s->out); |
191 | s->out = ff_get_video_buffer(outlink, outlink->w, outlink->h); |
192 | out = s->out; |
193 | if (!s->out) { |
194 | av_frame_free(&in); |
195 | return AVERROR(ENOMEM); |
196 | } |
197 | |
198 | clear_image(s, out, outlink); |
199 | } |
200 | av_frame_copy_props(out, in); |
201 | } |
202 | |
203 | metadata = av_frame_get_metadata(in); |
204 | |
205 | for (i = 0; i < 4; i++) { |
206 | double values[VAR_VARS_NB]; |
207 | int j, y, x, old; |
208 | uint32_t fg, bg; |
209 | float vf; |
210 | |
211 | if (s->slide == 4) |
212 | s->values[i][s->nb_values] = NAN; |
213 | |
214 | e = av_dict_get(metadata, s->key[i], NULL, 0); |
215 | if (!e || !e->value) |
216 | continue; |
217 | |
218 | if (sscanf(e->value, "%f", &vf) != 1) |
219 | continue; |
220 | |
221 | vf = av_clipf(vf, s->min, s->max); |
222 | |
223 | if (s->slide == 4) { |
224 | s->values[i][s->nb_values] = vf; |
225 | continue; |
226 | } |
227 | |
228 | values[VAR_MIN] = s->min; |
229 | values[VAR_MAX] = s->max; |
230 | values[VAR_VAL] = vf; |
231 | |
232 | fg = av_expr_eval(s->fg_expr[i], values, NULL); |
233 | bg = AV_RN32(s->bg); |
234 | |
235 | if (i == 0 && (s->x >= outlink->w || s->slide == 3)) { |
236 | if (s->slide == 0 || s->slide == 1) |
237 | s->x = 0; |
238 | |
239 | if (s->slide == 2) { |
240 | s->x = outlink->w - 1; |
241 | for (j = 0; j < outlink->h; j++) { |
242 | memmove(out->data[0] + j * out->linesize[0] , |
243 | out->data[0] + j * out->linesize[0] + 4, |
244 | (outlink->w - 1) * 4); |
245 | } |
246 | } else if (s->slide == 3) { |
247 | s->x = 0; |
248 | for (j = 0; j < outlink->h; j++) { |
249 | memmove(out->data[0] + j * out->linesize[0] + 4, |
250 | out->data[0] + j * out->linesize[0], |
251 | (outlink->w - 1) * 4); |
252 | } |
253 | } else if (s->slide == 0) { |
254 | clear_image(s, out, outlink); |
255 | } |
256 | } |
257 | |
258 | x = s->x; |
259 | y = (outlink->h - 1) * (1 - ((vf - s->min) / (s->max - s->min))); |
260 | |
261 | switch (s->mode) { |
262 | case 0: |
263 | if (i == 0 && (s->slide > 0)) |
264 | for (j = 0; j < outlink->h; j++) |
265 | draw_dot(bg, x, j, out); |
266 | |
267 | old = AV_RN32(out->data[0] + y * out->linesize[0] + x * 4); |
268 | for (j = y; j < outlink->h; j++) { |
269 | if (old != bg && |
270 | (AV_RN32(out->data[0] + j * out->linesize[0] + x * 4) != old) || |
271 | AV_RN32(out->data[0] + FFMIN(j+1, outlink->h - 1) * out->linesize[0] + x * 4) != old) { |
272 | draw_dot(fg, x, j, out); |
273 | break; |
274 | } |
275 | draw_dot(fg, x, j, out); |
276 | } |
277 | break; |
278 | case 1: |
279 | if (i == 0 && (s->slide > 0)) |
280 | for (j = 0; j < outlink->h; j++) |
281 | draw_dot(bg, x, j, out); |
282 | draw_dot(fg, x, y, out); |
283 | break; |
284 | case 2: |
285 | if (s->first) { |
286 | s->first = 0; |
287 | s->prev_y[i] = y; |
288 | } |
289 | |
290 | if (i == 0 && (s->slide > 0)) { |
291 | for (j = 0; j < y; j++) |
292 | draw_dot(bg, x, j, out); |
293 | for (j = outlink->h - 1; j > y; j--) |
294 | draw_dot(bg, x, j, out); |
295 | } |
296 | if (y <= s->prev_y[i]) { |
297 | for (j = y; j <= s->prev_y[i]; j++) |
298 | draw_dot(fg, x, j, out); |
299 | } else { |
300 | for (j = s->prev_y[i]; j <= y; j++) |
301 | draw_dot(fg, x, j, out); |
302 | } |
303 | s->prev_y[i] = y; |
304 | break; |
305 | } |
306 | } |
307 | |
308 | s->nb_values++; |
309 | s->x++; |
310 | |
311 | av_frame_free(&in); |
312 | |
313 | if (s->slide == 4) |
314 | return 0; |
315 | |
316 | return ff_filter_frame(outlink, av_frame_clone(s->out)); |
317 | } |
318 | |
319 | static int request_frame(AVFilterLink *outlink) |
320 | { |
321 | AVFilterContext *ctx = outlink->src; |
322 | DrawGraphContext *s = ctx->priv; |
323 | AVFrame *out = s->out; |
324 | int ret, i, k, step, l; |
325 | |
326 | ret = ff_request_frame(ctx->inputs[0]); |
327 | |
328 | if (s->slide == 4 && ret == AVERROR_EOF && s->nb_values > 0) { |
329 | s->x = l = 0; |
330 | step = ceil(s->nb_values / (float)s->w); |
331 | |
332 | for (k = 0; k < s->nb_values; k++) { |
333 | for (i = 0; i < 4; i++) { |
334 | double values[VAR_VARS_NB]; |
335 | int j, y, x, old; |
336 | uint32_t fg, bg; |
337 | float vf = s->values[i][k]; |
338 | |
339 | if (isnan(vf)) |
340 | continue; |
341 | |
342 | values[VAR_MIN] = s->min; |
343 | values[VAR_MAX] = s->max; |
344 | values[VAR_VAL] = vf; |
345 | |
346 | fg = av_expr_eval(s->fg_expr[i], values, NULL); |
347 | bg = AV_RN32(s->bg); |
348 | |
349 | x = s->x; |
350 | y = (outlink->h - 1) * (1 - ((vf - s->min) / (s->max - s->min))); |
351 | |
352 | switch (s->mode) { |
353 | case 0: |
354 | old = AV_RN32(out->data[0] + y * out->linesize[0] + x * 4); |
355 | for (j = y; j < outlink->h; j++) { |
356 | if (old != bg && |
357 | (AV_RN32(out->data[0] + j * out->linesize[0] + x * 4) != old) || |
358 | AV_RN32(out->data[0] + FFMIN(j+1, outlink->h - 1) * out->linesize[0] + x * 4) != old) { |
359 | draw_dot(fg, x, j, out); |
360 | break; |
361 | } |
362 | draw_dot(fg, x, j, out); |
363 | } |
364 | break; |
365 | case 1: |
366 | draw_dot(fg, x, y, out); |
367 | break; |
368 | case 2: |
369 | if (s->first) { |
370 | s->first = 0; |
371 | s->prev_y[i] = y; |
372 | } |
373 | |
374 | if (y <= s->prev_y[i]) { |
375 | for (j = y; j <= s->prev_y[i]; j++) |
376 | draw_dot(fg, x, j, out); |
377 | } else { |
378 | for (j = s->prev_y[i]; j <= y; j++) |
379 | draw_dot(fg, x, j, out); |
380 | } |
381 | s->prev_y[i] = y; |
382 | break; |
383 | } |
384 | } |
385 | |
386 | l++; |
387 | if (l >= step) { |
388 | l = 0; |
389 | s->x++; |
390 | } |
391 | } |
392 | |
393 | s->nb_values = 0; |
394 | out->pts = 0; |
395 | ret = ff_filter_frame(ctx->outputs[0], s->out); |
396 | } |
397 | |
398 | return ret; |
399 | } |
400 | |
401 | static int config_output(AVFilterLink *outlink) |
402 | { |
403 | DrawGraphContext *s = outlink->src->priv; |
404 | |
405 | outlink->w = s->w; |
406 | outlink->h = s->h; |
407 | outlink->sample_aspect_ratio = (AVRational){1,1}; |
408 | |
409 | return 0; |
410 | } |
411 | |
412 | static av_cold void uninit(AVFilterContext *ctx) |
413 | { |
414 | DrawGraphContext *s = ctx->priv; |
415 | int i; |
416 | |
417 | for (i = 0; i < 4; i++) |
418 | av_expr_free(s->fg_expr[i]); |
419 | |
420 | if (s->slide != 4) |
421 | av_frame_free(&s->out); |
422 | |
423 | av_freep(&s->values[0]); |
424 | av_freep(&s->values[1]); |
425 | av_freep(&s->values[2]); |
426 | av_freep(&s->values[3]); |
427 | } |
428 | |
429 | #if CONFIG_DRAWGRAPH_FILTER |
430 | |
431 | AVFILTER_DEFINE_CLASS(drawgraph); |
432 | |
433 | static const AVFilterPad drawgraph_inputs[] = { |
434 | { |
435 | .name = "default", |
436 | .type = AVMEDIA_TYPE_VIDEO, |
437 | .filter_frame = filter_frame, |
438 | }, |
439 | { NULL } |
440 | }; |
441 | |
442 | static const AVFilterPad drawgraph_outputs[] = { |
443 | { |
444 | .name = "default", |
445 | .type = AVMEDIA_TYPE_VIDEO, |
446 | .config_props = config_output, |
447 | .request_frame = request_frame, |
448 | }, |
449 | { NULL } |
450 | }; |
451 | |
452 | AVFilter ff_vf_drawgraph = { |
453 | .name = "drawgraph", |
454 | .description = NULL_IF_CONFIG_SMALL("Draw a graph using input video metadata."), |
455 | .priv_size = sizeof(DrawGraphContext), |
456 | .priv_class = &drawgraph_class, |
457 | .query_formats = query_formats, |
458 | .init = init, |
459 | .uninit = uninit, |
460 | .inputs = drawgraph_inputs, |
461 | .outputs = drawgraph_outputs, |
462 | }; |
463 | |
464 | #endif // CONFIG_DRAWGRAPH_FILTER |
465 | |
466 | #if CONFIG_ADRAWGRAPH_FILTER |
467 | |
468 | #define adrawgraph_options drawgraph_options |
469 | AVFILTER_DEFINE_CLASS(adrawgraph); |
470 | |
471 | static const AVFilterPad adrawgraph_inputs[] = { |
472 | { |
473 | .name = "default", |
474 | .type = AVMEDIA_TYPE_AUDIO, |
475 | .filter_frame = filter_frame, |
476 | }, |
477 | { NULL } |
478 | }; |
479 | |
480 | static const AVFilterPad adrawgraph_outputs[] = { |
481 | { |
482 | .name = "default", |
483 | .type = AVMEDIA_TYPE_VIDEO, |
484 | .config_props = config_output, |
485 | .request_frame = request_frame, |
486 | }, |
487 | { NULL } |
488 | }; |
489 | |
490 | AVFilter ff_avf_adrawgraph = { |
491 | .name = "adrawgraph", |
492 | .description = NULL_IF_CONFIG_SMALL("Draw a graph using input audio metadata."), |
493 | .priv_size = sizeof(DrawGraphContext), |
494 | .priv_class = &adrawgraph_class, |
495 | .query_formats = query_formats, |
496 | .init = init, |
497 | .uninit = uninit, |
498 | .inputs = adrawgraph_inputs, |
499 | .outputs = adrawgraph_outputs, |
500 | }; |
501 | #endif // CONFIG_ADRAWGRAPH_FILTER |
502 |