| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * Copyright (c) 2002 A'rpi | ||
| 3 | * This file is part of FFmpeg. | ||
| 4 | * | ||
| 5 | * FFmpeg is free software; you can redistribute it and/or modify | ||
| 6 | * it under the terms of the GNU General Public License as published by | ||
| 7 | * the Free Software Foundation; either version 2 of the License, or | ||
| 8 | * (at your option) any later version. | ||
| 9 | * | ||
| 10 | * FFmpeg is distributed in the hope that it will be useful, | ||
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 13 | * GNU General Public License for more details. | ||
| 14 | * | ||
| 15 | * You should have received a copy of the GNU General Public License along | ||
| 16 | * with FFmpeg; if not, write to the Free Software Foundation, Inc., | ||
| 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 18 | */ | ||
| 19 | |||
| 20 | /** | ||
| 21 | * @file | ||
| 22 | * border detection filter | ||
| 23 | * Ported from MPlayer libmpcodecs/vf_cropdetect.c. | ||
| 24 | */ | ||
| 25 | |||
| 26 | #include "libavutil/imgutils.h" | ||
| 27 | #include "libavutil/internal.h" | ||
| 28 | #include "libavutil/mem.h" | ||
| 29 | #include "libavutil/opt.h" | ||
| 30 | #include "libavutil/motion_vector.h" | ||
| 31 | #include "libavutil/qsort.h" | ||
| 32 | |||
| 33 | #include "avfilter.h" | ||
| 34 | #include "filters.h" | ||
| 35 | #include "video.h" | ||
| 36 | #include "edge_common.h" | ||
| 37 | |||
| 38 | typedef struct CropDetectContext { | ||
| 39 | const AVClass *class; | ||
| 40 | int x1, y1, x2, y2; | ||
| 41 | float limit; | ||
| 42 | float limit_upscaled; | ||
| 43 | int round; | ||
| 44 | int skip; | ||
| 45 | int reset_count; | ||
| 46 | int frame_nb; | ||
| 47 | int max_pixsteps[4]; | ||
| 48 | int max_outliers; | ||
| 49 | int mode; | ||
| 50 | int window_size; | ||
| 51 | int mv_threshold; | ||
| 52 | int bitdepth; | ||
| 53 | float low, high; | ||
| 54 | uint8_t low_u8, high_u8; | ||
| 55 | uint8_t *filterbuf; | ||
| 56 | uint8_t *tmpbuf; | ||
| 57 | uint16_t *gradients; | ||
| 58 | char *directions; | ||
| 59 | int *bboxes[4]; | ||
| 60 | } CropDetectContext; | ||
| 61 | |||
| 62 | static const enum AVPixelFormat pix_fmts[] = { | ||
| 63 | AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUVJ420P, | ||
| 64 | AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUVJ422P, | ||
| 65 | AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUVJ444P, | ||
| 66 | AV_PIX_FMT_YUV411P, AV_PIX_FMT_GRAY8, | ||
| 67 | AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV410P, | ||
| 68 | AV_PIX_FMT_YUV420P9 , AV_PIX_FMT_YUV422P9 , AV_PIX_FMT_YUV444P9, | ||
| 69 | AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV444P10, | ||
| 70 | AV_PIX_FMT_YUV420P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV444P12, | ||
| 71 | AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV444P14, | ||
| 72 | AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16, | ||
| 73 | AV_PIX_FMT_NV12, AV_PIX_FMT_NV21, | ||
| 74 | AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24, | ||
| 75 | AV_PIX_FMT_RGBA, AV_PIX_FMT_BGRA, | ||
| 76 | AV_PIX_FMT_NONE | ||
| 77 | }; | ||
| 78 | |||
| 79 | enum CropMode { | ||
| 80 | MODE_BLACK, | ||
| 81 | MODE_MV_EDGES, | ||
| 82 | MODE_NB | ||
| 83 | }; | ||
| 84 | |||
| 85 | 370 | static int comp(const int *a,const int *b) | |
| 86 | { | ||
| 87 | 370 | return FFDIFFSIGN(*a, *b); | |
| 88 | } | ||
| 89 | |||
| 90 | 3676 | static int checkline(void *ctx, const unsigned char *src, int stride, int len, int bpp) | |
| 91 | { | ||
| 92 | 3676 | int total = 0; | |
| 93 | 3676 | int div = len; | |
| 94 | 3676 | const uint16_t *src16 = (const uint16_t *)src; | |
| 95 | |||
| 96 |
1/4✓ Branch 0 taken 3676 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
3676 | switch (bpp) { |
| 97 | 3676 | case 1: | |
| 98 |
2/2✓ Branch 0 taken 330600 times.
✓ Branch 1 taken 3676 times.
|
334276 | while (len >= 8) { |
| 99 | 330600 | total += src[ 0] + src[ stride] + src[2*stride] + src[3*stride] | |
| 100 | 330600 | + src[4*stride] + src[5*stride] + src[6*stride] + src[7*stride]; | |
| 101 | 330600 | src += 8*stride; | |
| 102 | 330600 | len -= 8; | |
| 103 | } | ||
| 104 |
2/2✓ Branch 0 taken 48 times.
✓ Branch 1 taken 3676 times.
|
3724 | while (--len >= 0) { |
| 105 | 48 | total += src[0]; | |
| 106 | 48 | src += stride; | |
| 107 | } | ||
| 108 | 3676 | break; | |
| 109 | ✗ | case 2: | |
| 110 | ✗ | stride >>= 1; | |
| 111 | ✗ | while (len >= 8) { | |
| 112 | ✗ | total += src16[ 0] + src16[ stride] + src16[2*stride] + src16[3*stride] | |
| 113 | ✗ | + src16[4*stride] + src16[5*stride] + src16[6*stride] + src16[7*stride]; | |
| 114 | ✗ | src16 += 8*stride; | |
| 115 | ✗ | len -= 8; | |
| 116 | } | ||
| 117 | ✗ | while (--len >= 0) { | |
| 118 | ✗ | total += src16[0]; | |
| 119 | ✗ | src16 += stride; | |
| 120 | } | ||
| 121 | ✗ | break; | |
| 122 | ✗ | case 3: | |
| 123 | case 4: | ||
| 124 | ✗ | while (len >= 4) { | |
| 125 | ✗ | total += src[0] + src[1 ] + src[2 ] | |
| 126 | ✗ | + src[ stride] + src[1+ stride] + src[2+ stride] | |
| 127 | ✗ | + src[2*stride] + src[1+2*stride] + src[2+2*stride] | |
| 128 | ✗ | + src[3*stride] + src[1+3*stride] + src[2+3*stride]; | |
| 129 | ✗ | src += 4*stride; | |
| 130 | ✗ | len -= 4; | |
| 131 | } | ||
| 132 | ✗ | while (--len >= 0) { | |
| 133 | ✗ | total += src[0] + src[1] + src[2]; | |
| 134 | ✗ | src += stride; | |
| 135 | } | ||
| 136 | ✗ | div *= 3; | |
| 137 | ✗ | break; | |
| 138 | } | ||
| 139 | 3676 | total /= div; | |
| 140 | |||
| 141 | 3676 | av_log(ctx, AV_LOG_DEBUG, "total:%d\n", total); | |
| 142 | 3676 | return total; | |
| 143 | } | ||
| 144 | |||
| 145 | 2983 | static int checkline_edge(void *ctx, const unsigned char *src, int stride, int len, int bpp) | |
| 146 | { | ||
| 147 | 2983 | const uint16_t *src16 = (const uint16_t *)src; | |
| 148 | |||
| 149 |
1/4✓ Branch 0 taken 2983 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
2983 | switch (bpp) { |
| 150 | 2983 | case 1: | |
| 151 |
2/2✓ Branch 0 taken 442164 times.
✓ Branch 1 taken 107 times.
|
442271 | while (--len >= 0) { |
| 152 |
2/2✓ Branch 0 taken 2876 times.
✓ Branch 1 taken 439288 times.
|
442164 | if (src[0]) return 0; |
| 153 | 439288 | src += stride; | |
| 154 | } | ||
| 155 | 107 | break; | |
| 156 | ✗ | case 2: | |
| 157 | ✗ | stride >>= 1; | |
| 158 | ✗ | while (--len >= 0) { | |
| 159 | ✗ | if (src16[0]) return 0; | |
| 160 | ✗ | src16 += stride; | |
| 161 | } | ||
| 162 | ✗ | break; | |
| 163 | ✗ | case 3: | |
| 164 | case 4: | ||
| 165 | ✗ | while (--len >= 0) { | |
| 166 | ✗ | if (src[0] || src[1] || src[2]) return 0; | |
| 167 | ✗ | src += stride; | |
| 168 | } | ||
| 169 | ✗ | break; | |
| 170 | } | ||
| 171 | |||
| 172 | 107 | return 1; | |
| 173 | } | ||
| 174 | |||
| 175 | 3 | static av_cold int init(AVFilterContext *ctx) | |
| 176 | { | ||
| 177 | 3 | CropDetectContext *s = ctx->priv; | |
| 178 | |||
| 179 | 3 | s->frame_nb = -1 * s->skip; | |
| 180 | 3 | s->low_u8 = s->low * 255. + .5; | |
| 181 | 3 | s->high_u8 = s->high * 255. + .5; | |
| 182 | |||
| 183 | 3 | av_log(ctx, AV_LOG_VERBOSE, "limit:%f round:%d skip:%d reset_count:%d\n", | |
| 184 | 3 | s->limit, s->round, s->skip, s->reset_count); | |
| 185 | |||
| 186 | 3 | return 0; | |
| 187 | } | ||
| 188 | |||
| 189 | 3 | static av_cold void uninit(AVFilterContext *ctx) | |
| 190 | { | ||
| 191 | 3 | CropDetectContext *s = ctx->priv; | |
| 192 | |||
| 193 | 3 | av_freep(&s->tmpbuf); | |
| 194 | 3 | av_freep(&s->filterbuf); | |
| 195 | 3 | av_freep(&s->gradients); | |
| 196 | 3 | av_freep(&s->directions); | |
| 197 | 3 | av_freep(&s->bboxes[0]); | |
| 198 | 3 | av_freep(&s->bboxes[1]); | |
| 199 | 3 | av_freep(&s->bboxes[2]); | |
| 200 | 3 | av_freep(&s->bboxes[3]); | |
| 201 | 3 | } | |
| 202 | |||
| 203 | 3 | static int config_input(AVFilterLink *inlink) | |
| 204 | { | ||
| 205 | 3 | AVFilterContext *ctx = inlink->dst; | |
| 206 | 3 | CropDetectContext *s = ctx->priv; | |
| 207 | 3 | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); | |
| 208 | 3 | const int bufsize = inlink->w * inlink->h; | |
| 209 | |||
| 210 | 3 | av_image_fill_max_pixsteps(s->max_pixsteps, NULL, desc); | |
| 211 | |||
| 212 | 3 | s->bitdepth = desc->comp[0].depth; | |
| 213 | |||
| 214 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (s->limit < 1.0) |
| 215 | 3 | s->limit_upscaled = s->limit * ((1 << s->bitdepth) - 1); | |
| 216 | else | ||
| 217 | ✗ | s->limit_upscaled = s->limit; | |
| 218 | |||
| 219 | 3 | s->x1 = inlink->w - 1; | |
| 220 | 3 | s->y1 = inlink->h - 1; | |
| 221 | 3 | s->x2 = 0; | |
| 222 | 3 | s->y2 = 0; | |
| 223 | |||
| 224 | 3 | s->window_size = FFMAX(s->reset_count, 15); | |
| 225 | 3 | s->tmpbuf = av_malloc(bufsize); | |
| 226 | 3 | s->filterbuf = av_malloc(bufsize * s->max_pixsteps[0]); | |
| 227 | 3 | s->gradients = av_calloc(bufsize, sizeof(*s->gradients)); | |
| 228 | 3 | s->directions = av_malloc(bufsize); | |
| 229 | 3 | s->bboxes[0] = av_malloc(s->window_size * sizeof(*s->bboxes[0])); | |
| 230 | 3 | s->bboxes[1] = av_malloc(s->window_size * sizeof(*s->bboxes[1])); | |
| 231 | 3 | s->bboxes[2] = av_malloc(s->window_size * sizeof(*s->bboxes[2])); | |
| 232 | 3 | s->bboxes[3] = av_malloc(s->window_size * sizeof(*s->bboxes[3])); | |
| 233 | |||
| 234 |
4/8✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
|
3 | if (!s->tmpbuf || !s->filterbuf || !s->gradients || !s->directions || |
| 235 |
4/8✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 3 times.
|
3 | !s->bboxes[0] || !s->bboxes[1] || !s->bboxes[2] || !s->bboxes[3]) |
| 236 | ✗ | return AVERROR(ENOMEM); | |
| 237 | |||
| 238 | 3 | return 0; | |
| 239 | } | ||
| 240 | |||
| 241 | #define SET_META(key, value) \ | ||
| 242 | av_dict_set_int(metadata, key, value, 0) | ||
| 243 | |||
| 244 | 50 | static int filter_frame(AVFilterLink *inlink, AVFrame *frame) | |
| 245 | { | ||
| 246 | 50 | AVFilterContext *ctx = inlink->dst; | |
| 247 | 50 | CropDetectContext *s = ctx->priv; | |
| 248 | 50 | int bpp = s->max_pixsteps[0]; | |
| 249 | int w, h, x, y, shrink_by, i; | ||
| 250 | AVDictionary **metadata; | ||
| 251 | int outliers, last_y; | ||
| 252 | 50 | int limit_upscaled = lrint(s->limit_upscaled); | |
| 253 | char limit_str[22]; | ||
| 254 | |||
| 255 | 50 | const int inw = inlink->w; | |
| 256 | 50 | const int inh = inlink->h; | |
| 257 | 50 | uint8_t *tmpbuf = s->tmpbuf; | |
| 258 | 50 | uint8_t *filterbuf = s->filterbuf; | |
| 259 | 50 | uint16_t *gradients = s->gradients; | |
| 260 | 50 | int8_t *directions = s->directions; | |
| 261 | 50 | const AVFrameSideData *sd = NULL; | |
| 262 | int scan_w, scan_h, bboff; | ||
| 263 | |||
| 264 | 50 | void (*sobel)(int w, int h, uint16_t *dst, int dst_linesize, | |
| 265 | int8_t *dir, int dir_linesize, | ||
| 266 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 50 times.
|
50 | const uint8_t *src, int src_linesize, int src_stride) = (bpp == 2) ? &ff_sobel_16 : &ff_sobel_8; |
| 267 | 50 | void (*gaussian_blur)(int w, int h, | |
| 268 | uint8_t *dst, int dst_linesize, | ||
| 269 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 50 times.
|
50 | const uint8_t *src, int src_linesize, int src_stride) = (bpp == 2) ? &ff_gaussian_blur_16 : &ff_gaussian_blur_8; |
| 270 | |||
| 271 | |||
| 272 | // ignore first s->skip frames | ||
| 273 |
2/2✓ Branch 0 taken 44 times.
✓ Branch 1 taken 6 times.
|
50 | if (++s->frame_nb > 0) { |
| 274 | 44 | metadata = &frame->metadata; | |
| 275 | |||
| 276 | // Reset the crop area every reset_count frames, if reset_count is > 0 | ||
| 277 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
44 | if (s->reset_count > 0 && s->frame_nb > s->reset_count) { |
| 278 | ✗ | s->x1 = frame->width - 1; | |
| 279 | ✗ | s->y1 = frame->height - 1; | |
| 280 | ✗ | s->x2 = 0; | |
| 281 | ✗ | s->y2 = 0; | |
| 282 | ✗ | s->frame_nb = 1; | |
| 283 | } | ||
| 284 | |||
| 285 | #define FIND(DST, FROM, NOEND, INC, STEP0, STEP1, LEN) \ | ||
| 286 | outliers = 0;\ | ||
| 287 | for (last_y = y = FROM; NOEND; y = y INC) {\ | ||
| 288 | if (checkline(ctx, frame->data[0] + STEP0 * y, STEP1, LEN, bpp) > limit_upscaled) {\ | ||
| 289 | if (++outliers > s->max_outliers) { \ | ||
| 290 | DST = last_y;\ | ||
| 291 | break;\ | ||
| 292 | }\ | ||
| 293 | } else\ | ||
| 294 | last_y = y INC;\ | ||
| 295 | } | ||
| 296 | |||
| 297 |
2/2✓ Branch 0 taken 30 times.
✓ Branch 1 taken 14 times.
|
44 | if (s->mode == MODE_BLACK) { |
| 298 |
6/6✓ Branch 1 taken 62 times.
✓ Branch 2 taken 1772 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 61 times.
✓ Branch 5 taken 1834 times.
✓ Branch 6 taken 29 times.
|
1863 | FIND(s->y1, 0, y < s->y1, +1, frame->linesize[0], bpp, frame->width); |
| 299 |
6/6✓ Branch 1 taken 4 times.
✓ Branch 2 taken 1830 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 1834 times.
✓ Branch 6 taken 29 times.
|
1863 | FIND(s->y2, frame->height - 1, y > FFMAX(s->y2, s->y1), -1, frame->linesize[0], bpp, frame->width); |
| 300 |
5/6✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 29 times.
|
33 | FIND(s->x1, 0, y < s->x1, +1, bpp, frame->linesize[0], frame->height); |
| 301 |
5/6✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 29 times.
|
33 | FIND(s->x2, frame->width - 1, y > FFMAX(s->x2, s->x1), -1, bpp, frame->linesize[0], frame->height); |
| 302 | } else { // MODE_MV_EDGES | ||
| 303 | 14 | sd = av_frame_get_side_data(frame, AV_FRAME_DATA_MOTION_VECTORS); | |
| 304 | 14 | s->x1 = 0; | |
| 305 | 14 | s->y1 = 0; | |
| 306 | 14 | s->x2 = inw - 1; | |
| 307 | 14 | s->y2 = inh - 1; | |
| 308 | |||
| 309 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
|
14 | if (!sd) { |
| 310 | ✗ | av_log(ctx, AV_LOG_WARNING, "Cannot detect: no motion vectors available"); | |
| 311 | } else { | ||
| 312 | // gaussian filter to reduce noise | ||
| 313 | 14 | gaussian_blur(inw, inh, | |
| 314 | filterbuf, inw*bpp, | ||
| 315 | 14 | frame->data[0], frame->linesize[0], bpp); | |
| 316 | |||
| 317 | // compute the 16-bits gradients and directions for the next step | ||
| 318 | 14 | sobel(inw, inh, gradients, inw, directions, inw, filterbuf, inw*bpp, bpp); | |
| 319 | |||
| 320 | // non_maximum_suppression() will actually keep & clip what's necessary and | ||
| 321 | // ignore the rest, so we need a clean output buffer | ||
| 322 | 14 | memset(tmpbuf, 0, inw * inh); | |
| 323 | 14 | ff_non_maximum_suppression(inw, inh, tmpbuf, inw, directions, inw, gradients, inw); | |
| 324 | |||
| 325 | |||
| 326 | // keep high values, or low values surrounded by high values | ||
| 327 | 14 | ff_double_threshold(s->low_u8, s->high_u8, inw, inh, | |
| 328 | tmpbuf, inw, tmpbuf, inw); | ||
| 329 | |||
| 330 | // scan all MVs and store bounding box | ||
| 331 | 14 | s->x1 = inw - 1; | |
| 332 | 14 | s->y1 = inh - 1; | |
| 333 | 14 | s->x2 = 0; | |
| 334 | 14 | s->y2 = 0; | |
| 335 |
2/2✓ Branch 0 taken 178640 times.
✓ Branch 1 taken 14 times.
|
178654 | for (i = 0; i < sd->size / sizeof(AVMotionVector); i++) { |
| 336 | 178640 | const AVMotionVector *mv = (const AVMotionVector*)sd->data + i; | |
| 337 | 178640 | const int mx = mv->dst_x - mv->src_x; | |
| 338 | 178640 | const int my = mv->dst_y - mv->src_y; | |
| 339 | |||
| 340 |
2/4✓ Branch 0 taken 178640 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 178640 times.
✗ Branch 3 not taken.
|
178640 | if (mv->dst_x >= 0 && mv->dst_x < inw && |
| 341 |
2/4✓ Branch 0 taken 178640 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 178640 times.
✗ Branch 3 not taken.
|
178640 | mv->dst_y >= 0 && mv->dst_y < inh && |
| 342 |
2/4✓ Branch 0 taken 178640 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 178640 times.
✗ Branch 3 not taken.
|
178640 | mv->src_x >= 0 && mv->src_x < inw && |
| 343 |
2/4✓ Branch 0 taken 178640 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 178640 times.
✗ Branch 3 not taken.
|
178640 | mv->src_y >= 0 && mv->src_y < inh && |
| 344 |
2/2✓ Branch 0 taken 263 times.
✓ Branch 1 taken 178377 times.
|
178640 | mx * mx + my * my >= s->mv_threshold * s->mv_threshold) { |
| 345 | 263 | s->x1 = mv->dst_x < s->x1 ? mv->dst_x : s->x1; | |
| 346 | 263 | s->y1 = mv->dst_y < s->y1 ? mv->dst_y : s->y1; | |
| 347 | 263 | s->x2 = mv->dst_x > s->x2 ? mv->dst_x : s->x2; | |
| 348 | 263 | s->y2 = mv->dst_y > s->y2 ? mv->dst_y : s->y2; | |
| 349 | } | ||
| 350 | } | ||
| 351 | |||
| 352 | // assert x1<x2, y1<y2 | ||
| 353 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
|
14 | if (s->x1 > s->x2) FFSWAP(int, s->x1, s->x2); |
| 354 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
|
14 | if (s->y1 > s->y2) FFSWAP(int, s->y1, s->y2); |
| 355 | |||
| 356 | // scan outward looking for 0-edge-lines in edge image | ||
| 357 | 14 | scan_w = s->x2 - s->x1; | |
| 358 | 14 | scan_h = s->y2 - s->y1; | |
| 359 | |||
| 360 | #define FIND_EDGE(DST, FROM, NOEND, INC, STEP0, STEP1, LEN) \ | ||
| 361 | for (last_y = y = FROM; NOEND; y = y INC) { \ | ||
| 362 | if (checkline_edge(ctx, tmpbuf + STEP0 * y, STEP1, LEN, bpp)) { \ | ||
| 363 | if (last_y INC == y) { \ | ||
| 364 | DST = y; \ | ||
| 365 | break; \ | ||
| 366 | } else \ | ||
| 367 | last_y = y; \ | ||
| 368 | } \ | ||
| 369 | } \ | ||
| 370 | if (!(NOEND)) { \ | ||
| 371 | DST = y -(INC); \ | ||
| 372 | } | ||
| 373 | |||
| 374 |
6/8✓ Branch 1 taken 31 times.
✓ Branch 2 taken 464 times.
✓ Branch 3 taken 14 times.
✓ Branch 4 taken 17 times.
✓ Branch 5 taken 495 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 14 times.
|
495 | FIND_EDGE(s->y1, s->y1, y >= 0, -1, inw, bpp, scan_w); |
| 375 |
6/8✓ Branch 1 taken 28 times.
✓ Branch 2 taken 433 times.
✓ Branch 3 taken 14 times.
✓ Branch 4 taken 14 times.
✓ Branch 5 taken 461 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 14 times.
|
461 | FIND_EDGE(s->y2, s->y2, y < inh, +1, inw, bpp, scan_w); |
| 376 |
8/8✓ Branch 1 taken 24 times.
✓ Branch 2 taken 1009 times.
✓ Branch 3 taken 10 times.
✓ Branch 4 taken 14 times.
✓ Branch 5 taken 1033 times.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 4 times.
✓ Branch 8 taken 10 times.
|
1037 | FIND_EDGE(s->x1, s->x1, y >= 0, -1, bpp, inw, scan_h); |
| 377 |
8/8✓ Branch 1 taken 24 times.
✓ Branch 2 taken 970 times.
✓ Branch 3 taken 10 times.
✓ Branch 4 taken 14 times.
✓ Branch 5 taken 994 times.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 4 times.
✓ Branch 8 taken 10 times.
|
998 | FIND_EDGE(s->x2, s->x2, y < inw, +1, bpp, inw, scan_h); |
| 378 | |||
| 379 | // queue bboxes | ||
| 380 | 14 | bboff = (s->frame_nb - 1) % s->window_size; | |
| 381 | 14 | s->bboxes[0][bboff] = s->x1; | |
| 382 | 14 | s->bboxes[1][bboff] = s->x2; | |
| 383 | 14 | s->bboxes[2][bboff] = s->y1; | |
| 384 | 14 | s->bboxes[3][bboff] = s->y2; | |
| 385 | |||
| 386 | // sort queue | ||
| 387 | 14 | bboff = FFMIN(s->frame_nb, s->window_size); | |
| 388 |
43/44✓ Branch 0 taken 12 times.
✓ Branch 1 taken 5 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 11 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
✓ Branch 9 taken 1 times.
✓ Branch 10 taken 10 times.
✓ Branch 12 taken 3 times.
✓ Branch 13 taken 9 times.
✓ Branch 14 taken 3 times.
✓ Branch 15 taken 9 times.
✓ Branch 16 taken 11 times.
✓ Branch 17 taken 1 times.
✓ Branch 19 taken 1 times.
✓ Branch 20 taken 10 times.
✓ Branch 21 taken 15 times.
✓ Branch 22 taken 2 times.
✓ Branch 24 taken 6 times.
✓ Branch 25 taken 9 times.
✓ Branch 26 taken 9 times.
✓ Branch 27 taken 2 times.
✓ Branch 28 taken 11 times.
✓ Branch 29 taken 9 times.
✓ Branch 30 taken 7 times.
✓ Branch 31 taken 2 times.
✓ Branch 32 taken 5 times.
✓ Branch 33 taken 2 times.
✓ Branch 34 taken 4 times.
✓ Branch 35 taken 1 times.
✓ Branch 36 taken 26 times.
✓ Branch 37 taken 5 times.
✓ Branch 39 taken 25 times.
✓ Branch 40 taken 1 times.
✓ Branch 41 taken 5 times.
✓ Branch 42 taken 1 times.
✓ Branch 43 taken 1 times.
✓ Branch 44 taken 3 times.
✓ Branch 46 taken 3 times.
✓ Branch 47 taken 2 times.
✓ Branch 48 taken 17 times.
✓ Branch 49 taken 5 times.
✓ Branch 50 taken 18 times.
✓ Branch 51 taken 14 times.
|
88 | AV_QSORT(s->bboxes[0], bboff, int, comp); |
| 389 |
43/44✓ Branch 0 taken 13 times.
✓ Branch 1 taken 9 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 9 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 3 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 9 times.
✓ Branch 12 taken 7 times.
✓ Branch 13 taken 6 times.
✓ Branch 14 taken 3 times.
✓ Branch 15 taken 10 times.
✓ Branch 16 taken 12 times.
✓ Branch 17 taken 1 times.
✓ Branch 19 taken 3 times.
✓ Branch 20 taken 9 times.
✓ Branch 21 taken 17 times.
✓ Branch 22 taken 4 times.
✓ Branch 24 taken 11 times.
✓ Branch 25 taken 6 times.
✓ Branch 26 taken 6 times.
✓ Branch 27 taken 4 times.
✓ Branch 28 taken 10 times.
✓ Branch 29 taken 10 times.
✓ Branch 30 taken 5 times.
✓ Branch 31 taken 5 times.
✓ Branch 32 taken 3 times.
✓ Branch 33 taken 2 times.
✓ Branch 34 taken 2 times.
✓ Branch 35 taken 1 times.
✓ Branch 36 taken 15 times.
✓ Branch 37 taken 2 times.
✓ Branch 39 taken 13 times.
✓ Branch 40 taken 2 times.
✓ Branch 41 taken 2 times.
✓ Branch 42 taken 2 times.
✓ Branch 43 taken 1 times.
✓ Branch 44 taken 7 times.
✓ Branch 46 taken 4 times.
✓ Branch 47 taken 5 times.
✓ Branch 48 taken 22 times.
✓ Branch 49 taken 8 times.
✓ Branch 50 taken 22 times.
✓ Branch 51 taken 14 times.
|
91 | AV_QSORT(s->bboxes[1], bboff, int, comp); |
| 390 |
43/44✓ Branch 0 taken 13 times.
✓ Branch 1 taken 7 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
✓ Branch 9 taken 1 times.
✓ Branch 10 taken 11 times.
✓ Branch 12 taken 4 times.
✓ Branch 13 taken 9 times.
✓ Branch 14 taken 3 times.
✓ Branch 15 taken 10 times.
✓ Branch 16 taken 10 times.
✓ Branch 17 taken 2 times.
✓ Branch 19 taken 2 times.
✓ Branch 20 taken 8 times.
✓ Branch 21 taken 18 times.
✓ Branch 22 taken 3 times.
✓ Branch 24 taken 11 times.
✓ Branch 25 taken 7 times.
✓ Branch 26 taken 7 times.
✓ Branch 27 taken 3 times.
✓ Branch 28 taken 10 times.
✓ Branch 29 taken 10 times.
✓ Branch 30 taken 7 times.
✓ Branch 31 taken 3 times.
✓ Branch 32 taken 5 times.
✓ Branch 33 taken 2 times.
✓ Branch 34 taken 4 times.
✓ Branch 35 taken 1 times.
✓ Branch 36 taken 21 times.
✓ Branch 37 taken 4 times.
✓ Branch 39 taken 19 times.
✓ Branch 40 taken 2 times.
✓ Branch 41 taken 4 times.
✓ Branch 42 taken 2 times.
✓ Branch 43 taken 2 times.
✓ Branch 44 taken 4 times.
✓ Branch 46 taken 4 times.
✓ Branch 47 taken 3 times.
✓ Branch 48 taken 20 times.
✓ Branch 49 taken 6 times.
✓ Branch 50 taken 20 times.
✓ Branch 51 taken 14 times.
|
92 | AV_QSORT(s->bboxes[2], bboff, int, comp); |
| 391 |
37/44✓ Branch 0 taken 13 times.
✓ Branch 1 taken 4 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 10 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 3 times.
✓ Branch 9 taken 1 times.
✓ Branch 10 taken 9 times.
✓ Branch 12 taken 5 times.
✓ Branch 13 taken 8 times.
✓ Branch 14 taken 4 times.
✓ Branch 15 taken 9 times.
✓ Branch 16 taken 13 times.
✗ Branch 17 not taken.
✓ Branch 19 taken 4 times.
✓ Branch 20 taken 9 times.
✓ Branch 21 taken 16 times.
✓ Branch 22 taken 4 times.
✓ Branch 24 taken 11 times.
✓ Branch 25 taken 5 times.
✓ Branch 26 taken 5 times.
✓ Branch 27 taken 4 times.
✓ Branch 28 taken 9 times.
✓ Branch 29 taken 9 times.
✓ Branch 30 taken 4 times.
✓ Branch 31 taken 5 times.
✓ Branch 32 taken 1 times.
✓ Branch 33 taken 3 times.
✓ Branch 34 taken 1 times.
✗ Branch 35 not taken.
✓ Branch 36 taken 18 times.
✓ Branch 37 taken 4 times.
✓ Branch 39 taken 18 times.
✗ Branch 40 not taken.
✓ Branch 41 taken 4 times.
✗ Branch 42 not taken.
✗ Branch 43 not taken.
✓ Branch 44 taken 5 times.
✗ Branch 46 not taken.
✓ Branch 47 taken 4 times.
✓ Branch 48 taken 17 times.
✓ Branch 49 taken 7 times.
✓ Branch 50 taken 19 times.
✓ Branch 51 taken 14 times.
|
89 | AV_QSORT(s->bboxes[3], bboff, int, comp); |
| 392 | |||
| 393 | // return median of window_size elems | ||
| 394 | 14 | s->x1 = s->bboxes[0][bboff/2]; | |
| 395 | 14 | s->x2 = s->bboxes[1][bboff/2]; | |
| 396 | 14 | s->y1 = s->bboxes[2][bboff/2]; | |
| 397 | 14 | s->y2 = s->bboxes[3][bboff/2]; | |
| 398 | } | ||
| 399 | } | ||
| 400 | |||
| 401 | // round x and y (up), important for yuv colorspaces | ||
| 402 | // make sure they stay rounded! | ||
| 403 | 44 | x = (s->x1+1) & ~1; | |
| 404 | 44 | y = (s->y1+1) & ~1; | |
| 405 | |||
| 406 | 44 | w = s->x2 - x + 1; | |
| 407 | 44 | h = s->y2 - y + 1; | |
| 408 | |||
| 409 | // w and h must be divisible by 2 as well because of yuv | ||
| 410 | // colorspace problems. | ||
| 411 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
|
44 | if (s->round <= 1) |
| 412 | ✗ | s->round = 16; | |
| 413 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
|
44 | if (s->round % 2) |
| 414 | ✗ | s->round *= 2; | |
| 415 | |||
| 416 | 44 | shrink_by = w % s->round; | |
| 417 | 44 | w -= shrink_by; | |
| 418 | 44 | x += (shrink_by/2 + 1) & ~1; | |
| 419 | |||
| 420 | 44 | shrink_by = h % s->round; | |
| 421 | 44 | h -= shrink_by; | |
| 422 | 44 | y += (shrink_by/2 + 1) & ~1; | |
| 423 | |||
| 424 | 44 | SET_META("lavfi.cropdetect.x1", s->x1); | |
| 425 | 44 | SET_META("lavfi.cropdetect.x2", s->x2); | |
| 426 | 44 | SET_META("lavfi.cropdetect.y1", s->y1); | |
| 427 | 44 | SET_META("lavfi.cropdetect.y2", s->y2); | |
| 428 | 44 | SET_META("lavfi.cropdetect.w", w); | |
| 429 | 44 | SET_META("lavfi.cropdetect.h", h); | |
| 430 | 44 | SET_META("lavfi.cropdetect.x", x); | |
| 431 | 44 | SET_META("lavfi.cropdetect.y", y); | |
| 432 | |||
| 433 | 44 | snprintf(limit_str, sizeof(limit_str), "%f", s->limit); | |
| 434 | 44 | av_dict_set(metadata, "lavfi.cropdetect.limit", limit_str, 0); | |
| 435 | |||
| 436 | 44 | av_log(ctx, AV_LOG_INFO, | |
| 437 | "x1:%d x2:%d y1:%d y2:%d w:%d h:%d x:%d y:%d pts:%"PRId64" t:%f limit:%f crop=%d:%d:%d:%d\n", | ||
| 438 | s->x1, s->x2, s->y1, s->y2, w, h, x, y, frame->pts, | ||
| 439 | 44 | frame->pts == AV_NOPTS_VALUE ? -1 : frame->pts * av_q2d(inlink->time_base), | |
| 440 |
1/2✓ Branch 0 taken 44 times.
✗ Branch 1 not taken.
|
44 | s->limit, w, h, x, y); |
| 441 | } | ||
| 442 | |||
| 443 | 50 | return ff_filter_frame(inlink->dst->outputs[0], frame); | |
| 444 | } | ||
| 445 | |||
| 446 | ✗ | static int process_command(AVFilterContext *ctx, const char *cmd, const char *args, | |
| 447 | char *res, int res_len, int flags) | ||
| 448 | { | ||
| 449 | ✗ | CropDetectContext *s = ctx->priv; | |
| 450 | ✗ | float old_limit = s->limit; | |
| 451 | int ret; | ||
| 452 | |||
| 453 | ✗ | if ((ret = ff_filter_process_command(ctx, cmd, args, res, res_len, flags)) < 0) | |
| 454 | ✗ | return ret; | |
| 455 | |||
| 456 | ✗ | if (old_limit != s->limit) { | |
| 457 | ✗ | if (s->limit < 1.0) | |
| 458 | ✗ | s->limit_upscaled = s->limit * ((1 << s->bitdepth) - 1); | |
| 459 | else | ||
| 460 | ✗ | s->limit_upscaled = s->limit; | |
| 461 | ✗ | s->frame_nb = s->reset_count; | |
| 462 | } | ||
| 463 | |||
| 464 | ✗ | return 0; | |
| 465 | } | ||
| 466 | |||
| 467 | #define OFFSET(x) offsetof(CropDetectContext, x) | ||
| 468 | #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM | ||
| 469 | #define TFLAGS AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_RUNTIME_PARAM | ||
| 470 | |||
| 471 | static const AVOption cropdetect_options[] = { | ||
| 472 | { "limit", "Threshold below which the pixel is considered black", OFFSET(limit), AV_OPT_TYPE_FLOAT, { .dbl = 24.0/255 }, 0, 65535, TFLAGS }, | ||
| 473 | { "round", "Value by which the width/height should be divisible", OFFSET(round), AV_OPT_TYPE_INT, { .i64 = 16 }, 0, INT_MAX, FLAGS }, | ||
| 474 | { "reset", "Recalculate the crop area after this many frames", OFFSET(reset_count), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, FLAGS }, | ||
| 475 | { "skip", "Number of initial frames to skip", OFFSET(skip), AV_OPT_TYPE_INT, { .i64 = 2 }, 0, INT_MAX, FLAGS }, | ||
| 476 | { "reset_count", "Recalculate the crop area after this many frames",OFFSET(reset_count),AV_OPT_TYPE_INT,{ .i64 = 0 }, 0, INT_MAX, FLAGS }, | ||
| 477 | { "max_outliers", "Threshold count of outliers", OFFSET(max_outliers),AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, FLAGS }, | ||
| 478 | { "mode", "set mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=MODE_BLACK}, 0, MODE_NB-1, FLAGS, .unit = "mode" }, | ||
| 479 | { "black", "detect black pixels surrounding the video", 0, AV_OPT_TYPE_CONST, {.i64=MODE_BLACK}, INT_MIN, INT_MAX, FLAGS, .unit = "mode" }, | ||
| 480 | { "mvedges", "detect motion and edged surrounding the video", 0, AV_OPT_TYPE_CONST, {.i64=MODE_MV_EDGES}, INT_MIN, INT_MAX, FLAGS, .unit = "mode" }, | ||
| 481 | { "high", "Set high threshold for edge detection", OFFSET(high), AV_OPT_TYPE_FLOAT, {.dbl=25/255.}, 0, 1, FLAGS }, | ||
| 482 | { "low", "Set low threshold for edge detection", OFFSET(low), AV_OPT_TYPE_FLOAT, {.dbl=15/255.}, 0, 1, FLAGS }, | ||
| 483 | { "mv_threshold", "motion vector threshold when estimating video window size", OFFSET(mv_threshold), AV_OPT_TYPE_INT, {.i64=8}, 0, 100, FLAGS}, | ||
| 484 | { NULL } | ||
| 485 | }; | ||
| 486 | |||
| 487 | AVFILTER_DEFINE_CLASS(cropdetect); | ||
| 488 | |||
| 489 | static const AVFilterPad avfilter_vf_cropdetect_inputs[] = { | ||
| 490 | { | ||
| 491 | .name = "default", | ||
| 492 | .type = AVMEDIA_TYPE_VIDEO, | ||
| 493 | .config_props = config_input, | ||
| 494 | .filter_frame = filter_frame, | ||
| 495 | }, | ||
| 496 | }; | ||
| 497 | |||
| 498 | const FFFilter ff_vf_cropdetect = { | ||
| 499 | .p.name = "cropdetect", | ||
| 500 | .p.description = NULL_IF_CONFIG_SMALL("Auto-detect crop size."), | ||
| 501 | .p.priv_class = &cropdetect_class, | ||
| 502 | .p.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_METADATA_ONLY, | ||
| 503 | .priv_size = sizeof(CropDetectContext), | ||
| 504 | .init = init, | ||
| 505 | .uninit = uninit, | ||
| 506 | FILTER_INPUTS(avfilter_vf_cropdetect_inputs), | ||
| 507 | FILTER_OUTPUTS(ff_video_default_filterpad), | ||
| 508 | FILTER_PIXFMTS_ARRAY(pix_fmts), | ||
| 509 | .process_command = process_command, | ||
| 510 | }; | ||
| 511 |