Browse code

avfilter: add showspectrumpic filter

Signed-off-by: Paul B Mahol <onemda@gmail.com>

Paul B Mahol authored on 2016/01/01 04:45:37
Showing 6 changed files
... ...
@@ -50,6 +50,7 @@ version <next>:
50 50
 - VAAPI VP9 hwaccel
51 51
 - audio high-order multiband parametric equalizer
52 52
 - automatic bitstream filtering
53
+- showspectrumpic filter
53 54
 
54 55
 
55 56
 version 2.8:
... ...
@@ -14704,6 +14704,112 @@ ffplay -f lavfi 'amovie=input.mp3, asplit [a][out1];
14704 14704
 @end example
14705 14705
 @end itemize
14706 14706
 
14707
+@section showspectrumpic
14708
+
14709
+Convert input audio to a single video frame, representing the audio frequency
14710
+spectrum.
14711
+
14712
+The filter accepts the following options:
14713
+
14714
+@table @option
14715
+@item size, s
14716
+Specify the video size for the output. For the syntax of this option, check the
14717
+@ref{video size syntax,,"Video size" section in the ffmpeg-utils manual,ffmpeg-utils}.
14718
+Default value is @code{4096x2048}.
14719
+
14720
+@item mode
14721
+Specify display mode.
14722
+
14723
+It accepts the following values:
14724
+@table @samp
14725
+@item combined
14726
+all channels are displayed in the same row
14727
+@item separate
14728
+all channels are displayed in separate rows
14729
+@end table
14730
+Default value is @samp{combined}.
14731
+
14732
+@item color
14733
+Specify display color mode.
14734
+
14735
+It accepts the following values:
14736
+@table @samp
14737
+@item channel
14738
+each channel is displayed in a separate color
14739
+@item intensity
14740
+each channel is displayed using the same color scheme
14741
+@item rainbow
14742
+each channel is displayed using the rainbow color scheme
14743
+@item moreland
14744
+each channel is displayed using the moreland color scheme
14745
+@item nebulae
14746
+each channel is displayed using the nebulae color scheme
14747
+@item fire
14748
+each channel is displayed using the fire color scheme
14749
+@end table
14750
+Default value is @samp{intensity}.
14751
+
14752
+@item scale
14753
+Specify scale used for calculating intensity color values.
14754
+
14755
+It accepts the following values:
14756
+@table @samp
14757
+@item lin
14758
+linear
14759
+@item sqrt
14760
+square root, default
14761
+@item cbrt
14762
+cubic root
14763
+@item log
14764
+logarithmic
14765
+@end table
14766
+Default value is @samp{log}.
14767
+
14768
+@item saturation
14769
+Set saturation modifier for displayed colors. Negative values provide
14770
+alternative color scheme. @code{0} is no saturation at all.
14771
+Saturation must be in [-10.0, 10.0] range.
14772
+Default value is @code{1}.
14773
+
14774
+@item win_func
14775
+Set window function.
14776
+
14777
+It accepts the following values:
14778
+@table @samp
14779
+@item rect
14780
+@item bartlett
14781
+@item hann
14782
+@item hanning
14783
+@item hamming
14784
+@item blackman
14785
+@item welch
14786
+@item flattop
14787
+@item bharris
14788
+@item bnuttall
14789
+@item bhann
14790
+@item sine
14791
+@item nuttall
14792
+@item lanczos
14793
+@item gauss
14794
+@end table
14795
+Default value is @code{hann}.
14796
+
14797
+@item orientation
14798
+Set orientation of time vs frequency axis. Can be @code{vertical} or
14799
+@code{horizontal}. Default is @code{vertical}.
14800
+@end table
14801
+
14802
+@subsection Examples
14803
+
14804
+@itemize
14805
+@item
14806
+Extract an audio spectrogram of a whole audio track
14807
+in a 1024x1024 picture using @command{ffmpeg}:
14808
+@example
14809
+ffmpeg -i audio.flac -lavfi showspectrumpic=s=1024x1024 spectrogram.png
14810
+@end example
14811
+@end itemize
14812
+
14707 14813
 @section showvolume
14708 14814
 
14709 14815
 Convert input audio volume to a video output.
... ...
@@ -286,6 +286,7 @@ OBJS-$(CONFIG_CONCAT_FILTER)                 += avf_concat.o
286 286
 OBJS-$(CONFIG_SHOWCQT_FILTER)                += avf_showcqt.o lswsutils.o lavfutils.o
287 287
 OBJS-$(CONFIG_SHOWFREQS_FILTER)              += avf_showfreqs.o window_func.o
288 288
 OBJS-$(CONFIG_SHOWSPECTRUM_FILTER)           += avf_showspectrum.o window_func.o
289
+OBJS-$(CONFIG_SHOWSPECTRUMPIC_FILTER)        += avf_showspectrum.o window_func.o
289 290
 OBJS-$(CONFIG_SHOWVOLUME_FILTER)             += avf_showvolume.o
290 291
 OBJS-$(CONFIG_SHOWWAVES_FILTER)              += avf_showwaves.o
291 292
 OBJS-$(CONFIG_SHOWWAVESPIC_FILTER)           += avf_showwaves.o
... ...
@@ -306,6 +306,7 @@ void avfilter_register_all(void)
306 306
     REGISTER_FILTER(SHOWCQT,        showcqt,        avf);
307 307
     REGISTER_FILTER(SHOWFREQS,      showfreqs,      avf);
308 308
     REGISTER_FILTER(SHOWSPECTRUM,   showspectrum,   avf);
309
+    REGISTER_FILTER(SHOWSPECTRUMPIC, showspectrumpic, avf);
309 310
     REGISTER_FILTER(SHOWVOLUME,     showvolume,     avf);
310 311
     REGISTER_FILTER(SHOWWAVES,      showwaves,      avf);
311 312
     REGISTER_FILTER(SHOWWAVESPIC,   showwavespic,   avf);
... ...
@@ -63,6 +63,7 @@ typedef struct {
63 63
     int rdft_bits;              ///< number of bits (RDFT window size = 1<<rdft_bits)
64 64
     FFTSample **rdft_data;      ///< bins holder for each (displayed) channels
65 65
     float *window_func_lut;     ///< Window function LUT
66
+    float **magnitudes;
66 67
     int win_func;
67 68
     int win_size;
68 69
     double win_scale;
... ...
@@ -71,6 +72,7 @@ typedef struct {
71 71
     float *combine_buffer;      ///< color combining buffer (3 * h items)
72 72
     AVAudioFifo *fifo;
73 73
     int64_t pts;
74
+    int single_pic;
74 75
 } ShowSpectrumContext;
75 76
 
76 77
 #define OFFSET(x) offsetof(ShowSpectrumContext, x)
... ...
@@ -186,6 +188,9 @@ static av_cold void uninit(AVFilterContext *ctx)
186 186
         av_freep(&s->rdft_data[i]);
187 187
     av_freep(&s->rdft_data);
188 188
     av_freep(&s->window_func_lut);
189
+    for (i = 0; i < s->nb_display_channels; i++)
190
+        av_freep(&s->magnitudes[i]);
191
+    av_freep(&s->magnitudes);
189 192
     av_frame_free(&s->outpicref);
190 193
     av_audio_fifo_free(s->fifo);
191 194
 }
... ...
@@ -229,6 +234,9 @@ static int config_output(AVFilterLink *outlink)
229 229
     int i, rdft_bits, h, w;
230 230
     float overlap;
231 231
 
232
+    if (!strcmp(ctx->filter->name, "showspectrumpic"))
233
+        s->single_pic = 1;
234
+
232 235
     outlink->w = s->w;
233 236
     outlink->h = s->h;
234 237
 
... ...
@@ -267,6 +275,15 @@ static int config_output(AVFilterLink *outlink)
267 267
         av_freep(&s->rdft_data);
268 268
         s->nb_display_channels = inlink->channels;
269 269
 
270
+        s->magnitudes = av_calloc(s->nb_display_channels, sizeof(*s->magnitudes));
271
+        if (!s->magnitudes)
272
+            return AVERROR(ENOMEM);
273
+        for (i = 0; i < s->nb_display_channels; i++) {
274
+            s->magnitudes[i] = av_calloc(s->orientation == VERTICAL ? s->h : s->w, sizeof(**s->magnitudes));
275
+            if (!s->magnitudes[i])
276
+                return AVERROR(ENOMEM);
277
+        }
278
+
270 279
         s->rdft_data = av_calloc(s->nb_display_channels, sizeof(*s->rdft_data));
271 280
         if (!s->rdft_data)
272 281
             return AVERROR(ENOMEM);
... ...
@@ -340,34 +357,61 @@ static int config_output(AVFilterLink *outlink)
340 340
     return 0;
341 341
 }
342 342
 
343
-static int request_frame(AVFilterLink *outlink)
343
+static void run_rdft(ShowSpectrumContext *s, AVFrame *fin)
344 344
 {
345
-    ShowSpectrumContext *s = outlink->src->priv;
346
-    AVFilterLink *inlink = outlink->src->inputs[0];
347
-    unsigned i;
348
-    int ret;
345
+    int ch, n;
349 346
 
350
-    ret = ff_request_frame(inlink);
351
-    if (ret == AVERROR_EOF && s->sliding == FULLFRAME && s->xpos > 0 &&
352
-        s->outpicref) {
353
-        if (s->orientation == VERTICAL) {
354
-            for (i = 0; i < outlink->h; i++) {
355
-                memset(s->outpicref->data[0] + i * s->outpicref->linesize[0] + s->xpos,   0, outlink->w - s->xpos);
356
-                memset(s->outpicref->data[1] + i * s->outpicref->linesize[1] + s->xpos, 128, outlink->w - s->xpos);
357
-                memset(s->outpicref->data[2] + i * s->outpicref->linesize[2] + s->xpos, 128, outlink->w - s->xpos);
358
-            }
359
-        } else {
360
-            for (i = s->xpos; i < outlink->h; i++) {
361
-                memset(s->outpicref->data[0] + i * s->outpicref->linesize[0],   0, outlink->w);
362
-                memset(s->outpicref->data[1] + i * s->outpicref->linesize[1], 128, outlink->w);
363
-                memset(s->outpicref->data[2] + i * s->outpicref->linesize[2], 128, outlink->w);
364
-            }
365
-        }
366
-        ret = ff_filter_frame(outlink, s->outpicref);
367
-        s->outpicref = NULL;
347
+    /* fill RDFT input with the number of samples available */
348
+    for (ch = 0; ch < s->nb_display_channels; ch++) {
349
+        const int16_t *p = (int16_t *)fin->extended_data[ch];
350
+
351
+        for (n = 0; n < s->win_size; n++)
352
+            s->rdft_data[ch][n] = p[n] * s->window_func_lut[n];
368 353
     }
369 354
 
370
-    return ret;
355
+    /* run RDFT on each samples set */
356
+    for (ch = 0; ch < s->nb_display_channels; ch++)
357
+        av_rdft_calc(s->rdft, s->rdft_data[ch]);
358
+}
359
+
360
+#define RE(y, ch) s->rdft_data[ch][2 * (y) + 0]
361
+#define IM(y, ch) s->rdft_data[ch][2 * (y) + 1]
362
+#define MAGNITUDE(y, ch) hypot(RE(y, ch), IM(y, ch))
363
+
364
+static void calc_magnitudes(ShowSpectrumContext *s)
365
+{
366
+    int ch, y, h = s->orientation == VERTICAL ? s->h : s->w;
367
+
368
+    for (ch = 0; ch < s->nb_display_channels; ch++) {
369
+        float *magnitudes = s->magnitudes[ch];
370
+
371
+        for (y = 0; y < h; y++)
372
+            magnitudes[y] = MAGNITUDE(y, ch);
373
+    }
374
+}
375
+
376
+static void acalc_magnitudes(ShowSpectrumContext *s)
377
+{
378
+    int ch, y, h = s->orientation == VERTICAL ? s->h : s->w;
379
+
380
+    for (ch = 0; ch < s->nb_display_channels; ch++) {
381
+        float *magnitudes = s->magnitudes[ch];
382
+
383
+        for (y = 0; y < h; y++)
384
+            magnitudes[y] += MAGNITUDE(y, ch);
385
+    }
386
+}
387
+
388
+static void scale_magnitudes(ShowSpectrumContext *s, float scale)
389
+{
390
+    int ch, y, h = s->orientation == VERTICAL ? s->h : s->w;
391
+
392
+    for (ch = 0; ch < s->nb_display_channels; ch++) {
393
+        float *magnitudes = s->magnitudes[ch];
394
+
395
+        for (y = 0; y < h; y++)
396
+            magnitudes[y] *= scale;
397
+    }
371 398
 }
372 399
 
373 400
 static int plot_spectrum_column(AVFilterLink *inlink, AVFrame *insamples)
... ...
@@ -380,27 +424,9 @@ static int plot_spectrum_column(AVFilterLink *inlink, AVFrame *insamples)
380 380
     const double w = s->win_scale;
381 381
     int h = s->orientation == VERTICAL ? s->channel_height : s->channel_width;
382 382
 
383
-    int ch, plane, n, x, y;
384
-
385
-    av_assert0(insamples->nb_samples == s->win_size);
386
-
387
-    /* fill RDFT input with the number of samples available */
388
-    for (ch = 0; ch < s->nb_display_channels; ch++) {
389
-        const int16_t *p = (int16_t *)insamples->extended_data[ch];
390
-
391
-        for (n = 0; n < s->win_size; n++)
392
-            s->rdft_data[ch][n] = p[n] * s->window_func_lut[n];
393
-    }
394
-
395
-    /* run RDFT on each samples set */
396
-    for (ch = 0; ch < s->nb_display_channels; ch++)
397
-        av_rdft_calc(s->rdft, s->rdft_data[ch]);
383
+    int ch, plane, x, y;
398 384
 
399 385
     /* fill a new spectrum column */
400
-#define RE(y, ch) s->rdft_data[ch][2 * (y) + 0]
401
-#define IM(y, ch) s->rdft_data[ch][2 * (y) + 1]
402
-#define MAGNITUDE(y, ch) hypot(RE(y, ch), IM(y, ch))
403
-
404 386
     /* initialize buffer for combining to black */
405 387
     if (s->orientation == VERTICAL) {
406 388
         for (y = 0; y < outlink->h; y++) {
... ...
@@ -417,6 +443,7 @@ static int plot_spectrum_column(AVFilterLink *inlink, AVFrame *insamples)
417 417
     }
418 418
 
419 419
     for (ch = 0; ch < s->nb_display_channels; ch++) {
420
+        float *magnitudes = s->magnitudes[ch];
420 421
         float yf, uf, vf;
421 422
 
422 423
         /* decide color range */
... ...
@@ -471,7 +498,7 @@ static int plot_spectrum_column(AVFilterLink *inlink, AVFrame *insamples)
471 471
             float *out = &s->combine_buffer[3 * row];
472 472
 
473 473
             /* get magnitude */
474
-            float a = w * MAGNITUDE(y, ch);
474
+            float a = w * magnitudes[y];
475 475
 
476 476
             /* apply scale */
477 477
             switch (s->scale) {
... ...
@@ -600,7 +627,7 @@ static int plot_spectrum_column(AVFilterLink *inlink, AVFrame *insamples)
600 600
         s->xpos = 0;
601 601
     if (s->orientation == HORIZONTAL && s->xpos >= outlink->h)
602 602
         s->xpos = 0;
603
-    if (s->sliding != FULLFRAME || s->xpos == 0) {
603
+    if (!s->single_pic && (s->sliding != FULLFRAME || s->xpos == 0)) {
604 604
         ret = ff_filter_frame(outlink, av_frame_clone(s->outpicref));
605 605
         if (ret < 0)
606 606
             return ret;
... ...
@@ -609,6 +636,38 @@ static int plot_spectrum_column(AVFilterLink *inlink, AVFrame *insamples)
609 609
     return s->win_size;
610 610
 }
611 611
 
612
+#if CONFIG_SHOWSPECTRUM_FILTER
613
+
614
+static int request_frame(AVFilterLink *outlink)
615
+{
616
+    ShowSpectrumContext *s = outlink->src->priv;
617
+    AVFilterLink *inlink = outlink->src->inputs[0];
618
+    unsigned i;
619
+    int ret;
620
+
621
+    ret = ff_request_frame(inlink);
622
+    if (ret == AVERROR_EOF && s->sliding == FULLFRAME && s->xpos > 0 &&
623
+        s->outpicref) {
624
+        if (s->orientation == VERTICAL) {
625
+            for (i = 0; i < outlink->h; i++) {
626
+                memset(s->outpicref->data[0] + i * s->outpicref->linesize[0] + s->xpos,   0, outlink->w - s->xpos);
627
+                memset(s->outpicref->data[1] + i * s->outpicref->linesize[1] + s->xpos, 128, outlink->w - s->xpos);
628
+                memset(s->outpicref->data[2] + i * s->outpicref->linesize[2] + s->xpos, 128, outlink->w - s->xpos);
629
+            }
630
+        } else {
631
+            for (i = s->xpos; i < outlink->h; i++) {
632
+                memset(s->outpicref->data[0] + i * s->outpicref->linesize[0],   0, outlink->w);
633
+                memset(s->outpicref->data[1] + i * s->outpicref->linesize[1], 128, outlink->w);
634
+                memset(s->outpicref->data[2] + i * s->outpicref->linesize[2], 128, outlink->w);
635
+            }
636
+        }
637
+        ret = ff_filter_frame(outlink, s->outpicref);
638
+        s->outpicref = NULL;
639
+    }
640
+
641
+    return ret;
642
+}
643
+
612 644
 static int filter_frame(AVFilterLink *inlink, AVFrame *insamples)
613 645
 {
614 646
     AVFilterContext *ctx = inlink->dst;
... ...
@@ -631,6 +690,11 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *insamples)
631 631
         if (ret < 0)
632 632
             goto fail;
633 633
 
634
+        av_assert0(fin->nb_samples == s->win_size);
635
+
636
+        run_rdft(s, fin);
637
+        calc_magnitudes(s);
638
+
634 639
         ret = plot_spectrum_column(inlink, fin);
635 640
         av_frame_free(&fin);
636 641
         av_audio_fifo_drain(s->fifo, s->skip_samples);
... ...
@@ -672,3 +736,154 @@ AVFilter ff_avf_showspectrum = {
672 672
     .outputs       = showspectrum_outputs,
673 673
     .priv_class    = &showspectrum_class,
674 674
 };
675
+#endif // CONFIG_SHOWSPECTRUM_FILTER
676
+
677
+#if CONFIG_SHOWSPECTRUMPIC_FILTER
678
+
679
+static const AVOption showspectrumpic_options[] = {
680
+    { "size", "set video size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str = "4096x2048"}, 0, 0, FLAGS },
681
+    { "s",    "set video size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str = "4096x2048"}, 0, 0, FLAGS },
682
+    { "mode", "set channel display mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=COMBINED}, 0, NB_MODES-1, FLAGS, "mode" },
683
+        { "combined", "combined mode", 0, AV_OPT_TYPE_CONST, {.i64=COMBINED}, 0, 0, FLAGS, "mode" },
684
+        { "separate", "separate mode", 0, AV_OPT_TYPE_CONST, {.i64=SEPARATE}, 0, 0, FLAGS, "mode" },
685
+    { "color", "set channel coloring", OFFSET(color_mode), AV_OPT_TYPE_INT, {.i64=INTENSITY}, 0, NB_CLMODES-1, FLAGS, "color" },
686
+        { "channel",   "separate color for each channel", 0, AV_OPT_TYPE_CONST, {.i64=CHANNEL},   0, 0, FLAGS, "color" },
687
+        { "intensity", "intensity based coloring",        0, AV_OPT_TYPE_CONST, {.i64=INTENSITY}, 0, 0, FLAGS, "color" },
688
+        { "rainbow",   "rainbow based coloring",          0, AV_OPT_TYPE_CONST, {.i64=RAINBOW},   0, 0, FLAGS, "color" },
689
+        { "moreland",  "moreland based coloring",         0, AV_OPT_TYPE_CONST, {.i64=MORELAND},  0, 0, FLAGS, "color" },
690
+        { "nebulae",   "nebulae based coloring",          0, AV_OPT_TYPE_CONST, {.i64=NEBULAE},   0, 0, FLAGS, "color" },
691
+        { "fire",      "fire based coloring",             0, AV_OPT_TYPE_CONST, {.i64=FIRE},      0, 0, FLAGS, "color" },
692
+    { "scale", "set display scale", OFFSET(scale), AV_OPT_TYPE_INT, {.i64=LOG}, 0, NB_SCALES-1, FLAGS, "scale" },
693
+        { "sqrt", "square root", 0, AV_OPT_TYPE_CONST, {.i64=SQRT},   0, 0, FLAGS, "scale" },
694
+        { "cbrt", "cubic root",  0, AV_OPT_TYPE_CONST, {.i64=CBRT},   0, 0, FLAGS, "scale" },
695
+        { "log",  "logarithmic", 0, AV_OPT_TYPE_CONST, {.i64=LOG},    0, 0, FLAGS, "scale" },
696
+        { "lin",  "linear",      0, AV_OPT_TYPE_CONST, {.i64=LINEAR}, 0, 0, FLAGS, "scale" },
697
+    { "saturation", "color saturation multiplier", OFFSET(saturation), AV_OPT_TYPE_FLOAT, {.dbl = 1}, -10, 10, FLAGS },
698
+    { "win_func", "set window function", OFFSET(win_func), AV_OPT_TYPE_INT, {.i64 = WFUNC_HANNING}, 0, NB_WFUNC-1, FLAGS, "win_func" },
699
+        { "rect",     "Rectangular",      0, AV_OPT_TYPE_CONST, {.i64=WFUNC_RECT},     0, 0, FLAGS, "win_func" },
700
+        { "bartlett", "Bartlett",         0, AV_OPT_TYPE_CONST, {.i64=WFUNC_BARTLETT}, 0, 0, FLAGS, "win_func" },
701
+        { "hann",     "Hann",             0, AV_OPT_TYPE_CONST, {.i64=WFUNC_HANNING},  0, 0, FLAGS, "win_func" },
702
+        { "hanning",  "Hanning",          0, AV_OPT_TYPE_CONST, {.i64=WFUNC_HANNING},  0, 0, FLAGS, "win_func" },
703
+        { "hamming",  "Hamming",          0, AV_OPT_TYPE_CONST, {.i64=WFUNC_HAMMING},  0, 0, FLAGS, "win_func" },
704
+        { "blackman", "Blackman",         0, AV_OPT_TYPE_CONST, {.i64=WFUNC_BLACKMAN}, 0, 0, FLAGS, "win_func" },
705
+        { "welch",    "Welch",            0, AV_OPT_TYPE_CONST, {.i64=WFUNC_WELCH},    0, 0, FLAGS, "win_func" },
706
+        { "flattop",  "Flat-top",         0, AV_OPT_TYPE_CONST, {.i64=WFUNC_FLATTOP},  0, 0, FLAGS, "win_func" },
707
+        { "bharris",  "Blackman-Harris",  0, AV_OPT_TYPE_CONST, {.i64=WFUNC_BHARRIS},  0, 0, FLAGS, "win_func" },
708
+        { "bnuttall", "Blackman-Nuttall", 0, AV_OPT_TYPE_CONST, {.i64=WFUNC_BNUTTALL}, 0, 0, FLAGS, "win_func" },
709
+        { "bhann",    "Bartlett-Hann",    0, AV_OPT_TYPE_CONST, {.i64=WFUNC_BHANN},    0, 0, FLAGS, "win_func" },
710
+        { "sine",     "Sine",             0, AV_OPT_TYPE_CONST, {.i64=WFUNC_SINE},     0, 0, FLAGS, "win_func" },
711
+        { "nuttall",  "Nuttall",          0, AV_OPT_TYPE_CONST, {.i64=WFUNC_NUTTALL},  0, 0, FLAGS, "win_func" },
712
+        { "lanczos",  "Lanczos",          0, AV_OPT_TYPE_CONST, {.i64=WFUNC_LANCZOS},  0, 0, FLAGS, "win_func" },
713
+        { "gauss",    "Gauss",            0, AV_OPT_TYPE_CONST, {.i64=WFUNC_GAUSS},    0, 0, FLAGS, "win_func" },
714
+    { "orientation", "set orientation", OFFSET(orientation), AV_OPT_TYPE_INT, {.i64=VERTICAL}, 0, NB_ORIENTATIONS-1, FLAGS, "orientation" },
715
+        { "vertical",   NULL, 0, AV_OPT_TYPE_CONST, {.i64=VERTICAL},   0, 0, FLAGS, "orientation" },
716
+        { "horizontal", NULL, 0, AV_OPT_TYPE_CONST, {.i64=HORIZONTAL}, 0, 0, FLAGS, "orientation" },
717
+    { NULL }
718
+};
719
+
720
+AVFILTER_DEFINE_CLASS(showspectrumpic);
721
+
722
+static int showspectrumpic_request_frame(AVFilterLink *outlink)
723
+{
724
+    ShowSpectrumContext *s = outlink->src->priv;
725
+    AVFilterLink *inlink = outlink->src->inputs[0];
726
+    int ret;
727
+
728
+    ret = ff_request_frame(inlink);
729
+    if (ret == AVERROR_EOF && s->outpicref) {
730
+        int samples = av_audio_fifo_size(s->fifo);
731
+        int consumed = 0;
732
+        int x = 0, sz = s->orientation == VERTICAL ? s->w : s->h;
733
+        int ch, spf, spb;
734
+        AVFrame *fin;
735
+
736
+        spf = s->win_size * (samples / ((s->win_size * sz) * ceil(samples / (float)(s->win_size * sz))));
737
+        spb = (samples / (spf * sz)) * spf;
738
+
739
+        fin = ff_get_audio_buffer(inlink, s->win_size);
740
+        if (!fin)
741
+            return AVERROR(ENOMEM);
742
+
743
+        while (x < sz) {
744
+            ret = av_audio_fifo_peek(s->fifo, (void **)fin->extended_data, s->win_size);
745
+            if (ret < 0) {
746
+                av_frame_free(&fin);
747
+                return ret;
748
+            }
749
+
750
+            av_audio_fifo_drain(s->fifo, spf);
751
+
752
+            if (ret < s->win_size) {
753
+                for (ch = 0; ch < s->nb_display_channels; ch++) {
754
+                    memset(fin->extended_data[ch] + ret * sizeof(int16_t), 0,
755
+                           (s->win_size - ret) * sizeof(int16_t));
756
+                }
757
+            }
758
+
759
+            run_rdft(s, fin);
760
+            acalc_magnitudes(s);
761
+
762
+            consumed += spf;
763
+            if (consumed >= spb) {
764
+                int h = s->orientation == VERTICAL ? s->h : s->w;
765
+
766
+                scale_magnitudes(s, 1. / (consumed / spf));
767
+                plot_spectrum_column(inlink, fin);
768
+                consumed = 0;
769
+                x++;
770
+                for (ch = 0; ch < s->nb_display_channels; ch++)
771
+                    memset(s->magnitudes[ch], 0, h * sizeof(float));
772
+            }
773
+        }
774
+
775
+        av_frame_free(&fin);
776
+        s->outpicref->pts = 0;
777
+        ret = ff_filter_frame(outlink, s->outpicref);
778
+        s->outpicref = NULL;
779
+    }
780
+
781
+    return ret;
782
+}
783
+
784
+static int showspectrumpic_filter_frame(AVFilterLink *inlink, AVFrame *insamples)
785
+{
786
+    AVFilterContext *ctx = inlink->dst;
787
+    ShowSpectrumContext *s = ctx->priv;
788
+    int ret;
789
+
790
+    ret = av_audio_fifo_write(s->fifo, (void **)insamples->extended_data, insamples->nb_samples);
791
+    av_frame_free(&insamples);
792
+    return ret;
793
+}
794
+
795
+static const AVFilterPad showspectrumpic_inputs[] = {
796
+    {
797
+        .name         = "default",
798
+        .type         = AVMEDIA_TYPE_AUDIO,
799
+        .filter_frame = showspectrumpic_filter_frame,
800
+    },
801
+    { NULL }
802
+};
803
+
804
+static const AVFilterPad showspectrumpic_outputs[] = {
805
+    {
806
+        .name          = "default",
807
+        .type          = AVMEDIA_TYPE_VIDEO,
808
+        .config_props  = config_output,
809
+        .request_frame = showspectrumpic_request_frame,
810
+    },
811
+    { NULL }
812
+};
813
+
814
+AVFilter ff_avf_showspectrumpic = {
815
+    .name          = "showspectrumpic",
816
+    .description   = NULL_IF_CONFIG_SMALL("Convert input audio to a spectrum video output single picture."),
817
+    .uninit        = uninit,
818
+    .query_formats = query_formats,
819
+    .priv_size     = sizeof(ShowSpectrumContext),
820
+    .inputs        = showspectrumpic_inputs,
821
+    .outputs       = showspectrumpic_outputs,
822
+    .priv_class    = &showspectrumpic_class,
823
+};
824
+
825
+#endif // CONFIG_SHOWSPECTRUMPIC_FILTER
... ...
@@ -30,7 +30,7 @@
30 30
 #include "libavutil/version.h"
31 31
 
32 32
 #define LIBAVFILTER_VERSION_MAJOR   6
33
-#define LIBAVFILTER_VERSION_MINOR  22
33
+#define LIBAVFILTER_VERSION_MINOR  23
34 34
 #define LIBAVFILTER_VERSION_MICRO 100
35 35
 
36 36
 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \