| Line | Branch | Exec | Source |
|---|---|---|---|
| 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 | ||
| 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/intreadwrite.h" | ||
| 24 | #include "libavutil/mem.h" | ||
| 25 | #include "libavutil/opt.h" | ||
| 26 | #include "libavutil/pixdesc.h" | ||
| 27 | #include "filters.h" | ||
| 28 | |||
| 29 | enum FilterMode { | ||
| 30 | FILTER_NONE = -1, | ||
| 31 | FILTER_TOUT, | ||
| 32 | FILTER_VREP, | ||
| 33 | FILTER_BRNG, | ||
| 34 | FILT_NUMB | ||
| 35 | }; | ||
| 36 | |||
| 37 | typedef struct SignalstatsContext { | ||
| 38 | const AVClass *class; | ||
| 39 | int chromah; // height of chroma plane | ||
| 40 | int chromaw; // width of chroma plane | ||
| 41 | int hsub; // horizontal subsampling | ||
| 42 | int vsub; // vertical subsampling | ||
| 43 | int depth; // pixel depth | ||
| 44 | int fs; // pixel count per frame | ||
| 45 | int cfs; // pixel count per frame of chroma planes | ||
| 46 | int outfilter; // FilterMode | ||
| 47 | int filters; | ||
| 48 | AVFrame *frame_prev; | ||
| 49 | uint8_t rgba_color[4]; | ||
| 50 | int yuv_color[3]; | ||
| 51 | int nb_jobs; | ||
| 52 | int *jobs_rets; | ||
| 53 | |||
| 54 | int maxsize; // history stats array size | ||
| 55 | int *histy, *histu, *histv, *histsat; | ||
| 56 | |||
| 57 | AVFrame *frame_sat; | ||
| 58 | AVFrame *frame_hue; | ||
| 59 | } SignalstatsContext; | ||
| 60 | |||
| 61 | typedef struct ThreadData { | ||
| 62 | const AVFrame *in; | ||
| 63 | AVFrame *out; | ||
| 64 | } ThreadData; | ||
| 65 | |||
| 66 | typedef struct ThreadDataHueSatMetrics { | ||
| 67 | const AVFrame *src; | ||
| 68 | AVFrame *dst_sat, *dst_hue; | ||
| 69 | } ThreadDataHueSatMetrics; | ||
| 70 | |||
| 71 | #define OFFSET(x) offsetof(SignalstatsContext, x) | ||
| 72 | #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM | ||
| 73 | |||
| 74 | static const AVOption signalstats_options[] = { | ||
| 75 | {"stat", "set statistics filters", OFFSET(filters), AV_OPT_TYPE_FLAGS, {.i64=0}, 0, INT_MAX, FLAGS, .unit = "filters"}, | ||
| 76 | {"tout", "analyze pixels for temporal outliers", 0, AV_OPT_TYPE_CONST, {.i64=1<<FILTER_TOUT}, 0, 0, FLAGS, .unit = "filters"}, | ||
| 77 | {"vrep", "analyze video lines for vertical line repetition", 0, AV_OPT_TYPE_CONST, {.i64=1<<FILTER_VREP}, 0, 0, FLAGS, .unit = "filters"}, | ||
| 78 | {"brng", "analyze for pixels outside of broadcast range", 0, AV_OPT_TYPE_CONST, {.i64=1<<FILTER_BRNG}, 0, 0, FLAGS, .unit = "filters"}, | ||
| 79 | {"out", "set video filter", OFFSET(outfilter), AV_OPT_TYPE_INT, {.i64=FILTER_NONE}, -1, FILT_NUMB-1, FLAGS, .unit = "out"}, | ||
| 80 | {"tout", "highlight pixels that depict temporal outliers", 0, AV_OPT_TYPE_CONST, {.i64=FILTER_TOUT}, 0, 0, FLAGS, .unit = "out"}, | ||
| 81 | {"vrep", "highlight video lines that depict vertical line repetition", 0, AV_OPT_TYPE_CONST, {.i64=FILTER_VREP}, 0, 0, FLAGS, .unit = "out"}, | ||
| 82 | {"brng", "highlight pixels that are outside of broadcast range", 0, AV_OPT_TYPE_CONST, {.i64=FILTER_BRNG}, 0, 0, FLAGS, .unit = "out"}, | ||
| 83 | {"c", "set highlight color", OFFSET(rgba_color), AV_OPT_TYPE_COLOR, {.str="yellow"}, .flags=FLAGS}, | ||
| 84 | {"color", "set highlight color", OFFSET(rgba_color), AV_OPT_TYPE_COLOR, {.str="yellow"}, .flags=FLAGS}, | ||
| 85 | {NULL} | ||
| 86 | }; | ||
| 87 | |||
| 88 | AVFILTER_DEFINE_CLASS(signalstats); | ||
| 89 | |||
| 90 | 2 | static av_cold int init(AVFilterContext *ctx) | |
| 91 | { | ||
| 92 | uint8_t r, g, b; | ||
| 93 | 2 | SignalstatsContext *s = ctx->priv; | |
| 94 | |||
| 95 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (s->outfilter != FILTER_NONE) |
| 96 | ✗ | s->filters |= 1 << s->outfilter; | |
| 97 | |||
| 98 | 2 | r = s->rgba_color[0]; | |
| 99 | 2 | g = s->rgba_color[1]; | |
| 100 | 2 | b = s->rgba_color[2]; | |
| 101 | 2 | s->yuv_color[0] = (( 66*r + 129*g + 25*b + (1<<7)) >> 8) + 16; | |
| 102 | 2 | s->yuv_color[1] = ((-38*r + -74*g + 112*b + (1<<7)) >> 8) + 128; | |
| 103 | 2 | s->yuv_color[2] = ((112*r + -94*g + -18*b + (1<<7)) >> 8) + 128; | |
| 104 | 2 | return 0; | |
| 105 | } | ||
| 106 | |||
| 107 | 2 | static av_cold void uninit(AVFilterContext *ctx) | |
| 108 | { | ||
| 109 | 2 | SignalstatsContext *s = ctx->priv; | |
| 110 | 2 | av_frame_free(&s->frame_prev); | |
| 111 | 2 | av_frame_free(&s->frame_sat); | |
| 112 | 2 | av_frame_free(&s->frame_hue); | |
| 113 | 2 | av_freep(&s->jobs_rets); | |
| 114 | 2 | av_freep(&s->histy); | |
| 115 | 2 | av_freep(&s->histu); | |
| 116 | 2 | av_freep(&s->histv); | |
| 117 | 2 | av_freep(&s->histsat); | |
| 118 | 2 | } | |
| 119 | |||
| 120 | // TODO: add more | ||
| 121 | static const enum AVPixelFormat pix_fmts[] = { | ||
| 122 | AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV411P, | ||
| 123 | AV_PIX_FMT_YUV440P, | ||
| 124 | AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ411P, | ||
| 125 | AV_PIX_FMT_YUVJ440P, | ||
| 126 | AV_PIX_FMT_YUV444P9, AV_PIX_FMT_YUV422P9, AV_PIX_FMT_YUV420P9, | ||
| 127 | AV_PIX_FMT_YUV444P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV420P10, | ||
| 128 | AV_PIX_FMT_YUV440P10, | ||
| 129 | AV_PIX_FMT_YUV444P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV420P12, | ||
| 130 | AV_PIX_FMT_YUV440P12, | ||
| 131 | AV_PIX_FMT_YUV444P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV420P14, | ||
| 132 | AV_PIX_FMT_YUV444P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV420P16, | ||
| 133 | AV_PIX_FMT_NONE | ||
| 134 | }; | ||
| 135 | |||
| 136 | 4 | static AVFrame *alloc_frame(enum AVPixelFormat pixfmt, int w, int h) | |
| 137 | { | ||
| 138 | 4 | AVFrame *frame = av_frame_alloc(); | |
| 139 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (!frame) |
| 140 | ✗ | return NULL; | |
| 141 | |||
| 142 | 4 | frame->format = pixfmt; | |
| 143 | 4 | frame->width = w; | |
| 144 | 4 | frame->height = h; | |
| 145 | |||
| 146 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
|
4 | if (av_frame_get_buffer(frame, 0) < 0) { |
| 147 | ✗ | av_frame_free(&frame); | |
| 148 | ✗ | return NULL; | |
| 149 | } | ||
| 150 | |||
| 151 | 4 | return frame; | |
| 152 | } | ||
| 153 | |||
| 154 | 2 | static int config_output(AVFilterLink *outlink) | |
| 155 | { | ||
| 156 | 2 | AVFilterContext *ctx = outlink->src; | |
| 157 | 2 | SignalstatsContext *s = ctx->priv; | |
| 158 | 2 | AVFilterLink *inlink = outlink->src->inputs[0]; | |
| 159 | 2 | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(outlink->format); | |
| 160 | 2 | s->hsub = desc->log2_chroma_w; | |
| 161 | 2 | s->vsub = desc->log2_chroma_h; | |
| 162 | 2 | s->depth = desc->comp[0].depth; | |
| 163 | 2 | s->maxsize = 1 << s->depth; | |
| 164 | 2 | s->histy = av_malloc_array(s->maxsize, sizeof(*s->histy)); | |
| 165 | 2 | s->histu = av_malloc_array(s->maxsize, sizeof(*s->histu)); | |
| 166 | 2 | s->histv = av_malloc_array(s->maxsize, sizeof(*s->histv)); | |
| 167 | 2 | s->histsat = av_malloc_array(s->maxsize, sizeof(*s->histsat)); | |
| 168 | |||
| 169 |
4/8✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 2 times.
|
2 | if (!s->histy || !s->histu || !s->histv || !s->histsat) |
| 170 | ✗ | return AVERROR(ENOMEM); | |
| 171 | |||
| 172 | 2 | outlink->w = inlink->w; | |
| 173 | 2 | outlink->h = inlink->h; | |
| 174 | |||
| 175 | 2 | s->chromaw = AV_CEIL_RSHIFT(inlink->w, s->hsub); | |
| 176 | 2 | s->chromah = AV_CEIL_RSHIFT(inlink->h, s->vsub); | |
| 177 | |||
| 178 | 2 | s->fs = inlink->w * inlink->h; | |
| 179 | 2 | s->cfs = s->chromaw * s->chromah; | |
| 180 | |||
| 181 |
3/6✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
|
2 | s->nb_jobs = FFMAX(1, FFMIN(inlink->h, ff_filter_get_nb_threads(ctx))); |
| 182 | 2 | s->jobs_rets = av_malloc_array(s->nb_jobs, sizeof(*s->jobs_rets)); | |
| 183 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (!s->jobs_rets) |
| 184 | ✗ | return AVERROR(ENOMEM); | |
| 185 | |||
| 186 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | s->frame_sat = alloc_frame(s->depth > 8 ? AV_PIX_FMT_GRAY16 : AV_PIX_FMT_GRAY8, inlink->w, inlink->h); |
| 187 | 2 | s->frame_hue = alloc_frame(AV_PIX_FMT_GRAY16, inlink->w, inlink->h); | |
| 188 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
|
2 | if (!s->frame_sat || !s->frame_hue) |
| 189 | ✗ | return AVERROR(ENOMEM); | |
| 190 | |||
| 191 | 2 | return 0; | |
| 192 | } | ||
| 193 | |||
| 194 | ✗ | static void burn_frame8(const SignalstatsContext *s, AVFrame *f, int x, int y) | |
| 195 | { | ||
| 196 | ✗ | const int chromax = x >> s->hsub; | |
| 197 | ✗ | const int chromay = y >> s->vsub; | |
| 198 | ✗ | f->data[0][y * f->linesize[0] + x] = s->yuv_color[0]; | |
| 199 | ✗ | f->data[1][chromay * f->linesize[1] + chromax] = s->yuv_color[1]; | |
| 200 | ✗ | f->data[2][chromay * f->linesize[2] + chromax] = s->yuv_color[2]; | |
| 201 | ✗ | } | |
| 202 | |||
| 203 | ✗ | static void burn_frame16(const SignalstatsContext *s, AVFrame *f, int x, int y) | |
| 204 | { | ||
| 205 | ✗ | const int chromax = x >> s->hsub; | |
| 206 | ✗ | const int chromay = y >> s->vsub; | |
| 207 | ✗ | const int mult = 1 << (s->depth - 8); | |
| 208 | ✗ | AV_WN16(f->data[0] + y * f->linesize[0] + x * 2, s->yuv_color[0] * mult); | |
| 209 | ✗ | AV_WN16(f->data[1] + chromay * f->linesize[1] + chromax * 2, s->yuv_color[1] * mult); | |
| 210 | ✗ | AV_WN16(f->data[2] + chromay * f->linesize[2] + chromax * 2, s->yuv_color[2] * mult); | |
| 211 | ✗ | } | |
| 212 | |||
| 213 | ✗ | static int filter8_brng(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) | |
| 214 | { | ||
| 215 | ✗ | ThreadData *td = arg; | |
| 216 | ✗ | const SignalstatsContext *s = ctx->priv; | |
| 217 | ✗ | const AVFrame *in = td->in; | |
| 218 | ✗ | AVFrame *out = td->out; | |
| 219 | ✗ | const int w = in->width; | |
| 220 | ✗ | const int h = in->height; | |
| 221 | ✗ | const int slice_start = (h * jobnr ) / nb_jobs; | |
| 222 | ✗ | const int slice_end = (h * (jobnr+1)) / nb_jobs; | |
| 223 | ✗ | int x, y, score = 0; | |
| 224 | |||
| 225 | ✗ | for (y = slice_start; y < slice_end; y++) { | |
| 226 | ✗ | const int yc = y >> s->vsub; | |
| 227 | ✗ | const uint8_t *pluma = &in->data[0][y * in->linesize[0]]; | |
| 228 | ✗ | const uint8_t *pchromau = &in->data[1][yc * in->linesize[1]]; | |
| 229 | ✗ | const uint8_t *pchromav = &in->data[2][yc * in->linesize[2]]; | |
| 230 | |||
| 231 | ✗ | for (x = 0; x < w; x++) { | |
| 232 | ✗ | const int xc = x >> s->hsub; | |
| 233 | ✗ | const int luma = pluma[x]; | |
| 234 | ✗ | const int chromau = pchromau[xc]; | |
| 235 | ✗ | const int chromav = pchromav[xc]; | |
| 236 | ✗ | const int filt = luma < 16 || luma > 235 || | |
| 237 | ✗ | chromau < 16 || chromau > 240 || | |
| 238 | ✗ | chromav < 16 || chromav > 240; | |
| 239 | ✗ | score += filt; | |
| 240 | ✗ | if (out && filt) | |
| 241 | ✗ | burn_frame8(s, out, x, y); | |
| 242 | } | ||
| 243 | } | ||
| 244 | ✗ | return score; | |
| 245 | } | ||
| 246 | |||
| 247 | ✗ | static int filter16_brng(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) | |
| 248 | { | ||
| 249 | ✗ | ThreadData *td = arg; | |
| 250 | ✗ | const SignalstatsContext *s = ctx->priv; | |
| 251 | ✗ | const AVFrame *in = td->in; | |
| 252 | ✗ | AVFrame *out = td->out; | |
| 253 | ✗ | const int mult = 1 << (s->depth - 8); | |
| 254 | ✗ | const int w = in->width; | |
| 255 | ✗ | const int h = in->height; | |
| 256 | ✗ | const int slice_start = (h * jobnr ) / nb_jobs; | |
| 257 | ✗ | const int slice_end = (h * (jobnr+1)) / nb_jobs; | |
| 258 | ✗ | int x, y, score = 0; | |
| 259 | |||
| 260 | ✗ | for (y = slice_start; y < slice_end; y++) { | |
| 261 | ✗ | const int yc = y >> s->vsub; | |
| 262 | ✗ | const uint16_t *pluma = (uint16_t *)&in->data[0][y * in->linesize[0]]; | |
| 263 | ✗ | const uint16_t *pchromau = (uint16_t *)&in->data[1][yc * in->linesize[1]]; | |
| 264 | ✗ | const uint16_t *pchromav = (uint16_t *)&in->data[2][yc * in->linesize[2]]; | |
| 265 | |||
| 266 | ✗ | for (x = 0; x < w; x++) { | |
| 267 | ✗ | const int xc = x >> s->hsub; | |
| 268 | ✗ | const int luma = pluma[x]; | |
| 269 | ✗ | const int chromau = pchromau[xc]; | |
| 270 | ✗ | const int chromav = pchromav[xc]; | |
| 271 | ✗ | const int filt = luma < 16 * mult || luma > 235 * mult || | |
| 272 | ✗ | chromau < 16 * mult || chromau > 240 * mult || | |
| 273 | ✗ | chromav < 16 * mult || chromav > 240 * mult; | |
| 274 | ✗ | score += filt; | |
| 275 | ✗ | if (out && filt) | |
| 276 | ✗ | burn_frame16(s, out, x, y); | |
| 277 | } | ||
| 278 | } | ||
| 279 | ✗ | return score; | |
| 280 | } | ||
| 281 | |||
| 282 | ✗ | static int filter_tout_outlier(uint8_t x, uint8_t y, uint8_t z) | |
| 283 | { | ||
| 284 | ✗ | return ((abs(x - y) + abs (z - y)) / 2) - abs(z - x) > 4; // make 4 configurable? | |
| 285 | } | ||
| 286 | |||
| 287 | ✗ | static int filter8_tout(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) | |
| 288 | { | ||
| 289 | ✗ | ThreadData *td = arg; | |
| 290 | ✗ | const SignalstatsContext *s = ctx->priv; | |
| 291 | ✗ | const AVFrame *in = td->in; | |
| 292 | ✗ | AVFrame *out = td->out; | |
| 293 | ✗ | const int w = in->width; | |
| 294 | ✗ | const int h = in->height; | |
| 295 | ✗ | const int slice_start = (h * jobnr ) / nb_jobs; | |
| 296 | ✗ | const int slice_end = (h * (jobnr+1)) / nb_jobs; | |
| 297 | ✗ | const uint8_t *p = in->data[0]; | |
| 298 | ✗ | int lw = in->linesize[0]; | |
| 299 | ✗ | int x, y, score = 0, filt; | |
| 300 | |||
| 301 | ✗ | for (y = slice_start; y < slice_end; y++) { | |
| 302 | |||
| 303 | ✗ | if (y - 1 < 0 || y + 1 >= h) | |
| 304 | ✗ | continue; | |
| 305 | |||
| 306 | // detect two pixels above and below (to eliminate interlace artefacts) | ||
| 307 | // should check that video format is in fact interlaced. | ||
| 308 | |||
| 309 | #define FILTER(i, j) \ | ||
| 310 | filter_tout_outlier(p[(y-j) * lw + x + i], \ | ||
| 311 | p[ y * lw + x + i], \ | ||
| 312 | p[(y+j) * lw + x + i]) | ||
| 313 | |||
| 314 | #define FILTER3(j) (FILTER(-1, j) && FILTER(0, j) && FILTER(1, j)) | ||
| 315 | |||
| 316 | ✗ | if (y - 2 >= 0 && y + 2 < h) { | |
| 317 | ✗ | for (x = 1; x < w - 1; x++) { | |
| 318 | ✗ | filt = FILTER3(2) && FILTER3(1); | |
| 319 | ✗ | score += filt; | |
| 320 | ✗ | if (filt && out) | |
| 321 | ✗ | burn_frame8(s, out, x, y); | |
| 322 | } | ||
| 323 | } else { | ||
| 324 | ✗ | for (x = 1; x < w - 1; x++) { | |
| 325 | ✗ | filt = FILTER3(1); | |
| 326 | ✗ | score += filt; | |
| 327 | ✗ | if (filt && out) | |
| 328 | ✗ | burn_frame8(s, out, x, y); | |
| 329 | } | ||
| 330 | } | ||
| 331 | } | ||
| 332 | ✗ | return score; | |
| 333 | } | ||
| 334 | |||
| 335 | ✗ | static int filter16_tout(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) | |
| 336 | { | ||
| 337 | ✗ | ThreadData *td = arg; | |
| 338 | ✗ | const SignalstatsContext *s = ctx->priv; | |
| 339 | ✗ | const AVFrame *in = td->in; | |
| 340 | ✗ | AVFrame *out = td->out; | |
| 341 | ✗ | const int w = in->width; | |
| 342 | ✗ | const int h = in->height; | |
| 343 | ✗ | const int slice_start = (h * jobnr ) / nb_jobs; | |
| 344 | ✗ | const int slice_end = (h * (jobnr+1)) / nb_jobs; | |
| 345 | ✗ | const uint16_t *p = (uint16_t *)in->data[0]; | |
| 346 | ✗ | int lw = in->linesize[0] / 2; | |
| 347 | ✗ | int x, y, score = 0, filt; | |
| 348 | |||
| 349 | ✗ | for (y = slice_start; y < slice_end; y++) { | |
| 350 | |||
| 351 | ✗ | if (y - 1 < 0 || y + 1 >= h) | |
| 352 | ✗ | continue; | |
| 353 | |||
| 354 | // detect two pixels above and below (to eliminate interlace artefacts) | ||
| 355 | // should check that video format is in fact interlaced. | ||
| 356 | |||
| 357 | ✗ | if (y - 2 >= 0 && y + 2 < h) { | |
| 358 | ✗ | for (x = 1; x < w - 1; x++) { | |
| 359 | ✗ | filt = FILTER3(2) && FILTER3(1); | |
| 360 | ✗ | score += filt; | |
| 361 | ✗ | if (filt && out) | |
| 362 | ✗ | burn_frame16(s, out, x, y); | |
| 363 | } | ||
| 364 | } else { | ||
| 365 | ✗ | for (x = 1; x < w - 1; x++) { | |
| 366 | ✗ | filt = FILTER3(1); | |
| 367 | ✗ | score += filt; | |
| 368 | ✗ | if (filt && out) | |
| 369 | ✗ | burn_frame16(s, out, x, y); | |
| 370 | } | ||
| 371 | } | ||
| 372 | } | ||
| 373 | ✗ | return score; | |
| 374 | } | ||
| 375 | |||
| 376 | #define VREP_START 4 | ||
| 377 | |||
| 378 | ✗ | static int filter8_vrep(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) | |
| 379 | { | ||
| 380 | ✗ | ThreadData *td = arg; | |
| 381 | ✗ | const SignalstatsContext *s = ctx->priv; | |
| 382 | ✗ | const AVFrame *in = td->in; | |
| 383 | ✗ | AVFrame *out = td->out; | |
| 384 | ✗ | const int w = in->width; | |
| 385 | ✗ | const int h = in->height; | |
| 386 | ✗ | const int slice_start = (h * jobnr ) / nb_jobs; | |
| 387 | ✗ | const int slice_end = (h * (jobnr+1)) / nb_jobs; | |
| 388 | ✗ | const uint8_t *p = in->data[0]; | |
| 389 | ✗ | const int lw = in->linesize[0]; | |
| 390 | ✗ | int x, y, score = 0; | |
| 391 | |||
| 392 | ✗ | for (y = slice_start; y < slice_end; y++) { | |
| 393 | ✗ | const int y2lw = (y - VREP_START) * lw; | |
| 394 | ✗ | const int ylw = y * lw; | |
| 395 | ✗ | int filt, totdiff = 0; | |
| 396 | |||
| 397 | ✗ | if (y < VREP_START) | |
| 398 | ✗ | continue; | |
| 399 | |||
| 400 | ✗ | for (x = 0; x < w; x++) | |
| 401 | ✗ | totdiff += abs(p[y2lw + x] - p[ylw + x]); | |
| 402 | ✗ | filt = totdiff < w; | |
| 403 | |||
| 404 | ✗ | score += filt; | |
| 405 | ✗ | if (filt && out) | |
| 406 | ✗ | for (x = 0; x < w; x++) | |
| 407 | ✗ | burn_frame8(s, out, x, y); | |
| 408 | } | ||
| 409 | ✗ | return score * w; | |
| 410 | } | ||
| 411 | |||
| 412 | ✗ | static int filter16_vrep(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) | |
| 413 | { | ||
| 414 | ✗ | ThreadData *td = arg; | |
| 415 | ✗ | const SignalstatsContext *s = ctx->priv; | |
| 416 | ✗ | const AVFrame *in = td->in; | |
| 417 | ✗ | AVFrame *out = td->out; | |
| 418 | ✗ | const int w = in->width; | |
| 419 | ✗ | const int h = in->height; | |
| 420 | ✗ | const int slice_start = (h * jobnr ) / nb_jobs; | |
| 421 | ✗ | const int slice_end = (h * (jobnr+1)) / nb_jobs; | |
| 422 | ✗ | const uint16_t *p = (uint16_t *)in->data[0]; | |
| 423 | ✗ | const int lw = in->linesize[0] / 2; | |
| 424 | ✗ | int x, y, score = 0; | |
| 425 | |||
| 426 | ✗ | for (y = slice_start; y < slice_end; y++) { | |
| 427 | ✗ | const int y2lw = (y - VREP_START) * lw; | |
| 428 | ✗ | const int ylw = y * lw; | |
| 429 | ✗ | int64_t totdiff = 0; | |
| 430 | int filt; | ||
| 431 | |||
| 432 | ✗ | if (y < VREP_START) | |
| 433 | ✗ | continue; | |
| 434 | |||
| 435 | ✗ | for (x = 0; x < w; x++) | |
| 436 | ✗ | totdiff += abs(p[y2lw + x] - p[ylw + x]); | |
| 437 | ✗ | filt = totdiff < w; | |
| 438 | |||
| 439 | ✗ | score += filt; | |
| 440 | ✗ | if (filt && out) | |
| 441 | ✗ | for (x = 0; x < w; x++) | |
| 442 | ✗ | burn_frame16(s, out, x, y); | |
| 443 | } | ||
| 444 | ✗ | return score * w; | |
| 445 | } | ||
| 446 | |||
| 447 | static const struct { | ||
| 448 | const char *name; | ||
| 449 | int (*process8)(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs); | ||
| 450 | int (*process16)(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs); | ||
| 451 | } filters_def[] = { | ||
| 452 | {"TOUT", filter8_tout, filter16_tout}, | ||
| 453 | {"VREP", filter8_vrep, filter16_vrep}, | ||
| 454 | {"BRNG", filter8_brng, filter16_brng}, | ||
| 455 | {NULL} | ||
| 456 | }; | ||
| 457 | |||
| 458 | 9 | static int compute_sat_hue_metrics8(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) | |
| 459 | { | ||
| 460 | int i, j; | ||
| 461 | 9 | ThreadDataHueSatMetrics *td = arg; | |
| 462 | 9 | const SignalstatsContext *s = ctx->priv; | |
| 463 | 9 | const AVFrame *src = td->src; | |
| 464 | 9 | AVFrame *dst_sat = td->dst_sat; | |
| 465 | 9 | AVFrame *dst_hue = td->dst_hue; | |
| 466 | |||
| 467 | 9 | const int slice_start = (s->chromah * jobnr ) / nb_jobs; | |
| 468 | 9 | const int slice_end = (s->chromah * (jobnr+1)) / nb_jobs; | |
| 469 | |||
| 470 | 9 | const int lsz_u = src->linesize[1]; | |
| 471 | 9 | const int lsz_v = src->linesize[2]; | |
| 472 | 9 | const uint8_t *p_u = src->data[1] + slice_start * lsz_u; | |
| 473 | 9 | const uint8_t *p_v = src->data[2] + slice_start * lsz_v; | |
| 474 | |||
| 475 | 9 | const int lsz_sat = dst_sat->linesize[0]; | |
| 476 | 9 | const int lsz_hue = dst_hue->linesize[0]; | |
| 477 | 9 | uint8_t *p_sat = dst_sat->data[0] + slice_start * lsz_sat; | |
| 478 | 9 | uint8_t *p_hue = dst_hue->data[0] + slice_start * lsz_hue; | |
| 479 | |||
| 480 |
2/2✓ Branch 0 taken 120 times.
✓ Branch 1 taken 9 times.
|
129 | for (j = slice_start; j < slice_end; j++) { |
| 481 |
2/2✓ Branch 0 taken 19200 times.
✓ Branch 1 taken 120 times.
|
19320 | for (i = 0; i < s->chromaw; i++) { |
| 482 | 19200 | const int yuvu = p_u[i]; | |
| 483 | 19200 | const int yuvv = p_v[i]; | |
| 484 | 19200 | p_sat[i] = hypotf(yuvu - 128, yuvv - 128); // int or round? | |
| 485 | 19200 | ((int16_t*)p_hue)[i] = fmodf(floorf((180.f / M_PI) * atan2f(yuvu-128, yuvv-128) + 180.f), 360.f); | |
| 486 | } | ||
| 487 | 120 | p_u += lsz_u; | |
| 488 | 120 | p_v += lsz_v; | |
| 489 | 120 | p_sat += lsz_sat; | |
| 490 | 120 | p_hue += lsz_hue; | |
| 491 | } | ||
| 492 | |||
| 493 | 9 | return 0; | |
| 494 | } | ||
| 495 | |||
| 496 | 9 | static int compute_sat_hue_metrics16(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) | |
| 497 | { | ||
| 498 | int i, j; | ||
| 499 | 9 | ThreadDataHueSatMetrics *td = arg; | |
| 500 | 9 | const SignalstatsContext *s = ctx->priv; | |
| 501 | 9 | const AVFrame *src = td->src; | |
| 502 | 9 | AVFrame *dst_sat = td->dst_sat; | |
| 503 | 9 | AVFrame *dst_hue = td->dst_hue; | |
| 504 | 9 | const int mid = 1 << (s->depth - 1); | |
| 505 | |||
| 506 | 9 | const int slice_start = (s->chromah * jobnr ) / nb_jobs; | |
| 507 | 9 | const int slice_end = (s->chromah * (jobnr+1)) / nb_jobs; | |
| 508 | |||
| 509 | 9 | const int lsz_u = src->linesize[1] / 2; | |
| 510 | 9 | const int lsz_v = src->linesize[2] / 2; | |
| 511 | 9 | const uint16_t *p_u = (uint16_t*)src->data[1] + slice_start * lsz_u; | |
| 512 | 9 | const uint16_t *p_v = (uint16_t*)src->data[2] + slice_start * lsz_v; | |
| 513 | |||
| 514 | 9 | const int lsz_sat = dst_sat->linesize[0] / 2; | |
| 515 | 9 | const int lsz_hue = dst_hue->linesize[0] / 2; | |
| 516 | 9 | uint16_t *p_sat = (uint16_t*)dst_sat->data[0] + slice_start * lsz_sat; | |
| 517 | 9 | uint16_t *p_hue = (uint16_t*)dst_hue->data[0] + slice_start * lsz_hue; | |
| 518 | |||
| 519 |
2/2✓ Branch 0 taken 120 times.
✓ Branch 1 taken 9 times.
|
129 | for (j = slice_start; j < slice_end; j++) { |
| 520 |
2/2✓ Branch 0 taken 19200 times.
✓ Branch 1 taken 120 times.
|
19320 | for (i = 0; i < s->chromaw; i++) { |
| 521 | 19200 | const int yuvu = p_u[i]; | |
| 522 | 19200 | const int yuvv = p_v[i]; | |
| 523 | 19200 | p_sat[i] = hypotf(yuvu - mid, yuvv - mid); // int or round? | |
| 524 | 19200 | ((int16_t*)p_hue)[i] = fmodf(floorf((180.f / M_PI) * atan2f(yuvu-mid, yuvv-mid) + 180.f), 360.f); | |
| 525 | } | ||
| 526 | 120 | p_u += lsz_u; | |
| 527 | 120 | p_v += lsz_v; | |
| 528 | 120 | p_sat += lsz_sat; | |
| 529 | 120 | p_hue += lsz_hue; | |
| 530 | } | ||
| 531 | |||
| 532 | 9 | return 0; | |
| 533 | } | ||
| 534 | |||
| 535 | 6 | static unsigned compute_bit_depth(uint16_t mask) | |
| 536 | { | ||
| 537 | 6 | return av_popcount(mask); | |
| 538 | } | ||
| 539 | |||
| 540 | 2 | static int filter_frame(AVFilterLink *link, AVFrame *in) | |
| 541 | { | ||
| 542 | 2 | AVFilterContext *ctx = link->dst; | |
| 543 | 2 | SignalstatsContext *s = ctx->priv; | |
| 544 | 2 | AVFilterLink *outlink = ctx->outputs[0]; | |
| 545 | 2 | AVFrame *out = in; | |
| 546 | 2 | int w = 0, cw = 0, // in | |
| 547 | 2 | pw = 0, cpw = 0; // prev | |
| 548 | int fil; | ||
| 549 | char metabuf[128]; | ||
| 550 | 2 | unsigned int *histy = s->histy, | |
| 551 | 2 | *histu = s->histu, | |
| 552 | 2 | *histv = s->histv, | |
| 553 | 2 | histhue[360] = {0}, | |
| 554 | 2 | *histsat = s->histsat; | |
| 555 | 2 | int miny = -1, minu = -1, minv = -1; | |
| 556 | 2 | int maxy = -1, maxu = -1, maxv = -1; | |
| 557 | 2 | int lowy = -1, lowu = -1, lowv = -1; | |
| 558 | 2 | int highy = -1, highu = -1, highv = -1; | |
| 559 | 2 | int minsat = -1, maxsat = -1, lowsat = -1, highsat = -1; | |
| 560 | int lowp, highp, clowp, chighp; | ||
| 561 | int accy, accu, accv; | ||
| 562 | 2 | int accsat, acchue = 0; | |
| 563 | int medhue, maxhue; | ||
| 564 | 2 | int64_t toty = 0, totu = 0, totv = 0, totsat=0; | |
| 565 | 2 | int64_t tothue = 0; | |
| 566 | 2 | int64_t dify = 0, difu = 0, difv = 0; | |
| 567 | 2 | uint16_t masky = 0, masku = 0, maskv = 0; | |
| 568 | |||
| 569 | 2 | int filtot[FILT_NUMB] = {0}; | |
| 570 | AVFrame *prev; | ||
| 571 | int ret; | ||
| 572 | 2 | AVFrame *sat = s->frame_sat; | |
| 573 | 2 | AVFrame *hue = s->frame_hue; | |
| 574 | 2 | const int hbd = s->depth > 8; | |
| 575 | 2 | ThreadDataHueSatMetrics td_huesat = { | |
| 576 | .src = in, | ||
| 577 | .dst_sat = sat, | ||
| 578 | .dst_hue = hue, | ||
| 579 | }; | ||
| 580 | |||
| 581 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (!s->frame_prev) |
| 582 | 2 | s->frame_prev = av_frame_clone(in); | |
| 583 | |||
| 584 | 2 | prev = s->frame_prev; | |
| 585 | |||
| 586 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (s->outfilter != FILTER_NONE) { |
| 587 | ✗ | out = av_frame_clone(in); | |
| 588 | ✗ | if (!out) { | |
| 589 | ✗ | av_frame_free(&in); | |
| 590 | ✗ | return AVERROR(ENOMEM); | |
| 591 | } | ||
| 592 | ✗ | ret = ff_inlink_make_frame_writable(link, &out); | |
| 593 | ✗ | if (ret < 0) { | |
| 594 | ✗ | av_frame_free(&out); | |
| 595 | ✗ | av_frame_free(&in); | |
| 596 | ✗ | return ret; | |
| 597 | } | ||
| 598 | } | ||
| 599 | |||
| 600 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | ff_filter_execute(ctx, hbd ? compute_sat_hue_metrics16 |
| 601 | : compute_sat_hue_metrics8, &td_huesat, | ||
| 602 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | NULL, FFMIN(s->chromah, ff_filter_get_nb_threads(ctx))); |
| 603 | |||
| 604 | 2 | memset(s->histy, 0, s->maxsize * sizeof(*s->histy)); | |
| 605 | 2 | memset(s->histu, 0, s->maxsize * sizeof(*s->histu)); | |
| 606 | 2 | memset(s->histv, 0, s->maxsize * sizeof(*s->histv)); | |
| 607 | 2 | memset(s->histsat, 0, s->maxsize * sizeof(*s->histsat)); | |
| 608 | |||
| 609 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | if (hbd) { |
| 610 | 1 | const uint16_t *p_sat = (uint16_t *)sat->data[0]; | |
| 611 | 1 | const uint16_t *p_hue = (uint16_t *)hue->data[0]; | |
| 612 | 1 | const int lsz_sat = sat->linesize[0] / 2; | |
| 613 | 1 | const int lsz_hue = hue->linesize[0] / 2; | |
| 614 | // Calculate luma histogram and difference with previous frame or field. | ||
| 615 |
2/2✓ Branch 0 taken 240 times.
✓ Branch 1 taken 1 times.
|
241 | for (int j = 0; j < link->h; j++) { |
| 616 |
2/2✓ Branch 0 taken 76800 times.
✓ Branch 1 taken 240 times.
|
77040 | for (int i = 0; i < link->w; i++) { |
| 617 | 76800 | const int yuv = AV_RN16(in->data[0] + w + i * 2); | |
| 618 | |||
| 619 | 76800 | masky |= yuv; | |
| 620 | 76800 | histy[yuv]++; | |
| 621 | 76800 | dify += abs(yuv - (int)AV_RN16(prev->data[0] + pw + i * 2)); | |
| 622 | } | ||
| 623 | 240 | w += in->linesize[0]; | |
| 624 | 240 | pw += prev->linesize[0]; | |
| 625 | } | ||
| 626 | |||
| 627 | // Calculate chroma histogram and difference with previous frame or field. | ||
| 628 |
2/2✓ Branch 0 taken 120 times.
✓ Branch 1 taken 1 times.
|
121 | for (int j = 0; j < s->chromah; j++) { |
| 629 |
2/2✓ Branch 0 taken 19200 times.
✓ Branch 1 taken 120 times.
|
19320 | for (int i = 0; i < s->chromaw; i++) { |
| 630 | 19200 | const int yuvu = AV_RN16(in->data[1] + cw + i * 2); | |
| 631 | 19200 | const int yuvv = AV_RN16(in->data[2] + cw + i * 2); | |
| 632 | |||
| 633 | 19200 | masku |= yuvu; | |
| 634 | 19200 | maskv |= yuvv; | |
| 635 | 19200 | histu[yuvu]++; | |
| 636 | 19200 | difu += abs(yuvu - (int)AV_RN16(prev->data[1] + cpw + i * 2)); | |
| 637 | 19200 | histv[yuvv]++; | |
| 638 | 19200 | difv += abs(yuvv - (int)AV_RN16(prev->data[2] + cpw + i * 2)); | |
| 639 | |||
| 640 | 19200 | histsat[p_sat[i]]++; | |
| 641 | 19200 | histhue[((int16_t*)p_hue)[i]]++; | |
| 642 | } | ||
| 643 | 120 | cw += in->linesize[1]; | |
| 644 | 120 | cpw += prev->linesize[1]; | |
| 645 | 120 | p_sat += lsz_sat; | |
| 646 | 120 | p_hue += lsz_hue; | |
| 647 | } | ||
| 648 | } else { | ||
| 649 | 1 | const uint8_t *p_sat = sat->data[0]; | |
| 650 | 1 | const uint8_t *p_hue = hue->data[0]; | |
| 651 | 1 | const int lsz_sat = sat->linesize[0]; | |
| 652 | 1 | const int lsz_hue = hue->linesize[0]; | |
| 653 | // Calculate luma histogram and difference with previous frame or field. | ||
| 654 |
2/2✓ Branch 0 taken 240 times.
✓ Branch 1 taken 1 times.
|
241 | for (int j = 0; j < link->h; j++) { |
| 655 |
2/2✓ Branch 0 taken 76800 times.
✓ Branch 1 taken 240 times.
|
77040 | for (int i = 0; i < link->w; i++) { |
| 656 | 76800 | const int yuv = in->data[0][w + i]; | |
| 657 | |||
| 658 | 76800 | masky |= yuv; | |
| 659 | 76800 | histy[yuv]++; | |
| 660 | 76800 | dify += abs(yuv - prev->data[0][pw + i]); | |
| 661 | } | ||
| 662 | 240 | w += in->linesize[0]; | |
| 663 | 240 | pw += prev->linesize[0]; | |
| 664 | } | ||
| 665 | |||
| 666 | // Calculate chroma histogram and difference with previous frame or field. | ||
| 667 |
2/2✓ Branch 0 taken 120 times.
✓ Branch 1 taken 1 times.
|
121 | for (int j = 0; j < s->chromah; j++) { |
| 668 |
2/2✓ Branch 0 taken 19200 times.
✓ Branch 1 taken 120 times.
|
19320 | for (int i = 0; i < s->chromaw; i++) { |
| 669 | 19200 | const int yuvu = in->data[1][cw+i]; | |
| 670 | 19200 | const int yuvv = in->data[2][cw+i]; | |
| 671 | |||
| 672 | 19200 | masku |= yuvu; | |
| 673 | 19200 | maskv |= yuvv; | |
| 674 | 19200 | histu[yuvu]++; | |
| 675 | 19200 | difu += abs(yuvu - prev->data[1][cpw+i]); | |
| 676 | 19200 | histv[yuvv]++; | |
| 677 | 19200 | difv += abs(yuvv - prev->data[2][cpw+i]); | |
| 678 | |||
| 679 | 19200 | histsat[p_sat[i]]++; | |
| 680 | 19200 | histhue[((int16_t*)p_hue)[i]]++; | |
| 681 | } | ||
| 682 | 120 | cw += in->linesize[1]; | |
| 683 | 120 | cpw += prev->linesize[1]; | |
| 684 | 120 | p_sat += lsz_sat; | |
| 685 | 120 | p_hue += lsz_hue; | |
| 686 | } | ||
| 687 | } | ||
| 688 | |||
| 689 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
|
8 | for (fil = 0; fil < FILT_NUMB; fil ++) { |
| 690 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (s->filters & 1<<fil) { |
| 691 | ✗ | ThreadData td = { | |
| 692 | .in = in, | ||
| 693 | ✗ | .out = out != in && s->outfilter == fil ? out : NULL, | |
| 694 | }; | ||
| 695 | ✗ | memset(s->jobs_rets, 0, s->nb_jobs * sizeof(*s->jobs_rets)); | |
| 696 | ✗ | ff_filter_execute(ctx, hbd ? filters_def[fil].process16 : filters_def[fil].process8, | |
| 697 | &td, s->jobs_rets, s->nb_jobs); | ||
| 698 | ✗ | for (int i = 0; i < s->nb_jobs; i++) | |
| 699 | ✗ | filtot[fil] += s->jobs_rets[i]; | |
| 700 | } | ||
| 701 | } | ||
| 702 | |||
| 703 | // find low / high based on histogram percentile | ||
| 704 | // these only need to be calculated once. | ||
| 705 | |||
| 706 | 2 | lowp = lrint(s->fs * 10 / 100.); | |
| 707 | 2 | highp = lrint(s->fs * 90 / 100.); | |
| 708 | 2 | clowp = lrint(s->cfs * 10 / 100.); | |
| 709 | 2 | chighp = lrint(s->cfs * 90 / 100.); | |
| 710 | |||
| 711 | 2 | accy = accu = accv = accsat = 0; | |
| 712 |
2/2✓ Branch 0 taken 1280 times.
✓ Branch 1 taken 2 times.
|
1282 | for (fil = 0; fil < s->maxsize; fil++) { |
| 713 |
4/4✓ Branch 0 taken 1180 times.
✓ Branch 1 taken 100 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1178 times.
|
1280 | if (miny < 0 && histy[fil]) miny = fil; |
| 714 |
4/4✓ Branch 0 taken 644 times.
✓ Branch 1 taken 636 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 642 times.
|
1280 | if (minu < 0 && histu[fil]) minu = fil; |
| 715 |
4/4✓ Branch 0 taken 644 times.
✓ Branch 1 taken 636 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 642 times.
|
1280 | if (minv < 0 && histv[fil]) minv = fil; |
| 716 |
4/4✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1276 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
|
1280 | if (minsat < 0 && histsat[fil]) minsat = fil; |
| 717 | |||
| 718 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1278 times.
|
1280 | if (histy[fil]) maxy = fil; |
| 719 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1278 times.
|
1280 | if (histu[fil]) maxu = fil; |
| 720 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1278 times.
|
1280 | if (histv[fil]) maxv = fil; |
| 721 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1278 times.
|
1280 | if (histsat[fil]) maxsat = fil; |
| 722 | |||
| 723 | 1280 | toty += (uint64_t)histy[fil] * fil; | |
| 724 | 1280 | totu += (uint64_t)histu[fil] * fil; | |
| 725 | 1280 | totv += (uint64_t)histv[fil] * fil; | |
| 726 | 1280 | totsat += (uint64_t)histsat[fil] * fil; | |
| 727 | |||
| 728 | 1280 | accy += histy[fil]; | |
| 729 | 1280 | accu += histu[fil]; | |
| 730 | 1280 | accv += histv[fil]; | |
| 731 | 1280 | accsat += histsat[fil]; | |
| 732 | |||
| 733 |
4/4✓ Branch 0 taken 1180 times.
✓ Branch 1 taken 100 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1178 times.
|
1280 | if (lowy == -1 && accy >= lowp) lowy = fil; |
| 734 |
4/4✓ Branch 0 taken 644 times.
✓ Branch 1 taken 636 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 642 times.
|
1280 | if (lowu == -1 && accu >= clowp) lowu = fil; |
| 735 |
4/4✓ Branch 0 taken 644 times.
✓ Branch 1 taken 636 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 642 times.
|
1280 | if (lowv == -1 && accv >= clowp) lowv = fil; |
| 736 |
4/4✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1276 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
|
1280 | if (lowsat == -1 && accsat >= clowp) lowsat = fil; |
| 737 | |||
| 738 |
4/4✓ Branch 0 taken 1180 times.
✓ Branch 1 taken 100 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1178 times.
|
1280 | if (highy == -1 && accy >= highp) highy = fil; |
| 739 |
4/4✓ Branch 0 taken 644 times.
✓ Branch 1 taken 636 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 642 times.
|
1280 | if (highu == -1 && accu >= chighp) highu = fil; |
| 740 |
4/4✓ Branch 0 taken 644 times.
✓ Branch 1 taken 636 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 642 times.
|
1280 | if (highv == -1 && accv >= chighp) highv = fil; |
| 741 |
4/4✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1276 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
|
1280 | if (highsat == -1 && accsat >= chighp) highsat = fil; |
| 742 | } | ||
| 743 | |||
| 744 | 2 | maxhue = histhue[0]; | |
| 745 | 2 | medhue = -1; | |
| 746 |
2/2✓ Branch 0 taken 720 times.
✓ Branch 1 taken 2 times.
|
722 | for (fil = 0; fil < 360; fil++) { |
| 747 | 720 | tothue += (uint64_t)histhue[fil] * fil; | |
| 748 | 720 | acchue += histhue[fil]; | |
| 749 | |||
| 750 |
4/4✓ Branch 0 taken 407 times.
✓ Branch 1 taken 313 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 405 times.
|
720 | if (medhue == -1 && acchue > s->cfs / 2) |
| 751 | 2 | medhue = fil; | |
| 752 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 718 times.
|
720 | if (histhue[fil] > maxhue) { |
| 753 | 2 | maxhue = histhue[fil]; | |
| 754 | } | ||
| 755 | } | ||
| 756 | |||
| 757 | 2 | av_frame_free(&s->frame_prev); | |
| 758 | 2 | s->frame_prev = av_frame_clone(in); | |
| 759 | |||
| 760 | #define SET_META(key, fmt, val) do { \ | ||
| 761 | snprintf(metabuf, sizeof(metabuf), fmt, val); \ | ||
| 762 | av_dict_set(&out->metadata, "lavfi.signalstats." key, metabuf, 0); \ | ||
| 763 | } while (0) | ||
| 764 | |||
| 765 | 2 | av_dict_set_int(&out->metadata, "lavfi.signalstats.YMIN", miny, 0); | |
| 766 | 2 | av_dict_set_int(&out->metadata, "lavfi.signalstats.YLOW", lowy, 0); | |
| 767 | 2 | SET_META("YAVG", "%g", 1.0 * toty / s->fs); | |
| 768 | 2 | av_dict_set_int(&out->metadata, "lavfi.signalstats.YHIGH", highy, 0); | |
| 769 | 2 | av_dict_set_int(&out->metadata, "lavfi.signalstats.YMAX", maxy, 0); | |
| 770 | |||
| 771 | 2 | av_dict_set_int(&out->metadata, "lavfi.signalstats.UMIN", minu, 0); | |
| 772 | 2 | av_dict_set_int(&out->metadata, "lavfi.signalstats.ULOW", lowu, 0); | |
| 773 | 2 | SET_META("UAVG", "%g", 1.0 * totu / s->cfs); | |
| 774 | 2 | av_dict_set_int(&out->metadata, "lavfi.signalstats.UHIGH", highu, 0); | |
| 775 | 2 | av_dict_set_int(&out->metadata, "lavfi.signalstats.UMAX", maxu, 0); | |
| 776 | |||
| 777 | 2 | av_dict_set_int(&out->metadata, "lavfi.signalstats.VMIN", minv, 0); | |
| 778 | 2 | av_dict_set_int(&out->metadata, "lavfi.signalstats.VLOW", lowv, 0); | |
| 779 | 2 | SET_META("VAVG", "%g", 1.0 * totv / s->cfs); | |
| 780 | 2 | av_dict_set_int(&out->metadata, "lavfi.signalstats.VHIGH", highv, 0); | |
| 781 | 2 | av_dict_set_int(&out->metadata, "lavfi.signalstats.VMAX", maxv, 0); | |
| 782 | |||
| 783 | 2 | av_dict_set_int(&out->metadata, "lavfi.signalstats.SATMIN", minsat, 0); | |
| 784 | 2 | av_dict_set_int(&out->metadata, "lavfi.signalstats.SATLOW", lowsat, 0); | |
| 785 | 2 | SET_META("SATAVG", "%g", 1.0 * totsat / s->cfs); | |
| 786 | 2 | av_dict_set_int(&out->metadata, "lavfi.signalstats.SATHIGH", highsat, 0); | |
| 787 | 2 | av_dict_set_int(&out->metadata, "lavfi.signalstats.SATMAX", maxsat, 0); | |
| 788 | |||
| 789 | 2 | av_dict_set_int(&out->metadata, "lavfi.signalstats.HUEMED", medhue, 0); | |
| 790 | 2 | SET_META("HUEAVG", "%g", 1.0 * tothue / s->cfs); | |
| 791 | |||
| 792 | 2 | SET_META("YDIF", "%g", 1.0 * dify / s->fs); | |
| 793 | 2 | SET_META("UDIF", "%g", 1.0 * difu / s->cfs); | |
| 794 | 2 | SET_META("VDIF", "%g", 1.0 * difv / s->cfs); | |
| 795 | |||
| 796 | 2 | av_dict_set_int(&out->metadata, "lavfi.signalstats.YBITDEPTH", compute_bit_depth(masky), 0); | |
| 797 | 2 | av_dict_set_int(&out->metadata, "lavfi.signalstats.UBITDEPTH", compute_bit_depth(masku), 0); | |
| 798 | 2 | av_dict_set_int(&out->metadata, "lavfi.signalstats.VBITDEPTH", compute_bit_depth(maskv), 0); | |
| 799 | |||
| 800 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
|
8 | for (fil = 0; fil < FILT_NUMB; fil ++) { |
| 801 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (s->filters & 1<<fil) { |
| 802 | char metaname[128]; | ||
| 803 | ✗ | snprintf(metabuf, sizeof(metabuf), "%g", 1.0 * filtot[fil] / s->fs); | |
| 804 | ✗ | snprintf(metaname, sizeof(metaname), "lavfi.signalstats.%s", filters_def[fil].name); | |
| 805 | ✗ | av_dict_set(&out->metadata, metaname, metabuf, 0); | |
| 806 | } | ||
| 807 | } | ||
| 808 | |||
| 809 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (in != out) |
| 810 | ✗ | av_frame_free(&in); | |
| 811 | 2 | return ff_filter_frame(outlink, out); | |
| 812 | } | ||
| 813 | |||
| 814 | static const AVFilterPad signalstats_inputs[] = { | ||
| 815 | { | ||
| 816 | .name = "default", | ||
| 817 | .type = AVMEDIA_TYPE_VIDEO, | ||
| 818 | .filter_frame = filter_frame, | ||
| 819 | }, | ||
| 820 | }; | ||
| 821 | |||
| 822 | static const AVFilterPad signalstats_outputs[] = { | ||
| 823 | { | ||
| 824 | .name = "default", | ||
| 825 | .config_props = config_output, | ||
| 826 | .type = AVMEDIA_TYPE_VIDEO, | ||
| 827 | }, | ||
| 828 | }; | ||
| 829 | |||
| 830 | const FFFilter ff_vf_signalstats = { | ||
| 831 | .p.name = "signalstats", | ||
| 832 | .p.description = "Generate statistics from video analysis.", | ||
| 833 | .p.priv_class = &signalstats_class, | ||
| 834 | .p.flags = AVFILTER_FLAG_SLICE_THREADS, | ||
| 835 | .init = init, | ||
| 836 | .uninit = uninit, | ||
| 837 | .priv_size = sizeof(SignalstatsContext), | ||
| 838 | FILTER_INPUTS(signalstats_inputs), | ||
| 839 | FILTER_OUTPUTS(signalstats_outputs), | ||
| 840 | FILTER_PIXFMTS_ARRAY(pix_fmts), | ||
| 841 | }; | ||
| 842 |