Commit | Line | Data |
---|---|---|
2ba45a60 DM |
1 | /* |
2 | * Copyright (c) 2012 Stefano Sabatini | |
3 | * | |
4 | * This file is part of FFmpeg. | |
5 | * | |
6 | * FFmpeg is free software; you can redistribute it and/or | |
7 | * modify it under the terms of the GNU Lesser General Public | |
8 | * License as published by the Free Software Foundation; either | |
9 | * version 2.1 of the License, or (at your option) any later version. | |
10 | * | |
11 | * FFmpeg is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | * Lesser General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU Lesser General Public | |
17 | * License along with FFmpeg; if not, write to the Free Software | |
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
19 | */ | |
20 | ||
21 | /** | |
22 | * @file | |
23 | * Video black detector, loosely based on blackframe with extended | |
24 | * syntax and features | |
25 | */ | |
26 | ||
27 | #include <float.h> | |
28 | #include "libavutil/opt.h" | |
29 | #include "libavutil/timestamp.h" | |
30 | #include "avfilter.h" | |
31 | #include "internal.h" | |
32 | ||
33 | typedef struct { | |
34 | const AVClass *class; | |
35 | double black_min_duration_time; ///< minimum duration of detected black, in seconds | |
36 | int64_t black_min_duration; ///< minimum duration of detected black, expressed in timebase units | |
37 | int64_t black_start; ///< pts start time of the first black picture | |
38 | int64_t black_end; ///< pts end time of the last black picture | |
39 | int64_t last_picref_pts; ///< pts of the last input picture | |
40 | int black_started; | |
41 | ||
42 | double picture_black_ratio_th; | |
43 | double pixel_black_th; | |
44 | unsigned int pixel_black_th_i; | |
45 | ||
46 | unsigned int nb_black_pixels; ///< number of black pixels counted so far | |
47 | } BlackDetectContext; | |
48 | ||
49 | #define OFFSET(x) offsetof(BlackDetectContext, x) | |
50 | #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM | |
51 | ||
52 | static const AVOption blackdetect_options[] = { | |
53 | { "d", "set minimum detected black duration in seconds", OFFSET(black_min_duration_time), AV_OPT_TYPE_DOUBLE, {.dbl=2}, 0, DBL_MAX, FLAGS }, | |
54 | { "black_min_duration", "set minimum detected black duration in seconds", OFFSET(black_min_duration_time), AV_OPT_TYPE_DOUBLE, {.dbl=2}, 0, DBL_MAX, FLAGS }, | |
55 | { "picture_black_ratio_th", "set the picture black ratio threshold", OFFSET(picture_black_ratio_th), AV_OPT_TYPE_DOUBLE, {.dbl=.98}, 0, 1, FLAGS }, | |
56 | { "pic_th", "set the picture black ratio threshold", OFFSET(picture_black_ratio_th), AV_OPT_TYPE_DOUBLE, {.dbl=.98}, 0, 1, FLAGS }, | |
57 | { "pixel_black_th", "set the pixel black threshold", OFFSET(pixel_black_th), AV_OPT_TYPE_DOUBLE, {.dbl=.10}, 0, 1, FLAGS }, | |
58 | { "pix_th", "set the pixel black threshold", OFFSET(pixel_black_th), AV_OPT_TYPE_DOUBLE, {.dbl=.10}, 0, 1, FLAGS }, | |
59 | { NULL } | |
60 | }; | |
61 | ||
62 | AVFILTER_DEFINE_CLASS(blackdetect); | |
63 | ||
64 | #define YUVJ_FORMATS \ | |
65 | AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P | |
66 | ||
67 | static const enum AVPixelFormat yuvj_formats[] = { | |
68 | YUVJ_FORMATS, AV_PIX_FMT_NONE | |
69 | }; | |
70 | ||
71 | static int query_formats(AVFilterContext *ctx) | |
72 | { | |
73 | static const enum AVPixelFormat pix_fmts[] = { | |
74 | AV_PIX_FMT_GRAY8, | |
75 | AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV411P, | |
76 | AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV422P, | |
77 | AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV444P, | |
78 | AV_PIX_FMT_NV12, AV_PIX_FMT_NV21, | |
79 | YUVJ_FORMATS, | |
80 | AV_PIX_FMT_NONE | |
81 | }; | |
82 | ||
83 | ff_set_common_formats(ctx, ff_make_format_list(pix_fmts)); | |
84 | return 0; | |
85 | } | |
86 | ||
87 | static int config_input(AVFilterLink *inlink) | |
88 | { | |
89 | AVFilterContext *ctx = inlink->dst; | |
90 | BlackDetectContext *blackdetect = ctx->priv; | |
91 | ||
92 | blackdetect->black_min_duration = | |
93 | blackdetect->black_min_duration_time / av_q2d(inlink->time_base); | |
94 | ||
95 | blackdetect->pixel_black_th_i = ff_fmt_is_in(inlink->format, yuvj_formats) ? | |
96 | // luminance_minimum_value + pixel_black_th * luminance_range_size | |
97 | blackdetect->pixel_black_th * 255 : | |
98 | 16 + blackdetect->pixel_black_th * (235 - 16); | |
99 | ||
100 | av_log(blackdetect, AV_LOG_VERBOSE, | |
101 | "black_min_duration:%s pixel_black_th:%f pixel_black_th_i:%d picture_black_ratio_th:%f\n", | |
102 | av_ts2timestr(blackdetect->black_min_duration, &inlink->time_base), | |
103 | blackdetect->pixel_black_th, blackdetect->pixel_black_th_i, | |
104 | blackdetect->picture_black_ratio_th); | |
105 | return 0; | |
106 | } | |
107 | ||
108 | static void check_black_end(AVFilterContext *ctx) | |
109 | { | |
110 | BlackDetectContext *blackdetect = ctx->priv; | |
111 | AVFilterLink *inlink = ctx->inputs[0]; | |
112 | ||
113 | if ((blackdetect->black_end - blackdetect->black_start) >= blackdetect->black_min_duration) { | |
114 | av_log(blackdetect, AV_LOG_INFO, | |
115 | "black_start:%s black_end:%s black_duration:%s\n", | |
116 | av_ts2timestr(blackdetect->black_start, &inlink->time_base), | |
117 | av_ts2timestr(blackdetect->black_end, &inlink->time_base), | |
118 | av_ts2timestr(blackdetect->black_end - blackdetect->black_start, &inlink->time_base)); | |
119 | } | |
120 | } | |
121 | ||
122 | static int request_frame(AVFilterLink *outlink) | |
123 | { | |
124 | AVFilterContext *ctx = outlink->src; | |
125 | BlackDetectContext *blackdetect = ctx->priv; | |
126 | AVFilterLink *inlink = ctx->inputs[0]; | |
127 | int ret = ff_request_frame(inlink); | |
128 | ||
129 | if (ret == AVERROR_EOF && blackdetect->black_started) { | |
130 | // FIXME: black_end should be set to last_picref_pts + last_picref_duration | |
131 | blackdetect->black_end = blackdetect->last_picref_pts; | |
132 | check_black_end(ctx); | |
133 | } | |
134 | return ret; | |
135 | } | |
136 | ||
137 | // TODO: document metadata | |
138 | static int filter_frame(AVFilterLink *inlink, AVFrame *picref) | |
139 | { | |
140 | AVFilterContext *ctx = inlink->dst; | |
141 | BlackDetectContext *blackdetect = ctx->priv; | |
142 | double picture_black_ratio = 0; | |
143 | const uint8_t *p = picref->data[0]; | |
144 | int x, i; | |
145 | ||
146 | for (i = 0; i < inlink->h; i++) { | |
147 | for (x = 0; x < inlink->w; x++) | |
148 | blackdetect->nb_black_pixels += p[x] <= blackdetect->pixel_black_th_i; | |
149 | p += picref->linesize[0]; | |
150 | } | |
151 | ||
152 | picture_black_ratio = (double)blackdetect->nb_black_pixels / (inlink->w * inlink->h); | |
153 | ||
154 | av_log(ctx, AV_LOG_DEBUG, | |
155 | "frame:%"PRId64" picture_black_ratio:%f pts:%s t:%s type:%c\n", | |
156 | inlink->frame_count, picture_black_ratio, | |
157 | av_ts2str(picref->pts), av_ts2timestr(picref->pts, &inlink->time_base), | |
158 | av_get_picture_type_char(picref->pict_type)); | |
159 | ||
160 | if (picture_black_ratio >= blackdetect->picture_black_ratio_th) { | |
161 | if (!blackdetect->black_started) { | |
162 | /* black starts here */ | |
163 | blackdetect->black_started = 1; | |
164 | blackdetect->black_start = picref->pts; | |
165 | av_dict_set(avpriv_frame_get_metadatap(picref), "lavfi.black_start", | |
166 | av_ts2timestr(blackdetect->black_start, &inlink->time_base), 0); | |
167 | } | |
168 | } else if (blackdetect->black_started) { | |
169 | /* black ends here */ | |
170 | blackdetect->black_started = 0; | |
171 | blackdetect->black_end = picref->pts; | |
172 | check_black_end(ctx); | |
173 | av_dict_set(avpriv_frame_get_metadatap(picref), "lavfi.black_end", | |
174 | av_ts2timestr(blackdetect->black_end, &inlink->time_base), 0); | |
175 | } | |
176 | ||
177 | blackdetect->last_picref_pts = picref->pts; | |
178 | blackdetect->nb_black_pixels = 0; | |
179 | return ff_filter_frame(inlink->dst->outputs[0], picref); | |
180 | } | |
181 | ||
182 | static const AVFilterPad blackdetect_inputs[] = { | |
183 | { | |
184 | .name = "default", | |
185 | .type = AVMEDIA_TYPE_VIDEO, | |
186 | .config_props = config_input, | |
187 | .filter_frame = filter_frame, | |
188 | }, | |
189 | { NULL } | |
190 | }; | |
191 | ||
192 | static const AVFilterPad blackdetect_outputs[] = { | |
193 | { | |
194 | .name = "default", | |
195 | .type = AVMEDIA_TYPE_VIDEO, | |
196 | .request_frame = request_frame, | |
197 | }, | |
198 | { NULL } | |
199 | }; | |
200 | ||
201 | AVFilter ff_vf_blackdetect = { | |
202 | .name = "blackdetect", | |
203 | .description = NULL_IF_CONFIG_SMALL("Detect video intervals that are (almost) black."), | |
204 | .priv_size = sizeof(BlackDetectContext), | |
205 | .query_formats = query_formats, | |
206 | .inputs = blackdetect_inputs, | |
207 | .outputs = blackdetect_outputs, | |
208 | .priv_class = &blackdetect_class, | |
209 | }; |