| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * Original MPlayer filters by Richard Felker, Hampa Hug, Daniel Moreno, | ||
| 3 | * and Michael Niedermeyer. | ||
| 4 | * | ||
| 5 | * Copyright (c) 2014 James Darnley <james.darnley@gmail.com> | ||
| 6 | * Copyright (c) 2015 Arwa Arif <arwaarif1994@gmail.com> | ||
| 7 | * | ||
| 8 | * This file is part of FFmpeg. | ||
| 9 | * | ||
| 10 | * FFmpeg is free software; you can redistribute it and/or modify | ||
| 11 | * it under the terms of the GNU General Public License as published by | ||
| 12 | * the Free Software Foundation; either version 2 of the License, or | ||
| 13 | * (at your option) any later version. | ||
| 14 | * | ||
| 15 | * FFmpeg is distributed in the hope that it will be useful, | ||
| 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 18 | * GNU General Public License for more details. | ||
| 19 | * | ||
| 20 | * You should have received a copy of the GNU General Public License along | ||
| 21 | * with FFmpeg; if not, write to the Free Software Foundation, Inc., | ||
| 22 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 23 | */ | ||
| 24 | |||
| 25 | /** | ||
| 26 | * @file | ||
| 27 | * very simple video equalizer | ||
| 28 | */ | ||
| 29 | |||
| 30 | #include "libavutil/common.h" | ||
| 31 | #include "libavutil/imgutils.h" | ||
| 32 | #include "libavutil/opt.h" | ||
| 33 | #include "libavutil/pixdesc.h" | ||
| 34 | |||
| 35 | #include "filters.h" | ||
| 36 | #include "vf_eq.h" | ||
| 37 | #include "video.h" | ||
| 38 | |||
| 39 | ✗ | static void create_lut(EQParameters *param) | |
| 40 | { | ||
| 41 | int i; | ||
| 42 | ✗ | double g = 1.0 / param->gamma; | |
| 43 | ✗ | double lw = 1.0 - param->gamma_weight; | |
| 44 | |||
| 45 | ✗ | for (i = 0; i < 256; i++) { | |
| 46 | ✗ | double v = i / 255.0; | |
| 47 | ✗ | v = param->contrast * (v - 0.5) + 0.5 + param->brightness; | |
| 48 | |||
| 49 | ✗ | if (v <= 0.0) { | |
| 50 | ✗ | param->lut[i] = 0; | |
| 51 | } else { | ||
| 52 | ✗ | v = v * lw + pow(v, g) * param->gamma_weight; | |
| 53 | |||
| 54 | ✗ | if (v >= 1.0) | |
| 55 | ✗ | param->lut[i] = 255; | |
| 56 | else | ||
| 57 | ✗ | param->lut[i] = 256.0 * v; | |
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | ✗ | param->lut_clean = 1; | |
| 62 | ✗ | } | |
| 63 | |||
| 64 | ✗ | static void apply_lut(EQParameters *param, uint8_t *dst, int dst_stride, | |
| 65 | const uint8_t *src, int src_stride, int w, int h) | ||
| 66 | { | ||
| 67 | int x, y; | ||
| 68 | |||
| 69 | ✗ | if (!param->lut_clean) | |
| 70 | ✗ | create_lut(param); | |
| 71 | |||
| 72 | ✗ | for (y = 0; y < h; y++) { | |
| 73 | ✗ | for (x = 0; x < w; x++) { | |
| 74 | ✗ | dst[y * dst_stride + x] = param->lut[src[y * src_stride + x]]; | |
| 75 | } | ||
| 76 | } | ||
| 77 | ✗ | } | |
| 78 | |||
| 79 | ✗ | static void check_values(EQParameters *param, EQContext *eq) | |
| 80 | { | ||
| 81 | ✗ | if (param->contrast == 1.0 && param->brightness == 0.0 && param->gamma == 1.0) | |
| 82 | ✗ | param->adjust = NULL; | |
| 83 | ✗ | else if (param->gamma == 1.0 && fabs(param->contrast) < 7.9) | |
| 84 | ✗ | param->adjust = eq->process; | |
| 85 | else | ||
| 86 | ✗ | param->adjust = apply_lut; | |
| 87 | ✗ | } | |
| 88 | |||
| 89 | ✗ | static void set_contrast(EQContext *eq) | |
| 90 | { | ||
| 91 | ✗ | eq->contrast = av_clipf(av_expr_eval(eq->contrast_pexpr, eq->var_values, eq), -1000.0, 1000.0); | |
| 92 | ✗ | eq->param[0].contrast = eq->contrast; | |
| 93 | ✗ | eq->param[0].lut_clean = 0; | |
| 94 | ✗ | check_values(&eq->param[0], eq); | |
| 95 | ✗ | } | |
| 96 | |||
| 97 | ✗ | static void set_brightness(EQContext *eq) | |
| 98 | { | ||
| 99 | ✗ | eq->brightness = av_clipf(av_expr_eval(eq->brightness_pexpr, eq->var_values, eq), -1.0, 1.0); | |
| 100 | ✗ | eq->param[0].brightness = eq->brightness; | |
| 101 | ✗ | eq->param[0].lut_clean = 0; | |
| 102 | ✗ | check_values(&eq->param[0], eq); | |
| 103 | ✗ | } | |
| 104 | |||
| 105 | ✗ | static void set_gamma(EQContext *eq) | |
| 106 | { | ||
| 107 | int i; | ||
| 108 | |||
| 109 | ✗ | eq->gamma = av_clipf(av_expr_eval(eq->gamma_pexpr, eq->var_values, eq), 0.1, 10.0); | |
| 110 | ✗ | eq->gamma_r = av_clipf(av_expr_eval(eq->gamma_r_pexpr, eq->var_values, eq), 0.1, 10.0); | |
| 111 | ✗ | eq->gamma_g = av_clipf(av_expr_eval(eq->gamma_g_pexpr, eq->var_values, eq), 0.1, 10.0); | |
| 112 | ✗ | eq->gamma_b = av_clipf(av_expr_eval(eq->gamma_b_pexpr, eq->var_values, eq), 0.1, 10.0); | |
| 113 | ✗ | eq->gamma_weight = av_clipf(av_expr_eval(eq->gamma_weight_pexpr, eq->var_values, eq), 0.0, 1.0); | |
| 114 | |||
| 115 | ✗ | eq->param[0].gamma = eq->gamma * eq->gamma_g; | |
| 116 | ✗ | eq->param[1].gamma = sqrt(eq->gamma_b / eq->gamma_g); | |
| 117 | ✗ | eq->param[2].gamma = sqrt(eq->gamma_r / eq->gamma_g); | |
| 118 | |||
| 119 | ✗ | for (i = 0; i < 3; i++) { | |
| 120 | ✗ | eq->param[i].gamma_weight = eq->gamma_weight; | |
| 121 | ✗ | eq->param[i].lut_clean = 0; | |
| 122 | ✗ | check_values(&eq->param[i], eq); | |
| 123 | } | ||
| 124 | ✗ | } | |
| 125 | |||
| 126 | ✗ | static void set_saturation(EQContext *eq) | |
| 127 | { | ||
| 128 | int i; | ||
| 129 | |||
| 130 | ✗ | eq->saturation = av_clipf(av_expr_eval(eq->saturation_pexpr, eq->var_values, eq), 0.0, 3.0); | |
| 131 | |||
| 132 | ✗ | for (i = 1; i < 3; i++) { | |
| 133 | ✗ | eq->param[i].contrast = eq->saturation; | |
| 134 | ✗ | eq->param[i].lut_clean = 0; | |
| 135 | ✗ | check_values(&eq->param[i], eq); | |
| 136 | } | ||
| 137 | ✗ | } | |
| 138 | |||
| 139 | ✗ | static int set_expr(AVExpr **pexpr, const char *expr, const char *option, void *log_ctx) | |
| 140 | { | ||
| 141 | int ret; | ||
| 142 | ✗ | AVExpr *old = NULL; | |
| 143 | |||
| 144 | ✗ | if (*pexpr) | |
| 145 | ✗ | old = *pexpr; | |
| 146 | ✗ | ret = av_expr_parse(pexpr, expr, var_names, NULL, NULL, NULL, NULL, 0, log_ctx); | |
| 147 | ✗ | if (ret < 0) { | |
| 148 | ✗ | av_log(log_ctx, AV_LOG_ERROR, | |
| 149 | "Error when parsing the expression '%s' for %s\n", | ||
| 150 | expr, option); | ||
| 151 | ✗ | *pexpr = old; | |
| 152 | ✗ | return ret; | |
| 153 | } | ||
| 154 | |||
| 155 | ✗ | av_expr_free(old); | |
| 156 | ✗ | return 0; | |
| 157 | } | ||
| 158 | |||
| 159 | ✗ | static int initialize(AVFilterContext *ctx) | |
| 160 | { | ||
| 161 | ✗ | EQContext *eq = ctx->priv; | |
| 162 | int ret; | ||
| 163 | ✗ | ff_eq_init(eq); | |
| 164 | |||
| 165 | ✗ | if ((ret = set_expr(&eq->contrast_pexpr, eq->contrast_expr, "contrast", ctx)) < 0 || | |
| 166 | ✗ | (ret = set_expr(&eq->brightness_pexpr, eq->brightness_expr, "brightness", ctx)) < 0 || | |
| 167 | ✗ | (ret = set_expr(&eq->saturation_pexpr, eq->saturation_expr, "saturation", ctx)) < 0 || | |
| 168 | ✗ | (ret = set_expr(&eq->gamma_pexpr, eq->gamma_expr, "gamma", ctx)) < 0 || | |
| 169 | ✗ | (ret = set_expr(&eq->gamma_r_pexpr, eq->gamma_r_expr, "gamma_r", ctx)) < 0 || | |
| 170 | ✗ | (ret = set_expr(&eq->gamma_g_pexpr, eq->gamma_g_expr, "gamma_g", ctx)) < 0 || | |
| 171 | ✗ | (ret = set_expr(&eq->gamma_b_pexpr, eq->gamma_b_expr, "gamma_b", ctx)) < 0 || | |
| 172 | ✗ | (ret = set_expr(&eq->gamma_weight_pexpr, eq->gamma_weight_expr, "gamma_weight", ctx)) < 0 ) | |
| 173 | ✗ | return ret; | |
| 174 | |||
| 175 | ✗ | if (eq->eval_mode == EVAL_MODE_INIT) { | |
| 176 | ✗ | set_gamma(eq); | |
| 177 | ✗ | set_contrast(eq); | |
| 178 | ✗ | set_brightness(eq); | |
| 179 | ✗ | set_saturation(eq); | |
| 180 | } | ||
| 181 | |||
| 182 | ✗ | return 0; | |
| 183 | } | ||
| 184 | |||
| 185 | ✗ | static av_cold void uninit(AVFilterContext *ctx) | |
| 186 | { | ||
| 187 | ✗ | EQContext *eq = ctx->priv; | |
| 188 | |||
| 189 | ✗ | av_expr_free(eq->contrast_pexpr); eq->contrast_pexpr = NULL; | |
| 190 | ✗ | av_expr_free(eq->brightness_pexpr); eq->brightness_pexpr = NULL; | |
| 191 | ✗ | av_expr_free(eq->saturation_pexpr); eq->saturation_pexpr = NULL; | |
| 192 | ✗ | av_expr_free(eq->gamma_pexpr); eq->gamma_pexpr = NULL; | |
| 193 | ✗ | av_expr_free(eq->gamma_weight_pexpr); eq->gamma_weight_pexpr = NULL; | |
| 194 | ✗ | av_expr_free(eq->gamma_r_pexpr); eq->gamma_r_pexpr = NULL; | |
| 195 | ✗ | av_expr_free(eq->gamma_g_pexpr); eq->gamma_g_pexpr = NULL; | |
| 196 | ✗ | av_expr_free(eq->gamma_b_pexpr); eq->gamma_b_pexpr = NULL; | |
| 197 | ✗ | } | |
| 198 | |||
| 199 | ✗ | static int config_props(AVFilterLink *inlink) | |
| 200 | { | ||
| 201 | ✗ | FilterLink *l = ff_filter_link(inlink); | |
| 202 | ✗ | EQContext *eq = inlink->dst->priv; | |
| 203 | |||
| 204 | ✗ | eq->var_values[VAR_N] = 0; | |
| 205 | ✗ | eq->var_values[VAR_R] = l->frame_rate.num == 0 || l->frame_rate.den == 0 ? | |
| 206 | ✗ | NAN : av_q2d(l->frame_rate); | |
| 207 | |||
| 208 | ✗ | return 0; | |
| 209 | } | ||
| 210 | |||
| 211 | static const enum AVPixelFormat pixel_fmts_eq[] = { | ||
| 212 | AV_PIX_FMT_GRAY8, | ||
| 213 | AV_PIX_FMT_YUV410P, | ||
| 214 | AV_PIX_FMT_YUV411P, | ||
| 215 | AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUVA420P, | ||
| 216 | AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUVA422P, | ||
| 217 | AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUVA444P, | ||
| 218 | AV_PIX_FMT_NONE | ||
| 219 | }; | ||
| 220 | |||
| 221 | ✗ | static int filter_frame(AVFilterLink *inlink, AVFrame *in) | |
| 222 | { | ||
| 223 | ✗ | FilterLink *inl = ff_filter_link(inlink); | |
| 224 | ✗ | AVFilterContext *ctx = inlink->dst; | |
| 225 | ✗ | AVFilterLink *outlink = inlink->dst->outputs[0]; | |
| 226 | ✗ | EQContext *eq = ctx->priv; | |
| 227 | AVFrame *out; | ||
| 228 | const AVPixFmtDescriptor *desc; | ||
| 229 | int i; | ||
| 230 | |||
| 231 | ✗ | out = ff_get_video_buffer(outlink, inlink->w, inlink->h); | |
| 232 | ✗ | if (!out) { | |
| 233 | ✗ | av_frame_free(&in); | |
| 234 | ✗ | return AVERROR(ENOMEM); | |
| 235 | } | ||
| 236 | |||
| 237 | ✗ | av_frame_copy_props(out, in); | |
| 238 | ✗ | desc = av_pix_fmt_desc_get(inlink->format); | |
| 239 | |||
| 240 | ✗ | eq->var_values[VAR_N] = inl->frame_count_out; | |
| 241 | ✗ | eq->var_values[VAR_T] = TS2T(in->pts, inlink->time_base); | |
| 242 | |||
| 243 | ✗ | if (eq->eval_mode == EVAL_MODE_FRAME) { | |
| 244 | ✗ | set_gamma(eq); | |
| 245 | ✗ | set_contrast(eq); | |
| 246 | ✗ | set_brightness(eq); | |
| 247 | ✗ | set_saturation(eq); | |
| 248 | } | ||
| 249 | |||
| 250 | ✗ | for (i = 0; i < desc->nb_components; i++) { | |
| 251 | ✗ | int w = inlink->w; | |
| 252 | ✗ | int h = inlink->h; | |
| 253 | |||
| 254 | ✗ | if (i == 1 || i == 2) { | |
| 255 | ✗ | w = AV_CEIL_RSHIFT(w, desc->log2_chroma_w); | |
| 256 | ✗ | h = AV_CEIL_RSHIFT(h, desc->log2_chroma_h); | |
| 257 | } | ||
| 258 | |||
| 259 | ✗ | if (i == 3 || !eq->param[i].adjust) | |
| 260 | ✗ | av_image_copy_plane(out->data[i], out->linesize[i], | |
| 261 | ✗ | in->data[i], in->linesize[i], w, h); | |
| 262 | |||
| 263 | else | ||
| 264 | ✗ | eq->param[i].adjust(&eq->param[i], out->data[i], out->linesize[i], | |
| 265 | ✗ | in->data[i], in->linesize[i], w, h); | |
| 266 | } | ||
| 267 | |||
| 268 | ✗ | av_frame_free(&in); | |
| 269 | ✗ | return ff_filter_frame(outlink, out); | |
| 270 | } | ||
| 271 | |||
| 272 | ✗ | static inline int set_param(AVExpr **pexpr, const char *args, const char *cmd, | |
| 273 | void (*set_fn)(EQContext *eq), AVFilterContext *ctx) | ||
| 274 | { | ||
| 275 | ✗ | EQContext *eq = ctx->priv; | |
| 276 | int ret; | ||
| 277 | ✗ | if ((ret = set_expr(pexpr, args, cmd, ctx)) < 0) | |
| 278 | ✗ | return ret; | |
| 279 | ✗ | if (eq->eval_mode == EVAL_MODE_INIT) | |
| 280 | ✗ | set_fn(eq); | |
| 281 | ✗ | return 0; | |
| 282 | } | ||
| 283 | |||
| 284 | ✗ | static int process_command(AVFilterContext *ctx, const char *cmd, const char *args, | |
| 285 | char *res, int res_len, int flags) | ||
| 286 | { | ||
| 287 | ✗ | EQContext *eq = ctx->priv; | |
| 288 | |||
| 289 | #define SET_PARAM(param_name, set_fn_name) \ | ||
| 290 | if (!strcmp(cmd, #param_name)) return set_param(&eq->param_name##_pexpr, args, cmd, set_##set_fn_name, ctx); | ||
| 291 | |||
| 292 | ✗ | SET_PARAM(contrast, contrast) | |
| 293 | ✗ | else SET_PARAM(brightness, brightness) | |
| 294 | ✗ | else SET_PARAM(saturation, saturation) | |
| 295 | ✗ | else SET_PARAM(gamma, gamma) | |
| 296 | ✗ | else SET_PARAM(gamma_r, gamma) | |
| 297 | ✗ | else SET_PARAM(gamma_g, gamma) | |
| 298 | ✗ | else SET_PARAM(gamma_b, gamma) | |
| 299 | ✗ | else SET_PARAM(gamma_weight, gamma) | |
| 300 | ✗ | else return AVERROR(ENOSYS); | |
| 301 | } | ||
| 302 | |||
| 303 | static const AVFilterPad eq_inputs[] = { | ||
| 304 | { | ||
| 305 | .name = "default", | ||
| 306 | .type = AVMEDIA_TYPE_VIDEO, | ||
| 307 | .filter_frame = filter_frame, | ||
| 308 | .config_props = config_props, | ||
| 309 | }, | ||
| 310 | }; | ||
| 311 | |||
| 312 | #define OFFSET(x) offsetof(EQContext, x) | ||
| 313 | #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM | ||
| 314 | #define TFLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_RUNTIME_PARAM | ||
| 315 | static const AVOption eq_options[] = { | ||
| 316 | { "contrast", "set the contrast adjustment, negative values give a negative image", | ||
| 317 | OFFSET(contrast_expr), AV_OPT_TYPE_STRING, {.str = "1.0"}, 0, 0, TFLAGS }, | ||
| 318 | { "brightness", "set the brightness adjustment", | ||
| 319 | OFFSET(brightness_expr), AV_OPT_TYPE_STRING, {.str = "0.0"}, 0, 0, TFLAGS }, | ||
| 320 | { "saturation", "set the saturation adjustment", | ||
| 321 | OFFSET(saturation_expr), AV_OPT_TYPE_STRING, {.str = "1.0"}, 0, 0, TFLAGS }, | ||
| 322 | { "gamma", "set the initial gamma value", | ||
| 323 | OFFSET(gamma_expr), AV_OPT_TYPE_STRING, {.str = "1.0"}, 0, 0, TFLAGS }, | ||
| 324 | { "gamma_r", "gamma value for red", | ||
| 325 | OFFSET(gamma_r_expr), AV_OPT_TYPE_STRING, {.str = "1.0"}, 0, 0, TFLAGS }, | ||
| 326 | { "gamma_g", "gamma value for green", | ||
| 327 | OFFSET(gamma_g_expr), AV_OPT_TYPE_STRING, {.str = "1.0"}, 0, 0, TFLAGS }, | ||
| 328 | { "gamma_b", "gamma value for blue", | ||
| 329 | OFFSET(gamma_b_expr), AV_OPT_TYPE_STRING, {.str = "1.0"}, 0, 0, TFLAGS }, | ||
| 330 | { "gamma_weight", "set the gamma weight which reduces the effect of gamma on bright areas", | ||
| 331 | OFFSET(gamma_weight_expr), AV_OPT_TYPE_STRING, {.str = "1.0"}, 0, 0, TFLAGS }, | ||
| 332 | { "eval", "specify when to evaluate expressions", OFFSET(eval_mode), AV_OPT_TYPE_INT, {.i64 = EVAL_MODE_INIT}, 0, EVAL_MODE_NB-1, FLAGS, .unit = "eval" }, | ||
| 333 | { "init", "eval expressions once during initialization", 0, AV_OPT_TYPE_CONST, {.i64=EVAL_MODE_INIT}, .flags = FLAGS, .unit = "eval" }, | ||
| 334 | { "frame", "eval expressions per-frame", 0, AV_OPT_TYPE_CONST, {.i64=EVAL_MODE_FRAME}, .flags = FLAGS, .unit = "eval" }, | ||
| 335 | { NULL } | ||
| 336 | }; | ||
| 337 | |||
| 338 | AVFILTER_DEFINE_CLASS(eq); | ||
| 339 | |||
| 340 | const FFFilter ff_vf_eq = { | ||
| 341 | .p.name = "eq", | ||
| 342 | .p.description = NULL_IF_CONFIG_SMALL("Adjust brightness, contrast, gamma, and saturation."), | ||
| 343 | .p.priv_class = &eq_class, | ||
| 344 | .p.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, | ||
| 345 | .priv_size = sizeof(EQContext), | ||
| 346 | FILTER_INPUTS(eq_inputs), | ||
| 347 | FILTER_OUTPUTS(ff_video_default_filterpad), | ||
| 348 | FILTER_PIXFMTS_ARRAY(pixel_fmts_eq), | ||
| 349 | .process_command = process_command, | ||
| 350 | .init = initialize, | ||
| 351 | .uninit = uninit, | ||
| 352 | }; | ||
| 353 |