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 | |||
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 infact 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 infact 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 AVFilter ff_vf_signalstats = { | ||
831 | .name = "signalstats", | ||
832 | .description = "Generate statistics from video analysis.", | ||
833 | .init = init, | ||
834 | .uninit = uninit, | ||
835 | .priv_size = sizeof(SignalstatsContext), | ||
836 | FILTER_INPUTS(signalstats_inputs), | ||
837 | FILTER_OUTPUTS(signalstats_outputs), | ||
838 | FILTER_PIXFMTS_ARRAY(pix_fmts), | ||
839 | .priv_class = &signalstats_class, | ||
840 | .flags = AVFILTER_FLAG_SLICE_THREADS, | ||
841 | }; | ||
842 |