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.
pull/364/head
Paul B Mahol 3 years ago
parent e01d306c64
commit 6b11c12cf3
  1. 12
      doc/filters.texi
  2. 121
      libavfilter/avf_showspectrum.c

@ -26916,8 +26916,12 @@ Set upper frame rate limit. Default is @code{auto}, unlimited.
Draw time and frequency axes and legends. Default is disabled. Draw time and frequency axes and legends. Default is disabled.
@item drange @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. Allowed range is from 10 to 200.
@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 @end table
The usage is very similar to the showwaves filter; see the examples in that 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}. Set stop frequency to which to display spectrogram. Default is @code{0}.
@item drange @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. Allowed range is from 10 to 200.
@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 @end table
@subsection Examples @subsection Examples

@ -101,8 +101,8 @@ typedef struct ShowSpectrumContext {
int single_pic; int single_pic;
int legend; int legend;
int start_x, start_y; int start_x, start_y;
float drange; float drange, limit;
float dmin, dscale; float dmin, dmax;
int (*plot_channel)(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs); int (*plot_channel)(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs);
} ShowSpectrumContext; } 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 }, { "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 }, { "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 }, { "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 } { 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; 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) static int draw_legend(AVFilterContext *ctx, int samples)
{ {
ShowSpectrumContext *s = ctx->priv; 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); 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) { for (y = 0; ch == 0 && y < h + 5; y += 25) {
float value = s->drange * log10f(1.f - y / (float)h); 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; char *text;
if (value < -s->drange) text = av_asprintf(s->scale == LOG ? log_fmt : lin_fmt, value);
break;
text = av_asprintf("%.0f dB", value);
if (!text) if (!text)
continue; 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); 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; return 0;
} }
@ -910,31 +984,7 @@ static float get_value(AVFilterContext *ctx, int ch, int y)
av_assert0(0); av_assert0(0);
} }
/* apply scale */ return av_clipf(get_scale(ctx, s->scale, a), 0.f, 1.f);
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;
} }
static int plot_channel_lin(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) 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; int i, fft_size, h, w, ret;
float overlap; float overlap;
s->dmin = expf(-s->drange * M_LN10 / 20.f); s->dmax = expf(s->limit * M_LN10 / 20.f);
s->dscale = -1.f / log10f(s->dmin); s->dmin = expf((s->limit - s->drange) * M_LN10 / 20.f);
switch (s->fscale) { switch (s->fscale) {
case F_LINEAR: s->plot_channel = plot_channel_lin; break; 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 }, { "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 }, { "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 }, { "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 } { NULL }
}; };

Loading…
Cancel
Save