Hi, > On Jun 27, 2015, at 4:52 PM, Paul B Mahol <one...@gmail.com> wrote: > > Signed-off-by: Paul B Mahol <one...@gmail.com> > --- > doc/filters.texi | 76 ++++++++++++ > libavfilter/Makefile | 1 + > libavfilter/allfilters.c | 1 + > libavfilter/vf_drawgraph.c | 297 +++++++++++++++++++++++++++++++++++++++++++++ > 4 files changed, 375 insertions(+) > create mode 100644 libavfilter/vf_drawgraph.c > > diff --git a/doc/filters.texi b/doc/filters.texi > index d9f913f..a1f8805 100644 > --- a/doc/filters.texi > +++ b/doc/filters.texi > @@ -3965,6 +3965,81 @@ > drawbox=x=-t:y=0.5*(ih-iw/2.4)-t:w=iw+t*2:h=iw/2.4+t*2:t=2:c=red > @end example > @end itemize > > +@section drawgraph > + > +Draw a graph using input video metadata. > + > +It accepts the following parameters: > + > +@table @option > +@item metadata > +Set frame metadata key from which metadata values will be used to draw a > graph. > + > +@item min > +Set minimal value of metadata value. > + > +@item max > +Set maximal value of metadata value. > + > +@item background > +Set graph background color. Default is white. > + > +@item foreground > +Set foreground color expression. > + > +The expressions can use the following variables: > + > +@table @option > +@item MIN > +Minimal value of metadata value. > + > +@item MAX > +Maximal value of metadata value. > + > +@item VAL > +Current metadata key value. > +@end table > + > +@item mode > +Set graph mode. > + > +Available values for mode is: > +@table @samp > +@item bar > +@item dot > +@item line > +@end table > + > +Default is @code{bar}. > + > +@item slide > +Set slide mode. > + > +Available values for slide is: > +@table @samp > +@item frame > +Draw new frame when right border is reached. > + > +@item replace > +Replace old columns with new ones. > + > +@item scroll > +Scroll from right to left. > +@end table > + > +Default is @code{frame}. > + > +@item size > +Set size of graph video. For the syntax of this option, check the > +@ref{video size syntax,,"Video size" section in the ffmpeg-utils > manual,ffmpeg-utils}. > +The default value is @code{400x400}. > +@end table > + > +Example using metadata from @ref{signalstats} filter: > +@example > +signalstats,drawgraph=lavfi.signalstats.YAVG:min=0:max=255 > +@end example > + > @section drawgrid > > Draw a grid on the input image. > @@ -8604,6 +8679,7 @@ Swap the second and third planes of the input: > ffmpeg -i INPUT -vf shuffleplanes=0:2:1:3 OUTPUT > @end example > > +@anchor{signalstats} > @section signalstats > Evaluate various visual metrics that assist in determining issues associated > with the digitization of analog video media. > diff --git a/libavfilter/Makefile b/libavfilter/Makefile > index 55cd055..54a8bbb 100644 > --- a/libavfilter/Makefile > +++ b/libavfilter/Makefile > @@ -117,6 +117,7 @@ OBJS-$(CONFIG_DELOGO_FILTER) += > vf_delogo.o > OBJS-$(CONFIG_DESHAKE_FILTER) += vf_deshake.o > OBJS-$(CONFIG_DETELECINE_FILTER) += vf_detelecine.o > OBJS-$(CONFIG_DRAWBOX_FILTER) += vf_drawbox.o > +OBJS-$(CONFIG_DRAWGRAPH_FILTER) += vf_drawgraph.o > OBJS-$(CONFIG_DRAWGRID_FILTER) += vf_drawbox.o > OBJS-$(CONFIG_DRAWTEXT_FILTER) += vf_drawtext.o > OBJS-$(CONFIG_ELBG_FILTER) += vf_elbg.o > diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c > index 3898498..b9508f5 100644 > --- a/libavfilter/allfilters.c > +++ b/libavfilter/allfilters.c > @@ -133,6 +133,7 @@ void avfilter_register_all(void) > REGISTER_FILTER(DESHAKE, deshake, vf); > REGISTER_FILTER(DETELECINE, detelecine, vf); > REGISTER_FILTER(DRAWBOX, drawbox, vf); > + REGISTER_FILTER(DRAWGRAPH, drawgraph, vf); > REGISTER_FILTER(DRAWGRID, drawgrid, vf); > REGISTER_FILTER(DRAWTEXT, drawtext, vf); > REGISTER_FILTER(EDGEDETECT, edgedetect, vf); > diff --git a/libavfilter/vf_drawgraph.c b/libavfilter/vf_drawgraph.c > new file mode 100644 > index 0000000..642453a > --- /dev/null > +++ b/libavfilter/vf_drawgraph.c > @@ -0,0 +1,297 @@ > +/* > + * Copyright (c) 2015 Paul B Mahol > + * > + * This file is part of FFmpeg. > + * > + * FFmpeg is free software; you can redistribute it and/or > + * modify it under the terms of the GNU Lesser General Public > + * License as published by the Free Software Foundation; either > + * version 2.1 of the License, or (at your option) any later version. > + * > + * FFmpeg is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * Lesser General Public License for more details. > + * > + * You should have received a copy of the GNU Lesser General Public > + * License along with FFmpeg; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 > USA > + */ > + > +#include "float.h" > + > +#include "libavutil/eval.h" > +#include "libavutil/intreadwrite.h" > +#include "libavutil/opt.h" > +#include "avfilter.h" > +#include "formats.h" > +#include "internal.h" > +#include "video.h" > + > +typedef struct DrawGraphContext { > + const AVClass *class; > + > + char *key; > + float min; > + float max; > + char *fg_str; > + AVExpr *fg_expr; > + uint8_t bg[4], fg[4]; > + int mode; > + int slide; > + int w, h; > + > + AVFrame *out; > + int x; > + int prev_y; > + int first; > +} DrawGraphContext; > + > +#define OFFSET(x) offsetof(DrawGraphContext, x) > +#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM > + > +static const AVOption drawgraph_options[] = { > + { "metadata", "set metadata key", OFFSET(key), AV_OPT_TYPE_STRING, > {.str=""}, CHAR_MIN, CHAR_MAX, FLAGS }, > + { "min", "set minimal value", OFFSET(min), AV_OPT_TYPE_FLOAT, > {.dbl=-1.}, INT_MIN, INT_MAX, FLAGS }, > + { "max", "set maximal value", OFFSET(max), AV_OPT_TYPE_FLOAT, {.dbl=1.}, > INT_MIN, INT_MAX, FLAGS }, > + { "background", "set background color", OFFSET(bg), AV_OPT_TYPE_COLOR, > {.str="white"}, CHAR_MIN, CHAR_MAX, FLAGS }, > + { "bg", "set background color expression", OFFSET(bg), > AV_OPT_TYPE_COLOR, {.str="white"}, CHAR_MIN, CHAR_MAX, FLAGS }, > + { "foreground", "set foreground color expression", OFFSET(fg_str), > AV_OPT_TYPE_STRING, {.str="0xff0000"}, CHAR_MIN, CHAR_MAX, FLAGS }, > + { "fg", "set foreground color expression", OFFSET(fg_str), > AV_OPT_TYPE_STRING, {.str="0xff0000"}, CHAR_MIN, CHAR_MAX, FLAGS }, > + { "mode", "set graph mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=0}, 0, > 2, FLAGS, "mode" }, > + {"bar", "draw bars", OFFSET(mode), AV_OPT_TYPE_CONST, {.i64=0}, 0, > 0, FLAGS, "mode"}, > + {"dot", "draw dots", OFFSET(mode), AV_OPT_TYPE_CONST, {.i64=1}, 0, > 0, FLAGS, "mode"}, > + {"line", "draw lines", OFFSET(mode), AV_OPT_TYPE_CONST, {.i64=2}, 0, > 0, FLAGS, "mode"}, > + { "slide", "set slide mode", OFFSET(slide), AV_OPT_TYPE_INT, {.i64=0}, > 0, 2, FLAGS, "slide" }, > + {"frame", "draw new frames", OFFSET(slide), AV_OPT_TYPE_CONST, > {.i64=0}, 0, 0, FLAGS, "slide"}, > + {"replace", "replace old columns with new", OFFSET(slide), > AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, FLAGS, "slide"}, > + {"scroll", "scroll from right to left", OFFSET(slide), > AV_OPT_TYPE_CONST, {.i64=2}, 0, 0, FLAGS, "slide"}, > + { "size", "set graph size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, > {.str="400x400"}, 0, 0, FLAGS }, > + { "s", "set graph size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, > {.str="400x400"}, 0, 0, FLAGS }, > + { NULL } > +}; > + > +AVFILTER_DEFINE_CLASS(drawgraph); > + > +static const char *const var_names[] = { "MAX", "MIN", "VAL", NULL }; > +enum { VAR_MAX, VAR_MIN, VAR_VAL, > VAR_VARS_NB }; > + > +static av_cold int init(AVFilterContext *ctx) > +{ > + DrawGraphContext *s = ctx->priv; > + int ret; > + > + if (s->max <= s->min) { > + av_log(ctx, AV_LOG_ERROR, "max is same or lower than min\n"); > + return AVERROR(EINVAL); > + } > + > + if (s->fg_str) { > + ret = av_expr_parse(&s->fg_expr, s->fg_str, var_names, > + NULL, NULL, NULL, NULL, 0, ctx); > + > + if (ret < 0) > + return ret; > + } > + > + s->first = 1; > + > + return 0; > +} > + > +static int query_formats(AVFilterContext *ctx) > +{ > + AVFilterLink *outlink = ctx->outputs[0]; > + static const enum AVPixelFormat pix_fmts[] = { > + AV_PIX_FMT_GBRP, > + AV_PIX_FMT_NONE > + }; > + > + AVFilterFormats *fmts_list = ff_make_format_list(pix_fmts); > + if (!fmts_list) > + return AVERROR(ENOMEM); > + ff_formats_ref(fmts_list, &outlink->in_formats); > + > + return 0; > +} > + > +static void clear_image(DrawGraphContext *s, AVFrame *out, AVFilterLink > *outlink) > +{ > + int i; > + > + for (i = 0; i < out->height; i++) { > + memset(out->data[0] + i * out->linesize[0], s->bg[1], outlink->w); > + memset(out->data[1] + i * out->linesize[1], s->bg[2], outlink->w); > + memset(out->data[2] + i * out->linesize[2], s->bg[0], outlink->w); > + } > +} > + > +static inline void draw_dot(uint8_t fg[4], int x, int y, AVFrame *out) > +{ > + out->data[0][y * out->linesize[0] + x] = fg[1]; > + out->data[1][y * out->linesize[1] + x] = fg[2]; > + out->data[2][y * out->linesize[2] + x] = fg[0]; > +} > + > +static int filter_frame(AVFilterLink *inlink, AVFrame *in) > +{ > + AVFilterContext *ctx = inlink->dst; > + DrawGraphContext *s = ctx->priv; > + AVFilterLink *outlink = ctx->outputs[0]; > + AVDictionary *metadata; > + AVDictionaryEntry *e; > + AVFrame *out = s->out; > + int j; > + float vf; > + > + if (!s->out || s->out->width != outlink->w || > + s->out->height != outlink->h) { > + av_frame_free(&s->out); > + s->out = ff_get_video_buffer(outlink, outlink->w, outlink->h); > + out = s->out; > + if (!s->out) { > + av_frame_free(&in); > + return AVERROR(ENOMEM); > + } > + > + clear_image(s, out, outlink); > + } > + av_frame_copy_props(out, in); > + > + metadata = av_frame_get_metadata(in); > + e = av_dict_get(metadata, s->key, NULL, 0); > + > + if (e && e->value) { > + double values[VAR_VARS_NB]; > + int y, x, fg; > + > + if (sscanf(e->value, "%f", &vf) != 1) > + goto out; > + > + vf = av_clipf(vf, s->min, s->max); > + > + if (s->x >= outlink->w) { > + if (s->slide == 0 || s->slide == 1) > + s->x = 0; > + > + if (s->slide == 2) { > + s->x = outlink->w - 1; > + for (j = 0; j < outlink->h; j++) { > + memmove(out->data[0] + j * out->linesize[0], > + out->data[0] + j * out->linesize[0] + 1, > + outlink->w - 1); > + memmove(out->data[1] + j * out->linesize[1], > + out->data[1] + j * out->linesize[1] + 1, > + outlink->w - 1); > + memmove(out->data[2] + j * out->linesize[2], > + out->data[2] + j * out->linesize[2] + 1, > + outlink->w - 1); > + } > + } else if (s->slide == 0) { > + clear_image(s, out, outlink); > + } > + } > + > + x = s->x; > + y = outlink->h * (1 - ((vf - s->min) / (s->max - s->min))); > + > + values[VAR_MIN] = s->min; > + values[VAR_MAX] = s->max; > + values[VAR_VAL] = vf; > + > + fg = av_expr_eval(s->fg_expr, values, NULL); > + > + AV_WN32(s->fg, fg); > + > + switch (s->mode) { > + case 0: > + if (s->slide == 1 || s->slide == 2) > + for (j = 0; j < y; j++) > + draw_dot(s->bg, x, j, out); > + for (j = y; j < outlink->h; j++) > + draw_dot(s->fg, x, j, out); > + break; > + case 1: > + if (s->slide == 1 || s->slide == 2) > + for (j = 0; j < outlink->h; j++) > + draw_dot(s->bg, x, j, out); > + draw_dot(s->fg, x, y, out); > + break; > + case 2: > + if (s->first) { > + s->first = 0; > + s->prev_y = y; > + } > + > + if (s->slide == 1 || s->slide == 2) { > + for (j = 0; j < y; j++) > + draw_dot(s->bg, x, j, out); > + for (j = outlink->h - 1; j > y; j--) > + draw_dot(s->bg, x, j, out); > + } > + if (y <= s->prev_y) { > + for (j = y; j <= s->prev_y; j++) > + draw_dot(s->fg, x, j, out); > + } else { > + for (j = s->prev_y; j <= y; j++) > + draw_dot(s->fg, x, j, out); > + } > + s->prev_y = y; > + break; > + } > + s->x++; > + } > + > +out: > + av_frame_free(&in); > + return ff_filter_frame(outlink, av_frame_clone(s->out)); > +} > + > +static int config_output(AVFilterLink *outlink) > +{ > + DrawGraphContext *s = outlink->src->priv; > + > + outlink->w = s->w; > + outlink->h = s->h; > + outlink->sample_aspect_ratio = (AVRational){1,1}; > + > + return 0; > +} > + > +static av_cold void uninit(AVFilterContext *ctx) > +{ > + DrawGraphContext *s = ctx->priv; > + > + av_expr_free(s->fg_expr); > + av_frame_free(&s->out); > +} > + > +static const AVFilterPad drawgraph_inputs[] = { > + { > + .name = "default", > + .type = AVMEDIA_TYPE_VIDEO, > + .filter_frame = filter_frame, > + }, > + { NULL } > +}; > + > +static const AVFilterPad drawgraph_outputs[] = { > + { > + .name = "default", > + .type = AVMEDIA_TYPE_VIDEO, > + .config_props = config_output, > + }, > + { NULL } > +}; > + > +AVFilter ff_vf_drawgraph = { > + .name = "drawgraph", > + .description = NULL_IF_CONFIG_SMALL("Draw a graph using input video > metadata."), > + .priv_size = sizeof(DrawGraphContext), > + .priv_class = &drawgraph_class, > + .query_formats = query_formats, > + .init = init, > + .uninit = uninit, > + .inputs = drawgraph_inputs, > + .outputs = drawgraph_outputs, > +}; > -- > 1.7.11.2
With this version using a color name for foreground no longer works: This works: ffplay -f lavfi -i mandelbrot -vf signalstats,drawgraph=metadata=lavfi.signalstats.YAVG:min=0:max=255:slide=scroll:bg=green:fg=0x0000FF This doesn’t: ffplay -f lavfi -i mandelbrot -vf signalstats,drawgraph=metadata=lavfi.signalstats.YAVG:min=0:max=255:slide=scroll:bg=green:fg=red Also here’s an example with blend: ffplay -f lavfi -i mandelbrot -vf "signalstats,split=3[a][b][c];\ [a]drawgraph=metadata=lavfi.signalstats.YAVG:min=0:max=255:slide=scroll:fg=0xFFFF0000:mode=dot[a1];\ [b]drawgraph=metadata=lavfi.signalstats.UAVG:min=0:max=255:slide=scroll:fg=0xFF00FF00:mode=dot[b1];\ [c]drawgraph=metadata=lavfi.signalstats.VAVG:min=0:max=255:slide=scroll:fg=0xFFFFFF00:mode=dot[c1];\ [a1][b1]blend=and[ab];[ab][c1]blend=and[out]” Thanks, Dave Rice _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel