diff mbox

[FFmpeg-devel,WIP] avfilter: add video oscilloscope filter

Message ID 20170425221522.13192-1-onemda@gmail.com
State Superseded
Headers show

Commit Message

Paul B Mahol April 25, 2017, 10:15 p.m. UTC
Signed-off-by: Paul B Mahol <onemda@gmail.com>
---
 libavfilter/Makefile       |   1 +
 libavfilter/allfilters.c   |   1 +
 libavfilter/vf_datascope.c | 299 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 301 insertions(+)

Comments

Dave Rice April 26, 2017, 4:12 a.m. UTC | #1
> On Apr 25, 2017, at 6:15 PM, Paul B Mahol <onemda@gmail.com> wrote:
> 
> Signed-off-by: Paul B Mahol <onemda@gmail.com>
> ---
> libavfilter/Makefile       |   1 +
> libavfilter/allfilters.c   |   1 +
> libavfilter/vf_datascope.c | 299 +++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 301 insertions(+)
> 
> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> index e40c6fe..66c36e4 100644
> --- a/libavfilter/Makefile
> +++ b/libavfilter/Makefile
> @@ -236,6 +236,7 @@ OBJS-$(CONFIG_NULL_FILTER)                   += vf_null.o
> OBJS-$(CONFIG_OCR_FILTER)                    += vf_ocr.o
> OBJS-$(CONFIG_OCV_FILTER)                    += vf_libopencv.o
> OBJS-$(CONFIG_OPENCL)                        += deshake_opencl.o unsharp_opencl.o
> +OBJS-$(CONFIG_OSCILLOSCOPE_FILTER)           += vf_datascope.o
> OBJS-$(CONFIG_OVERLAY_FILTER)                += vf_overlay.o dualinput.o framesync.o
> OBJS-$(CONFIG_OWDENOISE_FILTER)              += vf_owdenoise.o
> OBJS-$(CONFIG_PAD_FILTER)                    += vf_pad.o
> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> index 0852b54..8fb87eb 100644
> --- a/libavfilter/allfilters.c
> +++ b/libavfilter/allfilters.c
> @@ -246,6 +246,7 @@ static void register_all(void)
>     REGISTER_FILTER(NULL,           null,           vf);
>     REGISTER_FILTER(OCR,            ocr,            vf);
>     REGISTER_FILTER(OCV,            ocv,            vf);
> +    REGISTER_FILTER(OSCILLOSCOPE,   oscilloscope,   vf);
>     REGISTER_FILTER(OVERLAY,        overlay,        vf);
>     REGISTER_FILTER(OWDENOISE,      owdenoise,      vf);
>     REGISTER_FILTER(PAD,            pad,            vf);
> diff --git a/libavfilter/vf_datascope.c b/libavfilter/vf_datascope.c
> index 5ad4bb8..080a97d 100644
> --- a/libavfilter/vf_datascope.c
> +++ b/libavfilter/vf_datascope.c
> @@ -630,3 +630,302 @@ AVFilter ff_vf_pixscope = {
>     .inputs        = pixscope_inputs,
>     .outputs       = pixscope_outputs,
> };
> +
> +typedef struct PixelValues {
> +    uint16_t p[4];
> +} PixelValues;
> +
> +typedef struct OscilloscopeContext {
> +    const AVClass *class;
> +
> +    float xpos, ypos;
> +    float ty;
> +    float size;
> +    float tilt;
> +    float tsize;
> +    float o;
> +    int components;
> +    int grid;
> +
> +    int x1, y1, x2, y2;
> +    int ox, oy;
> +    int width;
> +
> +    int nb_planes;
> +    int nb_comps;
> +    int is_rgb;
> +    uint8_t rgba_map[4];
> +    FFDrawContext draw;
> +    FFDrawColor   dark;
> +    FFDrawColor   black;
> +    FFDrawColor   white;
> +    FFDrawColor   green;
> +    FFDrawColor   blue;
> +    FFDrawColor   red;
> +    FFDrawColor   gray;
> +    FFDrawColor  *colors[4];
> +
> +    int nb_values;
> +    PixelValues  *values;
> +
> +    void (*pick_color)(FFDrawContext *draw, FFDrawColor *color, AVFrame *in, int x, int y, int *value);
> +} OscilloscopeContext;
> +
> +#define OOFFSET(x) offsetof(OscilloscopeContext, x)
> +
> +static const AVOption oscilloscope_options[] = {
> +    { "x",    "set scope x position", OOFFSET(xpos),    AV_OPT_TYPE_FLOAT, {.dbl=0.5}, 0, 1, FLAGS },
> +    { "y",    "set scope y position", OOFFSET(ypos),    AV_OPT_TYPE_FLOAT, {.dbl=0.5}, 0, 1, FLAGS },

I’m curious why floats are used for x,y coordinates within a frame rather than integers.

> +    { "s",    "set scope size",       OOFFSET(size),    AV_OPT_TYPE_FLOAT, {.dbl=0.8}, 0, 1, FLAGS },
> +    { "t",    "set scope tilt",       OOFFSET(tilt),    AV_OPT_TYPE_FLOAT, {.dbl=0.5}, 0, 1, FLAGS },
> +    { "o",    "set opacity",          OOFFSET(o),       AV_OPT_TYPE_FLOAT, {.dbl=0.8}, 0, 1, FLAGS },
> +    { "ty",   "set trace y position", OOFFSET(ty),      AV_OPT_TYPE_FLOAT, {.dbl=0.9}, 0, 1, FLAGS },
> +    { "ts",   "set trace size",       OOFFSET(tsize),   AV_OPT_TYPE_FLOAT, {.dbl=0.8},.1, 1, FLAGS },
> +    { "c",    "set components to trace", OOFFSET(components), AV_OPT_TYPE_INT,  {.i64=7}, 0, 15, FLAGS },
> +    { "g",    "draw trace grid",      OOFFSET(grid),    AV_OPT_TYPE_BOOL,  {.i64=0},   0, 1, FLAGS },
> +    { NULL }
> +};
> +
> +AVFILTER_DEFINE_CLASS(oscilloscope);
> +
> +static void oscilloscope_uninit(AVFilterContext *ctx)
> +{
> +    OscilloscopeContext *s = ctx->priv;
> +
> +    av_freep(&s->values);
> +}
> +
> +static int oscilloscope_config_input(AVFilterLink *inlink)
> +{
> +    OscilloscopeContext *s = inlink->dst->priv;
> +    int cx, cy, size;
> +    float tilt;
> +
> +    s->nb_planes = av_pix_fmt_count_planes(inlink->format);
> +    ff_draw_init(&s->draw, inlink->format, 0);
> +    ff_draw_color(&s->draw, &s->dark,  (uint8_t[]){ 0, 0, 0, s->o * 255} );
> +    ff_draw_color(&s->draw, &s->black, (uint8_t[]){ 0, 0, 0, 255} );
> +    ff_draw_color(&s->draw, &s->white, (uint8_t[]){ 255, 255, 255, 255} );
> +    ff_draw_color(&s->draw, &s->green, (uint8_t[]){   0, 255,   0, 255} );
> +    ff_draw_color(&s->draw, &s->blue,  (uint8_t[]){   0,   0, 255, 255} );
> +    ff_draw_color(&s->draw, &s->red,   (uint8_t[]){ 255,   0,   0, 255} );
> +    ff_draw_color(&s->draw, &s->gray,  (uint8_t[]){ 128, 128, 128, 255} );
> +    s->nb_comps = s->draw.desc->nb_components;
> +    s->is_rgb   = s->draw.desc->flags & AV_PIX_FMT_FLAG_RGB;
> +
> +    if (s->is_rgb) {
> +        s->colors[0] = &s->red;
> +        s->colors[1] = &s->green;
> +        s->colors[2] = &s->blue;
> +        s->colors[3] = &s->white;
> +        ff_fill_rgba_map(s->rgba_map, inlink->format);
> +    } else {
> +        s->colors[0] = &s->white;
> +        s->colors[1] = &s->blue;
> +        s->colors[2] = &s->red;
> +        s->colors[3] = &s->white;
> +        s->rgba_map[0] = 0;
> +        s->rgba_map[1] = 1;
> +        s->rgba_map[2] = 2;
> +        s->rgba_map[3] = 3;
> +    }
> +
> +    if (s->draw.desc->comp[0].depth <= 8) {
> +        s->pick_color = pick_color8;
> +    } else {
> +        s->pick_color = pick_color16;
> +    }
> +
> +    if (inlink->h < 300) {
> +        av_log(inlink->dst, AV_LOG_ERROR, "min supported height is 300\n");
> +        return AVERROR(EINVAL);
> +    }
> +
> +    cx = s->xpos * inlink->w;
> +    cy = s->ypos * inlink->h;
> +    size = s->size * hypot(inlink->w, inlink->h);
> +    s->width = s->tsize * (inlink->w);
> +
> +    s->values = av_calloc(size, sizeof(*s->values));
> +    if (!s->values)
> +        return AVERROR(ENOMEM);
> +
> +    tilt  = (s->tilt - 0.5) * M_PI;
> +    s->x1 = cx - size / 2.0 * cosf(tilt);
> +    s->x2 = cx + size / 2.0 * cosf(tilt);
> +    s->y1 = cy - size / 2.0 * sinf(tilt);
> +    s->y2 = cy + size / 2.0 * sinf(tilt);
> +    s->ox = (inlink->w - s->width) >> 1;
> +    s->oy = (inlink->h - 300) * s->ty;
> +
> +    return 0;
> +}
> +
> +static void draw_scope(OscilloscopeContext *s, int x0, int y0, int x1, int y1,
> +                       AVFrame *out, PixelValues *p)
> +{
> +    int dx = FFABS(x1 - x0), sx = x0 < x1 ? 1 : -1;
> +    int dy = FFABS(y1 - y0), sy = y0 < y1 ? 1 : -1;
> +    int err = (dx > dy ? dx : -dy) / 2, e2;
> +
> +    for (;;) {
> +        if (x0 > 0 && y0 > 0 && x0 < out->width && y0 < out->height) {
> +            FFDrawColor color = { { 0 } };
> +            int value[4] = { 0 };
> +
> +            s->pick_color(&s->draw, &color, out, x0, y0, value);
> +            s->values[s->nb_values].p[0] = value[0];
> +            s->values[s->nb_values].p[1] = value[1];
> +            s->values[s->nb_values].p[2] = value[2];
> +            s->values[s->nb_values].p[3] = value[3];
> +            s->nb_values++;
> +
> +            if (s->draw.nb_planes == 1) {
> +                int i;
> +
> +                for (i = 0; i < s->draw.pixelstep[0]; i++)
> +                    out->data[0][out->linesize[0] * y0 + x0 * s->draw.pixelstep[0] + i] = 255;
> +            } else {
> +                out->data[0][out->linesize[0] * y0 + x0] = 255;
> +            }
> +        }
> +
> +        if (x0 == x1 && y0 == y1)
> +            break;
> +
> +        e2 = err;
> +
> +        if (e2 >-dx) {
> +            err -= dy;
> +            x0 += sx;
> +        }
> +
> +        if (e2 < dy) {
> +            err += dx;
> +            y0 += sy;
> +        }
> +    }
> +}
> +
> +static void draw_line(FFDrawContext *draw, int x0, int y0, int x1, int y1,
> +                      AVFrame *out, FFDrawColor *color)
> +{
> +    int dx = FFABS(x1 - x0), sx = x0 < x1 ? 1 : -1;
> +    int dy = FFABS(y1 - y0), sy = y0 < y1 ? 1 : -1;
> +    int err = (dx > dy ? dx : -dy) / 2, e2;
> +    int p, i;
> +
> +    for (;;) {
> +        for (p = 0; p < draw->nb_planes; p++) {
> +            if (draw->nb_planes == 1) {
> +                for (i = 0; i < 4; i++) {
> +                    out->data[0][y0 * out->linesize[0] + x0 * draw->pixelstep[0] + i] = color->comp[0].u8[i];
> +                }
> +            } else {
> +                out->data[p][out->linesize[p] * (y0 >> draw->vsub[p]) + (x0 >> draw->hsub[p])] = color->comp[p].u8[0];
> +            }
> +        }
> +
> +        if (x0 == x1 && y0 == y1)
> +            break;
> +
> +        e2 = err;
> +
> +        if (e2 >-dx) {
> +            err -= dy;
> +            x0 += sx;
> +        }
> +
> +        if (e2 < dy) {
> +            err += dx;
> +            y0 += sy;
> +        }
> +    }
> +}
> +
> +static int oscilloscope_filter_frame(AVFilterLink *inlink, AVFrame *frame)
> +{
> +    AVFilterContext *ctx  = inlink->dst;
> +    OscilloscopeContext *s = ctx->priv;
> +    AVFilterLink *outlink = ctx->outputs[0];
> +    float average[4] = { 0 };
> +    int max[4] = { 0 };
> +    int min[4] = { INT_MAX, INT_MAX, INT_MAX, INT_MAX };
> +    int i, c;
> +
> +    s->nb_values = 0;
> +    draw_scope(s, s->x1, s->y1, s->x2, s->y2, frame, s->values);
> +    ff_blend_rectangle(&s->draw, &s->dark, frame->data, frame->linesize,
> +                       frame->width, frame->height,
> +                       s->ox, s->oy, s->width, 300);

It appears that the plotted scope is padded to 300, though the trace usually is 256 (2^8) lines tall. Are the other 44 lines intended to store something?

> +    if (s->grid) {
> +        ff_fill_rectangle(&s->draw, &s->gray, frame->data, frame->linesize,
> +                          s->ox, s->oy, s->width - 1, 1);
> +
> +        ff_fill_rectangle(&s->draw, &s->gray, frame->data, frame->linesize,
> +                          s->ox, s->oy + 64, s->width, 1);
> +
> +        ff_fill_rectangle(&s->draw, &s->gray, frame->data, frame->linesize,
> +                          s->ox, s->oy + 128, s->width, 1);
> +
> +        ff_fill_rectangle(&s->draw, &s->gray, frame->data, frame->linesize,
> +                          s->ox, s->oy + 192, s->width, 1);
> +
> +        ff_fill_rectangle(&s->draw, &s->gray, frame->data, frame->linesize,
> +                          s->ox, s->oy + 256, s->width, 1);
> +
> +        for (i = 0; i < 10; i++) {
> +            ff_fill_rectangle(&s->draw, &s->gray, frame->data, frame->linesize,
> +                              s->ox + i * (s->width - 1) / 10, s->oy, 1, 256);
> +        }
> +
> +        ff_fill_rectangle(&s->draw, &s->gray, frame->data, frame->linesize,
> +                          s->ox + s->width - 1, s->oy, 1, 256);
> +    }
> +
> +    for (i = 1; i < s->nb_values; i++) {
> +        for (c = 0; c < s->nb_comps; c++) {
> +            if ((1 << c) & s->components) {
> +                int x = i * s->width / s->nb_values;
> +                int px = (i - 1) * s->width / s->nb_values;
> +                int py = 255 - s->values[i-1].p[c];
> +                int y = 255 - s->values[i].p[c];
> +
> +                draw_line(&s->draw, s->ox + x, s->oy + y, s->ox + px, s->oy + py, frame, s->colors[c]);
> +            }
> +        }
> +    }
> +
> +    return ff_filter_frame(outlink, frame);
> +}
> +
> +static const AVFilterPad oscilloscope_inputs[] = {
> +    {
> +        .name           = "default",
> +        .type           = AVMEDIA_TYPE_VIDEO,
> +        .filter_frame   = oscilloscope_filter_frame,
> +        .config_props   = oscilloscope_config_input,
> +        .needs_writable = 1,
> +    },
> +    { NULL }
> +};
> +
> +static const AVFilterPad oscilloscope_outputs[] = {
> +    {
> +        .name         = "default",
> +        .type         = AVMEDIA_TYPE_VIDEO,
> +    },
> +    { NULL }
> +};
> +
> +AVFilter ff_vf_oscilloscope = {
> +    .name          = "oscilloscope",
> +    .description   = NULL_IF_CONFIG_SMALL("2D Video Oscilloscope."),
> +    .priv_size     = sizeof(OscilloscopeContext),
> +    .priv_class    = &oscilloscope_class,
> +    .query_formats = query_formats,
> +    .uninit        = oscilloscope_uninit,
> +    .inputs        = oscilloscope_inputs,
> +    .outputs       = oscilloscope_outputs,
> +};
> -- 
> 2.9.3

A crash on >8 bit:

./ffplay -f lavfi -i mandelbrot -vf format=yuv422p10le,oscilloscope
ffplay version N-85657-gd2e300a0a0 Copyright (c) 2003-2017 the FFmpeg developers
  built with Apple LLVM version 8.0.0 (clang-800.0.42.1)
  configuration: --disable-dwt
  libavutil      55. 61.100 / 55. 61.100
  libavcodec     57. 93.100 / 57. 93.100
  libavformat    57. 72.101 / 57. 72.101
  libavdevice    57.  7.100 / 57.  7.100
  libavfilter     6. 87.100 /  6. 87.100
  libswscale      4.  7.101 /  4.  7.101
  libswresample   2.  8.100 /  2.  8.100
Input #0, lavfi, from 'mandelbrot':0KB vq=    0KB sq=    0B f=0/0   
  Duration: N/A, start: 0.000000, bitrate: N/A
    Stream #0:0: Video: rawvideo (RGB[0] / 0x424752), rgb0, 640x480 [SAR 1:1 DAR 4:3], 25 tbr, 25 tbn, 25 tbc
Segmentation fault: 11


Dave Rice
Paul B Mahol April 26, 2017, 6:37 a.m. UTC | #2
On 4/26/17, Dave Rice <dave@dericed.com> wrote:
>
>
> I'm curious why floats are used for x,y coordinates within a frame rather
> than integers.

So it the position be set relatively. And for similar results for
different resolutions.

>
>
> It appears that the plotted scope is padded to 300, though the trace usually
> is 256 (2^8) lines tall. Are the other 44 lines intended to store something?
>

Yes, some statistics numbers.
Dave Rice April 26, 2017, 2:18 p.m. UTC | #3
> On Apr 26, 2017, at 2:37 AM, Paul B Mahol <onemda@gmail.com> wrote:
> 
> On 4/26/17, Dave Rice <dave@dericed.com> wrote:
>> 
>> 
>> I'm curious why floats are used for x,y coordinates within a frame rather
>> than integers.
> 
> So it the position be set relatively. And for similar results for
> different resolutions.

Having been used to other filters with integers as x,y, this seems a bit unintuitive to me. In other filters relative positions are set by variables, such as ih/2 as opposed to 0.5. If staying with floats, I'd suggest that the documentation include examples for the use of calling specific lines by integer by including math in the options. Perhaps the statistical area could also state the x,y coords of the two ends of the scope as ints so that a viewer of the image would be clear to what line(s) are included in the analysis.

>> It appears that the plotted scope is padded to 300, though the trace usually
>> is 256 (2^8) lines tall. Are the other 44 lines intended to store something?
>> 
> 
> Yes, some statistics numbers.
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
diff mbox

Patch

diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index e40c6fe..66c36e4 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -236,6 +236,7 @@  OBJS-$(CONFIG_NULL_FILTER)                   += vf_null.o
 OBJS-$(CONFIG_OCR_FILTER)                    += vf_ocr.o
 OBJS-$(CONFIG_OCV_FILTER)                    += vf_libopencv.o
 OBJS-$(CONFIG_OPENCL)                        += deshake_opencl.o unsharp_opencl.o
+OBJS-$(CONFIG_OSCILLOSCOPE_FILTER)           += vf_datascope.o
 OBJS-$(CONFIG_OVERLAY_FILTER)                += vf_overlay.o dualinput.o framesync.o
 OBJS-$(CONFIG_OWDENOISE_FILTER)              += vf_owdenoise.o
 OBJS-$(CONFIG_PAD_FILTER)                    += vf_pad.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 0852b54..8fb87eb 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -246,6 +246,7 @@  static void register_all(void)
     REGISTER_FILTER(NULL,           null,           vf);
     REGISTER_FILTER(OCR,            ocr,            vf);
     REGISTER_FILTER(OCV,            ocv,            vf);
+    REGISTER_FILTER(OSCILLOSCOPE,   oscilloscope,   vf);
     REGISTER_FILTER(OVERLAY,        overlay,        vf);
     REGISTER_FILTER(OWDENOISE,      owdenoise,      vf);
     REGISTER_FILTER(PAD,            pad,            vf);
diff --git a/libavfilter/vf_datascope.c b/libavfilter/vf_datascope.c
index 5ad4bb8..080a97d 100644
--- a/libavfilter/vf_datascope.c
+++ b/libavfilter/vf_datascope.c
@@ -630,3 +630,302 @@  AVFilter ff_vf_pixscope = {
     .inputs        = pixscope_inputs,
     .outputs       = pixscope_outputs,
 };
+
+typedef struct PixelValues {
+    uint16_t p[4];
+} PixelValues;
+
+typedef struct OscilloscopeContext {
+    const AVClass *class;
+
+    float xpos, ypos;
+    float ty;
+    float size;
+    float tilt;
+    float tsize;
+    float o;
+    int components;
+    int grid;
+
+    int x1, y1, x2, y2;
+    int ox, oy;
+    int width;
+
+    int nb_planes;
+    int nb_comps;
+    int is_rgb;
+    uint8_t rgba_map[4];
+    FFDrawContext draw;
+    FFDrawColor   dark;
+    FFDrawColor   black;
+    FFDrawColor   white;
+    FFDrawColor   green;
+    FFDrawColor   blue;
+    FFDrawColor   red;
+    FFDrawColor   gray;
+    FFDrawColor  *colors[4];
+
+    int nb_values;
+    PixelValues  *values;
+
+    void (*pick_color)(FFDrawContext *draw, FFDrawColor *color, AVFrame *in, int x, int y, int *value);
+} OscilloscopeContext;
+
+#define OOFFSET(x) offsetof(OscilloscopeContext, x)
+
+static const AVOption oscilloscope_options[] = {
+    { "x",    "set scope x position", OOFFSET(xpos),    AV_OPT_TYPE_FLOAT, {.dbl=0.5}, 0, 1, FLAGS },
+    { "y",    "set scope y position", OOFFSET(ypos),    AV_OPT_TYPE_FLOAT, {.dbl=0.5}, 0, 1, FLAGS },
+    { "s",    "set scope size",       OOFFSET(size),    AV_OPT_TYPE_FLOAT, {.dbl=0.8}, 0, 1, FLAGS },
+    { "t",    "set scope tilt",       OOFFSET(tilt),    AV_OPT_TYPE_FLOAT, {.dbl=0.5}, 0, 1, FLAGS },
+    { "o",    "set opacity",          OOFFSET(o),       AV_OPT_TYPE_FLOAT, {.dbl=0.8}, 0, 1, FLAGS },
+    { "ty",   "set trace y position", OOFFSET(ty),      AV_OPT_TYPE_FLOAT, {.dbl=0.9}, 0, 1, FLAGS },
+    { "ts",   "set trace size",       OOFFSET(tsize),   AV_OPT_TYPE_FLOAT, {.dbl=0.8},.1, 1, FLAGS },
+    { "c",    "set components to trace", OOFFSET(components), AV_OPT_TYPE_INT,  {.i64=7}, 0, 15, FLAGS },
+    { "g",    "draw trace grid",      OOFFSET(grid),    AV_OPT_TYPE_BOOL,  {.i64=0},   0, 1, FLAGS },
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(oscilloscope);
+
+static void oscilloscope_uninit(AVFilterContext *ctx)
+{
+    OscilloscopeContext *s = ctx->priv;
+
+    av_freep(&s->values);
+}
+
+static int oscilloscope_config_input(AVFilterLink *inlink)
+{
+    OscilloscopeContext *s = inlink->dst->priv;
+    int cx, cy, size;
+    float tilt;
+
+    s->nb_planes = av_pix_fmt_count_planes(inlink->format);
+    ff_draw_init(&s->draw, inlink->format, 0);
+    ff_draw_color(&s->draw, &s->dark,  (uint8_t[]){ 0, 0, 0, s->o * 255} );
+    ff_draw_color(&s->draw, &s->black, (uint8_t[]){ 0, 0, 0, 255} );
+    ff_draw_color(&s->draw, &s->white, (uint8_t[]){ 255, 255, 255, 255} );
+    ff_draw_color(&s->draw, &s->green, (uint8_t[]){   0, 255,   0, 255} );
+    ff_draw_color(&s->draw, &s->blue,  (uint8_t[]){   0,   0, 255, 255} );
+    ff_draw_color(&s->draw, &s->red,   (uint8_t[]){ 255,   0,   0, 255} );
+    ff_draw_color(&s->draw, &s->gray,  (uint8_t[]){ 128, 128, 128, 255} );
+    s->nb_comps = s->draw.desc->nb_components;
+    s->is_rgb   = s->draw.desc->flags & AV_PIX_FMT_FLAG_RGB;
+
+    if (s->is_rgb) {
+        s->colors[0] = &s->red;
+        s->colors[1] = &s->green;
+        s->colors[2] = &s->blue;
+        s->colors[3] = &s->white;
+        ff_fill_rgba_map(s->rgba_map, inlink->format);
+    } else {
+        s->colors[0] = &s->white;
+        s->colors[1] = &s->blue;
+        s->colors[2] = &s->red;
+        s->colors[3] = &s->white;
+        s->rgba_map[0] = 0;
+        s->rgba_map[1] = 1;
+        s->rgba_map[2] = 2;
+        s->rgba_map[3] = 3;
+    }
+
+    if (s->draw.desc->comp[0].depth <= 8) {
+        s->pick_color = pick_color8;
+    } else {
+        s->pick_color = pick_color16;
+    }
+
+    if (inlink->h < 300) {
+        av_log(inlink->dst, AV_LOG_ERROR, "min supported height is 300\n");
+        return AVERROR(EINVAL);
+    }
+
+    cx = s->xpos * inlink->w;
+    cy = s->ypos * inlink->h;
+    size = s->size * hypot(inlink->w, inlink->h);
+    s->width = s->tsize * (inlink->w);
+
+    s->values = av_calloc(size, sizeof(*s->values));
+    if (!s->values)
+        return AVERROR(ENOMEM);
+
+    tilt  = (s->tilt - 0.5) * M_PI;
+    s->x1 = cx - size / 2.0 * cosf(tilt);
+    s->x2 = cx + size / 2.0 * cosf(tilt);
+    s->y1 = cy - size / 2.0 * sinf(tilt);
+    s->y2 = cy + size / 2.0 * sinf(tilt);
+    s->ox = (inlink->w - s->width) >> 1;
+    s->oy = (inlink->h - 300) * s->ty;
+
+    return 0;
+}
+
+static void draw_scope(OscilloscopeContext *s, int x0, int y0, int x1, int y1,
+                       AVFrame *out, PixelValues *p)
+{
+    int dx = FFABS(x1 - x0), sx = x0 < x1 ? 1 : -1;
+    int dy = FFABS(y1 - y0), sy = y0 < y1 ? 1 : -1;
+    int err = (dx > dy ? dx : -dy) / 2, e2;
+
+    for (;;) {
+        if (x0 > 0 && y0 > 0 && x0 < out->width && y0 < out->height) {
+            FFDrawColor color = { { 0 } };
+            int value[4] = { 0 };
+
+            s->pick_color(&s->draw, &color, out, x0, y0, value);
+            s->values[s->nb_values].p[0] = value[0];
+            s->values[s->nb_values].p[1] = value[1];
+            s->values[s->nb_values].p[2] = value[2];
+            s->values[s->nb_values].p[3] = value[3];
+            s->nb_values++;
+
+            if (s->draw.nb_planes == 1) {
+                int i;
+
+                for (i = 0; i < s->draw.pixelstep[0]; i++)
+                    out->data[0][out->linesize[0] * y0 + x0 * s->draw.pixelstep[0] + i] = 255;
+            } else {
+                out->data[0][out->linesize[0] * y0 + x0] = 255;
+            }
+        }
+
+        if (x0 == x1 && y0 == y1)
+            break;
+
+        e2 = err;
+
+        if (e2 >-dx) {
+            err -= dy;
+            x0 += sx;
+        }
+
+        if (e2 < dy) {
+            err += dx;
+            y0 += sy;
+        }
+    }
+}
+
+static void draw_line(FFDrawContext *draw, int x0, int y0, int x1, int y1,
+                      AVFrame *out, FFDrawColor *color)
+{
+    int dx = FFABS(x1 - x0), sx = x0 < x1 ? 1 : -1;
+    int dy = FFABS(y1 - y0), sy = y0 < y1 ? 1 : -1;
+    int err = (dx > dy ? dx : -dy) / 2, e2;
+    int p, i;
+
+    for (;;) {
+        for (p = 0; p < draw->nb_planes; p++) {
+            if (draw->nb_planes == 1) {
+                for (i = 0; i < 4; i++) {
+                    out->data[0][y0 * out->linesize[0] + x0 * draw->pixelstep[0] + i] = color->comp[0].u8[i];
+                }
+            } else {
+                out->data[p][out->linesize[p] * (y0 >> draw->vsub[p]) + (x0 >> draw->hsub[p])] = color->comp[p].u8[0];
+            }
+        }
+
+        if (x0 == x1 && y0 == y1)
+            break;
+
+        e2 = err;
+
+        if (e2 >-dx) {
+            err -= dy;
+            x0 += sx;
+        }
+
+        if (e2 < dy) {
+            err += dx;
+            y0 += sy;
+        }
+    }
+}
+
+static int oscilloscope_filter_frame(AVFilterLink *inlink, AVFrame *frame)
+{
+    AVFilterContext *ctx  = inlink->dst;
+    OscilloscopeContext *s = ctx->priv;
+    AVFilterLink *outlink = ctx->outputs[0];
+    float average[4] = { 0 };
+    int max[4] = { 0 };
+    int min[4] = { INT_MAX, INT_MAX, INT_MAX, INT_MAX };
+    int i, c;
+
+    s->nb_values = 0;
+    draw_scope(s, s->x1, s->y1, s->x2, s->y2, frame, s->values);
+    ff_blend_rectangle(&s->draw, &s->dark, frame->data, frame->linesize,
+                       frame->width, frame->height,
+                       s->ox, s->oy, s->width, 300);
+
+    if (s->grid) {
+        ff_fill_rectangle(&s->draw, &s->gray, frame->data, frame->linesize,
+                          s->ox, s->oy, s->width - 1, 1);
+
+        ff_fill_rectangle(&s->draw, &s->gray, frame->data, frame->linesize,
+                          s->ox, s->oy + 64, s->width, 1);
+
+        ff_fill_rectangle(&s->draw, &s->gray, frame->data, frame->linesize,
+                          s->ox, s->oy + 128, s->width, 1);
+
+        ff_fill_rectangle(&s->draw, &s->gray, frame->data, frame->linesize,
+                          s->ox, s->oy + 192, s->width, 1);
+
+        ff_fill_rectangle(&s->draw, &s->gray, frame->data, frame->linesize,
+                          s->ox, s->oy + 256, s->width, 1);
+
+        for (i = 0; i < 10; i++) {
+            ff_fill_rectangle(&s->draw, &s->gray, frame->data, frame->linesize,
+                              s->ox + i * (s->width - 1) / 10, s->oy, 1, 256);
+        }
+
+        ff_fill_rectangle(&s->draw, &s->gray, frame->data, frame->linesize,
+                          s->ox + s->width - 1, s->oy, 1, 256);
+    }
+
+    for (i = 1; i < s->nb_values; i++) {
+        for (c = 0; c < s->nb_comps; c++) {
+            if ((1 << c) & s->components) {
+                int x = i * s->width / s->nb_values;
+                int px = (i - 1) * s->width / s->nb_values;
+                int py = 255 - s->values[i-1].p[c];
+                int y = 255 - s->values[i].p[c];
+
+                draw_line(&s->draw, s->ox + x, s->oy + y, s->ox + px, s->oy + py, frame, s->colors[c]);
+            }
+        }
+    }
+
+    return ff_filter_frame(outlink, frame);
+}
+
+static const AVFilterPad oscilloscope_inputs[] = {
+    {
+        .name           = "default",
+        .type           = AVMEDIA_TYPE_VIDEO,
+        .filter_frame   = oscilloscope_filter_frame,
+        .config_props   = oscilloscope_config_input,
+        .needs_writable = 1,
+    },
+    { NULL }
+};
+
+static const AVFilterPad oscilloscope_outputs[] = {
+    {
+        .name         = "default",
+        .type         = AVMEDIA_TYPE_VIDEO,
+    },
+    { NULL }
+};
+
+AVFilter ff_vf_oscilloscope = {
+    .name          = "oscilloscope",
+    .description   = NULL_IF_CONFIG_SMALL("2D Video Oscilloscope."),
+    .priv_size     = sizeof(OscilloscopeContext),
+    .priv_class    = &oscilloscope_class,
+    .query_formats = query_formats,
+    .uninit        = oscilloscope_uninit,
+    .inputs        = oscilloscope_inputs,
+    .outputs       = oscilloscope_outputs,
+};