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