Hello,

Please find attached a patch to add a new mode to avf_showfreqs.

Also, there is a demo of the new mode showing various settings available
here:
- https://youtube[dot]com/watch?v=pONf74ZgQwU

Thanks,
Nicholas Saney
From dac286ac7f27c7cd0191205a0d02a06a93060ac1 Mon Sep 17 00:00:00 2001
From: Nicholas Saney <[email protected]>
Date: Sat, 17 Jan 2026 02:01:38 -0500
Subject: [PATCH] avfilter/avf_showfreqs: Add pipe mode code and docs

Signed-off-by: Nicholas Saney <[email protected]>
---
 doc/filters.texi            |  10 ++
 libavfilter/avf_showfreqs.c | 203 +++++++++++++++++++++++++++++++++++-
 2 files changed, 210 insertions(+), 3 deletions(-)

diff --git a/doc/filters.texi b/doc/filters.texi
index 8a7e370a60..a8927f4a30 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -32981,6 +32981,7 @@ It accepts the following values:
 @item line
 @item bar
 @item dot
+@item pipe
 @end table
 Default is @code{bar}.
 
@@ -33092,6 +33093,15 @@ Default is @code{magnitude}.
 
 @item channels
 Set channels to use when processing audio. By default all are processed.
+
+@item pipe_border_color
+Specify color of pipe border for @code{pipe} mode. Default is @code{0x3f3f3f}.
+
+@item pipe_padding_color
+Specify color of pipe padding for @code{pipe} mode. Default is @code{0xdfdfdf}.
+
+@item pipe_min_width
+Set minimum width for pipes in @code{pipe} mode. In range @code{[0, 65536]}. Default is @code{14}.
 @end table
 
 @section showspatial
diff --git a/libavfilter/avf_showfreqs.c b/libavfilter/avf_showfreqs.c
index 244b013ada..8b78bde6c4 100644
--- a/libavfilter/avf_showfreqs.c
+++ b/libavfilter/avf_showfreqs.c
@@ -37,11 +37,28 @@
 #include "window_func.h"
 
 enum DataMode       { MAGNITUDE, PHASE, DELAY, NB_DATA };
-enum DisplayMode    { LINE, BAR, DOT, NB_MODES };
+enum DisplayMode    { LINE, BAR, DOT, PIPE, NB_MODES };
 enum ChannelMode    { COMBINED, SEPARATE, NB_CMODES };
 enum FrequencyScale { FS_LINEAR, FS_LOG, FS_RLOG, NB_FSCALES };
 enum AmplitudeScale { AS_LINEAR, AS_SQRT, AS_CBRT, AS_LOG, NB_ASCALES };
 
+typedef struct RectangleBounds {
+    int x_lo;
+    int x_hi;
+    int y_lo;
+    int y_hi;
+} RectangleBounds;
+
+typedef struct ShowFreqsPipeModeContext {
+    char *pipe_border_color;
+    uint8_t bd[4];
+    char *pipe_padding_color;
+    uint8_t pg[4];
+    int pipe_min_width;
+    int pipe_curr_unused_min_y;
+    int pipe_next_x0;
+} ShowFreqsPipeModeContext;
+
 typedef struct ShowFreqsContext {
     const AVClass *class;
     int w, h;
@@ -74,9 +91,11 @@ typedef struct ShowFreqsContext {
     int64_t pts;
     int64_t old_pts;
     AVRational frame_rate;
+    ShowFreqsPipeModeContext pipe_mode_ctx;
 } ShowFreqsContext;
 
 #define OFFSET(x) offsetof(ShowFreqsContext, x)
+#define PIPE_MODE_OFFSET(x) OFFSET(pipe_mode_ctx) + offsetof(ShowFreqsPipeModeContext, x)
 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
 
 static const AVOption showfreqs_options[] = {
@@ -88,6 +107,7 @@ static const AVOption showfreqs_options[] = {
         { "line", "show lines",  0, AV_OPT_TYPE_CONST, {.i64=LINE},   0, 0, FLAGS, .unit = "mode" },
         { "bar",  "show bars",   0, AV_OPT_TYPE_CONST, {.i64=BAR},    0, 0, FLAGS, .unit = "mode" },
         { "dot",  "show dots",   0, AV_OPT_TYPE_CONST, {.i64=DOT},    0, 0, FLAGS, .unit = "mode" },
+        { "pipe", "show pipes",  0, AV_OPT_TYPE_CONST, {.i64=PIPE},   0, 0, FLAGS, .unit = "mode" },
     { "ascale", "set amplitude scale", OFFSET(ascale), AV_OPT_TYPE_INT, {.i64=AS_LOG}, 0, NB_ASCALES-1, FLAGS, .unit = "ascale" },
         { "lin",  "linear",      0, AV_OPT_TYPE_CONST, {.i64=AS_LINEAR}, 0, 0, FLAGS, .unit = "ascale" },
         { "sqrt", "square root", 0, AV_OPT_TYPE_CONST, {.i64=AS_SQRT},   0, 0, FLAGS, .unit = "ascale" },
@@ -111,6 +131,9 @@ static const AVOption showfreqs_options[] = {
         { "phase",     "show phase",      0, AV_OPT_TYPE_CONST, {.i64=PHASE},     0, 0, FLAGS, .unit = "data" },
         { "delay",     "show group delay",0, AV_OPT_TYPE_CONST, {.i64=DELAY},     0, 0, FLAGS, .unit = "data" },
     { "channels", "set channels to draw", OFFSET(ch_layout_str), AV_OPT_TYPE_STRING, {.str="all"}, 0, 0, FLAGS },
+    { "pipe_border_color",  "set pipe_border_color",  PIPE_MODE_OFFSET(pipe_border_color),  AV_OPT_TYPE_STRING, {.str="0x3f3f3f"},  0,  0, FLAGS },
+    { "pipe_padding_color", "set pipe_padding_color", PIPE_MODE_OFFSET(pipe_padding_color), AV_OPT_TYPE_STRING, {.str="0xdfdfdf"},  0,  0, FLAGS },
+    { "pipe_min_width",     "set pipe_min_width",     PIPE_MODE_OFFSET(pipe_min_width),     AV_OPT_TYPE_INT,    {.i64=14},       0, 65536, FLAGS },
     { NULL }
 };
 
@@ -243,7 +266,7 @@ static int config_output(AVFilterLink *outlink)
     return 0;
 }
 
-static inline void draw_dot(AVFrame *out, int x, int y, uint8_t fg[4])
+static inline void draw_dot(AVFrame *out, int x, int y, const uint8_t fg[4])
 {
 
     uint32_t color = AV_RL32(out->data[0] + y * out->linesize[0] + x * 4);
@@ -254,6 +277,39 @@ static inline void draw_dot(AVFrame *out, int x, int y, uint8_t fg[4])
         AV_WL32(out->data[0] + y * out->linesize[0] + x * 4, AV_RL32(fg));
 }
 
+static inline void fill_rectangle(AVFrame *out, const uint8_t fg[4],
+                                  RectangleBounds *rb,
+                                  int x0, int xf, int y0, int yf)
+{
+    int x;
+    if (xf < x0) { x = xf; xf = x0; x0 = x; }
+    if (x0 < rb->x_lo) x0 = rb->x_lo;
+    if (xf > rb->x_hi) xf = rb->x_hi;
+    int y;
+    if (yf < y0) { y = yf; yf = y0; y0 = y; }
+    if (y0 < rb->y_lo) y0 = rb->y_lo;
+    if (yf > rb->y_hi) yf = rb->y_hi;
+    for (x = x0; x < xf; x++)
+        for (y = y0; y < yf; y++)
+            draw_dot(out, x, y, fg);
+}
+
+static inline void fill_and_mirror_rectangle(AVFrame *out, const uint8_t fg[4],
+                                             RectangleBounds *rb,
+                                             int x0, int xf, int y0, int yf)
+{
+    int x_md = (rb->x_hi + rb->x_lo) / 2 + 1;
+    int rx0 = rb->x_hi - (x0 - rb->x_lo);
+    int rxf = rb->x_hi - (xf - rb->x_lo);
+    int y_md = (rb->y_hi + rb->y_lo) / 2 + 1;
+    int ry0 = rb->y_hi - (y0 - rb->y_lo);
+    int ryf = rb->y_hi - (yf - rb->y_lo);
+    fill_rectangle(out, fg, &(RectangleBounds){ rb->x_lo,     x_md, rb->y_lo,     y_md },  x0,  xf,  y0,  yf);
+    fill_rectangle(out, fg, &(RectangleBounds){     x_md, rb->x_hi, rb->y_lo,     y_md }, rx0, rxf,  y0,  yf);
+    fill_rectangle(out, fg, &(RectangleBounds){ rb->x_lo,     x_md,     y_md, rb->y_hi },  x0,  xf, ry0, ryf);
+    fill_rectangle(out, fg, &(RectangleBounds){     x_md, rb->x_hi,     y_md, rb->y_hi }, rx0, rxf, ry0, ryf);
+}
+
 static int get_sx(ShowFreqsContext *s, int f)
 {
     switch (s->fscale) {
@@ -295,6 +351,11 @@ static inline void plot_freq(ShowFreqsContext *s, int ch,
     const float bsize = get_bsize(s, f);
     const int sx = get_sx(s, f);
     int end = outlink->h;
+    RectangleBounds rb;
+    int top;
+    ShowFreqsPipeModeContext *p = &s->pipe_mode_ctx;
+    int x_md, y_md;
+    int pipe_width, pipe_gap;
     int x, y, i;
 
     switch(s->ascale) {
@@ -314,11 +375,14 @@ static inline void plot_freq(ShowFreqsContext *s, int ch,
 
     switch (s->cmode) {
     case COMBINED:
+        top = 0;
+        end = outlink->h;
         y = a * outlink->h - 1;
         break;
     case SEPARATE:
+        top = (outlink->h / s->nb_draw_channels) * ch;
         end = (outlink->h / s->nb_draw_channels) * (ch + 1);
-        y = (outlink->h / s->nb_draw_channels) * ch + a * (outlink->h / s->nb_draw_channels) - 1;
+        y = top + a * (outlink->h / s->nb_draw_channels) - 1;
         break;
     default:
         av_assert0(0);
@@ -365,6 +429,129 @@ static inline void plot_freq(ShowFreqsContext *s, int ch,
         for (x = sx; x < sx + bsize && x < w; x++)
             draw_dot(out, x, y, fg);
         break;
+    case PIPE:
+        rb.x_lo = sx;
+        rb.x_hi = sx + bsize - 1;
+        pipe_width = rb.x_hi - rb.x_lo;
+        if (pipe_width < p->pipe_min_width && y < p->pipe_curr_unused_min_y)
+            p->pipe_curr_unused_min_y = y;
+        if (p->pipe_min_width + 1 <= rb.x_lo - p->pipe_next_x0) {
+            y = p->pipe_curr_unused_min_y;
+            rb.x_lo = p->pipe_next_x0;
+            rb.x_hi = rb.x_lo + p->pipe_min_width;
+            pipe_width = p->pipe_min_width;
+        }
+        if (pipe_width < p->pipe_min_width)
+            break;
+        if (rb.x_hi > w)
+            break;
+        p->pipe_curr_unused_min_y = end;
+        p->pipe_next_x0 = rb.x_hi + 1;
+        x_md = (rb.x_lo + rb.x_hi) / 2 + 1;
+        //      top  - - - y - -|- - - - - -  end
+        // BAR   [........]<====|==============>
+        // PIPE  [....<=========|=========>....]
+        pipe_gap = (y - top) / 2;
+        rb.y_lo = y - pipe_gap;
+        rb.y_hi = end - pipe_gap;
+        y_md = (rb.y_lo + rb.y_hi) / 2 + 1;
+        if (22 <= pipe_width) {
+            // A  B  C  D  E  E  ... E  E  D  C  B  A
+            // .  .  .  .  ## ## ...
+            // .  .  ## ## [] []
+            // .  ## [] [] fg fg
+            // .  ## [] fg fg fg
+            // ## [] fg fg fg fg
+            // ## [] fg fg fg fg ...
+            // ...            ...
+            // cols A
+            x = rb.x_lo;
+            y = rb.y_lo + 8;
+            fill_and_mirror_rectangle(out, p->bd, &rb, x, x + 2, y    , y_md);
+            // cols B
+            x = rb.x_lo + 2;
+            y = rb.y_lo + 4;
+            fill_and_mirror_rectangle(out, p->bd, &rb, x, x + 2, y    , y + 4);
+            fill_and_mirror_rectangle(out, p->pg, &rb, x, x + 2, y + 4, y_md );
+            // cols C
+            x = rb.x_lo + 4;
+            y = rb.y_lo + 2;
+            fill_and_mirror_rectangle(out, p->bd, &rb, x, x + 2, y    , y + 2);
+            fill_and_mirror_rectangle(out, p->pg, &rb, x, x + 2, y + 2, y + 6);
+            fill_and_mirror_rectangle(out,    fg, &rb, x, x + 2, y + 6, y_md );
+            // cols D
+            x = rb.x_lo + 6;
+            y = rb.y_lo + 2;
+            fill_and_mirror_rectangle(out, p->bd, &rb, x, x + 2, y    , y + 2);
+            fill_and_mirror_rectangle(out, p->pg, &rb, x, x + 2, y + 2, y + 4);
+            fill_and_mirror_rectangle(out,    fg, &rb, x, x + 2, y + 4, y_md );
+            // cols E
+            x = rb.x_lo + 8;
+            y = rb.y_lo;
+            fill_and_mirror_rectangle(out, p->bd, &rb, x, x_md , y    , y + 2);
+            fill_and_mirror_rectangle(out, p->pg, &rb, x, x_md , y + 2, y + 4);
+            fill_and_mirror_rectangle(out,    fg, &rb, x, x_md , y + 4, y_md );
+        } else if (18 <= pipe_width) {
+            // A  B  C  D  D  ... D  D  C  B  A
+            // .  .  .  ## ## ...
+            // .  .  ## [] []
+            // .  ## [] fg fg
+            // ## [] fg fg fg ...
+            // ## [] fg fg fg ...
+            // ...      ...
+            // cols A
+            x = rb.x_lo;
+            y = rb.y_lo + 6;
+            fill_and_mirror_rectangle(out, p->bd, &rb, x, x + 2, y    , y_md );
+            // cols B
+            x = rb.x_lo + 2;
+            y = rb.y_lo + 4;
+            fill_and_mirror_rectangle(out, p->bd, &rb, x, x + 2, y    , y + 2);
+            fill_and_mirror_rectangle(out, p->pg, &rb, x, x + 2, y + 2, y_md );
+            // cols C
+            x = rb.x_lo + 4;
+            y = rb.y_lo + 2;
+            fill_and_mirror_rectangle(out, p->bd, &rb, x, x + 2, y    , y + 2);
+            fill_and_mirror_rectangle(out, p->pg, &rb, x, x + 2, y + 2, y + 4);
+            fill_and_mirror_rectangle(out,    fg, &rb, x, x + 2, y + 4, y_md );
+            // cols D
+            x = rb.x_lo + 6;
+            y = rb.y_lo;
+            fill_and_mirror_rectangle(out, p->bd, &rb, x, x_md , y    , y + 2);
+            fill_and_mirror_rectangle(out, p->pg, &rb, x, x_md , y + 2, y + 4);
+            fill_and_mirror_rectangle(out,    fg, &rb, x, x_md , y + 4, y_md );
+        } else if (14 <= pipe_width) {
+            // A  B  C  C  ... C  C  B  A
+            // .  .  ## ## ...
+            // .  ## [] []
+            // ## [] fg fg
+            // ## [] fg fg ...
+            // ...      ...
+            // cols A
+            x = rb.x_lo;
+            y = rb.y_lo + 4;
+            fill_and_mirror_rectangle(out, p->bd, &rb, x, x + 2, y    , y_md );
+            // cols B
+            x = rb.x_lo + 2;
+            y = rb.y_lo + 2;
+            fill_and_mirror_rectangle(out, p->bd, &rb, x, x + 2, y    , y + 2);
+            fill_and_mirror_rectangle(out, p->pg, &rb, x, x + 2, y + 2, y_md );
+            // cols C
+            x = rb.x_lo + 4;
+            y = rb.y_lo;
+            fill_and_mirror_rectangle(out, p->bd, &rb, x, x_md , y    , y + 2);
+            fill_and_mirror_rectangle(out, p->pg, &rb, x, x_md , y + 2, y + 4);
+            fill_and_mirror_rectangle(out,    fg, &rb, x, x_md , y + 4, y_md );
+        } else {
+            if (bsize < 2)
+                x_md = rb.x_hi = rb.x_lo + 1;
+            x = rb.x_lo;
+            y = rb.y_lo;
+            fill_and_mirror_rectangle(out, p->bd, &rb, x, x_md , y    , y + 1);
+            fill_and_mirror_rectangle(out, p->pg, &rb, x, x_md , y + 1, y + 2);
+            fill_and_mirror_rectangle(out,    fg, &rb, x, x_md , y + 2, y_md );
+        }
+        break;
     }
 }
 
@@ -373,6 +560,7 @@ static int plot_freqs(AVFilterLink *inlink, int64_t pts)
     AVFilterContext *ctx = inlink->dst;
     AVFilterLink *outlink = ctx->outputs[0];
     ShowFreqsContext *s = ctx->priv;
+    ShowFreqsPipeModeContext *p = &s->pipe_mode_ctx;
     AVFrame *in = s->window;
     const int win_size = s->win_size;
     char *colors, *color, *saveptr = NULL;
@@ -432,6 +620,15 @@ static int plot_freqs(AVFilterLink *inlink, int64_t pts)
         if (color)
             av_parse_color(fg, color, -1, ctx);
 
+        if (s->mode == PIPE) {
+            if (p->pipe_border_color)
+                av_parse_color(p->bd, p->pipe_border_color, -1, ctx);
+            if (p->pipe_padding_color)
+                av_parse_color(p->pg, p->pipe_padding_color, -1, ctx);
+            p->pipe_curr_unused_min_y = outlink->h;
+            p->pipe_next_x0 = 0;
+        }
+
         if (s->bypass[ch])
             continue;
 
-- 
2.43.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to