[FFmpeg-cvslog] avfilter/avf_showspectrum: improve dBFS scale legend

Paul B Mahol git at videolan.org
Sat Aug 7 14:09:19 EEST 2021


ffmpeg | branch: master | Paul B Mahol <onemda at gmail.com> | Fri Aug  6 22:03:07 2021 +0200| [6b11c12cf33b9761c3ac460737728228e86e9f04] | committer: Paul B Mahol

avfilter/avf_showspectrum: improve dBFS scale legend

Make it more intuitive looking and correct for non-log scaling.
Add option to set upper limit of input audio value in dBFS.

> http://git.videolan.org/gitweb.cgi/ffmpeg.git/?a=commit;h=6b11c12cf33b9761c3ac460737728228e86e9f04
---

 doc/filters.texi               |  12 +++-
 libavfilter/avf_showspectrum.c | 121 +++++++++++++++++++++++++++++------------
 2 files changed, 96 insertions(+), 37 deletions(-)

diff --git a/doc/filters.texi b/doc/filters.texi
index 790d165433..f5099f2b7f 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -26916,8 +26916,12 @@ Set upper frame rate limit. Default is @code{auto}, unlimited.
 Draw time and frequency axes and legends. Default is disabled.
 
 @item drange
-Set dynamic range used to calculate intensity color values. Default is 120dBFS.
+Set dynamic range used to calculate intensity color values. Default is 120 dBFS.
 Allowed range is from 10 to 200.
+
+ at item limit
+Set upper limit of input audio samples volume in dBFS. Default is 0 dBFS.
+Allowed range is from -100 to 100.
 @end table
 
 The usage is very similar to the showwaves filter; see the examples in that
@@ -27093,8 +27097,12 @@ Set start frequency from which to display spectrogram. Default is @code{0}.
 Set stop frequency to which to display spectrogram. Default is @code{0}.
 
 @item drange
-Set dynamic range used to calculate intensity color values. Default is 120dBFS.
+Set dynamic range used to calculate intensity color values. Default is 120 dBFS.
 Allowed range is from 10 to 200.
+
+ at item limit
+Set upper limit of input audio samples volume in dBFS. Default is 0 dBFS.
+Allowed range is from -100 to 100.
 @end table
 
 @subsection Examples
diff --git a/libavfilter/avf_showspectrum.c b/libavfilter/avf_showspectrum.c
index db3435cdbf..ff81d54661 100644
--- a/libavfilter/avf_showspectrum.c
+++ b/libavfilter/avf_showspectrum.c
@@ -101,8 +101,8 @@ typedef struct ShowSpectrumContext {
     int single_pic;
     int legend;
     int start_x, start_y;
-    float drange;
-    float dmin, dscale;
+    float drange, limit;
+    float dmin, dmax;
     int (*plot_channel)(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs);
 } ShowSpectrumContext;
 
@@ -184,6 +184,7 @@ static const AVOption showspectrum_options[] = {
     { "fps",   "set video rate",  OFFSET(rate_str), AV_OPT_TYPE_STRING, {.str = "auto"}, 0, 0, FLAGS },
     { "legend", "draw legend", OFFSET(legend), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, FLAGS },
     { "drange", "set dynamic range in dBFS", OFFSET(drange), AV_OPT_TYPE_FLOAT, {.dbl = 120}, 10, 200, FLAGS },
+    { "limit", "set upper limit in dBFS", OFFSET(limit), AV_OPT_TYPE_FLOAT, {.dbl = 0}, -100, 100, FLAGS },
     { NULL }
 };
 
@@ -692,6 +693,75 @@ static float bin_pos(const int bin, const int num_bins, const float sample_rate)
     return num_bins * scaled_freq / max_freq;
 }
 
+static float get_scale(AVFilterContext *ctx, int scale, float a)
+{
+    ShowSpectrumContext *s = ctx->priv;
+    const float dmin = s->dmin;
+    const float dmax = s->dmax;
+
+    a = av_clipf(a, dmin, dmax);
+    if (scale != LOG)
+        a = (a - dmin) / (dmax - dmin);
+
+    switch (scale) {
+    case LINEAR:
+        break;
+    case SQRT:
+        a = sqrtf(a);
+        break;
+    case CBRT:
+        a = cbrtf(a);
+        break;
+    case FOURTHRT:
+        a = sqrtf(sqrtf(a));
+        break;
+    case FIFTHRT:
+        a = powf(a, 0.2f);
+        break;
+    case LOG:
+        a = (s->drange - s->limit + log10f(a) * 20.f) / s->drange;
+        break;
+    default:
+        av_assert0(0);
+    }
+
+    return a;
+}
+
+static float get_iscale(AVFilterContext *ctx, int scale, float a)
+{
+    ShowSpectrumContext *s = ctx->priv;
+    const float dmin = s->dmin;
+    const float dmax = s->dmax;
+
+    switch (scale) {
+    case LINEAR:
+        break;
+    case SQRT:
+        a = a * a;
+        break;
+    case CBRT:
+        a = a * a * a;
+        break;
+    case FOURTHRT:
+        a = a * a * a * a;
+        break;
+    case FIFTHRT:
+        a = a * a * a * a * a;
+        break;
+    case LOG:
+        a = expf(M_LN10 * (a * s->drange - s->drange + s->limit) / 20.f);
+        break;
+    default:
+        av_assert0(0);
+    }
+
+    if (scale != LOG)
+        a = a * (dmax - dmin) + dmin;
+
+    return a;
+}
+
 static int draw_legend(AVFilterContext *ctx, int samples)
 {
     ShowSpectrumContext *s = ctx->priv;
@@ -873,20 +943,24 @@ static int draw_legend(AVFilterContext *ctx, int samples)
             memset(s->outpicref->data[2]+(s->start_y + h * (ch + 1) - y - 1) * s->outpicref->linesize[2] + s->w + s->start_x + 20, av_clip_uint8(out[2]), 10);
         }
 
-        for (y = 0; ch == 0 && y < h; y += h / 10) {
-            float value = s->drange * log10f(1.f - y / (float)h);
+        for (y = 0; ch == 0 && y < h + 5; y += 25) {
+            static const char *log_fmt = "%.0f";
+            static const char *lin_fmt = "%.3f";
+            const float a = av_clipf(1.f - y / (float)(h - 1), 0.f, 1.f);
+            const float value = s->scale == LOG ? log10f(get_iscale(ctx, s->scale, a)) * 20.f : get_iscale(ctx, s->scale, a);
             char *text;
 
-            if (value < -s->drange)
-                break;
-            text = av_asprintf("%.0f dB", value);
+            text = av_asprintf(s->scale == LOG ? log_fmt : lin_fmt, value);
             if (!text)
                 continue;
-            drawtext(s->outpicref, s->w + s->start_x + 35, s->start_y + y - 5, text, 0);
+            drawtext(s->outpicref, s->w + s->start_x + 35, s->start_y + y - 3, text, 0);
             av_free(text);
         }
     }
 
+    if (s->scale == LOG)
+        drawtext(s->outpicref, s->w + s->start_x + 22, s->start_y + s->h + 20, "dBFS", 0);
+
     return 0;
 }
 
@@ -910,31 +984,7 @@ static float get_value(AVFilterContext *ctx, int ch, int y)
         av_assert0(0);
     }
 
-    /* apply scale */
-    switch (s->scale) {
-    case LINEAR:
-        a = av_clipf(a, 0, 1);
-        break;
-    case SQRT:
-        a = av_clipf(sqrtf(a), 0, 1);
-        break;
-    case CBRT:
-        a = av_clipf(cbrtf(a), 0, 1);
-        break;
-    case FOURTHRT:
-        a = av_clipf(sqrtf(sqrtf(a)), 0, 1);
-        break;
-    case FIFTHRT:
-        a = av_clipf(powf(a, 0.20), 0, 1);
-        break;
-    case LOG:
-        a = 1.f + log10f(av_clipf(a, s->dmin, 1.f)) * s->dscale;
-        break;
-    default:
-        av_assert0(0);
-    }
-
-    return a;
+    return av_clipf(get_scale(ctx, s->scale, a), 0.f, 1.f);
 }
 
 static int plot_channel_lin(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
@@ -1002,8 +1052,8 @@ static int config_output(AVFilterLink *outlink)
     int i, fft_size, h, w, ret;
     float overlap;
 
-    s->dmin = expf(-s->drange * M_LN10 / 20.f);
-    s->dscale = -1.f / log10f(s->dmin);
+    s->dmax = expf(s->limit * M_LN10 / 20.f);
+    s->dmin = expf((s->limit - s->drange) * M_LN10 / 20.f);
 
     switch (s->fscale) {
     case F_LINEAR: s->plot_channel = plot_channel_lin; break;
@@ -1648,6 +1698,7 @@ static const AVOption showspectrumpic_options[] = {
     { "start", "start frequency", OFFSET(start), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT32_MAX, FLAGS },
     { "stop",  "stop frequency",  OFFSET(stop),  AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT32_MAX, FLAGS },
     { "drange", "set dynamic range in dBFS", OFFSET(drange), AV_OPT_TYPE_FLOAT, {.dbl = 120}, 10, 200, FLAGS },
+    { "limit", "set upper limit in dBFS", OFFSET(limit), AV_OPT_TYPE_FLOAT, {.dbl = 0}, -100, 100, FLAGS },
     { NULL }
 };
 



More information about the ffmpeg-cvslog mailing list