Line | Branch | Exec | Source |
---|---|---|---|
1 | /* | ||
2 | * This file is part of FFmpeg. | ||
3 | * | ||
4 | * FFmpeg is free software; you can redistribute it and/or | ||
5 | * modify it under the terms of the GNU Lesser General Public | ||
6 | * License as published by the Free Software Foundation; either | ||
7 | * version 2.1 of the License, or (at your option) any later version. | ||
8 | * | ||
9 | * FFmpeg is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
12 | * Lesser General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU Lesser General Public | ||
15 | * License along with FFmpeg; if not, write to the Free Software | ||
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
17 | */ | ||
18 | |||
19 | /** | ||
20 | * @file | ||
21 | * video freeze detection filter | ||
22 | */ | ||
23 | |||
24 | #include "libavutil/imgutils.h" | ||
25 | #include "libavutil/opt.h" | ||
26 | #include "libavutil/pixdesc.h" | ||
27 | #include "libavutil/timestamp.h" | ||
28 | |||
29 | #include "avfilter.h" | ||
30 | #include "filters.h" | ||
31 | #include "scene_sad.h" | ||
32 | #include "video.h" | ||
33 | |||
34 | typedef struct FreezeDetectContext { | ||
35 | const AVClass *class; | ||
36 | |||
37 | ptrdiff_t width[4]; | ||
38 | ptrdiff_t height[4]; | ||
39 | ff_scene_sad_fn sad; | ||
40 | int bitdepth; | ||
41 | AVFrame *reference_frame; | ||
42 | int64_t n; | ||
43 | int64_t reference_n; | ||
44 | int frozen; | ||
45 | |||
46 | double noise; | ||
47 | int64_t duration; ///< minimum duration of frozen frame until notification | ||
48 | } FreezeDetectContext; | ||
49 | |||
50 | #define OFFSET(x) offsetof(FreezeDetectContext, x) | ||
51 | #define V AV_OPT_FLAG_VIDEO_PARAM | ||
52 | #define F AV_OPT_FLAG_FILTERING_PARAM | ||
53 | |||
54 | static const AVOption freezedetect_options[] = { | ||
55 | { "n", "set noise tolerance", OFFSET(noise), AV_OPT_TYPE_DOUBLE, {.dbl=0.001}, 0, 1.0, V|F }, | ||
56 | { "noise", "set noise tolerance", OFFSET(noise), AV_OPT_TYPE_DOUBLE, {.dbl=0.001}, 0, 1.0, V|F }, | ||
57 | { "d", "set minimum duration in seconds", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64=2000000}, 0, INT64_MAX, V|F }, | ||
58 | { "duration", "set minimum duration in seconds", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64=2000000}, 0, INT64_MAX, V|F }, | ||
59 | |||
60 | {NULL} | ||
61 | }; | ||
62 | |||
63 | AVFILTER_DEFINE_CLASS(freezedetect); | ||
64 | |||
65 | static const enum AVPixelFormat pix_fmts[] = { | ||
66 | AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUYV422, AV_PIX_FMT_RGB24, | ||
67 | AV_PIX_FMT_BGR24, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV444P, | ||
68 | AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_GRAY8, | ||
69 | AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ444P, | ||
70 | AV_PIX_FMT_UYVY422, AV_PIX_FMT_NV12, AV_PIX_FMT_NV21, AV_PIX_FMT_ARGB, | ||
71 | AV_PIX_FMT_RGBA, AV_PIX_FMT_ABGR, AV_PIX_FMT_BGRA, AV_PIX_FMT_GRAY16, | ||
72 | AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUVJ440P, AV_PIX_FMT_YUVA420P, | ||
73 | AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16, | ||
74 | AV_PIX_FMT_YA8, AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV420P10, | ||
75 | AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV444P9, AV_PIX_FMT_YUV444P10, | ||
76 | AV_PIX_FMT_YUV422P9, AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRP9, | ||
77 | AV_PIX_FMT_GBRP10, AV_PIX_FMT_GBRP16, AV_PIX_FMT_YUVA422P, | ||
78 | AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA422P9, | ||
79 | AV_PIX_FMT_YUVA444P9, AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA422P10, | ||
80 | AV_PIX_FMT_YUVA444P10, AV_PIX_FMT_YUVA420P16, AV_PIX_FMT_YUVA422P16, | ||
81 | AV_PIX_FMT_YUVA444P16, AV_PIX_FMT_NV16, AV_PIX_FMT_YVYU422, | ||
82 | AV_PIX_FMT_GBRAP, AV_PIX_FMT_GBRAP16, AV_PIX_FMT_YUV420P12, | ||
83 | AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV422P14, | ||
84 | AV_PIX_FMT_YUV444P12, AV_PIX_FMT_YUV444P14, AV_PIX_FMT_GBRP12, | ||
85 | AV_PIX_FMT_GBRP14, AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUV440P10, | ||
86 | AV_PIX_FMT_YUV440P12, AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP10, | ||
87 | AV_PIX_FMT_GRAY12, AV_PIX_FMT_GRAY10, AV_PIX_FMT_GRAY9, | ||
88 | AV_PIX_FMT_GRAY14, | ||
89 | AV_PIX_FMT_NONE | ||
90 | }; | ||
91 | |||
92 | 1 | static int config_input(AVFilterLink *inlink) | |
93 | { | ||
94 | 1 | AVFilterContext *ctx = inlink->dst; | |
95 | 1 | FreezeDetectContext *s = ctx->priv; | |
96 | 1 | const AVPixFmtDescriptor *pix_desc = av_pix_fmt_desc_get(inlink->format); | |
97 | |||
98 | 1 | s->bitdepth = pix_desc->comp[0].depth; | |
99 | |||
100 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
|
5 | for (int plane = 0; plane < 4; plane++) { |
101 | 4 | ptrdiff_t line_size = av_image_get_linesize(inlink->format, inlink->w, plane); | |
102 | 4 | s->width[plane] = line_size >> (s->bitdepth > 8); | |
103 |
4/4✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2 times.
|
4 | s->height[plane] = inlink->h >> ((plane == 1 || plane == 2) ? pix_desc->log2_chroma_h : 0); |
104 | } | ||
105 | |||
106 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | s->sad = ff_scene_sad_get_fn(s->bitdepth == 8 ? 8 : 16); |
107 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (!s->sad) |
108 | ✗ | return AVERROR(EINVAL); | |
109 | |||
110 | 1 | return 0; | |
111 | } | ||
112 | |||
113 | 1 | static av_cold void uninit(AVFilterContext *ctx) | |
114 | { | ||
115 | 1 | FreezeDetectContext *s = ctx->priv; | |
116 | 1 | av_frame_free(&s->reference_frame); | |
117 | 1 | } | |
118 | |||
119 | 250 | static int is_frozen(FreezeDetectContext *s, AVFrame *reference, AVFrame *frame) | |
120 | { | ||
121 | 250 | uint64_t sad = 0; | |
122 | 250 | uint64_t count = 0; | |
123 | double mafd; | ||
124 |
2/2✓ Branch 0 taken 1000 times.
✓ Branch 1 taken 250 times.
|
1250 | for (int plane = 0; plane < 4; plane++) { |
125 |
2/2✓ Branch 0 taken 750 times.
✓ Branch 1 taken 250 times.
|
1000 | if (s->width[plane]) { |
126 | uint64_t plane_sad; | ||
127 | 750 | s->sad(frame->data[plane], frame->linesize[plane], | |
128 | 750 | reference->data[plane], reference->linesize[plane], | |
129 | s->width[plane], s->height[plane], &plane_sad); | ||
130 | 750 | sad += plane_sad; | |
131 | 750 | count += s->width[plane] * s->height[plane]; | |
132 | } | ||
133 | } | ||
134 | 250 | mafd = (double)sad / count / (1ULL << s->bitdepth); | |
135 | 250 | return (mafd <= s->noise); | |
136 | } | ||
137 | |||
138 | 6 | static int set_meta(FreezeDetectContext *s, AVFrame *frame, const char *key, const char *value) | |
139 | { | ||
140 | 6 | av_log(s, AV_LOG_INFO, "%s: %s\n", key, value); | |
141 | 6 | return av_dict_set(&frame->metadata, key, value, 0); | |
142 | } | ||
143 | |||
144 | 504 | static int activate(AVFilterContext *ctx) | |
145 | { | ||
146 | int ret; | ||
147 | 504 | AVFilterLink *inlink = ctx->inputs[0]; | |
148 | 504 | AVFilterLink *outlink = ctx->outputs[0]; | |
149 | 504 | FilterLink *l = ff_filter_link(inlink); | |
150 | 504 | FreezeDetectContext *s = ctx->priv; | |
151 | AVFrame *frame; | ||
152 | |||
153 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 504 times.
|
504 | FF_FILTER_FORWARD_STATUS_BACK(outlink, inlink); |
154 | |||
155 | 504 | ret = ff_inlink_consume_frame(inlink, &frame); | |
156 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 504 times.
|
504 | if (ret < 0) |
157 | ✗ | return ret; | |
158 | |||
159 |
2/2✓ Branch 0 taken 251 times.
✓ Branch 1 taken 253 times.
|
504 | if (frame) { |
160 | 251 | int frozen = 0; | |
161 | 251 | s->n++; | |
162 | |||
163 |
2/2✓ Branch 0 taken 250 times.
✓ Branch 1 taken 1 times.
|
251 | if (s->reference_frame) { |
164 | int64_t duration; | ||
165 |
3/6✓ Branch 0 taken 250 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 250 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 250 times.
|
250 | if (s->reference_frame->pts == AV_NOPTS_VALUE || frame->pts == AV_NOPTS_VALUE || frame->pts < s->reference_frame->pts) // Discontinuity? |
166 | ✗ | duration = l->frame_rate.num > 0 ? av_rescale_q(s->n - s->reference_n, av_inv_q(l->frame_rate), AV_TIME_BASE_Q) : 0; | |
167 | else | ||
168 | 250 | duration = av_rescale_q(frame->pts - s->reference_frame->pts, inlink->time_base, AV_TIME_BASE_Q); | |
169 | |||
170 | 250 | frozen = is_frozen(s, s->reference_frame, frame); | |
171 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 248 times.
|
250 | if (duration >= s->duration) { |
172 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (!s->frozen) |
173 | 2 | set_meta(s, frame, "lavfi.freezedetect.freeze_start", av_ts2timestr(s->reference_frame->pts, &inlink->time_base)); | |
174 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (!frozen) { |
175 | 2 | set_meta(s, frame, "lavfi.freezedetect.freeze_duration", av_ts2timestr(duration, &AV_TIME_BASE_Q)); | |
176 | 2 | set_meta(s, frame, "lavfi.freezedetect.freeze_end", av_ts2timestr(frame->pts, &inlink->time_base)); | |
177 | } | ||
178 | 2 | s->frozen = frozen; | |
179 | } | ||
180 | } | ||
181 | |||
182 |
2/2✓ Branch 0 taken 36 times.
✓ Branch 1 taken 215 times.
|
251 | if (!frozen) { |
183 | 36 | av_frame_free(&s->reference_frame); | |
184 | 36 | s->reference_frame = av_frame_clone(frame); | |
185 | 36 | s->reference_n = s->n; | |
186 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 36 times.
|
36 | if (!s->reference_frame) { |
187 | ✗ | av_frame_free(&frame); | |
188 | ✗ | return AVERROR(ENOMEM); | |
189 | } | ||
190 | } | ||
191 | 251 | return ff_filter_frame(outlink, frame); | |
192 | } | ||
193 | |||
194 |
2/2✓ Branch 1 taken 1 times.
✓ Branch 2 taken 252 times.
|
253 | FF_FILTER_FORWARD_STATUS(inlink, outlink); |
195 |
1/2✓ Branch 1 taken 252 times.
✗ Branch 2 not taken.
|
252 | FF_FILTER_FORWARD_WANTED(outlink, inlink); |
196 | |||
197 | ✗ | return FFERROR_NOT_READY; | |
198 | } | ||
199 | |||
200 | static const AVFilterPad freezedetect_inputs[] = { | ||
201 | { | ||
202 | .name = "default", | ||
203 | .type = AVMEDIA_TYPE_VIDEO, | ||
204 | .config_props = config_input, | ||
205 | }, | ||
206 | }; | ||
207 | |||
208 | const AVFilter ff_vf_freezedetect = { | ||
209 | .name = "freezedetect", | ||
210 | .description = NULL_IF_CONFIG_SMALL("Detects frozen video input."), | ||
211 | .priv_size = sizeof(FreezeDetectContext), | ||
212 | .priv_class = &freezedetect_class, | ||
213 | .uninit = uninit, | ||
214 | .flags = AVFILTER_FLAG_METADATA_ONLY, | ||
215 | FILTER_INPUTS(freezedetect_inputs), | ||
216 | FILTER_OUTPUTS(ff_video_default_filterpad), | ||
217 | FILTER_PIXFMTS_ARRAY(pix_fmts), | ||
218 | .activate = activate, | ||
219 | }; | ||
220 |