| 1 | /* |
| 2 | * Copyright (c) 2010 Mark Heath mjpeg0 @ silicontrip dot org |
| 3 | * Copyright (c) 2014 Clément Bœsch |
| 4 | * Copyright (c) 2014 Dave Rice @dericed |
| 5 | * |
| 6 | * This file is part of FFmpeg. |
| 7 | * |
| 8 | * FFmpeg is free software; you can redistribute it and/or |
| 9 | * modify it under the terms of the GNU Lesser General Public |
| 10 | * License as published by the Free Software Foundation; either |
| 11 | * version 2.1 of the License, or (at your option) any later version. |
| 12 | * |
| 13 | * FFmpeg is distributed in the hope that it will be useful, |
| 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 16 | * Lesser General Public License for more details. |
| 17 | * |
| 18 | * You should have received a copy of the GNU Lesser General Public |
| 19 | * License along with FFmpeg; if not, write to the Free Software |
| 20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| 21 | */ |
| 22 | |
| 23 | #include "libavutil/opt.h" |
| 24 | #include "libavutil/pixdesc.h" |
| 25 | #include "internal.h" |
| 26 | |
| 27 | enum FilterMode { |
| 28 | FILTER_NONE = -1, |
| 29 | FILTER_TOUT, |
| 30 | FILTER_VREP, |
| 31 | FILTER_BRNG, |
| 32 | FILT_NUMB |
| 33 | }; |
| 34 | |
| 35 | typedef struct { |
| 36 | const AVClass *class; |
| 37 | int chromah; // height of chroma plane |
| 38 | int chromaw; // width of chroma plane |
| 39 | int hsub; // horizontal subsampling |
| 40 | int vsub; // vertical subsampling |
| 41 | int fs; // pixel count per frame |
| 42 | int cfs; // pixel count per frame of chroma planes |
| 43 | enum FilterMode outfilter; |
| 44 | int filters; |
| 45 | AVFrame *frame_prev; |
| 46 | uint8_t rgba_color[4]; |
| 47 | int yuv_color[3]; |
| 48 | int nb_jobs; |
| 49 | int *jobs_rets; |
| 50 | |
| 51 | AVFrame *frame_sat; |
| 52 | AVFrame *frame_hue; |
| 53 | } SignalstatsContext; |
| 54 | |
| 55 | typedef struct ThreadData { |
| 56 | const AVFrame *in; |
| 57 | AVFrame *out; |
| 58 | } ThreadData; |
| 59 | |
| 60 | typedef struct ThreadDataHueSatMetrics { |
| 61 | const AVFrame *src; |
| 62 | AVFrame *dst_sat, *dst_hue; |
| 63 | } ThreadDataHueSatMetrics; |
| 64 | |
| 65 | #define OFFSET(x) offsetof(SignalstatsContext, x) |
| 66 | #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM |
| 67 | |
| 68 | static const AVOption signalstats_options[] = { |
| 69 | {"stat", "set statistics filters", OFFSET(filters), AV_OPT_TYPE_FLAGS, {.i64=0}, 0, INT_MAX, FLAGS, "filters"}, |
| 70 | {"tout", "analyze pixels for temporal outliers", 0, AV_OPT_TYPE_CONST, {.i64=1<<FILTER_TOUT}, 0, 0, FLAGS, "filters"}, |
| 71 | {"vrep", "analyze video lines for vertical line repetition", 0, AV_OPT_TYPE_CONST, {.i64=1<<FILTER_VREP}, 0, 0, FLAGS, "filters"}, |
| 72 | {"brng", "analyze for pixels outside of broadcast range", 0, AV_OPT_TYPE_CONST, {.i64=1<<FILTER_BRNG}, 0, 0, FLAGS, "filters"}, |
| 73 | {"out", "set video filter", OFFSET(outfilter), AV_OPT_TYPE_INT, {.i64=FILTER_NONE}, -1, FILT_NUMB-1, FLAGS, "out"}, |
| 74 | {"tout", "highlight pixels that depict temporal outliers", 0, AV_OPT_TYPE_CONST, {.i64=FILTER_TOUT}, 0, 0, FLAGS, "out"}, |
| 75 | {"vrep", "highlight video lines that depict vertical line repetition", 0, AV_OPT_TYPE_CONST, {.i64=FILTER_VREP}, 0, 0, FLAGS, "out"}, |
| 76 | {"brng", "highlight pixels that are outside of broadcast range", 0, AV_OPT_TYPE_CONST, {.i64=FILTER_BRNG}, 0, 0, FLAGS, "out"}, |
| 77 | {"c", "set highlight color", OFFSET(rgba_color), AV_OPT_TYPE_COLOR, {.str="yellow"}, .flags=FLAGS}, |
| 78 | {"color", "set highlight color", OFFSET(rgba_color), AV_OPT_TYPE_COLOR, {.str="yellow"}, .flags=FLAGS}, |
| 79 | {NULL} |
| 80 | }; |
| 81 | |
| 82 | AVFILTER_DEFINE_CLASS(signalstats); |
| 83 | |
| 84 | static av_cold int init(AVFilterContext *ctx) |
| 85 | { |
| 86 | uint8_t r, g, b; |
| 87 | SignalstatsContext *s = ctx->priv; |
| 88 | |
| 89 | if (s->outfilter != FILTER_NONE) |
| 90 | s->filters |= 1 << s->outfilter; |
| 91 | |
| 92 | r = s->rgba_color[0]; |
| 93 | g = s->rgba_color[1]; |
| 94 | b = s->rgba_color[2]; |
| 95 | s->yuv_color[0] = (( 66*r + 129*g + 25*b + (1<<7)) >> 8) + 16; |
| 96 | s->yuv_color[1] = ((-38*r + -74*g + 112*b + (1<<7)) >> 8) + 128; |
| 97 | s->yuv_color[2] = ((112*r + -94*g + -18*b + (1<<7)) >> 8) + 128; |
| 98 | return 0; |
| 99 | } |
| 100 | |
| 101 | static av_cold void uninit(AVFilterContext *ctx) |
| 102 | { |
| 103 | SignalstatsContext *s = ctx->priv; |
| 104 | av_frame_free(&s->frame_prev); |
| 105 | av_frame_free(&s->frame_sat); |
| 106 | av_frame_free(&s->frame_hue); |
| 107 | av_freep(&s->jobs_rets); |
| 108 | } |
| 109 | |
| 110 | static int query_formats(AVFilterContext *ctx) |
| 111 | { |
| 112 | // TODO: add more |
| 113 | static const enum AVPixelFormat pix_fmts[] = { |
| 114 | AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV411P, |
| 115 | AV_PIX_FMT_YUV440P, |
| 116 | AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ411P, |
| 117 | AV_PIX_FMT_YUVJ440P, |
| 118 | AV_PIX_FMT_NONE |
| 119 | }; |
| 120 | |
| 121 | ff_set_common_formats(ctx, ff_make_format_list(pix_fmts)); |
| 122 | return 0; |
| 123 | } |
| 124 | |
| 125 | static AVFrame *alloc_frame(enum AVPixelFormat pixfmt, int w, int h) |
| 126 | { |
| 127 | AVFrame *frame = av_frame_alloc(); |
| 128 | if (!frame) |
| 129 | return NULL; |
| 130 | |
| 131 | frame->format = pixfmt; |
| 132 | frame->width = w; |
| 133 | frame->height = h; |
| 134 | |
| 135 | if (av_frame_get_buffer(frame, 32) < 0) |
| 136 | return NULL; |
| 137 | |
| 138 | return frame; |
| 139 | } |
| 140 | |
| 141 | static int config_props(AVFilterLink *outlink) |
| 142 | { |
| 143 | AVFilterContext *ctx = outlink->src; |
| 144 | SignalstatsContext *s = ctx->priv; |
| 145 | AVFilterLink *inlink = outlink->src->inputs[0]; |
| 146 | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(outlink->format); |
| 147 | s->hsub = desc->log2_chroma_w; |
| 148 | s->vsub = desc->log2_chroma_h; |
| 149 | |
| 150 | outlink->w = inlink->w; |
| 151 | outlink->h = inlink->h; |
| 152 | |
| 153 | s->chromaw = FF_CEIL_RSHIFT(inlink->w, s->hsub); |
| 154 | s->chromah = FF_CEIL_RSHIFT(inlink->h, s->vsub); |
| 155 | |
| 156 | s->fs = inlink->w * inlink->h; |
| 157 | s->cfs = s->chromaw * s->chromah; |
| 158 | |
| 159 | s->nb_jobs = FFMAX(1, FFMIN(inlink->h, ctx->graph->nb_threads)); |
| 160 | s->jobs_rets = av_malloc_array(s->nb_jobs, sizeof(*s->jobs_rets)); |
| 161 | if (!s->jobs_rets) |
| 162 | return AVERROR(ENOMEM); |
| 163 | |
| 164 | s->frame_sat = alloc_frame(AV_PIX_FMT_GRAY8, inlink->w, inlink->h); |
| 165 | s->frame_hue = alloc_frame(AV_PIX_FMT_GRAY16, inlink->w, inlink->h); |
| 166 | if (!s->frame_sat || !s->frame_hue) |
| 167 | return AVERROR(ENOMEM); |
| 168 | |
| 169 | return 0; |
| 170 | } |
| 171 | |
| 172 | static void burn_frame(const SignalstatsContext *s, AVFrame *f, int x, int y) |
| 173 | { |
| 174 | const int chromax = x >> s->hsub; |
| 175 | const int chromay = y >> s->vsub; |
| 176 | f->data[0][y * f->linesize[0] + x] = s->yuv_color[0]; |
| 177 | f->data[1][chromay * f->linesize[1] + chromax] = s->yuv_color[1]; |
| 178 | f->data[2][chromay * f->linesize[2] + chromax] = s->yuv_color[2]; |
| 179 | } |
| 180 | |
| 181 | static int filter_brng(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) |
| 182 | { |
| 183 | ThreadData *td = arg; |
| 184 | const SignalstatsContext *s = ctx->priv; |
| 185 | const AVFrame *in = td->in; |
| 186 | AVFrame *out = td->out; |
| 187 | const int w = in->width; |
| 188 | const int h = in->height; |
| 189 | const int slice_start = (h * jobnr ) / nb_jobs; |
| 190 | const int slice_end = (h * (jobnr+1)) / nb_jobs; |
| 191 | int x, y, score = 0; |
| 192 | |
| 193 | for (y = slice_start; y < slice_end; y++) { |
| 194 | const int yc = y >> s->vsub; |
| 195 | const uint8_t *pluma = &in->data[0][y * in->linesize[0]]; |
| 196 | const uint8_t *pchromau = &in->data[1][yc * in->linesize[1]]; |
| 197 | const uint8_t *pchromav = &in->data[2][yc * in->linesize[2]]; |
| 198 | |
| 199 | for (x = 0; x < w; x++) { |
| 200 | const int xc = x >> s->hsub; |
| 201 | const int luma = pluma[x]; |
| 202 | const int chromau = pchromau[xc]; |
| 203 | const int chromav = pchromav[xc]; |
| 204 | const int filt = luma < 16 || luma > 235 || |
| 205 | chromau < 16 || chromau > 240 || |
| 206 | chromav < 16 || chromav > 240; |
| 207 | score += filt; |
| 208 | if (out && filt) |
| 209 | burn_frame(s, out, x, y); |
| 210 | } |
| 211 | } |
| 212 | return score; |
| 213 | } |
| 214 | |
| 215 | static int filter_tout_outlier(uint8_t x, uint8_t y, uint8_t z) |
| 216 | { |
| 217 | return ((abs(x - y) + abs (z - y)) / 2) - abs(z - x) > 4; // make 4 configurable? |
| 218 | } |
| 219 | |
| 220 | static int filter_tout(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) |
| 221 | { |
| 222 | ThreadData *td = arg; |
| 223 | const SignalstatsContext *s = ctx->priv; |
| 224 | const AVFrame *in = td->in; |
| 225 | AVFrame *out = td->out; |
| 226 | const int w = in->width; |
| 227 | const int h = in->height; |
| 228 | const int slice_start = (h * jobnr ) / nb_jobs; |
| 229 | const int slice_end = (h * (jobnr+1)) / nb_jobs; |
| 230 | const uint8_t *p = in->data[0]; |
| 231 | int lw = in->linesize[0]; |
| 232 | int x, y, score = 0, filt; |
| 233 | |
| 234 | for (y = slice_start; y < slice_end; y++) { |
| 235 | |
| 236 | if (y - 1 < 0 || y + 1 >= h) |
| 237 | continue; |
| 238 | |
| 239 | // detect two pixels above and below (to eliminate interlace artefacts) |
| 240 | // should check that video format is infact interlaced. |
| 241 | |
| 242 | #define FILTER(i, j) \ |
| 243 | filter_tout_outlier(p[(y-j) * lw + x + i], \ |
| 244 | p[ y * lw + x + i], \ |
| 245 | p[(y+j) * lw + x + i]) |
| 246 | |
| 247 | #define FILTER3(j) (FILTER(-1, j) && FILTER(0, j) && FILTER(1, j)) |
| 248 | |
| 249 | if (y - 2 >= 0 && y + 2 < h) { |
| 250 | for (x = 1; x < w - 1; x++) { |
| 251 | filt = FILTER3(2) && FILTER3(1); |
| 252 | score += filt; |
| 253 | if (filt && out) |
| 254 | burn_frame(s, out, x, y); |
| 255 | } |
| 256 | } else { |
| 257 | for (x = 1; x < w - 1; x++) { |
| 258 | filt = FILTER3(1); |
| 259 | score += filt; |
| 260 | if (filt && out) |
| 261 | burn_frame(s, out, x, y); |
| 262 | } |
| 263 | } |
| 264 | } |
| 265 | return score; |
| 266 | } |
| 267 | |
| 268 | #define VREP_START 4 |
| 269 | |
| 270 | static int filter_vrep(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) |
| 271 | { |
| 272 | ThreadData *td = arg; |
| 273 | const SignalstatsContext *s = ctx->priv; |
| 274 | const AVFrame *in = td->in; |
| 275 | AVFrame *out = td->out; |
| 276 | const int w = in->width; |
| 277 | const int h = in->height; |
| 278 | const int slice_start = (h * jobnr ) / nb_jobs; |
| 279 | const int slice_end = (h * (jobnr+1)) / nb_jobs; |
| 280 | const uint8_t *p = in->data[0]; |
| 281 | const int lw = in->linesize[0]; |
| 282 | int x, y, score = 0; |
| 283 | |
| 284 | for (y = slice_start; y < slice_end; y++) { |
| 285 | const int y2lw = (y - VREP_START) * lw; |
| 286 | const int ylw = y * lw; |
| 287 | int filt, totdiff = 0; |
| 288 | |
| 289 | if (y < VREP_START) |
| 290 | continue; |
| 291 | |
| 292 | for (x = 0; x < w; x++) |
| 293 | totdiff += abs(p[y2lw + x] - p[ylw + x]); |
| 294 | filt = totdiff < w; |
| 295 | |
| 296 | score += filt; |
| 297 | if (filt && out) |
| 298 | for (x = 0; x < w; x++) |
| 299 | burn_frame(s, out, x, y); |
| 300 | } |
| 301 | return score * w; |
| 302 | } |
| 303 | |
| 304 | static const struct { |
| 305 | const char *name; |
| 306 | int (*process)(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs); |
| 307 | } filters_def[] = { |
| 308 | {"TOUT", filter_tout}, |
| 309 | {"VREP", filter_vrep}, |
| 310 | {"BRNG", filter_brng}, |
| 311 | {NULL} |
| 312 | }; |
| 313 | |
| 314 | #define DEPTH 256 |
| 315 | |
| 316 | static int compute_sat_hue_metrics(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) |
| 317 | { |
| 318 | int i, j; |
| 319 | ThreadDataHueSatMetrics *td = arg; |
| 320 | const SignalstatsContext *s = ctx->priv; |
| 321 | const AVFrame *src = td->src; |
| 322 | AVFrame *dst_sat = td->dst_sat; |
| 323 | AVFrame *dst_hue = td->dst_hue; |
| 324 | |
| 325 | const int slice_start = (s->chromah * jobnr ) / nb_jobs; |
| 326 | const int slice_end = (s->chromah * (jobnr+1)) / nb_jobs; |
| 327 | |
| 328 | const int lsz_u = src->linesize[1]; |
| 329 | const int lsz_v = src->linesize[2]; |
| 330 | const uint8_t *p_u = src->data[1] + slice_start * lsz_u; |
| 331 | const uint8_t *p_v = src->data[2] + slice_start * lsz_v; |
| 332 | |
| 333 | const int lsz_sat = dst_sat->linesize[0]; |
| 334 | const int lsz_hue = dst_hue->linesize[0]; |
| 335 | uint8_t *p_sat = dst_sat->data[0] + slice_start * lsz_sat; |
| 336 | uint8_t *p_hue = dst_hue->data[0] + slice_start * lsz_hue; |
| 337 | |
| 338 | for (j = slice_start; j < slice_end; j++) { |
| 339 | for (i = 0; i < s->chromaw; i++) { |
| 340 | const int yuvu = p_u[i]; |
| 341 | const int yuvv = p_v[i]; |
| 342 | p_sat[i] = hypot(yuvu - 128, yuvv - 128); // int or round? |
| 343 | ((int16_t*)p_hue)[i] = floor((180 / M_PI) * atan2f(yuvu-128, yuvv-128) + 180); |
| 344 | } |
| 345 | p_u += lsz_u; |
| 346 | p_v += lsz_v; |
| 347 | p_sat += lsz_sat; |
| 348 | p_hue += lsz_hue; |
| 349 | } |
| 350 | |
| 351 | return 0; |
| 352 | } |
| 353 | |
| 354 | static int filter_frame(AVFilterLink *link, AVFrame *in) |
| 355 | { |
| 356 | AVFilterContext *ctx = link->dst; |
| 357 | SignalstatsContext *s = ctx->priv; |
| 358 | AVFilterLink *outlink = ctx->outputs[0]; |
| 359 | AVFrame *out = in; |
| 360 | int i, j; |
| 361 | int w = 0, cw = 0, // in |
| 362 | pw = 0, cpw = 0; // prev |
| 363 | int fil; |
| 364 | char metabuf[128]; |
| 365 | unsigned int histy[DEPTH] = {0}, |
| 366 | histu[DEPTH] = {0}, |
| 367 | histv[DEPTH] = {0}, |
| 368 | histhue[360] = {0}, |
| 369 | histsat[DEPTH] = {0}; // limited to 8 bit data. |
| 370 | int miny = -1, minu = -1, minv = -1; |
| 371 | int maxy = -1, maxu = -1, maxv = -1; |
| 372 | int lowy = -1, lowu = -1, lowv = -1; |
| 373 | int highy = -1, highu = -1, highv = -1; |
| 374 | int minsat = -1, maxsat = -1, lowsat = -1, highsat = -1; |
| 375 | int lowp, highp, clowp, chighp; |
| 376 | int accy, accu, accv; |
| 377 | int accsat, acchue = 0; |
| 378 | int medhue, maxhue; |
| 379 | int toty = 0, totu = 0, totv = 0, totsat=0; |
| 380 | int tothue = 0; |
| 381 | int dify = 0, difu = 0, difv = 0; |
| 382 | |
| 383 | int filtot[FILT_NUMB] = {0}; |
| 384 | AVFrame *prev; |
| 385 | |
| 386 | AVFrame *sat = s->frame_sat; |
| 387 | AVFrame *hue = s->frame_hue; |
| 388 | const uint8_t *p_sat = sat->data[0]; |
| 389 | const uint8_t *p_hue = hue->data[0]; |
| 390 | const int lsz_sat = sat->linesize[0]; |
| 391 | const int lsz_hue = hue->linesize[0]; |
| 392 | ThreadDataHueSatMetrics td_huesat = { |
| 393 | .src = in, |
| 394 | .dst_sat = sat, |
| 395 | .dst_hue = hue, |
| 396 | }; |
| 397 | |
| 398 | if (!s->frame_prev) |
| 399 | s->frame_prev = av_frame_clone(in); |
| 400 | |
| 401 | prev = s->frame_prev; |
| 402 | |
| 403 | if (s->outfilter != FILTER_NONE) { |
| 404 | out = av_frame_clone(in); |
| 405 | av_frame_make_writable(out); |
| 406 | } |
| 407 | |
| 408 | ctx->internal->execute(ctx, compute_sat_hue_metrics, &td_huesat, |
| 409 | NULL, FFMIN(s->chromah, ctx->graph->nb_threads)); |
| 410 | |
| 411 | // Calculate luma histogram and difference with previous frame or field. |
| 412 | for (j = 0; j < link->h; j++) { |
| 413 | for (i = 0; i < link->w; i++) { |
| 414 | const int yuv = in->data[0][w + i]; |
| 415 | histy[yuv]++; |
| 416 | dify += abs(yuv - prev->data[0][pw + i]); |
| 417 | } |
| 418 | w += in->linesize[0]; |
| 419 | pw += prev->linesize[0]; |
| 420 | } |
| 421 | |
| 422 | // Calculate chroma histogram and difference with previous frame or field. |
| 423 | for (j = 0; j < s->chromah; j++) { |
| 424 | for (i = 0; i < s->chromaw; i++) { |
| 425 | const int yuvu = in->data[1][cw+i]; |
| 426 | const int yuvv = in->data[2][cw+i]; |
| 427 | histu[yuvu]++; |
| 428 | difu += abs(yuvu - prev->data[1][cpw+i]); |
| 429 | histv[yuvv]++; |
| 430 | difv += abs(yuvv - prev->data[2][cpw+i]); |
| 431 | |
| 432 | histsat[p_sat[i]]++; |
| 433 | histhue[((int16_t*)p_hue)[i]]++; |
| 434 | } |
| 435 | cw += in->linesize[1]; |
| 436 | cpw += prev->linesize[1]; |
| 437 | p_sat += lsz_sat; |
| 438 | p_hue += lsz_hue; |
| 439 | } |
| 440 | |
| 441 | for (fil = 0; fil < FILT_NUMB; fil ++) { |
| 442 | if (s->filters & 1<<fil) { |
| 443 | ThreadData td = { |
| 444 | .in = in, |
| 445 | .out = out != in && s->outfilter == fil ? out : NULL, |
| 446 | }; |
| 447 | memset(s->jobs_rets, 0, s->nb_jobs * sizeof(*s->jobs_rets)); |
| 448 | ctx->internal->execute(ctx, filters_def[fil].process, |
| 449 | &td, s->jobs_rets, s->nb_jobs); |
| 450 | for (i = 0; i < s->nb_jobs; i++) |
| 451 | filtot[fil] += s->jobs_rets[i]; |
| 452 | } |
| 453 | } |
| 454 | |
| 455 | // find low / high based on histogram percentile |
| 456 | // these only need to be calculated once. |
| 457 | |
| 458 | lowp = lrint(s->fs * 10 / 100.); |
| 459 | highp = lrint(s->fs * 90 / 100.); |
| 460 | clowp = lrint(s->cfs * 10 / 100.); |
| 461 | chighp = lrint(s->cfs * 90 / 100.); |
| 462 | |
| 463 | accy = accu = accv = accsat = 0; |
| 464 | for (fil = 0; fil < DEPTH; fil++) { |
| 465 | if (miny < 0 && histy[fil]) miny = fil; |
| 466 | if (minu < 0 && histu[fil]) minu = fil; |
| 467 | if (minv < 0 && histv[fil]) minv = fil; |
| 468 | if (minsat < 0 && histsat[fil]) minsat = fil; |
| 469 | |
| 470 | if (histy[fil]) maxy = fil; |
| 471 | if (histu[fil]) maxu = fil; |
| 472 | if (histv[fil]) maxv = fil; |
| 473 | if (histsat[fil]) maxsat = fil; |
| 474 | |
| 475 | toty += histy[fil] * fil; |
| 476 | totu += histu[fil] * fil; |
| 477 | totv += histv[fil] * fil; |
| 478 | totsat += histsat[fil] * fil; |
| 479 | |
| 480 | accy += histy[fil]; |
| 481 | accu += histu[fil]; |
| 482 | accv += histv[fil]; |
| 483 | accsat += histsat[fil]; |
| 484 | |
| 485 | if (lowy == -1 && accy >= lowp) lowy = fil; |
| 486 | if (lowu == -1 && accu >= clowp) lowu = fil; |
| 487 | if (lowv == -1 && accv >= clowp) lowv = fil; |
| 488 | if (lowsat == -1 && accsat >= clowp) lowsat = fil; |
| 489 | |
| 490 | if (highy == -1 && accy >= highp) highy = fil; |
| 491 | if (highu == -1 && accu >= chighp) highu = fil; |
| 492 | if (highv == -1 && accv >= chighp) highv = fil; |
| 493 | if (highsat == -1 && accsat >= chighp) highsat = fil; |
| 494 | } |
| 495 | |
| 496 | maxhue = histhue[0]; |
| 497 | medhue = -1; |
| 498 | for (fil = 0; fil < 360; fil++) { |
| 499 | tothue += histhue[fil] * fil; |
| 500 | acchue += histhue[fil]; |
| 501 | |
| 502 | if (medhue == -1 && acchue > s->cfs / 2) |
| 503 | medhue = fil; |
| 504 | if (histhue[fil] > maxhue) { |
| 505 | maxhue = histhue[fil]; |
| 506 | } |
| 507 | } |
| 508 | |
| 509 | av_frame_free(&s->frame_prev); |
| 510 | s->frame_prev = av_frame_clone(in); |
| 511 | |
| 512 | #define SET_META(key, fmt, val) do { \ |
| 513 | snprintf(metabuf, sizeof(metabuf), fmt, val); \ |
| 514 | av_dict_set(&out->metadata, "lavfi.signalstats." key, metabuf, 0); \ |
| 515 | } while (0) |
| 516 | |
| 517 | SET_META("YMIN", "%d", miny); |
| 518 | SET_META("YLOW", "%d", lowy); |
| 519 | SET_META("YAVG", "%g", 1.0 * toty / s->fs); |
| 520 | SET_META("YHIGH", "%d", highy); |
| 521 | SET_META("YMAX", "%d", maxy); |
| 522 | |
| 523 | SET_META("UMIN", "%d", minu); |
| 524 | SET_META("ULOW", "%d", lowu); |
| 525 | SET_META("UAVG", "%g", 1.0 * totu / s->cfs); |
| 526 | SET_META("UHIGH", "%d", highu); |
| 527 | SET_META("UMAX", "%d", maxu); |
| 528 | |
| 529 | SET_META("VMIN", "%d", minv); |
| 530 | SET_META("VLOW", "%d", lowv); |
| 531 | SET_META("VAVG", "%g", 1.0 * totv / s->cfs); |
| 532 | SET_META("VHIGH", "%d", highv); |
| 533 | SET_META("VMAX", "%d", maxv); |
| 534 | |
| 535 | SET_META("SATMIN", "%d", minsat); |
| 536 | SET_META("SATLOW", "%d", lowsat); |
| 537 | SET_META("SATAVG", "%g", 1.0 * totsat / s->cfs); |
| 538 | SET_META("SATHIGH", "%d", highsat); |
| 539 | SET_META("SATMAX", "%d", maxsat); |
| 540 | |
| 541 | SET_META("HUEMED", "%d", medhue); |
| 542 | SET_META("HUEAVG", "%g", 1.0 * tothue / s->cfs); |
| 543 | |
| 544 | SET_META("YDIF", "%g", 1.0 * dify / s->fs); |
| 545 | SET_META("UDIF", "%g", 1.0 * difu / s->cfs); |
| 546 | SET_META("VDIF", "%g", 1.0 * difv / s->cfs); |
| 547 | |
| 548 | for (fil = 0; fil < FILT_NUMB; fil ++) { |
| 549 | if (s->filters & 1<<fil) { |
| 550 | char metaname[128]; |
| 551 | snprintf(metabuf, sizeof(metabuf), "%g", 1.0 * filtot[fil] / s->fs); |
| 552 | snprintf(metaname, sizeof(metaname), "lavfi.signalstats.%s", filters_def[fil].name); |
| 553 | av_dict_set(&out->metadata, metaname, metabuf, 0); |
| 554 | } |
| 555 | } |
| 556 | |
| 557 | if (in != out) |
| 558 | av_frame_free(&in); |
| 559 | return ff_filter_frame(outlink, out); |
| 560 | } |
| 561 | |
| 562 | static const AVFilterPad signalstats_inputs[] = { |
| 563 | { |
| 564 | .name = "default", |
| 565 | .type = AVMEDIA_TYPE_VIDEO, |
| 566 | .filter_frame = filter_frame, |
| 567 | }, |
| 568 | { NULL } |
| 569 | }; |
| 570 | |
| 571 | static const AVFilterPad signalstats_outputs[] = { |
| 572 | { |
| 573 | .name = "default", |
| 574 | .config_props = config_props, |
| 575 | .type = AVMEDIA_TYPE_VIDEO, |
| 576 | }, |
| 577 | { NULL } |
| 578 | }; |
| 579 | |
| 580 | AVFilter ff_vf_signalstats = { |
| 581 | .name = "signalstats", |
| 582 | .description = "Generate statistics from video analysis.", |
| 583 | .init = init, |
| 584 | .uninit = uninit, |
| 585 | .query_formats = query_formats, |
| 586 | .priv_size = sizeof(SignalstatsContext), |
| 587 | .inputs = signalstats_inputs, |
| 588 | .outputs = signalstats_outputs, |
| 589 | .priv_class = &signalstats_class, |
| 590 | .flags = AVFILTER_FLAG_SLICE_THREADS, |
| 591 | }; |