]> git.sesse.net Git - ffmpeg/blobdiff - libavfilter/avf_showcqt.c
avfilter/avf_showcqt: add bar_t option
[ffmpeg] / libavfilter / avf_showcqt.c
index 16bb2be8d7d977d5e77fec2eabcda5f2479fdf63..a25176dff7dc6be902af856942dbd966a79bf98b 100644 (file)
 #include FT_FREETYPE_H
 #endif
 
+#if CONFIG_LIBFONTCONFIG
+#include <fontconfig/fontconfig.h>
+#endif
+
 #include "avf_showcqt.h"
 
 #define BASEFREQ        20.01523126408007475
@@ -46,6 +50,7 @@
 #define FONTCOLOR       "st(0, (midi(f)-59.5)/12);" \
     "st(1, if(between(ld(0),0,1), 0.5-0.5*cos(2*PI*ld(0)), 0));" \
     "r(1-ld(1)) + b(ld(1))"
+#define CSCHEME         "1|0.5|0|0|0.5|1"
 #define PTS_STEP 10
 #define PTS_TOLERANCE 1
 
@@ -70,6 +75,7 @@ static const AVOption showcqt_options[] = {
     { "gamma",    "set sonogram gamma", OFFSET(sono_g),     AV_OPT_TYPE_FLOAT, { .dbl = 3.0 },            1.0, 7.0,      FLAGS },
     { "bar_g",    "set bargraph gamma", OFFSET(bar_g),      AV_OPT_TYPE_FLOAT, { .dbl = 1.0 },            1.0, 7.0,      FLAGS },
     { "gamma2",   "set bargraph gamma", OFFSET(bar_g),      AV_OPT_TYPE_FLOAT, { .dbl = 1.0 },            1.0, 7.0,      FLAGS },
+    { "bar_t",  "set bar transparency", OFFSET(bar_t),      AV_OPT_TYPE_FLOAT, { .dbl = 1.0 },            0.0, 1.0,      FLAGS },
     { "timeclamp",     "set timeclamp", OFFSET(timeclamp), AV_OPT_TYPE_DOUBLE, { .dbl = 0.17 },           0.1, 1.0,      FLAGS },
     { "tc",            "set timeclamp", OFFSET(timeclamp), AV_OPT_TYPE_DOUBLE, { .dbl = 0.17 },           0.1, 1.0,      FLAGS },
     { "basefreq", "set base frequency", OFFSET(basefreq),  AV_OPT_TYPE_DOUBLE, { .dbl = BASEFREQ },      10.0, 100000.0, FLAGS },
@@ -78,11 +84,21 @@ static const AVOption showcqt_options[] = {
     { "tlength",         "set tlength", OFFSET(tlength),   AV_OPT_TYPE_STRING, { .str = TLENGTH },   CHAR_MIN, CHAR_MAX, FLAGS },
     { "count",   "set transform count", OFFSET(count),        AV_OPT_TYPE_INT, { .i64 = 6 },                1, 30,       FLAGS },
     { "fcount",  "set frequency count", OFFSET(fcount),       AV_OPT_TYPE_INT, { .i64 = 0 },                0, 10,       FLAGS },
-    { "fontfile",      "set axis font", OFFSET(fontfile),  AV_OPT_TYPE_STRING, { .str = NULL },      CHAR_MIN, CHAR_MAX, FLAGS },
+    { "fontfile", "set axis font file", OFFSET(fontfile),  AV_OPT_TYPE_STRING, { .str = NULL },      CHAR_MIN, CHAR_MAX, FLAGS },
+    { "font",          "set axis font", OFFSET(font),      AV_OPT_TYPE_STRING, { .str = NULL },      CHAR_MIN, CHAR_MAX, FLAGS },
     { "fontcolor",    "set font color", OFFSET(fontcolor), AV_OPT_TYPE_STRING, { .str = FONTCOLOR }, CHAR_MIN, CHAR_MAX, FLAGS },
     { "axisfile",     "set axis image", OFFSET(axisfile),  AV_OPT_TYPE_STRING, { .str = NULL },      CHAR_MIN, CHAR_MAX, FLAGS },
     { "axis",              "draw axis", OFFSET(axis),        AV_OPT_TYPE_BOOL, { .i64 = 1 },                0, 1,        FLAGS },
     { "text",              "draw axis", OFFSET(axis),        AV_OPT_TYPE_BOOL, { .i64 = 1 },                0, 1,        FLAGS },
+    { "csp",         "set color space", OFFSET(csp),          AV_OPT_TYPE_INT, { .i64 = AVCOL_SPC_UNSPECIFIED }, 0, INT_MAX, FLAGS, "csp" },
+        { "unspecified", "unspecified", 0,                  AV_OPT_TYPE_CONST, { .i64 = AVCOL_SPC_UNSPECIFIED }, 0, 0, FLAGS, "csp" },
+        { "bt709",             "bt709", 0,                  AV_OPT_TYPE_CONST, { .i64 = AVCOL_SPC_BT709 },       0, 0, FLAGS, "csp" },
+        { "fcc",                 "fcc", 0,                  AV_OPT_TYPE_CONST, { .i64 = AVCOL_SPC_FCC },         0, 0, FLAGS, "csp" },
+        { "bt470bg",         "bt470bg", 0,                  AV_OPT_TYPE_CONST, { .i64 = AVCOL_SPC_BT470BG },     0, 0, FLAGS, "csp" },
+        { "smpte170m",     "smpte170m", 0,                  AV_OPT_TYPE_CONST, { .i64 = AVCOL_SPC_SMPTE170M },   0, 0, FLAGS, "csp" },
+        { "smpte240m",     "smpte240m", 0,                  AV_OPT_TYPE_CONST, { .i64 = AVCOL_SPC_SMPTE240M },   0, 0, FLAGS, "csp" },
+        { "bt2020ncl",     "bt2020ncl", 0,                  AV_OPT_TYPE_CONST, { .i64 = AVCOL_SPC_BT2020_NCL },  0, 0, FLAGS, "csp" },
+    { "cscheme",    "set color scheme", OFFSET(cscheme),   AV_OPT_TYPE_STRING, { .str = CSCHEME },   CHAR_MIN, CHAR_MAX, FLAGS },
     { NULL }
 };
 
@@ -484,7 +500,7 @@ static int init_axis_color(ShowCQTContext *s, AVFrame *tmp, int half)
     return 0;
 }
 
-static int render_freetype(ShowCQTContext *s, AVFrame *tmp)
+static int render_freetype(ShowCQTContext *s, AVFrame *tmp, char *fontfile)
 {
 #if CONFIG_LIBFREETYPE
     const char *str = "EF G A BC D ";
@@ -498,13 +514,13 @@ static int render_freetype(ShowCQTContext *s, AVFrame *tmp)
     int non_monospace_warning = 0;
     int x;
 
-    if (!s->fontfile)
+    if (!fontfile)
         return AVERROR(EINVAL);
 
     if (FT_Init_FreeType(&lib))
         goto fail;
 
-    if (FT_New_Face(lib, s->fontfile, 0, &face))
+    if (FT_New_Face(lib, fontfile, 0, &face))
         goto fail;
 
     if (FT_Set_Char_Size(face, 16*64, 0, 0, 0))
@@ -557,17 +573,81 @@ static int render_freetype(ShowCQTContext *s, AVFrame *tmp)
     return 0;
 
 fail:
-    av_log(s->ctx, AV_LOG_WARNING, "error while loading freetype font, using default font instead.\n");
+    av_log(s->ctx, AV_LOG_WARNING, "error while loading freetype font.\n");
     FT_Done_Face(face);
     FT_Done_FreeType(lib);
     return AVERROR(EINVAL);
 #else
-    if (s->fontfile)
+    if (fontfile)
         av_log(s->ctx, AV_LOG_WARNING, "freetype is not available, ignoring fontfile option.\n");
     return AVERROR(EINVAL);
 #endif
 }
 
+static int render_fontconfig(ShowCQTContext *s, AVFrame *tmp, char* font)
+{
+#if CONFIG_LIBFONTCONFIG
+    FcConfig *fontconfig;
+    FcPattern *pat, *best;
+    FcResult result = FcResultMatch;
+    char *filename;
+    int i, ret;
+
+    if (!font)
+        return AVERROR(EINVAL);
+
+    for (i = 0; font[i]; i++) {
+        if (font[i] == '|')
+            font[i] = ':';
+    }
+
+    if (!(fontconfig = FcInitLoadConfigAndFonts())) {
+        av_log(s->ctx, AV_LOG_ERROR, "impossible to init fontconfig.\n");
+        return AVERROR_UNKNOWN;
+    }
+
+    if (!(pat = FcNameParse((uint8_t *)font))) {
+        av_log(s->ctx, AV_LOG_ERROR, "could not parse fontconfig pat.\n");
+        FcConfigDestroy(fontconfig);
+        return AVERROR(EINVAL);
+    }
+
+    FcDefaultSubstitute(pat);
+
+    if (!FcConfigSubstitute(fontconfig, pat, FcMatchPattern)) {
+        av_log(s->ctx, AV_LOG_ERROR, "could not substitue fontconfig options.\n");
+        FcPatternDestroy(pat);
+        FcConfigDestroy(fontconfig);
+        return AVERROR(ENOMEM);
+    }
+
+    best = FcFontMatch(fontconfig, pat, &result);
+    FcPatternDestroy(pat);
+
+    ret = AVERROR(EINVAL);
+    if (!best || result != FcResultMatch) {
+        av_log(s->ctx, AV_LOG_ERROR, "cannot find a valid font for %s.\n", font);
+        goto fail;
+    }
+
+    if (FcPatternGetString(best, FC_FILE, 0, (FcChar8 **)&filename) != FcResultMatch) {
+        av_log(s->ctx, AV_LOG_ERROR, "no file path for %s\n", font);
+        goto fail;
+    }
+
+    ret = render_freetype(s, tmp, filename);
+
+fail:
+    FcPatternDestroy(best);
+    FcConfigDestroy(fontconfig);
+    return ret;
+#else
+    if (font)
+        av_log(s->ctx, AV_LOG_WARNING, "fontconfig is not available, ignoring font option.\n");
+    return AVERROR(EINVAL);
+#endif
+}
+
 static int render_default_font(AVFrame *tmp)
 {
     const char *str = "EF G A BC D ";
@@ -607,7 +687,9 @@ static int init_axis_from_font(ShowCQTContext *s)
     if (!(s->axis_frame = av_frame_alloc()))
         goto fail;
 
-    if (render_freetype(s, tmp) < 0 && (default_font = 1, ret = render_default_font(tmp)) < 0)
+    if (render_freetype(s, tmp, s->fontfile) < 0 &&
+        render_fontconfig(s, tmp, s->font) < 0 &&
+        (default_font = 1, ret = render_default_font(tmp)) < 0)
         goto fail;
 
     if (default_font)
@@ -646,35 +728,35 @@ static float calculate_gamma(float v, float g)
     return expf(logf(v) / g);
 }
 
-static void rgb_from_cqt(ColorFloat *c, const FFTComplex *v, float g, int len)
+static void rgb_from_cqt(ColorFloat *c, const FFTComplex *v, float g, int len, float cscheme[6])
 {
     int x;
     for (x = 0; x < len; x++) {
-        c[x].rgb.r = 255.0f * calculate_gamma(FFMIN(1.0f, v[x].re), g);
-        c[x].rgb.g = 255.0f * calculate_gamma(FFMIN(1.0f, 0.5f * (v[x].re + v[x].im)), g);
-        c[x].rgb.b = 255.0f * calculate_gamma(FFMIN(1.0f, v[x].im), g);
+        c[x].rgb.r = 255.0f * calculate_gamma(FFMIN(1.0f, cscheme[0] * v[x].re + cscheme[3] * v[x].im), g);
+        c[x].rgb.g = 255.0f * calculate_gamma(FFMIN(1.0f, cscheme[1] * v[x].re + cscheme[4] * v[x].im), g);
+        c[x].rgb.b = 255.0f * calculate_gamma(FFMIN(1.0f, cscheme[2] * v[x].re + cscheme[5] * v[x].im), g);
     }
 }
 
-static void yuv_from_cqt(ColorFloat *c, const FFTComplex *v, float gamma, int len)
+static void yuv_from_cqt(ColorFloat *c, const FFTComplex *v, float gamma, int len, float cm[3][3], float cscheme[6])
 {
     int x;
     for (x = 0; x < len; x++) {
         float r, g, b;
-        r = calculate_gamma(FFMIN(1.0f, v[x].re), gamma);
-        g = calculate_gamma(FFMIN(1.0f, 0.5f * (v[x].re + v[x].im)), gamma);
-        b = calculate_gamma(FFMIN(1.0f, v[x].im), gamma);
-        c[x].yuv.y = 65.481f * r + 128.553f * g + 24.966f * b;
-        c[x].yuv.u = -37.797f * r - 74.203f * g + 112.0f * b;
-        c[x].yuv.v = 112.0f * r - 93.786f * g - 18.214 * b;
+        r = calculate_gamma(FFMIN(1.0f, cscheme[0] * v[x].re + cscheme[3] * v[x].im), gamma);
+        g = calculate_gamma(FFMIN(1.0f, cscheme[1] * v[x].re + cscheme[4] * v[x].im), gamma);
+        b = calculate_gamma(FFMIN(1.0f, cscheme[2] * v[x].re + cscheme[5] * v[x].im), gamma);
+        c[x].yuv.y = cm[0][0] * r + cm[0][1] * g + cm[0][2] * b;
+        c[x].yuv.u = cm[1][0] * r + cm[1][1] * g + cm[1][2] * b;
+        c[x].yuv.v = cm[2][0] * r + cm[2][1] * g + cm[2][2] * b;
     }
 }
 
 static void draw_bar_rgb(AVFrame *out, const float *h, const float *rcp_h,
-                         const ColorFloat *c, int bar_h)
+                         const ColorFloat *c, int bar_h, float bar_t)
 {
     int x, y, w = out->width;
-    float mul, ht, rcp_bar_h = 1.0f / bar_h;
+    float mul, ht, rcp_bar_h = 1.0f / bar_h, rcp_bar_t = 1.0f / bar_t;
     uint8_t *v = out->data[0], *lp;
     int ls = out->linesize[0];
 
@@ -688,6 +770,7 @@ static void draw_bar_rgb(AVFrame *out, const float *h, const float *rcp_h,
                 *lp++ = 0;
             } else {
                 mul = (h[x] - ht) * rcp_h[x];
+                mul = (mul < bar_t) ? (mul * rcp_bar_t) : 1.0f;
                 *lp++ = lrintf(mul * c[x].rgb.r);
                 *lp++ = lrintf(mul * c[x].rgb.g);
                 *lp++ = lrintf(mul * c[x].rgb.b);
@@ -704,6 +787,7 @@ do { \
         *lpv++ = 128; \
     } else { \
         mul = (h[x] - ht) * rcp_h[x]; \
+        mul = (mul < bar_t) ? (mul * rcp_bar_t) : 1.0f; \
         *lpy++ = lrintf(mul * c[x].yuv.y + 16.0f); \
         *lpu++ = lrintf(mul * c[x].yuv.u + 128.0f); \
         *lpv++ = lrintf(mul * c[x].yuv.v + 128.0f); \
@@ -716,15 +800,16 @@ do { \
         *lpy++ = 16; \
     } else { \
         mul = (h[x] - ht) * rcp_h[x]; \
+        mul = (mul < bar_t) ? (mul * rcp_bar_t) : 1.0f; \
         *lpy++ = lrintf(mul * c[x].yuv.y + 16.0f); \
     } \
 } while (0)
 
 static void draw_bar_yuv(AVFrame *out, const float *h, const float *rcp_h,
-                         const ColorFloat *c, int bar_h)
+                         const ColorFloat *c, int bar_h, float bar_t)
 {
     int x, y, yh, w = out->width;
-    float mul, ht, rcp_bar_h = 1.0f / bar_h;
+    float mul, ht, rcp_bar_h = 1.0f / bar_h, rcp_bar_t = 1.0f / bar_t;
     uint8_t *vy = out->data[0], *vu = out->data[1], *vv = out->data[2];
     uint8_t *lpy, *lpu, *lpv;
     int lsy = out->linesize[0], lsu = out->linesize[1], lsv = out->linesize[2];
@@ -1034,9 +1119,9 @@ static void process_cqt(ShowCQTContext *s)
     }
 
     if (s->format == AV_PIX_FMT_RGB24)
-        rgb_from_cqt(s->c_buf, s->cqt_result, s->sono_g, s->width);
+        rgb_from_cqt(s->c_buf, s->cqt_result, s->sono_g, s->width, s->cscheme_v);
     else
-        yuv_from_cqt(s->c_buf, s->cqt_result, s->sono_g, s->width);
+        yuv_from_cqt(s->c_buf, s->cqt_result, s->sono_g, s->width, s->cmatrix, s->cscheme_v);
 }
 
 static int plot_cqt(AVFilterContext *ctx, AVFrame **frameout)
@@ -1075,10 +1160,11 @@ static int plot_cqt(AVFilterContext *ctx, AVFrame **frameout)
             return AVERROR(ENOMEM);
         out->sample_aspect_ratio = av_make_q(1, 1);
         av_frame_set_color_range(out, AVCOL_RANGE_MPEG);
+        av_frame_set_colorspace(out, s->csp);
         UPDATE_TIME(s->alloc_time);
 
         if (s->bar_h) {
-            s->draw_bar(out, s->h_buf, s->rcp_h_buf, s->c_buf, s->bar_h);
+            s->draw_bar(out, s->h_buf, s->rcp_h_buf, s->c_buf, s->bar_h, s->bar_t);
             UPDATE_TIME(s->bar_time);
         }
 
@@ -1100,6 +1186,62 @@ static int plot_cqt(AVFilterContext *ctx, AVFrame **frameout)
     return 0;
 }
 
+static void init_colormatrix(ShowCQTContext *s)
+{
+    double kr, kg, kb;
+
+    /* from vf_colorspace.c */
+    switch (s->csp) {
+    default:
+        av_log(s->ctx, AV_LOG_WARNING, "unsupported colorspace, setting it to unspecified.\n");
+        s->csp = AVCOL_SPC_UNSPECIFIED;
+    case AVCOL_SPC_UNSPECIFIED:
+    case AVCOL_SPC_BT470BG:
+    case AVCOL_SPC_SMPTE170M:
+        kr = 0.299; kb = 0.114; break;
+    case AVCOL_SPC_BT709:
+        kr = 0.2126; kb = 0.0722; break;
+    case AVCOL_SPC_FCC:
+        kr = 0.30; kb = 0.11; break;
+    case AVCOL_SPC_SMPTE240M:
+        kr = 0.212; kb = 0.087; break;
+    case AVCOL_SPC_BT2020_NCL:
+        kr = 0.2627; kb = 0.0593; break;
+    }
+
+    kg = 1.0 - kr - kb;
+    s->cmatrix[0][0] = 219.0 * kr;
+    s->cmatrix[0][1] = 219.0 * kg;
+    s->cmatrix[0][2] = 219.0 * kb;
+    s->cmatrix[1][0] = -112.0 * kr / (1.0 - kb);
+    s->cmatrix[1][1] = -112.0 * kg / (1.0 - kb);
+    s->cmatrix[1][2] = 112.0;
+    s->cmatrix[2][0] = 112.0;
+    s->cmatrix[2][1] = -112.0 * kg / (1.0 - kr);
+    s->cmatrix[2][2] = -112.0 * kb / (1.0 - kr);
+}
+
+static int init_cscheme(ShowCQTContext *s)
+{
+    char tail[2];
+    int k;
+
+    if (sscanf(s->cscheme, " %f | %f | %f | %f | %f | %f %1s", &s->cscheme_v[0],
+        &s->cscheme_v[1], &s->cscheme_v[2], &s->cscheme_v[3], &s->cscheme_v[4],
+        &s->cscheme_v[5], tail) != 6)
+        goto fail;
+
+    for (k = 0; k < 6; k++)
+        if (isnan(s->cscheme_v[k]) || s->cscheme_v[k] < 0.0f || s->cscheme_v[k] > 1.0f)
+            goto fail;
+
+    return 0;
+
+fail:
+    av_log(s->ctx, AV_LOG_ERROR, "invalid cscheme.\n");
+    return AVERROR(EINVAL);
+}
+
 /* main filter control */
 static av_cold int init(AVFilterContext *ctx)
 {
@@ -1153,7 +1295,9 @@ static av_cold int init(AVFilterContext *ctx)
         } while(s->fcount * s->width < 1920 && s->fcount < 10);
     }
 
-    return 0;
+    init_colormatrix(s);
+
+    return init_cscheme(s);
 }
 
 static av_cold void uninit(AVFilterContext *ctx)