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
;
46 uint8_t rgba_color
[4];
55 typedef struct ThreadData
{
60 typedef struct ThreadDataHueSatMetrics
{
62 AVFrame
*dst_sat
, *dst_hue
;
63 } ThreadDataHueSatMetrics
;
65 #define OFFSET(x) offsetof(SignalstatsContext, x)
66 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
68 static const AVOption signalstats_options
[] = {
69 {"stat", "set statistics filters", OFFSET(filters
), AV_OPT_TYPE_FLAGS
, {.i64
=0}, 0, INT_MAX
, FLAGS
, "filters"},
70 {"tout", "analyze pixels for temporal outliers", 0, AV_OPT_TYPE_CONST
, {.i64
=1<<FILTER_TOUT
}, 0, 0, FLAGS
, "filters"},
71 {"vrep", "analyze video lines for vertical line repetition", 0, AV_OPT_TYPE_CONST
, {.i64
=1<<FILTER_VREP
}, 0, 0, FLAGS
, "filters"},
72 {"brng", "analyze for pixels outside of broadcast range", 0, AV_OPT_TYPE_CONST
, {.i64
=1<<FILTER_BRNG
}, 0, 0, FLAGS
, "filters"},
73 {"out", "set video filter", OFFSET(outfilter
), AV_OPT_TYPE_INT
, {.i64
=FILTER_NONE
}, -1, FILT_NUMB
-1, FLAGS
, "out"},
74 {"tout", "highlight pixels that depict temporal outliers", 0, AV_OPT_TYPE_CONST
, {.i64
=FILTER_TOUT
}, 0, 0, FLAGS
, "out"},
75 {"vrep", "highlight video lines that depict vertical line repetition", 0, AV_OPT_TYPE_CONST
, {.i64
=FILTER_VREP
}, 0, 0, FLAGS
, "out"},
76 {"brng", "highlight pixels that are outside of broadcast range", 0, AV_OPT_TYPE_CONST
, {.i64
=FILTER_BRNG
}, 0, 0, FLAGS
, "out"},
77 {"c", "set highlight color", OFFSET(rgba_color
), AV_OPT_TYPE_COLOR
, {.str
="yellow"}, .flags
=FLAGS
},
78 {"color", "set highlight color", OFFSET(rgba_color
), AV_OPT_TYPE_COLOR
, {.str
="yellow"}, .flags
=FLAGS
},
82 AVFILTER_DEFINE_CLASS(signalstats
);
84 static av_cold
int init(AVFilterContext
*ctx
)
87 SignalstatsContext
*s
= ctx
->priv
;
89 if (s
->outfilter
!= FILTER_NONE
)
90 s
->filters
|= 1 << s
->outfilter
;
95 s
->yuv_color
[0] = (( 66*r
+ 129*g
+ 25*b
+ (1<<7)) >> 8) + 16;
96 s
->yuv_color
[1] = ((-38*r
+ -74*g
+ 112*b
+ (1<<7)) >> 8) + 128;
97 s
->yuv_color
[2] = ((112*r
+ -94*g
+ -18*b
+ (1<<7)) >> 8) + 128;
101 static av_cold
void uninit(AVFilterContext
*ctx
)
103 SignalstatsContext
*s
= ctx
->priv
;
104 av_frame_free(&s
->frame_prev
);
105 av_frame_free(&s
->frame_sat
);
106 av_frame_free(&s
->frame_hue
);
107 av_freep(&s
->jobs_rets
);
110 static int query_formats(AVFilterContext
*ctx
)
113 static const enum AVPixelFormat pix_fmts
[] = {
114 AV_PIX_FMT_YUV444P
, AV_PIX_FMT_YUV422P
, AV_PIX_FMT_YUV420P
, AV_PIX_FMT_YUV411P
,
116 AV_PIX_FMT_YUVJ422P
, AV_PIX_FMT_YUVJ444P
, AV_PIX_FMT_YUVJ420P
, AV_PIX_FMT_YUVJ411P
,
121 ff_set_common_formats(ctx
, ff_make_format_list(pix_fmts
));
125 static AVFrame
*alloc_frame(enum AVPixelFormat pixfmt
, int w
, int h
)
127 AVFrame
*frame
= av_frame_alloc();
131 frame
->format
= pixfmt
;
135 if (av_frame_get_buffer(frame
, 32) < 0)
141 static int config_props(AVFilterLink
*outlink
)
143 AVFilterContext
*ctx
= outlink
->src
;
144 SignalstatsContext
*s
= ctx
->priv
;
145 AVFilterLink
*inlink
= outlink
->src
->inputs
[0];
146 const AVPixFmtDescriptor
*desc
= av_pix_fmt_desc_get(outlink
->format
);
147 s
->hsub
= desc
->log2_chroma_w
;
148 s
->vsub
= desc
->log2_chroma_h
;
150 outlink
->w
= inlink
->w
;
151 outlink
->h
= inlink
->h
;
153 s
->chromaw
= FF_CEIL_RSHIFT(inlink
->w
, s
->hsub
);
154 s
->chromah
= FF_CEIL_RSHIFT(inlink
->h
, s
->vsub
);
156 s
->fs
= inlink
->w
* inlink
->h
;
157 s
->cfs
= s
->chromaw
* s
->chromah
;
159 s
->nb_jobs
= FFMAX(1, FFMIN(inlink
->h
, ctx
->graph
->nb_threads
));
160 s
->jobs_rets
= av_malloc_array(s
->nb_jobs
, sizeof(*s
->jobs_rets
));
162 return AVERROR(ENOMEM
);
164 s
->frame_sat
= alloc_frame(AV_PIX_FMT_GRAY8
, inlink
->w
, inlink
->h
);
165 s
->frame_hue
= alloc_frame(AV_PIX_FMT_GRAY16
, inlink
->w
, inlink
->h
);
166 if (!s
->frame_sat
|| !s
->frame_hue
)
167 return AVERROR(ENOMEM
);
172 static void burn_frame(const SignalstatsContext
*s
, AVFrame
*f
, int x
, int y
)
174 const int chromax
= x
>> s
->hsub
;
175 const int chromay
= y
>> s
->vsub
;
176 f
->data
[0][y
* f
->linesize
[0] + x
] = s
->yuv_color
[0];
177 f
->data
[1][chromay
* f
->linesize
[1] + chromax
] = s
->yuv_color
[1];
178 f
->data
[2][chromay
* f
->linesize
[2] + chromax
] = s
->yuv_color
[2];
181 static int filter_brng(AVFilterContext
*ctx
, void *arg
, int jobnr
, int nb_jobs
)
183 ThreadData
*td
= arg
;
184 const SignalstatsContext
*s
= ctx
->priv
;
185 const AVFrame
*in
= td
->in
;
186 AVFrame
*out
= td
->out
;
187 const int w
= in
->width
;
188 const int h
= in
->height
;
189 const int slice_start
= (h
* jobnr
) / nb_jobs
;
190 const int slice_end
= (h
* (jobnr
+1)) / nb_jobs
;
193 for (y
= slice_start
; y
< slice_end
; y
++) {
194 const int yc
= y
>> s
->vsub
;
195 const uint8_t *pluma
= &in
->data
[0][y
* in
->linesize
[0]];
196 const uint8_t *pchromau
= &in
->data
[1][yc
* in
->linesize
[1]];
197 const uint8_t *pchromav
= &in
->data
[2][yc
* in
->linesize
[2]];
199 for (x
= 0; x
< w
; x
++) {
200 const int xc
= x
>> s
->hsub
;
201 const int luma
= pluma
[x
];
202 const int chromau
= pchromau
[xc
];
203 const int chromav
= pchromav
[xc
];
204 const int filt
= luma
< 16 || luma
> 235 ||
205 chromau
< 16 || chromau
> 240 ||
206 chromav
< 16 || chromav
> 240;
209 burn_frame(s
, out
, x
, y
);
215 static int filter_tout_outlier(uint8_t x
, uint8_t y
, uint8_t z
)
217 return ((abs(x
- y
) + abs (z
- y
)) / 2) - abs(z
- x
) > 4; // make 4 configurable?
220 static int filter_tout(AVFilterContext
*ctx
, void *arg
, int jobnr
, int nb_jobs
)
222 ThreadData
*td
= arg
;
223 const SignalstatsContext
*s
= ctx
->priv
;
224 const AVFrame
*in
= td
->in
;
225 AVFrame
*out
= td
->out
;
226 const int w
= in
->width
;
227 const int h
= in
->height
;
228 const int slice_start
= (h
* jobnr
) / nb_jobs
;
229 const int slice_end
= (h
* (jobnr
+1)) / nb_jobs
;
230 const uint8_t *p
= in
->data
[0];
231 int lw
= in
->linesize
[0];
232 int x
, y
, score
= 0, filt
;
234 for (y
= slice_start
; y
< slice_end
; y
++) {
236 if (y
- 1 < 0 || y
+ 1 >= h
)
239 // detect two pixels above and below (to eliminate interlace artefacts)
240 // should check that video format is infact interlaced.
242 #define FILTER(i, j) \
243 filter_tout_outlier(p[(y-j) * lw + x + i], \
244 p[ y * lw + x + i], \
245 p[(y+j) * lw + x + i])
247 #define FILTER3(j) (FILTER(-1, j) && FILTER(0, j) && FILTER(1, j))
249 if (y
- 2 >= 0 && y
+ 2 < h
) {
250 for (x
= 1; x
< w
- 1; x
++) {
251 filt
= FILTER3(2) && FILTER3(1);
254 burn_frame(s
, out
, x
, y
);
257 for (x
= 1; x
< w
- 1; x
++) {
261 burn_frame(s
, out
, x
, y
);
270 static int filter_vrep(AVFilterContext
*ctx
, void *arg
, int jobnr
, int nb_jobs
)
272 ThreadData
*td
= arg
;
273 const SignalstatsContext
*s
= ctx
->priv
;
274 const AVFrame
*in
= td
->in
;
275 AVFrame
*out
= td
->out
;
276 const int w
= in
->width
;
277 const int h
= in
->height
;
278 const int slice_start
= (h
* jobnr
) / nb_jobs
;
279 const int slice_end
= (h
* (jobnr
+1)) / nb_jobs
;
280 const uint8_t *p
= in
->data
[0];
281 const int lw
= in
->linesize
[0];
284 for (y
= slice_start
; y
< slice_end
; y
++) {
285 const int y2lw
= (y
- VREP_START
) * lw
;
286 const int ylw
= y
* lw
;
287 int filt
, totdiff
= 0;
292 for (x
= 0; x
< w
; x
++)
293 totdiff
+= abs(p
[y2lw
+ x
] - p
[ylw
+ x
]);
298 for (x
= 0; x
< w
; x
++)
299 burn_frame(s
, out
, x
, y
);
304 static const struct {
306 int (*process
)(AVFilterContext
*ctx
, void *arg
, int jobnr
, int nb_jobs
);
308 {"TOUT", filter_tout
},
309 {"VREP", filter_vrep
},
310 {"BRNG", filter_brng
},
316 static int compute_sat_hue_metrics(AVFilterContext
*ctx
, void *arg
, int jobnr
, int nb_jobs
)
319 ThreadDataHueSatMetrics
*td
= arg
;
320 const SignalstatsContext
*s
= ctx
->priv
;
321 const AVFrame
*src
= td
->src
;
322 AVFrame
*dst_sat
= td
->dst_sat
;
323 AVFrame
*dst_hue
= td
->dst_hue
;
325 const int slice_start
= (s
->chromah
* jobnr
) / nb_jobs
;
326 const int slice_end
= (s
->chromah
* (jobnr
+1)) / nb_jobs
;
328 const int lsz_u
= src
->linesize
[1];
329 const int lsz_v
= src
->linesize
[2];
330 const uint8_t *p_u
= src
->data
[1] + slice_start
* lsz_u
;
331 const uint8_t *p_v
= src
->data
[2] + slice_start
* lsz_v
;
333 const int lsz_sat
= dst_sat
->linesize
[0];
334 const int lsz_hue
= dst_hue
->linesize
[0];
335 uint8_t *p_sat
= dst_sat
->data
[0] + slice_start
* lsz_sat
;
336 uint8_t *p_hue
= dst_hue
->data
[0] + slice_start
* lsz_hue
;
338 for (j
= slice_start
; j
< slice_end
; j
++) {
339 for (i
= 0; i
< s
->chromaw
; i
++) {
340 const int yuvu
= p_u
[i
];
341 const int yuvv
= p_v
[i
];
342 p_sat
[i
] = hypot(yuvu
- 128, yuvv
- 128); // int or round?
343 ((int16_t*)p_hue
)[i
] = floor((180 / M_PI
) * atan2f(yuvu
-128, yuvv
-128) + 180);
354 static int filter_frame(AVFilterLink
*link
, AVFrame
*in
)
356 AVFilterContext
*ctx
= link
->dst
;
357 SignalstatsContext
*s
= ctx
->priv
;
358 AVFilterLink
*outlink
= ctx
->outputs
[0];
361 int w
= 0, cw
= 0, // in
362 pw
= 0, cpw
= 0; // prev
365 unsigned int histy
[DEPTH
] = {0},
369 histsat
[DEPTH
] = {0}; // limited to 8 bit data.
370 int miny
= -1, minu
= -1, minv
= -1;
371 int maxy
= -1, maxu
= -1, maxv
= -1;
372 int lowy
= -1, lowu
= -1, lowv
= -1;
373 int highy
= -1, highu
= -1, highv
= -1;
374 int minsat
= -1, maxsat
= -1, lowsat
= -1, highsat
= -1;
375 int lowp
, highp
, clowp
, chighp
;
376 int accy
, accu
, accv
;
377 int accsat
, acchue
= 0;
379 int toty
= 0, totu
= 0, totv
= 0, totsat
=0;
381 int dify
= 0, difu
= 0, difv
= 0;
383 int filtot
[FILT_NUMB
] = {0};
386 AVFrame
*sat
= s
->frame_sat
;
387 AVFrame
*hue
= s
->frame_hue
;
388 const uint8_t *p_sat
= sat
->data
[0];
389 const uint8_t *p_hue
= hue
->data
[0];
390 const int lsz_sat
= sat
->linesize
[0];
391 const int lsz_hue
= hue
->linesize
[0];
392 ThreadDataHueSatMetrics td_huesat
= {
399 s
->frame_prev
= av_frame_clone(in
);
401 prev
= s
->frame_prev
;
403 if (s
->outfilter
!= FILTER_NONE
) {
404 out
= av_frame_clone(in
);
405 av_frame_make_writable(out
);
408 ctx
->internal
->execute(ctx
, compute_sat_hue_metrics
, &td_huesat
,
409 NULL
, FFMIN(s
->chromah
, ctx
->graph
->nb_threads
));
411 // Calculate luma histogram and difference with previous frame or field.
412 for (j
= 0; j
< link
->h
; j
++) {
413 for (i
= 0; i
< link
->w
; i
++) {
414 const int yuv
= in
->data
[0][w
+ i
];
416 dify
+= abs(yuv
- prev
->data
[0][pw
+ i
]);
418 w
+= in
->linesize
[0];
419 pw
+= prev
->linesize
[0];
422 // Calculate chroma histogram and difference with previous frame or field.
423 for (j
= 0; j
< s
->chromah
; j
++) {
424 for (i
= 0; i
< s
->chromaw
; i
++) {
425 const int yuvu
= in
->data
[1][cw
+i
];
426 const int yuvv
= in
->data
[2][cw
+i
];
428 difu
+= abs(yuvu
- prev
->data
[1][cpw
+i
]);
430 difv
+= abs(yuvv
- prev
->data
[2][cpw
+i
]);
433 histhue
[((int16_t*)p_hue
)[i
]]++;
435 cw
+= in
->linesize
[1];
436 cpw
+= prev
->linesize
[1];
441 for (fil
= 0; fil
< FILT_NUMB
; fil
++) {
442 if (s
->filters
& 1<<fil
) {
445 .out
= out
!= in
&& s
->outfilter
== fil
? out
: NULL
,
447 memset(s
->jobs_rets
, 0, s
->nb_jobs
* sizeof(*s
->jobs_rets
));
448 ctx
->internal
->execute(ctx
, filters_def
[fil
].process
,
449 &td
, s
->jobs_rets
, s
->nb_jobs
);
450 for (i
= 0; i
< s
->nb_jobs
; i
++)
451 filtot
[fil
] += s
->jobs_rets
[i
];
455 // find low / high based on histogram percentile
456 // these only need to be calculated once.
458 lowp
= lrint(s
->fs
* 10 / 100.);
459 highp
= lrint(s
->fs
* 90 / 100.);
460 clowp
= lrint(s
->cfs
* 10 / 100.);
461 chighp
= lrint(s
->cfs
* 90 / 100.);
463 accy
= accu
= accv
= accsat
= 0;
464 for (fil
= 0; fil
< DEPTH
; fil
++) {
465 if (miny
< 0 && histy
[fil
]) miny
= fil
;
466 if (minu
< 0 && histu
[fil
]) minu
= fil
;
467 if (minv
< 0 && histv
[fil
]) minv
= fil
;
468 if (minsat
< 0 && histsat
[fil
]) minsat
= fil
;
470 if (histy
[fil
]) maxy
= fil
;
471 if (histu
[fil
]) maxu
= fil
;
472 if (histv
[fil
]) maxv
= fil
;
473 if (histsat
[fil
]) maxsat
= fil
;
475 toty
+= histy
[fil
] * fil
;
476 totu
+= histu
[fil
] * fil
;
477 totv
+= histv
[fil
] * fil
;
478 totsat
+= histsat
[fil
] * fil
;
483 accsat
+= histsat
[fil
];
485 if (lowy
== -1 && accy
>= lowp
) lowy
= fil
;
486 if (lowu
== -1 && accu
>= clowp
) lowu
= fil
;
487 if (lowv
== -1 && accv
>= clowp
) lowv
= fil
;
488 if (lowsat
== -1 && accsat
>= clowp
) lowsat
= fil
;
490 if (highy
== -1 && accy
>= highp
) highy
= fil
;
491 if (highu
== -1 && accu
>= chighp
) highu
= fil
;
492 if (highv
== -1 && accv
>= chighp
) highv
= fil
;
493 if (highsat
== -1 && accsat
>= chighp
) highsat
= fil
;
498 for (fil
= 0; fil
< 360; fil
++) {
499 tothue
+= histhue
[fil
] * fil
;
500 acchue
+= histhue
[fil
];
502 if (medhue
== -1 && acchue
> s
->cfs
/ 2)
504 if (histhue
[fil
] > maxhue
) {
505 maxhue
= histhue
[fil
];
509 av_frame_free(&s
->frame_prev
);
510 s
->frame_prev
= av_frame_clone(in
);
512 #define SET_META(key, fmt, val) do { \
513 snprintf(metabuf, sizeof(metabuf), fmt, val); \
514 av_dict_set(&out->metadata, "lavfi.signalstats." key, metabuf, 0); \
517 SET_META("YMIN", "%d", miny
);
518 SET_META("YLOW", "%d", lowy
);
519 SET_META("YAVG", "%g", 1.0 * toty
/ s
->fs
);
520 SET_META("YHIGH", "%d", highy
);
521 SET_META("YMAX", "%d", maxy
);
523 SET_META("UMIN", "%d", minu
);
524 SET_META("ULOW", "%d", lowu
);
525 SET_META("UAVG", "%g", 1.0 * totu
/ s
->cfs
);
526 SET_META("UHIGH", "%d", highu
);
527 SET_META("UMAX", "%d", maxu
);
529 SET_META("VMIN", "%d", minv
);
530 SET_META("VLOW", "%d", lowv
);
531 SET_META("VAVG", "%g", 1.0 * totv
/ s
->cfs
);
532 SET_META("VHIGH", "%d", highv
);
533 SET_META("VMAX", "%d", maxv
);
535 SET_META("SATMIN", "%d", minsat
);
536 SET_META("SATLOW", "%d", lowsat
);
537 SET_META("SATAVG", "%g", 1.0 * totsat
/ s
->cfs
);
538 SET_META("SATHIGH", "%d", highsat
);
539 SET_META("SATMAX", "%d", maxsat
);
541 SET_META("HUEMED", "%d", medhue
);
542 SET_META("HUEAVG", "%g", 1.0 * tothue
/ s
->cfs
);
544 SET_META("YDIF", "%g", 1.0 * dify
/ s
->fs
);
545 SET_META("UDIF", "%g", 1.0 * difu
/ s
->cfs
);
546 SET_META("VDIF", "%g", 1.0 * difv
/ s
->cfs
);
548 for (fil
= 0; fil
< FILT_NUMB
; fil
++) {
549 if (s
->filters
& 1<<fil
) {
551 snprintf(metabuf
, sizeof(metabuf
), "%g", 1.0 * filtot
[fil
] / s
->fs
);
552 snprintf(metaname
, sizeof(metaname
), "lavfi.signalstats.%s", filters_def
[fil
].name
);
553 av_dict_set(&out
->metadata
, metaname
, metabuf
, 0);
559 return ff_filter_frame(outlink
, out
);
562 static const AVFilterPad signalstats_inputs
[] = {
565 .type
= AVMEDIA_TYPE_VIDEO
,
566 .filter_frame
= filter_frame
,
571 static const AVFilterPad signalstats_outputs
[] = {
574 .config_props
= config_props
,
575 .type
= AVMEDIA_TYPE_VIDEO
,
580 AVFilter ff_vf_signalstats
= {
581 .name
= "signalstats",
582 .description
= "Generate statistics from video analysis.",
585 .query_formats
= query_formats
,
586 .priv_size
= sizeof(SignalstatsContext
),
587 .inputs
= signalstats_inputs
,
588 .outputs
= signalstats_outputs
,
589 .priv_class
= &signalstats_class
,
590 .flags
= AVFILTER_FLAG_SLICE_THREADS
,