1 /*
2 * Copyright (c) 2014 Muhammad Faiz <mfcc64@gmail.com>
3 *
4 * This file is part of FFmpeg.
5 *
6 * FFmpeg is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * FFmpeg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with FFmpeg; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include "config.h"
32
33 #include <math.h>
34 #include <stdlib.h>
35
36 #if CONFIG_LIBFREETYPE
37 #include <ft2build.h>
38 #include FT_FREETYPE_H
39 #endif
40
41 /* this filter is designed to do 16 bins/semitones constant Q transform with Brown-Puckette algorithm
42 * start from E0 to D#10 (10 octaves)
43 * so there are 16 bins/semitones * 12 semitones/octaves * 10 octaves = 1920 bins
44 * match with full HD resolution */
45
46 #define VIDEO_WIDTH 1920
47 #define VIDEO_HEIGHT 1080
48 #define FONT_HEIGHT 32
49 #define SPECTOGRAM_HEIGHT ((VIDEO_HEIGHT-FONT_HEIGHT)/2)
50 #define SPECTOGRAM_START (VIDEO_HEIGHT-SPECTOGRAM_HEIGHT)
51 #define BASE_FREQ 20.051392800492
52 #define COEFF_CLAMP 1.0e-4
53 #define TLENGTH_MIN 0.001
54 #define TLENGTH_DEFAULT "384/f*tc/(384/f+tc)"
55 #define VOLUME_MIN 1e-10
56 #define VOLUME_MAX 100.0
57 #define FONTCOLOR_DEFAULT "st(0, (midi(f)-59.5)/12);" \
58 "st(1, if(between(ld(0),0,1), 0.5-0.5*cos(2*PI*ld(0)), 0));" \
59 "r(1-ld(1)) + b(ld(1))"
60
65
89 double timeclamp;
/* lower timeclamp, time-accurate, higher timeclamp, freq-accurate (at low freq)*/
90 float coeffclamp;
/* lower coeffclamp, more precise, higher coeffclamp, faster */
91 int fullhd;
/* if true, output video is at full HD resolution, otherwise it will be halved */
92 float gamma;
/* lower gamma, more contrast, higher gamma, more range */
93 float gamma2;
/* gamma of bargraph */
94 int fps;
/* the required fps is so strict, so it's enough to be int, but 24000/1001 etc cannot be encoded */
95 int count;
/* fps * count = transform rate */
97
98 #define OFFSET(x) offsetof(ShowCQTContext, x)
99 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
100
114 };
115
117
119 {
120 int k;
121
134 }
135
137 {
145 static const int samplerates[] = { 44100, 48000, -1 };
146
147 /* set input audio formats */
149 if (!formats)
152
154 if (!layouts)
157
159 if (!formats)
162
163 /* set output video format */
165 if (!formats)
168
169 return 0;
170 }
171
172 #if CONFIG_LIBFREETYPE
174 {
175 static const char str[] = "EF G A BC D ";
177 FT_Library lib =
NULL;
179 int video_scale = s->
fullhd ? 2 : 1;
182 int font_width = 8 * video_scale;
183 int font_repeat = font_width * 12;
184 int linear_hori_advance = font_width * 65536;
185 int non_monospace_warning = 0;
186 int x;
187
189
191 return;
192
193 if (FT_Init_FreeType(&lib))
194 goto fail;
195
196 if (FT_New_Face(lib, s->
fontfile, 0, &face))
197 goto fail;
198
199 if (FT_Set_Char_Size(face, 16*64, 0, 0, 0))
200 goto fail;
201
202 if (FT_Load_Char(face, 'A', FT_LOAD_RENDER))
203 goto fail;
204
205 if (FT_Set_Char_Size(face, 16*64 * linear_hori_advance / face->glyph->linearHoriAdvance, 0, 0, 0))
206 goto fail;
207
210 goto fail;
211
212 memset(s->
font_alpha, 0, font_height * video_width);
213
214 for (x = 0; x < 12; x++) {
215 int sx, sy, rx, bx, by, dx, dy;
216
217 if (str[x] == ' ')
218 continue;
219
220 if (FT_Load_Char(face, str[x], FT_LOAD_RENDER))
221 goto fail;
222
223 if (face->glyph->advance.x != font_width*64 && !non_monospace_warning) {
225 non_monospace_warning = 1;
226 }
227
228 sy = font_height - 4*video_scale - face->glyph->bitmap_top;
229 for (rx = 0; rx < 10; rx++) {
230 sx = rx * font_repeat + x * font_width + face->glyph->bitmap_left;
231 for (by = 0; by < face->glyph->bitmap.rows; by++) {
232 dy = by + sy;
233 if (dy < 0)
234 continue;
235 if (dy >= font_height)
236 break;
237
238 for (bx = 0; bx < face->glyph->bitmap.width; bx++) {
239 dx = bx + sx;
240 if (dx < 0)
241 continue;
242 if (dx >= video_width)
243 break;
244 s->
font_alpha[dy*video_width+dx] = face->glyph->bitmap.buffer[by*face->glyph->bitmap.width+bx];
245 }
246 }
247 }
248 }
249
250 FT_Done_Face(face);
251 FT_Done_FreeType(lib);
252 return;
253
254 fail:
256 FT_Done_Face(face);
257 FT_Done_FreeType(lib);
259 return;
260 }
261 #endif
262
264 {
265 double ret = 12200.0*12200.0 * (f*f*f*f);
266 ret /= (f*f + 20.6*20.6) * (f*f + 12200.0*12200.0) *
267 sqrt((f*f + 107.7*107.7) * (f*f + 737.9*737.9));
269 }
270
272 {
273 double ret = 12200.0*12200.0 * (f*f*f);
274 ret /= (f*f + 20.6*20.6) * (f*f + 12200.0*12200.0) * sqrt(f*f + 158.5*158.5);
276 }
277
279 {
280 double ret = 12200.0*12200.0 * (f*f);
281 ret /= (f*f + 20.6*20.6) * (f*f + 12200.0*12200.0);
283 }
284
285 static double midi(
void *p,
double f)
286 {
287 return log2(f/440.0) * 12.0 + 69.0;
288 }
289
291 {
292 x = av_clipd(x, 0.0, 1.0);
293 return (int)(x*255.0+0.5) << 16;
294 }
295
297 {
298 x = av_clipd(x, 0.0, 1.0);
299 return (int)(x*255.0+0.5) << 8;
300 }
301
303 {
304 x = av_clipd(x, 0.0, 1.0);
305 return (int)(x*255.0+0.5);
306 }
307
309 {
311 return 1;
312 else
313 return -1;
314 }
315
317 {
323 static const char *
const expr_vars[] = {
"timeclamp",
"tc",
"frequency",
"freq",
"f",
NULL };
324 static const char *
const expr_func_names[] = {
"a_weighting",
"b_weighting",
"c_weighting",
NULL };
325 static const char *
const expr_fontcolor_func_names[] = {
"midi",
"r",
"g",
"b",
NULL };
328 int fft_len, k, x,
y,
ret;
329 int num_coeffs = 0;
331 double max_len = rate * (double) s->
timeclamp;
333 int video_scale = s->
fullhd ? 2 : 1;
337
340
344 }
345
351
354
355 #if CONFIG_LIBFREETYPE
356 load_freetype_font(ctx);
357 #else
361 #endif
362
366 if (ret < 0)
367 goto eval_error;
368
371 if (ret < 0)
372 goto eval_error;
373
375 expr_fontcolor_funcs,
NULL,
NULL, 0, ctx);
376 if (ret < 0)
377 goto eval_error;
378
380 int hlen = fft_len >> 1;
381 float total = 0;
382 float partial = 0;
384 double tlen, tlength, volume;
386 /* a window function from Albert H. Nuttall,
387 * "Some Windows with Very Good Sidelobe Behavior"
388 * -93.32 dB peak sidelobe and 18 dB/octave asymptotic decay
389 * coefficient normalized to a0 = 1 */
390 double a0 = 0.355768;
391 double a1 = 0.487396/
a0;
392 double a2 = 0.144232/
a0;
393 double a3 = 0.012604/
a0;
394 double sv_step, cv_step, sv, cv;
395 double sw_step, cw_step, sw, cw, w;
396
398 if (
isnan(tlength)) {
407 }
408
418 }
419
420 if (s->
fullhd || !(k & 1)) {
422 fontcolor_value[0] = (fontcolor >> 16) & 0xFF;
423 fontcolor_value[1] = (fontcolor >> 8) & 0xFF;
424 fontcolor_value[2] = fontcolor & 0xFF;
425 fontcolor_value += 3;
426 }
427
428 tlen = tlength * rate;
431 s->
fft_data[hlen].
re = (1.0 + a1 + a2 +
a3) * (1.0/tlen) * volume * (1.0/fft_len);
433 sv_step = sv = sin(2.0*
M_PI*freq*(1.0/rate));
434 cv_step = cv = cos(2.0*
M_PI*freq*(1.0/rate));
435 /* also optimizing window func */
436 sw_step = sw = sin(2.0*
M_PI*(1.0/tlen));
437 cw_step = cw = cos(2.0*
M_PI*(1.0/tlen));
438 for (x = 1; x < 0.5 * tlen; x++) {
439 double cv_tmp, cw_tmp;
440 double cw2, cw3, sw2;
441
442 cw2 = cw * cw - sw * sw;
443 sw2 = cw * sw + sw * cw;
444 cw3 = cw * cw2 - sw * sw2;
445 w = (1.0 + a1 * cw + a2 * cw2 + a3 * cw3) * (1.0/tlen) * volume * (1.0/fft_len);
450
451 cv_tmp = cv * cv_step - sv * sv_step;
452 sv = sv * cv_step + cv * sv_step;
453 cv = cv_tmp;
454 cw_tmp = cw * cw_step - sw * sw_step;
455 sw = sw * cw_step + cw * sw_step;
456 cw = cw_tmp;
457 }
458 for (; x < hlen; x++) {
463 }
466
467 for (x = 0; x < fft_len; x++) {
470 }
471
473 for (x = 0; x < fft_len; x++)
475
476 for (x = 0; x < fft_len; x++) {
484 goto eval_error;
485 }
488 break;
489 }
490 }
491 }
496 av_log(ctx,
AV_LOG_INFO,
"Elapsed time %.6f s (fft_len=%u, num_coeffs=%u)\n", 1e-6 * (end_time-start_time), fft_len, num_coeffs);
497
498 outlink->
w = video_width;
499 outlink->
h = video_height;
500
507
511
515
519 return 0;
520
521 eval_error:
526 }
527
529 {
537 int video_scale = s->
fullhd ? 2 : 1;
542
543 /* real part contains left samples, imaginary part contains right samples */
547
548 /* separate left and right, (and multiply by 2.0) */
553 for (x = 1; x <= fft_len >> 1; x++) {
555
560
565 }
566
567 /* calculating cqt */
570 float g = 1.0f / s->
gamma;
571 float g2 = 1.0f / s->
gamma2;
574
582 }
583 /* result is power, not amplitude */
584 result[x][0] = l.
re * l.
re + l.
im * l.
im;
585 result[x][2] = r.
re * r.
re + r.
im * r.
im;
586 result[x][1] = 0.5f * (result[x][0] + result[x][2]);
587 result[x][3] = (g2 == 1.0f) ? result[x][1] :
powf(result[x][1], g2);
588 result[x][0] = 255.0f *
powf(
FFMIN(1.0f,result[x][0]), g);
589 result[x][1] = 255.0f *
powf(
FFMIN(1.0f,result[x][1]), g);
590 result[x][2] = 255.0f *
powf(
FFMIN(1.0f,result[x][2]), g);
591 }
592
594 for (x = 0; x < video_width; x++) {
595 result[x][0] = 0.5f * (result[2*x][0] + result[2*x+1][0]);
596 result[x][1] = 0.5f * (result[2*x][1] + result[2*x+1][1]);
597 result[x][2] = 0.5f * (result[2*x][2] + result[2*x+1][2]);
598 result[x][3] = 0.5f * (result[2*x][3] + result[2*x+1][3]);
599 }
600 }
601
602 for (x = 0; x < video_width; x++) {
606 }
607
608 /* drawing */
611 float rcp_result[VIDEO_WIDTH];
612 int total_length = linesize * spectogram_height;
614
615 for (x = 0; x < video_width; x++)
616 rcp_result[x] = 1.0f / (result[x][3]+0.0001f);
617
618 /* drawing bar */
619 for (y = 0; y < spectogram_height; y++) {
620 float height = (spectogram_height -
y) * (1.0f/spectogram_height);
621 uint8_t *lineptr = data + y * linesize;
622 for (x = 0; x < video_width; x++) {
623 float mul;
624 if (result[x][3] <= height) {
625 *lineptr++ = 0;
626 *lineptr++ = 0;
627 *lineptr++ = 0;
628 } else {
629 mul = (result[x][3] -
height) * rcp_result[x];
630 *lineptr++ = mul * result[x][0] + 0.5f;
631 *lineptr++ = mul * result[x][1] + 0.5f;
632 *lineptr++ = mul * result[x][2] + 0.5f;
633 }
634 }
635 }
636
637 /* drawing font */
639 for (y = 0; y < font_height; y++) {
640 uint8_t *lineptr = data + (spectogram_height +
y) * linesize;
643 for (x = 0; x < video_width; x++) {
645 lineptr[3*x] = (spectogram_src[3*x] * (255-
alpha) + fontcolor_value[0] * alpha + 255) >> 8;
646 lineptr[3*x+1] = (spectogram_src[3*x+1] * (255-
alpha) + fontcolor_value[1] * alpha + 255) >> 8;
647 lineptr[3*x+2] = (spectogram_src[3*x+2] * (255-
alpha) + fontcolor_value[2] * alpha + 255) >> 8;
648 fontcolor_value += 3;
649 }
650 }
651 } else {
652 for (y = 0; y < font_height; y++) {
653 uint8_t *lineptr = data + (spectogram_height +
y) * linesize;
655 }
656 for (x = 0; x < video_width; x += video_width/10) {
658 static const char str[] = "EF G A BC D ";
659 uint8_t *startptr = data + spectogram_height * linesize + x * 3;
660 for (u = 0; str[
u]; u++) {
662 for (v = 0; v < 16; v++) {
663 uint8_t *p = startptr + v * linesize * video_scale + 8 * 3 * u * video_scale;
664 int ux = x + 8 * u * video_scale;
666 for (mask = 0x80;
mask; mask >>= 1) {
671 if (video_scale == 2) {
672 p[linesize] = p[0];
673 p[linesize+1] = p[1];
674 p[linesize+2] = p[2];
678 }
679 }
680 p += 3 * video_scale;
681 ux += video_scale;
682 }
683 }
684 }
685 }
686 }
687
688 /* drawing spectogram/sonogram */
689 data += spectogram_start * linesize;
691
692 data += total_length - back_length;
693 if (back_length)
695
700 }
704 }
705
707 {
712 int remaining;
713 float *audio_data;
714
715 if (!insamples) {
720 if (ret < 0)
722 for (x = 0; x < (fft_len-step); x++)
725 }
727 }
728
730 audio_data = (
float*) insamples->
data[0];
731
732 while (remaining) {
740 }
742 if (ret < 0) {
745 }
747 for (m = 0; m < fft_len-step; m++)
750 } else {
754 for (m = 0; m < remaining; m++) {
757 }
759 remaining = 0;
760 }
761 }
763 return 0;
764 }
765
767 {
771
773 do {
776
780 }
781
783 {
787 },
789 };
790
792 {
797 },
799 };
800
803 .description =
NULL_IF_CONFIG_SMALL(
"Convert input audio to a CQT (Constant Q Transform) spectrum video output."),
809 .priv_class = &showcqt_class,
810 };