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