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
6 * This file is part of FFmpeg.
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.
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.
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
23 #include "libavutil/opt.h"
24 #include "libavutil/pixdesc.h"
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
;
47 uint8_t rgba_color
[4];
51 #define OFFSET(x) offsetof(SignalstatsContext, x)
52 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
54 static const AVOption signalstats_options
[] = {
55 {"stat", "set statistics filters", OFFSET(filters
), AV_OPT_TYPE_FLAGS
, {.i64
=0}, 0, INT_MAX
, FLAGS
, "filters"},
56 {"tout", "analyze pixels for temporal outliers", 0, AV_OPT_TYPE_CONST
, {.i64
=1<<FILTER_TOUT
}, 0, 0, FLAGS
, "filters"},
57 {"vrep", "analyze video lines for vertical line repitition", 0, AV_OPT_TYPE_CONST
, {.i64
=1<<FILTER_VREP
}, 0, 0, FLAGS
, "filters"},
58 {"brng", "analyze for pixels outside of broadcast range", 0, AV_OPT_TYPE_CONST
, {.i64
=1<<FILTER_BRNG
}, 0, 0, FLAGS
, "filters"},
59 {"out", "set video filter", OFFSET(outfilter
), AV_OPT_TYPE_INT
, {.i64
=FILTER_NONE
}, -1, FILT_NUMB
-1, FLAGS
, "out"},
60 {"tout", "highlight pixels that depict temporal outliers", 0, AV_OPT_TYPE_CONST
, {.i64
=FILTER_TOUT
}, 0, 0, FLAGS
, "out"},
61 {"vrep", "highlight video lines that depict vertical line repitition", 0, AV_OPT_TYPE_CONST
, {.i64
=FILTER_VREP
}, 0, 0, FLAGS
, "out"},
62 {"brng", "highlight pixels that are outside of broadcast range", 0, AV_OPT_TYPE_CONST
, {.i64
=FILTER_BRNG
}, 0, 0, FLAGS
, "out"},
63 {"c", "set highlight color", OFFSET(rgba_color
), AV_OPT_TYPE_COLOR
, {.str
="yellow"}, .flags
=FLAGS
},
64 {"color", "set highlight color", OFFSET(rgba_color
), AV_OPT_TYPE_COLOR
, {.str
="yellow"}, .flags
=FLAGS
},
68 AVFILTER_DEFINE_CLASS(signalstats
);
70 static av_cold
int init(AVFilterContext
*ctx
)
73 SignalstatsContext
*s
= ctx
->priv
;
75 if (s
->outfilter
!= FILTER_NONE
)
76 s
->filters
|= 1 << s
->outfilter
;
81 s
->yuv_color
[0] = (( 66*r
+ 129*g
+ 25*b
+ (1<<7)) >> 8) + 16;
82 s
->yuv_color
[1] = ((-38*r
+ -74*g
+ 112*b
+ (1<<7)) >> 8) + 128;
83 s
->yuv_color
[2] = ((112*r
+ -94*g
+ -18*b
+ (1<<7)) >> 8) + 128;
87 static av_cold
void uninit(AVFilterContext
*ctx
)
89 SignalstatsContext
*s
= ctx
->priv
;
90 av_frame_free(&s
->frame_prev
);
91 av_freep(&s
->vrep_line
);
94 static int query_formats(AVFilterContext
*ctx
)
97 static const enum AVPixelFormat pix_fmts
[] = {
98 AV_PIX_FMT_YUV444P
, AV_PIX_FMT_YUV422P
, AV_PIX_FMT_YUV420P
, AV_PIX_FMT_YUV411P
,
102 ff_set_common_formats(ctx
, ff_make_format_list(pix_fmts
));
106 static int config_props(AVFilterLink
*outlink
)
108 AVFilterContext
*ctx
= outlink
->src
;
109 SignalstatsContext
*s
= ctx
->priv
;
110 AVFilterLink
*inlink
= outlink
->src
->inputs
[0];
111 const AVPixFmtDescriptor
*desc
= av_pix_fmt_desc_get(outlink
->format
);
112 s
->hsub
= desc
->log2_chroma_w
;
113 s
->vsub
= desc
->log2_chroma_h
;
115 outlink
->w
= inlink
->w
;
116 outlink
->h
= inlink
->h
;
118 s
->chromaw
= FF_CEIL_RSHIFT(inlink
->w
, s
->hsub
);
119 s
->chromah
= FF_CEIL_RSHIFT(inlink
->h
, s
->vsub
);
121 s
->fs
= inlink
->w
* inlink
->h
;
122 s
->cfs
= s
->chromaw
* s
->chromah
;
124 if (s
->filters
& 1<<FILTER_VREP
) {
125 s
->vrep_line
= av_malloc(inlink
->h
* sizeof(*s
->vrep_line
));
127 return AVERROR(ENOMEM
);
133 static void burn_frame(SignalstatsContext
*s
, AVFrame
*f
, int x
, int y
)
135 const int chromax
= x
>> s
->hsub
;
136 const int chromay
= y
>> s
->vsub
;
137 f
->data
[0][y
* f
->linesize
[0] + x
] = s
->yuv_color
[0];
138 f
->data
[1][chromay
* f
->linesize
[1] + chromax
] = s
->yuv_color
[1];
139 f
->data
[2][chromay
* f
->linesize
[2] + chromax
] = s
->yuv_color
[2];
142 static int filter_brng(SignalstatsContext
*s
, const AVFrame
*in
, AVFrame
*out
, int y
, int w
, int h
)
145 const int yc
= y
>> s
->vsub
;
146 const uint8_t *pluma
= &in
->data
[0][y
* in
->linesize
[0]];
147 const uint8_t *pchromau
= &in
->data
[1][yc
* in
->linesize
[1]];
148 const uint8_t *pchromav
= &in
->data
[2][yc
* in
->linesize
[2]];
150 for (x
= 0; x
< w
; x
++) {
151 const int xc
= x
>> s
->hsub
;
152 const int luma
= pluma
[x
];
153 const int chromau
= pchromau
[xc
];
154 const int chromav
= pchromav
[xc
];
155 const int filt
= luma
< 16 || luma
> 235 ||
156 chromau
< 16 || chromau
> 240 ||
157 chromav
< 16 || chromav
> 240;
160 burn_frame(s
, out
, x
, y
);
165 static int filter_tout_outlier(uint8_t x
, uint8_t y
, uint8_t z
)
167 return ((abs(x
- y
) + abs (z
- y
)) / 2) - abs(z
- x
) > 4; // make 4 configurable?
170 static int filter_tout(SignalstatsContext
*s
, const AVFrame
*in
, AVFrame
*out
, int y
, int w
, int h
)
172 const uint8_t *p
= in
->data
[0];
173 int lw
= in
->linesize
[0];
174 int x
, score
= 0, filt
;
176 if (y
- 1 < 0 || y
+ 1 >= h
)
179 // detect two pixels above and below (to eliminate interlace artefacts)
180 // should check that video format is infact interlaced.
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])
187 #define FILTER3(j) (FILTER(-1, j) && FILTER(0, j) && FILTER(1, j))
189 if (y
- 2 >= 0 && y
+ 2 < h
) {
190 for (x
= 1; x
< w
- 1; x
++) {
191 filt
= FILTER3(2) && FILTER3(1);
194 burn_frame(s
, out
, x
, y
);
197 for (x
= 1; x
< w
- 1; x
++) {
201 burn_frame(s
, out
, x
, y
);
209 static void filter_init_vrep(SignalstatsContext
*s
, const AVFrame
*p
, int w
, int h
)
212 int lw
= p
->linesize
[0];
214 for (y
= VREP_START
; y
< h
; y
++) {
216 int y2lw
= (y
- VREP_START
) * lw
;
219 for (i
= 0; i
< w
; i
++)
220 totdiff
+= abs(p
->data
[0][y2lw
+ i
] - p
->data
[0][ylw
+ i
]);
222 /* this value should be definable */
223 s
->vrep_line
[y
] = totdiff
< w
;
227 static int filter_vrep(SignalstatsContext
*s
, const AVFrame
*in
, AVFrame
*out
, int y
, int w
, int h
)
234 for (x
= 0; x
< w
; x
++) {
235 if (s
->vrep_line
[y
]) {
238 burn_frame(s
, out
, x
, y
);
244 static const struct {
246 void (*init
)(SignalstatsContext
*s
, const AVFrame
*p
, int w
, int h
);
247 int (*process
)(SignalstatsContext
*s
, const AVFrame
*in
, AVFrame
*out
, int y
, int w
, int h
);
249 {"TOUT", NULL
, filter_tout
},
250 {"VREP", filter_init_vrep
, filter_vrep
},
251 {"BRNG", NULL
, filter_brng
},
257 static int filter_frame(AVFilterLink
*link
, AVFrame
*in
)
259 SignalstatsContext
*s
= link
->dst
->priv
;
260 AVFilterLink
*outlink
= link
->dst
->outputs
[0];
263 int w
= 0, cw
= 0, // in
264 pw
= 0, cpw
= 0; // prev
268 unsigned int histy
[DEPTH
] = {0},
272 histsat
[DEPTH
] = {0}; // limited to 8 bit data.
273 int miny
= -1, minu
= -1, minv
= -1;
274 int maxy
= -1, maxu
= -1, maxv
= -1;
275 int lowy
= -1, lowu
= -1, lowv
= -1;
276 int highy
= -1, highu
= -1, highv
= -1;
277 int minsat
= -1, maxsat
= -1, lowsat
= -1, highsat
= -1;
278 int lowp
, highp
, clowp
, chighp
;
279 int accy
, accu
, accv
;
280 int accsat
, acchue
= 0;
282 int toty
= 0, totu
= 0, totv
= 0, totsat
=0;
284 int dify
= 0, difu
= 0, difv
= 0;
286 int filtot
[FILT_NUMB
] = {0};
290 s
->frame_prev
= av_frame_clone(in
);
292 prev
= s
->frame_prev
;
294 if (s
->outfilter
!= FILTER_NONE
)
295 out
= av_frame_clone(in
);
297 for (fil
= 0; fil
< FILT_NUMB
; fil
++)
298 if ((s
->filters
& 1<<fil
) && filters_def
[fil
].init
)
299 filters_def
[fil
].init(s
, in
, link
->w
, link
->h
);
301 // Calculate luma histogram and difference with previous frame or field.
302 for (j
= 0; j
< link
->h
; j
++) {
303 for (i
= 0; i
< link
->w
; i
++) {
304 yuv
= in
->data
[0][w
+ i
];
306 dify
+= abs(in
->data
[0][w
+ i
] - prev
->data
[0][pw
+ i
]);
308 w
+= in
->linesize
[0];
309 pw
+= prev
->linesize
[0];
312 // Calculate chroma histogram and difference with previous frame or field.
313 for (j
= 0; j
< s
->chromah
; j
++) {
314 for (i
= 0; i
< s
->chromaw
; i
++) {
317 yuvu
= in
->data
[1][cw
+i
];
318 yuvv
= in
->data
[2][cw
+i
];
320 difu
+= abs(in
->data
[1][cw
+i
] - prev
->data
[1][cpw
+i
]);
322 difv
+= abs(in
->data
[2][cw
+i
] - prev
->data
[2][cpw
+i
]);
325 sat
= hypot(yuvu
- 128, yuvv
- 128);
327 hue
= floor((180 / M_PI
) * atan2f(yuvu
-128, yuvv
-128) + 180);
330 cw
+= in
->linesize
[1];
331 cpw
+= prev
->linesize
[1];
334 for (j
= 0; j
< link
->h
; j
++) {
335 for (fil
= 0; fil
< FILT_NUMB
; fil
++) {
336 if (s
->filters
& 1<<fil
) {
337 AVFrame
*dbg
= out
!= in
&& s
->outfilter
== fil
? out
: NULL
;
338 filtot
[fil
] += filters_def
[fil
].process(s
, in
, dbg
, j
, link
->w
, link
->h
);
343 // find low / high based on histogram percentile
344 // these only need to be calculated once.
346 lowp
= lrint(s
->fs
* 10 / 100.);
347 highp
= lrint(s
->fs
* 90 / 100.);
348 clowp
= lrint(s
->cfs
* 10 / 100.);
349 chighp
= lrint(s
->cfs
* 90 / 100.);
351 accy
= accu
= accv
= accsat
= 0;
352 for (fil
= 0; fil
< DEPTH
; fil
++) {
353 if (miny
< 0 && histy
[fil
]) miny
= fil
;
354 if (minu
< 0 && histu
[fil
]) minu
= fil
;
355 if (minv
< 0 && histv
[fil
]) minv
= fil
;
356 if (minsat
< 0 && histsat
[fil
]) minsat
= fil
;
358 if (histy
[fil
]) maxy
= fil
;
359 if (histu
[fil
]) maxu
= fil
;
360 if (histv
[fil
]) maxv
= fil
;
361 if (histsat
[fil
]) maxsat
= fil
;
363 toty
+= histy
[fil
] * fil
;
364 totu
+= histu
[fil
] * fil
;
365 totv
+= histv
[fil
] * fil
;
366 totsat
+= histsat
[fil
] * fil
;
371 accsat
+= histsat
[fil
];
373 if (lowy
== -1 && accy
>= lowp
) lowy
= fil
;
374 if (lowu
== -1 && accu
>= clowp
) lowu
= fil
;
375 if (lowv
== -1 && accv
>= clowp
) lowv
= fil
;
376 if (lowsat
== -1 && accsat
>= clowp
) lowsat
= fil
;
378 if (highy
== -1 && accy
>= highp
) highy
= fil
;
379 if (highu
== -1 && accu
>= chighp
) highu
= fil
;
380 if (highv
== -1 && accv
>= chighp
) highv
= fil
;
381 if (highsat
== -1 && accsat
>= chighp
) highsat
= fil
;
386 for (fil
= 0; fil
< 360; fil
++) {
387 tothue
+= histhue
[fil
] * fil
;
388 acchue
+= histhue
[fil
];
390 if (medhue
== -1 && acchue
> s
->cfs
/ 2)
392 if (histhue
[fil
] > maxhue
) {
393 maxhue
= histhue
[fil
];
397 av_frame_free(&s
->frame_prev
);
398 s
->frame_prev
= av_frame_clone(in
);
400 #define SET_META(key, fmt, val) do { \
401 snprintf(metabuf, sizeof(metabuf), fmt, val); \
402 av_dict_set(&out->metadata, "lavfi.signalstats." key, metabuf, 0); \
405 SET_META("YMIN", "%d", miny
);
406 SET_META("YLOW", "%d", lowy
);
407 SET_META("YAVG", "%g", 1.0 * toty
/ s
->fs
);
408 SET_META("YHIGH", "%d", highy
);
409 SET_META("YMAX", "%d", maxy
);
411 SET_META("UMIN", "%d", minu
);
412 SET_META("ULOW", "%d", lowu
);
413 SET_META("UAVG", "%g", 1.0 * totu
/ s
->cfs
);
414 SET_META("UHIGH", "%d", highu
);
415 SET_META("UMAX", "%d", maxu
);
417 SET_META("VMIN", "%d", minv
);
418 SET_META("VLOW", "%d", lowv
);
419 SET_META("VAVG", "%g", 1.0 * totv
/ s
->cfs
);
420 SET_META("VHIGH", "%d", highv
);
421 SET_META("VMAX", "%d", maxv
);
423 SET_META("SATMIN", "%d", minsat
);
424 SET_META("SATLOW", "%d", lowsat
);
425 SET_META("SATAVG", "%g", 1.0 * totsat
/ s
->cfs
);
426 SET_META("SATHIGH", "%d", highsat
);
427 SET_META("SATMAX", "%d", maxsat
);
429 SET_META("HUEMED", "%d", medhue
);
430 SET_META("HUEAVG", "%g", 1.0 * tothue
/ s
->cfs
);
432 SET_META("YDIF", "%g", 1.0 * dify
/ s
->fs
);
433 SET_META("UDIF", "%g", 1.0 * difu
/ s
->cfs
);
434 SET_META("VDIF", "%g", 1.0 * difv
/ s
->cfs
);
436 for (fil
= 0; fil
< FILT_NUMB
; fil
++) {
437 if (s
->filters
& 1<<fil
) {
439 snprintf(metabuf
, sizeof(metabuf
), "%g", 1.0 * filtot
[fil
] / s
->fs
);
440 snprintf(metaname
, sizeof(metaname
), "lavfi.signalstats.%s", filters_def
[fil
].name
);
441 av_dict_set(&out
->metadata
, metaname
, metabuf
, 0);
447 return ff_filter_frame(outlink
, out
);
450 static const AVFilterPad signalstats_inputs
[] = {
453 .type
= AVMEDIA_TYPE_VIDEO
,
454 .filter_frame
= filter_frame
,
459 static const AVFilterPad signalstats_outputs
[] = {
462 .config_props
= config_props
,
463 .type
= AVMEDIA_TYPE_VIDEO
,
468 AVFilter ff_vf_signalstats
= {
469 .name
= "signalstats",
470 .description
= "Generate statistics from video analysis.",
473 .query_formats
= query_formats
,
474 .priv_size
= sizeof(SignalstatsContext
),
475 .inputs
= signalstats_inputs
,
476 .outputs
= signalstats_outputs
,
477 .priv_class
= &signalstats_class
,