X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavfilter%2Favf_showspectrum.c;h=41693a0ce153875d0a0a55187267dc53c8ed2d8f;hb=d6a1f711bcd1035bea95157ae6559988000e453d;hp=04fcc8d75636640367fb7e76c8daec75f58b0f31;hpb=ce47f1589e9f5a6cf8372a269bdd862ff0cc3f91;p=ffmpeg diff --git a/libavfilter/avf_showspectrum.c b/libavfilter/avf_showspectrum.c index 04fcc8d7563..41693a0ce15 100644 --- a/libavfilter/avf_showspectrum.c +++ b/libavfilter/avf_showspectrum.c @@ -34,23 +34,28 @@ #include "libavutil/avstring.h" #include "libavutil/channel_layout.h" #include "libavutil/opt.h" +#include "libavutil/parseutils.h" #include "libavutil/xga_font_data.h" #include "audio.h" #include "video.h" #include "avfilter.h" +#include "filters.h" #include "internal.h" #include "window_func.h" enum DisplayMode { COMBINED, SEPARATE, NB_MODES }; enum DataMode { D_MAGNITUDE, D_PHASE, NB_DMODES }; enum DisplayScale { LINEAR, SQRT, CBRT, LOG, FOURTHRT, FIFTHRT, NB_SCALES }; -enum ColorMode { CHANNEL, INTENSITY, RAINBOW, MORELAND, NEBULAE, FIRE, FIERY, FRUIT, COOL, NB_CLMODES }; +enum ColorMode { CHANNEL, INTENSITY, RAINBOW, MORELAND, NEBULAE, FIRE, FIERY, FRUIT, COOL, MAGMA, GREEN, NB_CLMODES }; enum SlideMode { REPLACE, SCROLL, FULLFRAME, RSCROLL, NB_SLIDES }; enum Orientation { VERTICAL, HORIZONTAL, NB_ORIENTATIONS }; typedef struct ShowSpectrumContext { const AVClass *class; int w, h; + char *rate_str; + AVRational auto_frame_rate; + AVRational frame_rate; AVFrame *outpicref; int nb_display_channels; int orientation; @@ -62,24 +67,31 @@ typedef struct ShowSpectrumContext { int scale; float saturation; ///< color saturation multiplier float rotation; ///< color rotation + int start, stop; ///< zoom mode int data; int xpos; ///< x position (current column) FFTContext **fft; ///< Fast Fourier Transform context + FFTContext **ifft; ///< Inverse Fast Fourier Transform context int fft_bits; ///< number of bits (FFT window size = 1<fft[i]); } av_freep(&s->fft); + if (s->ifft) { + for (i = 0; i < s->nb_display_channels; i++) + av_fft_end(s->ifft[i]); + } + av_freep(&s->ifft); if (s->fft_data) { for (i = 0; i < s->nb_display_channels; i++) av_freep(&s->fft_data[i]); } av_freep(&s->fft_data); + if (s->fft_scratch) { + for (i = 0; i < s->nb_display_channels; i++) + av_freep(&s->fft_scratch[i]); + } + av_freep(&s->fft_scratch); if (s->color_buffer) { for (i = 0; i < s->nb_display_channels; i++) av_freep(&s->color_buffer[i]); @@ -291,6 +332,446 @@ static int query_formats(AVFilterContext *ctx) return 0; } +static int run_channel_fft(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + ShowSpectrumContext *s = ctx->priv; + AVFilterLink *inlink = ctx->inputs[0]; + const float *window_func_lut = s->window_func_lut; + AVFrame *fin = arg; + const int ch = jobnr; + int n; + + /* fill FFT input with the number of samples available */ + const float *p = (float *)fin->extended_data[ch]; + + for (n = 0; n < s->win_size; n++) { + s->fft_data[ch][n].re = p[n] * window_func_lut[n]; + s->fft_data[ch][n].im = 0; + } + + if (s->stop) { + double theta, phi, psi, a, b, S, c; + FFTComplex *g = s->fft_data[ch]; + FFTComplex *h = s->fft_scratch[ch]; + int L = s->buf_size; + int N = s->win_size; + int M = s->win_size / 2; + + phi = 2.0 * M_PI * (s->stop - s->start) / (double)inlink->sample_rate / (M - 1); + theta = 2.0 * M_PI * s->start / (double)inlink->sample_rate; + + for (int n = 0; n < M; n++) { + h[n].re = cos(n * n / 2.0 * phi); + h[n].im = sin(n * n / 2.0 * phi); + } + + for (int n = M; n < L; n++) { + h[n].re = 0.0; + h[n].im = 0.0; + } + + for (int n = L - N; n < L; n++) { + h[n].re = cos((L - n) * (L - n) / 2.0 * phi); + h[n].im = sin((L - n) * (L - n) / 2.0 * phi); + } + + for (int n = 0; n < N; n++) { + g[n].re = s->fft_data[ch][n].re; + g[n].im = s->fft_data[ch][n].im; + } + + for (int n = N; n < L; n++) { + g[n].re = 0.; + g[n].im = 0.; + } + + for (int n = 0; n < N; n++) { + psi = n * theta + n * n / 2.0 * phi; + c = cos(psi); + S = -sin(psi); + a = c * g[n].re - S * g[n].im; + b = S * g[n].re + c * g[n].im; + g[n].re = a; + g[n].im = b; + } + + av_fft_permute(s->fft[ch], h); + av_fft_calc(s->fft[ch], h); + + av_fft_permute(s->fft[ch], g); + av_fft_calc(s->fft[ch], g); + + for (int n = 0; n < L; n++) { + c = g[n].re; + S = g[n].im; + a = c * h[n].re - S * h[n].im; + b = S * h[n].re + c * h[n].im; + + g[n].re = a / L; + g[n].im = b / L; + } + + av_fft_permute(s->ifft[ch], g); + av_fft_calc(s->ifft[ch], g); + + for (int k = 0; k < M; k++) { + psi = k * k / 2.0 * phi; + c = cos(psi); + S = -sin(psi); + a = c * g[k].re - S * g[k].im; + b = S * g[k].re + c * g[k].im; + s->fft_data[ch][k].re = a; + s->fft_data[ch][k].im = b; + } + } else { + /* run FFT on each samples set */ + av_fft_permute(s->fft[ch], s->fft_data[ch]); + av_fft_calc(s->fft[ch], s->fft_data[ch]); + } + + return 0; +} + +static void drawtext(AVFrame *pic, int x, int y, const char *txt, int o) +{ + const uint8_t *font; + int font_height; + int i; + + font = avpriv_cga_font, font_height = 8; + + for (i = 0; txt[i]; i++) { + int char_y, mask; + + if (o) { + for (char_y = font_height - 1; char_y >= 0; char_y--) { + uint8_t *p = pic->data[0] + (y + i * 10) * pic->linesize[0] + x; + for (mask = 0x80; mask; mask >>= 1) { + if (font[txt[i] * font_height + font_height - 1 - char_y] & mask) + p[char_y] = ~p[char_y]; + p += pic->linesize[0]; + } + } + } else { + uint8_t *p = pic->data[0] + y*pic->linesize[0] + (x + i*8); + for (char_y = 0; char_y < font_height; char_y++) { + for (mask = 0x80; mask; mask >>= 1) { + if (font[txt[i] * font_height + char_y] & mask) + *p = ~(*p); + p++; + } + p += pic->linesize[0] - 8; + } + } + } +} + +static void color_range(ShowSpectrumContext *s, int ch, + float *yf, float *uf, float *vf) +{ + switch (s->mode) { + case COMBINED: + // reduce range by channel count + *yf = 256.0f / s->nb_display_channels; + switch (s->color_mode) { + case RAINBOW: + case MORELAND: + case NEBULAE: + case FIRE: + case FIERY: + case FRUIT: + case COOL: + case GREEN: + case MAGMA: + case INTENSITY: + *uf = *yf; + *vf = *yf; + break; + case CHANNEL: + /* adjust saturation for mixed UV coloring */ + /* this factor is correct for infinite channels, an approximation otherwise */ + *uf = *yf * M_PI; + *vf = *yf * M_PI; + break; + default: + av_assert0(0); + } + break; + case SEPARATE: + // full range + *yf = 256.0f; + *uf = 256.0f; + *vf = 256.0f; + break; + default: + av_assert0(0); + } + + if (s->color_mode == CHANNEL) { + if (s->nb_display_channels > 1) { + *uf *= 0.5 * sin((2 * M_PI * ch) / s->nb_display_channels + M_PI * s->rotation); + *vf *= 0.5 * cos((2 * M_PI * ch) / s->nb_display_channels + M_PI * s->rotation); + } else { + *uf *= 0.5 * sin(M_PI * s->rotation); + *vf *= 0.5 * cos(M_PI * s->rotation + M_PI_2); + } + } else { + *uf += *uf * sin(M_PI * s->rotation); + *vf += *vf * cos(M_PI * s->rotation + M_PI_2); + } + + *uf *= s->saturation; + *vf *= s->saturation; +} + +static void pick_color(ShowSpectrumContext *s, + float yf, float uf, float vf, + float a, float *out) +{ + if (s->color_mode > CHANNEL) { + const int cm = s->color_mode; + float y, u, v; + int i; + + for (i = 1; i < FF_ARRAY_ELEMS(color_table[cm]) - 1; i++) + if (color_table[cm][i].a >= a) + break; + // i now is the first item >= the color + // now we know to interpolate between item i - 1 and i + if (a <= color_table[cm][i - 1].a) { + y = color_table[cm][i - 1].y; + u = color_table[cm][i - 1].u; + v = color_table[cm][i - 1].v; + } else if (a >= color_table[cm][i].a) { + y = color_table[cm][i].y; + u = color_table[cm][i].u; + v = color_table[cm][i].v; + } else { + float start = color_table[cm][i - 1].a; + float end = color_table[cm][i].a; + float lerpfrac = (a - start) / (end - start); + y = color_table[cm][i - 1].y * (1.0f - lerpfrac) + + color_table[cm][i].y * lerpfrac; + u = color_table[cm][i - 1].u * (1.0f - lerpfrac) + + color_table[cm][i].u * lerpfrac; + v = color_table[cm][i - 1].v * (1.0f - lerpfrac) + + color_table[cm][i].v * lerpfrac; + } + + out[0] = y * yf; + out[1] = u * uf; + out[2] = v * vf; + } else { + out[0] = a * yf; + out[1] = a * uf; + out[2] = a * vf; + } +} + +static char *get_time(AVFilterContext *ctx, float seconds, int x) +{ + char *units; + + if (x == 0) + units = av_asprintf("0"); + else if (log10(seconds) > 6) + units = av_asprintf("%.2fh", seconds / (60 * 60)); + else if (log10(seconds) > 3) + units = av_asprintf("%.2fm", seconds / 60); + else + units = av_asprintf("%.2fs", seconds); + return units; +} + +static int draw_legend(AVFilterContext *ctx, int samples) +{ + ShowSpectrumContext *s = ctx->priv; + AVFilterLink *inlink = ctx->inputs[0]; + AVFilterLink *outlink = ctx->outputs[0]; + int ch, y, x = 0, sz = s->orientation == VERTICAL ? s->w : s->h; + int multi = (s->mode == SEPARATE && s->color_mode == CHANNEL); + float spp = samples / (float)sz; + char *text; + uint8_t *dst; + char chlayout_str[128]; + + av_get_channel_layout_string(chlayout_str, sizeof(chlayout_str), inlink->channels, + inlink->channel_layout); + + text = av_asprintf("%d Hz | %s", inlink->sample_rate, chlayout_str); + + drawtext(s->outpicref, 2, outlink->h - 10, "CREATED BY LIBAVFILTER", 0); + drawtext(s->outpicref, outlink->w - 2 - strlen(text) * 10, outlink->h - 10, text, 0); + if (s->stop) { + char *text = av_asprintf("Zoom: %d Hz - %d Hz", s->start, s->stop); + drawtext(s->outpicref, outlink->w - 2 - strlen(text) * 10, 3, text, 0); + av_freep(&text); + } + + av_freep(&text); + + dst = s->outpicref->data[0] + (s->start_y - 1) * s->outpicref->linesize[0] + s->start_x - 1; + for (x = 0; x < s->w + 1; x++) + dst[x] = 200; + dst = s->outpicref->data[0] + (s->start_y + s->h) * s->outpicref->linesize[0] + s->start_x - 1; + for (x = 0; x < s->w + 1; x++) + dst[x] = 200; + for (y = 0; y < s->h + 2; y++) { + dst = s->outpicref->data[0] + (y + s->start_y - 1) * s->outpicref->linesize[0]; + dst[s->start_x - 1] = 200; + dst[s->start_x + s->w] = 200; + } + if (s->orientation == VERTICAL) { + int h = s->mode == SEPARATE ? s->h / s->nb_display_channels : s->h; + int hh = s->mode == SEPARATE ? -(s->h % s->nb_display_channels) + 1 : 1; + for (ch = 0; ch < (s->mode == SEPARATE ? s->nb_display_channels : 1); ch++) { + for (y = 0; y < h; y += 20) { + dst = s->outpicref->data[0] + (s->start_y + h * (ch + 1) - y - hh) * s->outpicref->linesize[0]; + dst[s->start_x - 2] = 200; + dst[s->start_x + s->w + 1] = 200; + } + for (y = 0; y < h; y += 40) { + dst = s->outpicref->data[0] + (s->start_y + h * (ch + 1) - y - hh) * s->outpicref->linesize[0]; + dst[s->start_x - 3] = 200; + dst[s->start_x + s->w + 2] = 200; + } + dst = s->outpicref->data[0] + (s->start_y - 2) * s->outpicref->linesize[0] + s->start_x; + for (x = 0; x < s->w; x+=40) + dst[x] = 200; + dst = s->outpicref->data[0] + (s->start_y - 3) * s->outpicref->linesize[0] + s->start_x; + for (x = 0; x < s->w; x+=80) + dst[x] = 200; + dst = s->outpicref->data[0] + (s->h + s->start_y + 1) * s->outpicref->linesize[0] + s->start_x; + for (x = 0; x < s->w; x+=40) { + dst[x] = 200; + } + dst = s->outpicref->data[0] + (s->h + s->start_y + 2) * s->outpicref->linesize[0] + s->start_x; + for (x = 0; x < s->w; x+=80) { + dst[x] = 200; + } + for (y = 0; y < h; y += 40) { + float range = s->stop ? s->stop - s->start : inlink->sample_rate / 2; + float hertz = s->start + y * range / (float)(1 << (int)ceil(log2(h))); + char *units; + + if (hertz == 0) + units = av_asprintf("DC"); + else + units = av_asprintf("%.2f", hertz); + if (!units) + return AVERROR(ENOMEM); + + drawtext(s->outpicref, s->start_x - 8 * strlen(units) - 4, h * (ch + 1) + s->start_y - y - 4 - hh, units, 0); + av_free(units); + } + } + + for (x = 0; x < s->w && s->single_pic; x+=80) { + float seconds = x * spp / inlink->sample_rate; + char *units = get_time(ctx, seconds, x); + + drawtext(s->outpicref, s->start_x + x - 4 * strlen(units), s->h + s->start_y + 6, units, 0); + drawtext(s->outpicref, s->start_x + x - 4 * strlen(units), s->start_y - 12, units, 0); + av_free(units); + } + + drawtext(s->outpicref, outlink->w / 2 - 4 * 4, outlink->h - s->start_y / 2, "TIME", 0); + drawtext(s->outpicref, s->start_x / 7, outlink->h / 2 - 14 * 4, "FREQUENCY (Hz)", 1); + } else { + int w = s->mode == SEPARATE ? s->w / s->nb_display_channels : s->w; + for (y = 0; y < s->h; y += 20) { + dst = s->outpicref->data[0] + (s->start_y + y) * s->outpicref->linesize[0]; + dst[s->start_x - 2] = 200; + dst[s->start_x + s->w + 1] = 200; + } + for (y = 0; y < s->h; y += 40) { + dst = s->outpicref->data[0] + (s->start_y + y) * s->outpicref->linesize[0]; + dst[s->start_x - 3] = 200; + dst[s->start_x + s->w + 2] = 200; + } + for (ch = 0; ch < (s->mode == SEPARATE ? s->nb_display_channels : 1); ch++) { + dst = s->outpicref->data[0] + (s->start_y - 2) * s->outpicref->linesize[0] + s->start_x + w * ch; + for (x = 0; x < w; x+=40) + dst[x] = 200; + dst = s->outpicref->data[0] + (s->start_y - 3) * s->outpicref->linesize[0] + s->start_x + w * ch; + for (x = 0; x < w; x+=80) + dst[x] = 200; + dst = s->outpicref->data[0] + (s->h + s->start_y + 1) * s->outpicref->linesize[0] + s->start_x + w * ch; + for (x = 0; x < w; x+=40) { + dst[x] = 200; + } + dst = s->outpicref->data[0] + (s->h + s->start_y + 2) * s->outpicref->linesize[0] + s->start_x + w * ch; + for (x = 0; x < w; x+=80) { + dst[x] = 200; + } + for (x = 0; x < w - 79; x += 80) { + float range = s->stop ? s->stop - s->start : inlink->sample_rate / 2; + float hertz = s->start + x * range / (float)(1 << (int)ceil(log2(w))); + char *units; + + if (hertz == 0) + units = av_asprintf("DC"); + else + units = av_asprintf("%.2f", hertz); + if (!units) + return AVERROR(ENOMEM); + + drawtext(s->outpicref, s->start_x - 4 * strlen(units) + x + w * ch, s->start_y - 12, units, 0); + drawtext(s->outpicref, s->start_x - 4 * strlen(units) + x + w * ch, s->h + s->start_y + 6, units, 0); + av_free(units); + } + } + for (y = 0; y < s->h && s->single_pic; y+=40) { + float seconds = y * spp / inlink->sample_rate; + char *units = get_time(ctx, seconds, x); + + drawtext(s->outpicref, s->start_x - 8 * strlen(units) - 4, s->start_y + y - 4, units, 0); + av_free(units); + } + drawtext(s->outpicref, s->start_x / 7, outlink->h / 2 - 4 * 4, "TIME", 1); + drawtext(s->outpicref, outlink->w / 2 - 14 * 4, outlink->h - s->start_y / 2, "FREQUENCY (Hz)", 0); + } + + for (ch = 0; ch < (multi ? s->nb_display_channels : 1); ch++) { + int h = multi ? s->h / s->nb_display_channels : s->h; + + for (y = 0; y < h; y++) { + float out[3] = { 0., 127.5, 127.5}; + int chn; + + for (chn = 0; chn < (s->mode == SEPARATE ? 1 : s->nb_display_channels); chn++) { + float yf, uf, vf; + int channel = (multi) ? s->nb_display_channels - ch - 1 : chn; + float lout[3]; + + color_range(s, channel, &yf, &uf, &vf); + pick_color(s, yf, uf, vf, y / (float)h, lout); + out[0] += lout[0]; + out[1] += lout[1]; + out[2] += lout[2]; + } + memset(s->outpicref->data[0]+(s->start_y + h * (ch + 1) - y - 1) * s->outpicref->linesize[0] + s->w + s->start_x + 20, av_clip_uint8(out[0]), 10); + memset(s->outpicref->data[1]+(s->start_y + h * (ch + 1) - y - 1) * s->outpicref->linesize[1] + s->w + s->start_x + 20, av_clip_uint8(out[1]), 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) { + float value = 120.0 * log10(1. - y / (float)h); + char *text; + + if (value < -120) + break; + text = av_asprintf("%.0f dB", value); + if (!text) + continue; + drawtext(s->outpicref, s->w + s->start_x + 35, s->start_y + y - 5, text, 0); + av_free(text); + } + } + + return 0; +} + static int config_output(AVFilterLink *outlink) { AVFilterContext *ctx = outlink->src; @@ -299,7 +780,11 @@ static int config_output(AVFilterLink *outlink) int i, fft_bits, h, w; float overlap; - s->pts = AV_NOPTS_VALUE; + s->stop = FFMIN(s->stop, inlink->sample_rate / 2); + if (s->stop && s->stop <= s->start) { + av_log(ctx, AV_LOG_ERROR, "Stop frequency should be greater than start.\n"); + return AVERROR(EINVAL); + } if (!strcmp(ctx->filter->name, "showspectrumpic")) s->single_pic = 1; @@ -309,7 +794,7 @@ static int config_output(AVFilterLink *outlink) outlink->sample_aspect_ratio = (AVRational){1,1}; if (s->legend) { - s->start_x = log10(inlink->sample_rate) * 25; + s->start_x = (log10(inlink->sample_rate) + 1) * 25; s->start_y = 64; outlink->w += s->start_x * 2; outlink->h += s->start_y * 2; @@ -327,7 +812,9 @@ static int config_output(AVFilterLink *outlink) /* FFT window size (precision) according to the requested output frame width */ for (fft_bits = 1; 1 << fft_bits < 2 * w; fft_bits++); } + s->win_size = 1 << fft_bits; + s->buf_size = s->win_size << !!s->stop; if (!s->fft) { s->fft = av_calloc(inlink->channels, sizeof(*s->fft)); @@ -335,6 +822,14 @@ static int config_output(AVFilterLink *outlink) return AVERROR(ENOMEM); } + if (s->stop) { + if (!s->ifft) { + s->ifft = av_calloc(inlink->channels, sizeof(*s->ifft)); + if (!s->ifft) + return AVERROR(ENOMEM); + } + } + /* (re-)configuration if the video output changed (or first init) */ if (fft_bits != s->fft_bits) { AVFrame *outpicref; @@ -345,6 +840,10 @@ static int config_output(AVFilterLink *outlink) * Note: we use free and malloc instead of a realloc-like function to * make sure the buffer is aligned in memory for the FFT functions. */ for (i = 0; i < s->nb_display_channels; i++) { + if (s->stop) { + av_fft_end(s->ifft[i]); + av_freep(&s->fft_scratch[i]); + } av_fft_end(s->fft[i]); av_freep(&s->fft_data[i]); } @@ -352,7 +851,15 @@ static int config_output(AVFilterLink *outlink) s->nb_display_channels = inlink->channels; for (i = 0; i < s->nb_display_channels; i++) { - s->fft[i] = av_fft_init(fft_bits, 0); + s->fft[i] = av_fft_init(fft_bits + !!s->stop, 0); + if (s->stop) { + s->ifft[i] = av_fft_init(fft_bits + !!s->stop, 1); + if (!s->ifft[i]) { + av_log(ctx, AV_LOG_ERROR, "Unable to create Inverse FFT context. " + "The window size might be too high.\n"); + return AVERROR(EINVAL); + } + } if (!s->fft[i]) { av_log(ctx, AV_LOG_ERROR, "Unable to create FFT context. " "The window size might be too high.\n"); @@ -391,10 +898,17 @@ static int config_output(AVFilterLink *outlink) s->fft_data = av_calloc(s->nb_display_channels, sizeof(*s->fft_data)); if (!s->fft_data) return AVERROR(ENOMEM); + s->fft_scratch = av_calloc(s->nb_display_channels, sizeof(*s->fft_scratch)); + if (!s->fft_scratch) + return AVERROR(ENOMEM); for (i = 0; i < s->nb_display_channels; i++) { - s->fft_data[i] = av_calloc(s->win_size, sizeof(**s->fft_data)); + s->fft_data[i] = av_calloc(s->buf_size, sizeof(**s->fft_data)); if (!s->fft_data[i]) return AVERROR(ENOMEM); + + s->fft_scratch[i] = av_calloc(s->buf_size, sizeof(**s->fft_scratch)); + if (!s->fft_scratch[i]) + return AVERROR(ENOMEM); } /* pre-calc windowing function */ @@ -430,17 +944,29 @@ static int config_output(AVFilterLink *outlink) memset(outpicref->data[2] + i * outpicref->linesize[2], 128, outlink->w); } outpicref->color_range = AVCOL_RANGE_JPEG; + + if (!s->single_pic && s->legend) + draw_legend(ctx, 0); } if ((s->orientation == VERTICAL && s->xpos >= s->w) || (s->orientation == HORIZONTAL && s->xpos >= s->h)) s->xpos = 0; - outlink->frame_rate = av_make_q(inlink->sample_rate, s->win_size * (1.-s->overlap)); + s->auto_frame_rate = av_make_q(inlink->sample_rate, s->hop_size); if (s->orientation == VERTICAL && s->sliding == FULLFRAME) - outlink->frame_rate.den *= s->w; + s->auto_frame_rate.den *= s->w; if (s->orientation == HORIZONTAL && s->sliding == FULLFRAME) - outlink->frame_rate.den *= s->h; + s->auto_frame_rate.den *= s->h; + if (!s->single_pic && strcmp(s->rate_str, "auto")) { + int ret = av_parse_video_rate(&s->frame_rate, s->rate_str); + if (ret < 0) + return ret; + } else { + s->frame_rate = s->auto_frame_rate; + } + outlink->frame_rate = s->frame_rate; + outlink->time_base = av_inv_q(outlink->frame_rate); if (s->orientation == VERTICAL) { s->combine_buffer = @@ -462,29 +988,6 @@ static int config_output(AVFilterLink *outlink) return 0; } -static int run_channel_fft(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) -{ - ShowSpectrumContext *s = ctx->priv; - const float *window_func_lut = s->window_func_lut; - AVFrame *fin = arg; - const int ch = jobnr; - int n; - - /* fill FFT input with the number of samples available */ - const float *p = (float *)fin->extended_data[ch]; - - for (n = 0; n < s->win_size; n++) { - s->fft_data[ch][n].re = p[n] * window_func_lut[n]; - s->fft_data[ch][n].im = 0; - } - - /* run FFT on each samples set */ - av_fft_permute(s->fft[ch], s->fft_data[ch]); - av_fft_calc(s->fft[ch], s->fft_data[ch]); - - return 0; -} - #define RE(y, ch) s->fft_data[ch][y].re #define IM(y, ch) s->fft_data[ch][y].im #define MAGNITUDE(y, ch) hypot(RE(y, ch), IM(y, ch)) @@ -545,106 +1048,6 @@ static void scale_magnitudes(ShowSpectrumContext *s, float scale) } } -static void color_range(ShowSpectrumContext *s, int ch, - float *yf, float *uf, float *vf) -{ - switch (s->mode) { - case COMBINED: - // reduce range by channel count - *yf = 256.0f / s->nb_display_channels; - switch (s->color_mode) { - case RAINBOW: - case MORELAND: - case NEBULAE: - case FIRE: - case FIERY: - case FRUIT: - case COOL: - case INTENSITY: - *uf = *yf; - *vf = *yf; - break; - case CHANNEL: - /* adjust saturation for mixed UV coloring */ - /* this factor is correct for infinite channels, an approximation otherwise */ - *uf = *yf * M_PI; - *vf = *yf * M_PI; - break; - default: - av_assert0(0); - } - break; - case SEPARATE: - // full range - *yf = 256.0f; - *uf = 256.0f; - *vf = 256.0f; - break; - default: - av_assert0(0); - } - - if (s->color_mode == CHANNEL) { - if (s->nb_display_channels > 1) { - *uf *= 0.5 * sin((2 * M_PI * ch) / s->nb_display_channels + M_PI * s->rotation); - *vf *= 0.5 * cos((2 * M_PI * ch) / s->nb_display_channels + M_PI * s->rotation); - } else { - *uf *= 0.5 * sin(M_PI * s->rotation); - *vf *= 0.5 * cos(M_PI * s->rotation + M_PI_2); - } - } else { - *uf += *uf * sin(M_PI * s->rotation); - *vf += *vf * cos(M_PI * s->rotation + M_PI_2); - } - - *uf *= s->saturation; - *vf *= s->saturation; -} - -static void pick_color(ShowSpectrumContext *s, - float yf, float uf, float vf, - float a, float *out) -{ - if (s->color_mode > CHANNEL) { - const int cm = s->color_mode; - float y, u, v; - int i; - - for (i = 1; i < FF_ARRAY_ELEMS(color_table[cm]) - 1; i++) - if (color_table[cm][i].a >= a) - break; - // i now is the first item >= the color - // now we know to interpolate between item i - 1 and i - if (a <= color_table[cm][i - 1].a) { - y = color_table[cm][i - 1].y; - u = color_table[cm][i - 1].u; - v = color_table[cm][i - 1].v; - } else if (a >= color_table[cm][i].a) { - y = color_table[cm][i].y; - u = color_table[cm][i].u; - v = color_table[cm][i].v; - } else { - float start = color_table[cm][i - 1].a; - float end = color_table[cm][i].a; - float lerpfrac = (a - start) / (end - start); - y = color_table[cm][i - 1].y * (1.0f - lerpfrac) - + color_table[cm][i].y * lerpfrac; - u = color_table[cm][i - 1].u * (1.0f - lerpfrac) - + color_table[cm][i].u * lerpfrac; - v = color_table[cm][i - 1].v * (1.0f - lerpfrac) - + color_table[cm][i].v * lerpfrac; - } - - out[0] = y * yf; - out[1] = u * uf; - out[2] = v * vf; - } else { - out[0] = a * yf; - out[1] = a * uf; - out[2] = a * vf; - } -} - static void clear_combine_buffer(ShowSpectrumContext *s, int size) { int y; @@ -744,8 +1147,8 @@ static int plot_spectrum_column(AVFilterLink *inlink, AVFrame *insamples) if (s->sliding == SCROLL) { for (plane = 0; plane < 3; plane++) { for (y = 0; y < s->h; y++) { - uint8_t *p = outpicref->data[plane] + - y * outpicref->linesize[plane]; + uint8_t *p = outpicref->data[plane] + s->start_x + + (y + s->start_y) * outpicref->linesize[plane]; memmove(p, p + 1, s->w - 1); } } @@ -753,8 +1156,8 @@ static int plot_spectrum_column(AVFilterLink *inlink, AVFrame *insamples) } else if (s->sliding == RSCROLL) { for (plane = 0; plane < 3; plane++) { for (y = 0; y < s->h; y++) { - uint8_t *p = outpicref->data[plane] + - y * outpicref->linesize[plane]; + uint8_t *p = outpicref->data[plane] + s->start_x + + (y + s->start_y) * outpicref->linesize[plane]; memmove(p + 1, p, s->w - 1); } } @@ -773,8 +1176,8 @@ static int plot_spectrum_column(AVFilterLink *inlink, AVFrame *insamples) if (s->sliding == SCROLL) { for (plane = 0; plane < 3; plane++) { for (y = 1; y < s->h; y++) { - memmove(outpicref->data[plane] + (y-1) * outpicref->linesize[plane], - outpicref->data[plane] + (y ) * outpicref->linesize[plane], + memmove(outpicref->data[plane] + (y-1 + s->start_y) * outpicref->linesize[plane] + s->start_x, + outpicref->data[plane] + (y + s->start_y) * outpicref->linesize[plane] + s->start_x, s->w); } } @@ -782,8 +1185,8 @@ static int plot_spectrum_column(AVFilterLink *inlink, AVFrame *insamples) } else if (s->sliding == RSCROLL) { for (plane = 0; plane < 3; plane++) { for (y = s->h - 1; y >= 1; y--) { - memmove(outpicref->data[plane] + (y ) * outpicref->linesize[plane], - outpicref->data[plane] + (y-1) * outpicref->linesize[plane], + memmove(outpicref->data[plane] + (y + s->start_y) * outpicref->linesize[plane] + s->start_x, + outpicref->data[plane] + (y-1 + s->start_y) * outpicref->linesize[plane] + s->start_x, s->w); } } @@ -800,7 +1203,7 @@ static int plot_spectrum_column(AVFilterLink *inlink, AVFrame *insamples) } if (s->sliding != FULLFRAME || s->xpos == 0) - outpicref->pts = insamples->pts; + outpicref->pts = av_rescale_q(insamples->pts, inlink->time_base, outlink->time_base); s->xpos++; if (s->orientation == VERTICAL && s->xpos >= s->w) @@ -808,70 +1211,82 @@ static int plot_spectrum_column(AVFilterLink *inlink, AVFrame *insamples) if (s->orientation == HORIZONTAL && s->xpos >= s->h) s->xpos = 0; if (!s->single_pic && (s->sliding != FULLFRAME || s->xpos == 0)) { - ret = ff_filter_frame(outlink, av_frame_clone(s->outpicref)); - if (ret < 0) - return ret; + if (s->old_pts < outpicref->pts) { + if (s->legend) { + char *units = get_time(ctx, insamples->pts /(float)inlink->sample_rate, x); + + if (s->orientation == VERTICAL) { + for (y = 0; y < 10; y++) { + memset(s->outpicref->data[0] + outlink->w / 2 - 4 * s->old_len + + (outlink->h - s->start_y / 2 - 20 + y) * s->outpicref->linesize[0], 0, 10 * s->old_len); + } + drawtext(s->outpicref, + outlink->w / 2 - 4 * strlen(units), + outlink->h - s->start_y / 2 - 20, + units, 0); + } else { + for (y = 0; y < 10 * s->old_len; y++) { + memset(s->outpicref->data[0] + s->start_x / 7 + 20 + + (outlink->h / 2 - 4 * s->old_len + y) * s->outpicref->linesize[0], 0, 10); + } + drawtext(s->outpicref, + s->start_x / 7 + 20, + outlink->h / 2 - 4 * strlen(units), + units, 1); + } + s->old_len = strlen(units); + av_free(units); + } + s->old_pts = outpicref->pts; + ret = ff_filter_frame(outlink, av_frame_clone(s->outpicref)); + if (ret < 0) + return ret; + return 0; + } } - return s->win_size; + return 1; } #if CONFIG_SHOWSPECTRUM_FILTER -static int request_frame(AVFilterLink *outlink) +static int activate(AVFilterContext *ctx) { - ShowSpectrumContext *s = outlink->src->priv; - AVFilterLink *inlink = outlink->src->inputs[0]; - unsigned i; + AVFilterLink *inlink = ctx->inputs[0]; + AVFilterLink *outlink = ctx->outputs[0]; + ShowSpectrumContext *s = ctx->priv; int ret; - ret = ff_request_frame(inlink); - if (ret == AVERROR_EOF && s->sliding == FULLFRAME && s->xpos > 0 && - s->outpicref) { - if (s->orientation == VERTICAL) { - for (i = 0; i < outlink->h; i++) { - memset(s->outpicref->data[0] + i * s->outpicref->linesize[0] + s->xpos, 0, outlink->w - s->xpos); - memset(s->outpicref->data[1] + i * s->outpicref->linesize[1] + s->xpos, 128, outlink->w - s->xpos); - memset(s->outpicref->data[2] + i * s->outpicref->linesize[2] + s->xpos, 128, outlink->w - s->xpos); - } - } else { - for (i = s->xpos; i < outlink->h; i++) { - memset(s->outpicref->data[0] + i * s->outpicref->linesize[0], 0, outlink->w); - memset(s->outpicref->data[1] + i * s->outpicref->linesize[1], 128, outlink->w); - memset(s->outpicref->data[2] + i * s->outpicref->linesize[2], 128, outlink->w); - } - } - ret = ff_filter_frame(outlink, s->outpicref); - s->outpicref = NULL; - } - - return ret; -} + FF_FILTER_FORWARD_STATUS_BACK(outlink, inlink); -static int filter_frame(AVFilterLink *inlink, AVFrame *insamples) -{ - AVFilterContext *ctx = inlink->dst; - ShowSpectrumContext *s = ctx->priv; - AVFrame *fin = NULL; - int ret = 0, consumed = 0; + if (av_audio_fifo_size(s->fifo) < s->win_size) { + AVFrame *frame = NULL; - if (s->pts == AV_NOPTS_VALUE) - s->pts = insamples->pts - av_audio_fifo_size(s->fifo); + ret = ff_inlink_consume_frame(inlink, &frame); + if (ret < 0) + return ret; + if (ret > 0) { + s->pts = frame->pts; + s->consumed = 0; - av_audio_fifo_write(s->fifo, (void **)insamples->extended_data, insamples->nb_samples); - av_frame_free(&insamples); - while (av_audio_fifo_size(s->fifo) >= s->win_size) { - fin = ff_get_audio_buffer(inlink, s->win_size); - if (!fin) { - ret = AVERROR(ENOMEM); - goto fail; + av_audio_fifo_write(s->fifo, (void **)frame->extended_data, frame->nb_samples); + av_frame_free(&frame); } + } - fin->pts = s->pts + consumed; - consumed += s->hop_size; - ret = av_audio_fifo_peek(s->fifo, (void **)fin->extended_data, s->win_size); - if (ret < 0) - goto fail; + if (s->outpicref && av_audio_fifo_size(s->fifo) >= s->win_size) { + AVFrame *fin = ff_get_audio_buffer(inlink, s->win_size); + if (!fin) + return AVERROR(ENOMEM); + + fin->pts = s->pts + s->consumed; + s->consumed += s->hop_size; + ret = av_audio_fifo_peek(s->fifo, (void **)fin->extended_data, + FFMIN(s->win_size, av_audio_fifo_size(s->fifo))); + if (ret < 0) { + av_frame_free(&fin); + return ret; + } av_assert0(fin->nb_samples == s->win_size); @@ -884,23 +1299,56 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *insamples) ctx->internal->execute(ctx, calc_channel_phases, NULL, NULL, s->nb_display_channels); ret = plot_spectrum_column(inlink, fin); + av_frame_free(&fin); av_audio_fifo_drain(s->fifo, s->hop_size); - if (ret < 0) - goto fail; + if (ret <= 0) + return ret; } -fail: - s->pts = AV_NOPTS_VALUE; - av_frame_free(&fin); - return ret; + if (ff_outlink_get_status(inlink) == AVERROR_EOF && + s->sliding == FULLFRAME && + s->xpos > 0 && s->outpicref) { + int64_t pts; + + if (s->orientation == VERTICAL) { + for (int i = 0; i < outlink->h; i++) { + memset(s->outpicref->data[0] + i * s->outpicref->linesize[0] + s->xpos, 0, outlink->w - s->xpos); + memset(s->outpicref->data[1] + i * s->outpicref->linesize[1] + s->xpos, 128, outlink->w - s->xpos); + memset(s->outpicref->data[2] + i * s->outpicref->linesize[2] + s->xpos, 128, outlink->w - s->xpos); + } + } else { + for (int i = s->xpos; i < outlink->h; i++) { + memset(s->outpicref->data[0] + i * s->outpicref->linesize[0], 0, outlink->w); + memset(s->outpicref->data[1] + i * s->outpicref->linesize[1], 128, outlink->w); + memset(s->outpicref->data[2] + i * s->outpicref->linesize[2], 128, outlink->w); + } + } + s->outpicref->pts += s->consumed; + pts = s->outpicref->pts; + ret = ff_filter_frame(outlink, s->outpicref); + s->outpicref = NULL; + ff_outlink_set_status(outlink, AVERROR_EOF, pts); + return 0; + } + + FF_FILTER_FORWARD_STATUS(inlink, outlink); + if (ff_outlink_frame_wanted(outlink) && av_audio_fifo_size(s->fifo) < s->win_size) { + ff_inlink_request_frame(inlink); + return 0; + } + + if (av_audio_fifo_size(s->fifo) >= s->win_size) { + ff_filter_set_ready(ctx, 10); + return 0; + } + return FFERROR_NOT_READY; } static const AVFilterPad showspectrum_inputs[] = { { .name = "default", .type = AVMEDIA_TYPE_AUDIO, - .filter_frame = filter_frame, }, { NULL } }; @@ -910,7 +1358,6 @@ static const AVFilterPad showspectrum_outputs[] = { .name = "default", .type = AVMEDIA_TYPE_VIDEO, .config_props = config_output, - .request_frame = request_frame, }, { NULL } }; @@ -923,6 +1370,7 @@ AVFilter ff_avf_showspectrum = { .priv_size = sizeof(ShowSpectrumContext), .inputs = showspectrum_inputs, .outputs = showspectrum_outputs, + .activate = activate, .priv_class = &showspectrum_class, .flags = AVFILTER_FLAG_SLICE_THREADS, }; @@ -946,6 +1394,8 @@ static const AVOption showspectrumpic_options[] = { { "fiery", "fiery based coloring", 0, AV_OPT_TYPE_CONST, {.i64=FIERY}, 0, 0, FLAGS, "color" }, { "fruit", "fruit based coloring", 0, AV_OPT_TYPE_CONST, {.i64=FRUIT}, 0, 0, FLAGS, "color" }, { "cool", "cool based coloring", 0, AV_OPT_TYPE_CONST, {.i64=COOL}, 0, 0, FLAGS, "color" }, + { "magma", "magma based coloring", 0, AV_OPT_TYPE_CONST, {.i64=MAGMA}, 0, 0, FLAGS, "color" }, + { "green", "green based coloring", 0, AV_OPT_TYPE_CONST, {.i64=GREEN}, 0, 0, FLAGS, "color" }, { "scale", "set display scale", OFFSET(scale), AV_OPT_TYPE_INT, {.i64=LOG}, 0, NB_SCALES-1, FLAGS, "scale" }, { "lin", "linear", 0, AV_OPT_TYPE_CONST, {.i64=LINEAR}, 0, 0, FLAGS, "scale" }, { "sqrt", "square root", 0, AV_OPT_TYPE_CONST, {.i64=SQRT}, 0, 0, FLAGS, "scale" }, @@ -981,45 +1431,13 @@ static const AVOption showspectrumpic_options[] = { { "gain", "set scale gain", OFFSET(gain), AV_OPT_TYPE_FLOAT, {.dbl = 1}, 0, 128, FLAGS }, { "legend", "draw legend", OFFSET(legend), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, FLAGS }, { "rotation", "color rotation", OFFSET(rotation), AV_OPT_TYPE_FLOAT, {.dbl = 0}, -1, 1, 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 }, { NULL } }; AVFILTER_DEFINE_CLASS(showspectrumpic); -static void drawtext(AVFrame *pic, int x, int y, const char *txt, int o) -{ - const uint8_t *font; - int font_height; - int i; - - font = avpriv_cga_font, font_height = 8; - - for (i = 0; txt[i]; i++) { - int char_y, mask; - - if (o) { - for (char_y = font_height - 1; char_y >= 0; char_y--) { - uint8_t *p = pic->data[0] + (y + i * 10) * pic->linesize[0] + x; - for (mask = 0x80; mask; mask >>= 1) { - if (font[txt[i] * font_height + font_height - 1 - char_y] & mask) - p[char_y] = ~p[char_y]; - p += pic->linesize[0]; - } - } - } else { - uint8_t *p = pic->data[0] + y*pic->linesize[0] + (x + i*8); - for (char_y = 0; char_y < font_height; char_y++) { - for (mask = 0x80; mask; mask >>= 1) { - if (font[txt[i] * font_height + char_y] & mask) - *p = ~(*p); - p++; - } - p += pic->linesize[0] - 8; - } - } - } -} - static int showspectrumpic_request_frame(AVFilterLink *outlink) { AVFilterContext *ctx = outlink->src; @@ -1031,7 +1449,7 @@ static int showspectrumpic_request_frame(AVFilterLink *outlink) samples = av_audio_fifo_size(s->fifo); if (ret == AVERROR_EOF && s->outpicref && samples > 0) { int consumed = 0; - int y, x = 0, sz = s->orientation == VERTICAL ? s->w : s->h; + int x = 0, sz = s->orientation == VERTICAL ? s->w : s->h; int ch, spf, spb; AVFrame *fin; @@ -1079,202 +1497,8 @@ static int showspectrumpic_request_frame(AVFilterLink *outlink) av_frame_free(&fin); s->outpicref->pts = 0; - if (s->legend) { - int multi = (s->mode == SEPARATE && s->color_mode == CHANNEL); - float spp = samples / (float)sz; - char *text; - uint8_t *dst; - char chlayout_str[128]; - - av_get_channel_layout_string(chlayout_str, sizeof(chlayout_str), inlink->channels, - inlink->channel_layout); - - text = av_asprintf("%d Hz | %s", inlink->sample_rate, chlayout_str); - - drawtext(s->outpicref, 2, outlink->h - 10, "CREATED BY LIBAVFILTER", 0); - drawtext(s->outpicref, outlink->w - 2 - strlen(text) * 10, outlink->h - 10, text, 0); - - av_freep(&text); - - dst = s->outpicref->data[0] + (s->start_y - 1) * s->outpicref->linesize[0] + s->start_x - 1; - for (x = 0; x < s->w + 1; x++) - dst[x] = 200; - dst = s->outpicref->data[0] + (s->start_y + s->h) * s->outpicref->linesize[0] + s->start_x - 1; - for (x = 0; x < s->w + 1; x++) - dst[x] = 200; - for (y = 0; y < s->h + 2; y++) { - dst = s->outpicref->data[0] + (y + s->start_y - 1) * s->outpicref->linesize[0]; - dst[s->start_x - 1] = 200; - dst[s->start_x + s->w] = 200; - } - if (s->orientation == VERTICAL) { - int h = s->mode == SEPARATE ? s->h / s->nb_display_channels : s->h; - int hh = s->mode == SEPARATE ? -(s->h % s->nb_display_channels) + 1 : 1; - for (ch = 0; ch < (s->mode == SEPARATE ? s->nb_display_channels : 1); ch++) { - for (y = 0; y < h; y += 20) { - dst = s->outpicref->data[0] + (s->start_y + h * (ch + 1) - y - hh) * s->outpicref->linesize[0]; - dst[s->start_x - 2] = 200; - dst[s->start_x + s->w + 1] = 200; - } - for (y = 0; y < h; y += 40) { - dst = s->outpicref->data[0] + (s->start_y + h * (ch + 1) - y - hh) * s->outpicref->linesize[0]; - dst[s->start_x - 3] = 200; - dst[s->start_x + s->w + 2] = 200; - } - dst = s->outpicref->data[0] + (s->start_y - 2) * s->outpicref->linesize[0] + s->start_x; - for (x = 0; x < s->w; x+=40) - dst[x] = 200; - dst = s->outpicref->data[0] + (s->start_y - 3) * s->outpicref->linesize[0] + s->start_x; - for (x = 0; x < s->w; x+=80) - dst[x] = 200; - dst = s->outpicref->data[0] + (s->h + s->start_y + 1) * s->outpicref->linesize[0] + s->start_x; - for (x = 0; x < s->w; x+=40) { - dst[x] = 200; - } - dst = s->outpicref->data[0] + (s->h + s->start_y + 2) * s->outpicref->linesize[0] + s->start_x; - for (x = 0; x < s->w; x+=80) { - dst[x] = 200; - } - for (y = 0; y < h; y += 40) { - float hertz = y * (inlink->sample_rate / 2) / (float)(1 << (int)ceil(log2(h))); - char *units; - - if (hertz == 0) - units = av_asprintf("DC"); - else - units = av_asprintf("%.2f", hertz); - if (!units) - return AVERROR(ENOMEM); - - drawtext(s->outpicref, s->start_x - 8 * strlen(units) - 4, h * (ch + 1) + s->start_y - y - 4 - hh, units, 0); - av_free(units); - } - } - - for (x = 0; x < s->w; x+=80) { - float seconds = x * spp / inlink->sample_rate; - char *units; - - if (x == 0) - units = av_asprintf("0"); - else if (log10(seconds) > 6) - units = av_asprintf("%.2fh", seconds / (60 * 60)); - else if (log10(seconds) > 3) - units = av_asprintf("%.2fm", seconds / 60); - else - units = av_asprintf("%.2fs", seconds); - if (!units) - return AVERROR(ENOMEM); - - drawtext(s->outpicref, s->start_x + x - 4 * strlen(units), s->h + s->start_y + 6, units, 0); - drawtext(s->outpicref, s->start_x + x - 4 * strlen(units), s->start_y - 12, units, 0); - av_free(units); - } - - drawtext(s->outpicref, outlink->w / 2 - 4 * 4, outlink->h - s->start_y / 2, "TIME", 0); - drawtext(s->outpicref, s->start_x / 7, outlink->h / 2 - 14 * 4, "FREQUENCY (Hz)", 1); - } else { - int w = s->mode == SEPARATE ? s->w / s->nb_display_channels : s->w; - for (y = 0; y < s->h; y += 20) { - dst = s->outpicref->data[0] + (s->start_y + y) * s->outpicref->linesize[0]; - dst[s->start_x - 2] = 200; - dst[s->start_x + s->w + 1] = 200; - } - for (y = 0; y < s->h; y += 40) { - dst = s->outpicref->data[0] + (s->start_y + y) * s->outpicref->linesize[0]; - dst[s->start_x - 3] = 200; - dst[s->start_x + s->w + 2] = 200; - } - for (ch = 0; ch < (s->mode == SEPARATE ? s->nb_display_channels : 1); ch++) { - dst = s->outpicref->data[0] + (s->start_y - 2) * s->outpicref->linesize[0] + s->start_x + w * ch; - for (x = 0; x < w; x+=40) - dst[x] = 200; - dst = s->outpicref->data[0] + (s->start_y - 3) * s->outpicref->linesize[0] + s->start_x + w * ch; - for (x = 0; x < w; x+=80) - dst[x] = 200; - dst = s->outpicref->data[0] + (s->h + s->start_y + 1) * s->outpicref->linesize[0] + s->start_x + w * ch; - for (x = 0; x < w; x+=40) { - dst[x] = 200; - } - dst = s->outpicref->data[0] + (s->h + s->start_y + 2) * s->outpicref->linesize[0] + s->start_x + w * ch; - for (x = 0; x < w; x+=80) { - dst[x] = 200; - } - for (x = 0; x < w - 79; x += 80) { - float hertz = x * (inlink->sample_rate / 2) / (float)(1 << (int)ceil(log2(w))); - char *units; - - if (hertz == 0) - units = av_asprintf("DC"); - else - units = av_asprintf("%.2f", hertz); - if (!units) - return AVERROR(ENOMEM); - - drawtext(s->outpicref, s->start_x - 4 * strlen(units) + x + w * ch, s->start_y - 12, units, 0); - drawtext(s->outpicref, s->start_x - 4 * strlen(units) + x + w * ch, s->h + s->start_y + 6, units, 0); - av_free(units); - } - } - for (y = 0; y < s->h; y+=40) { - float seconds = y * spp / inlink->sample_rate; - char *units; - - if (x == 0) - units = av_asprintf("0"); - else if (log10(seconds) > 6) - units = av_asprintf("%.2fh", seconds / (60 * 60)); - else if (log10(seconds) > 3) - units = av_asprintf("%.2fm", seconds / 60); - else - units = av_asprintf("%.2fs", seconds); - if (!units) - return AVERROR(ENOMEM); - - drawtext(s->outpicref, s->start_x - 8 * strlen(units) - 4, s->start_y + y - 4, units, 0); - av_free(units); - } - drawtext(s->outpicref, s->start_x / 7, outlink->h / 2 - 4 * 4, "TIME", 1); - drawtext(s->outpicref, outlink->w / 2 - 14 * 4, outlink->h - s->start_y / 2, "FREQUENCY (Hz)", 0); - } - - for (ch = 0; ch < (multi ? s->nb_display_channels : 1); ch++) { - int h = multi ? s->h / s->nb_display_channels : s->h; - - for (y = 0; y < h; y++) { - float out[3] = { 0., 127.5, 127.5}; - int chn; - - for (chn = 0; chn < (s->mode == SEPARATE ? 1 : s->nb_display_channels); chn++) { - float yf, uf, vf; - int channel = (multi) ? s->nb_display_channels - ch - 1 : chn; - float lout[3]; - - color_range(s, channel, &yf, &uf, &vf); - pick_color(s, yf, uf, vf, y / (float)h, lout); - out[0] += lout[0]; - out[1] += lout[1]; - out[2] += lout[2]; - } - memset(s->outpicref->data[0]+(s->start_y + h * (ch + 1) - y - 1) * s->outpicref->linesize[0] + s->w + s->start_x + 20, av_clip_uint8(out[0]), 10); - memset(s->outpicref->data[1]+(s->start_y + h * (ch + 1) - y - 1) * s->outpicref->linesize[1] + s->w + s->start_x + 20, av_clip_uint8(out[1]), 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) { - float value = 120.0 * log10(1. - y / (float)h); - char *text; - - if (value < -120) - break; - text = av_asprintf("%.0f dB", value); - if (!text) - continue; - drawtext(s->outpicref, s->w + s->start_x + 35, s->start_y + y - 5, text, 0); - av_free(text); - } - } - } + if (s->legend) + draw_legend(ctx, samples); ret = ff_filter_frame(outlink, s->outpicref); s->outpicref = NULL;