Browse code

lavfi: add blackdetect filter

Address trac ticket #901.

Stefano Sabatini authored on 2012/03/02 23:51:26
Showing 6 changed files
... ...
@@ -11,6 +11,7 @@ version next:
11 11
 - ID3v2 attached pictures reading and writing
12 12
 - WMA Lossless decoder
13 13
 - bluray protocol
14
+- blackdetect filter
14 15
 
15 16
 
16 17
 version 0.10:
... ...
@@ -761,6 +761,60 @@ video, use the command:
761 761
 ass=sub.ass
762 762
 @end example
763 763
 
764
+@section blackdetect
765
+
766
+Detect video intervals that are (almost) completely black. Can be
767
+useful to detect chapter transitions, commercials, or invalid
768
+recordings. Output lines contains the time for the start, end and
769
+duration of the detected black interval expressed in seconds.
770
+
771
+In order to display the output lines, you need to set the loglevel at
772
+least to the AV_LOG_INFO value.
773
+
774
+This filter accepts a list of options in the form of
775
+@var{key}=@var{value} pairs separated by ":". A description of the
776
+accepted options follows.
777
+
778
+@table @option
779
+@item black_min_duration, d
780
+Set the minimum detected black duration expressed in seconds. It must
781
+be a non-negative floating point number.
782
+
783
+Default value is 2.0.
784
+
785
+@item picture_black_ratio_th, pic_th
786
+Set the threshold for considering a picture "black".
787
+Express the minimum value for the ratio:
788
+@example
789
+@var{nb_black_pixels} / @var{nb_pixels}
790
+@end example
791
+
792
+for which a picture is considered black.
793
+Default value is 0.98.
794
+
795
+@item pixel_black_th, pix_th
796
+Set the threshold for considering a pixel "black".
797
+
798
+The threshold expresses the maximum pixel luminance value for which a
799
+pixel is considered "black". The provided value is scaled according to
800
+the following equation:
801
+@example
802
+@var{absolute_threshold} = @var{luminance_minimum_value} + @var{pixel_black_th} * @var{luminance_range_size}
803
+@end example
804
+
805
+@var{luminance_range_size} and @var{luminance_minimum_value} depend on
806
+the input video format, the range is [0-255] for YUV full-range
807
+formats and [16-235] for YUV non full-range formats.
808
+
809
+Default value is 0.10.
810
+@end table
811
+
812
+The following example sets the maximum pixel threshold to the minimum
813
+value, and detects only black intervals of 2 or more seconds:
814
+@example
815
+blackdetect=d=2:pix_th=0.00
816
+@end example
817
+
764 818
 @section blackframe
765 819
 
766 820
 Detect frames that are (almost) completely black. Can be useful to
... ...
@@ -48,6 +48,7 @@ OBJS-$(CONFIG_ABUFFERSINK_FILTER)            += sink_buffer.o
48 48
 OBJS-$(CONFIG_ANULLSINK_FILTER)              += asink_anullsink.o
49 49
 
50 50
 OBJS-$(CONFIG_ASS_FILTER)                    += vf_ass.o
51
+OBJS-$(CONFIG_BLACKDETECT_FILTER)            += vf_blackdetect.o
51 52
 OBJS-$(CONFIG_BLACKFRAME_FILTER)             += vf_blackframe.o
52 53
 OBJS-$(CONFIG_BOXBLUR_FILTER)                += vf_boxblur.o
53 54
 OBJS-$(CONFIG_COPY_FILTER)                   += vf_copy.o
... ...
@@ -56,6 +56,7 @@ void avfilter_register_all(void)
56 56
     REGISTER_FILTER (ANULLSINK,   anullsink,   asink);
57 57
 
58 58
     REGISTER_FILTER (ASS,         ass,  vf);
59
+    REGISTER_FILTER (BLACKDETECT, blackdetect, vf);
59 60
     REGISTER_FILTER (BLACKFRAME,  blackframe,  vf);
60 61
     REGISTER_FILTER (BOXBLUR,     boxblur,     vf);
61 62
     REGISTER_FILTER (COPY,        copy,        vf);
... ...
@@ -29,8 +29,8 @@
29 29
 #include "libavutil/avutil.h"
30 30
 
31 31
 #define LIBAVFILTER_VERSION_MAJOR  2
32
-#define LIBAVFILTER_VERSION_MINOR 62
33
-#define LIBAVFILTER_VERSION_MICRO 101
32
+#define LIBAVFILTER_VERSION_MINOR 63
33
+#define LIBAVFILTER_VERSION_MICRO 100
34 34
 
35 35
 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
36 36
                                                LIBAVFILTER_VERSION_MINOR, \
37 37
new file mode 100644
... ...
@@ -0,0 +1,209 @@
0
+/*
1
+ * Copyright (c) 2012 Stefano Sabatini
2
+ *
3
+ * This file is part of FFmpeg.
4
+ *
5
+ * FFmpeg is free software; you can redistribute it and/or
6
+ * modify it under the terms of the GNU Lesser General Public
7
+ * License as published by the Free Software Foundation; either
8
+ * version 2.1 of the License, or (at your option) any later version.
9
+ *
10
+ * FFmpeg is distributed in the hope that it will be useful,
11
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
+ * Lesser General Public License for more details.
14
+ *
15
+ * You should have received a copy of the GNU Lesser General Public
16
+ * License along with FFmpeg; if not, write to the Free Software
17
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+ */
19
+
20
+/**
21
+ * @file
22
+ * Video black detector, loosely based on blackframe with extended
23
+ * syntax and features
24
+ */
25
+
26
+#include <float.h>
27
+#include "libavutil/opt.h"
28
+#include "libavutil/timestamp.h"
29
+#include "avfilter.h"
30
+#include "internal.h"
31
+
32
+typedef struct {
33
+    const AVClass *class;
34
+    double  black_min_duration_time; ///< minimum duration of detected black, in seconds
35
+    int64_t black_min_duration;      ///< minimum duration of detected black, expressed in timebase units
36
+    int64_t black_start;             ///< pts start time of the first black picture
37
+    int64_t black_end;               ///< pts end time of the last black picture
38
+    int black_started;
39
+
40
+    double       picture_black_ratio_th;
41
+    double       pixel_black_th;
42
+    unsigned int pixel_black_th_i;
43
+
44
+    unsigned int frame_count;       ///< frame number
45
+    unsigned int nb_black_pixels;   ///< number of black pixels counted so far
46
+} BlackDetectContext;
47
+
48
+#define OFFSET(x) offsetof(BlackDetectContext, x)
49
+static const AVOption blackdetect_options[] = {
50
+    { "d",                  "set minimum detected black duration in seconds", OFFSET(black_min_duration_time), AV_OPT_TYPE_DOUBLE, {.dbl=2}, 0, DBL_MAX},
51
+    { "black_min_duration", "set minimum detected black duration in seconds", OFFSET(black_min_duration_time), AV_OPT_TYPE_DOUBLE, {.dbl=2}, 0, DBL_MAX},
52
+    { "picture_black_ratio_th", "set the picture black ratio threshold", OFFSET(picture_black_ratio_th), AV_OPT_TYPE_DOUBLE, {.dbl=.98}, 0, 1},
53
+    { "pic_th",                 "set the picture black ratio threshold", OFFSET(picture_black_ratio_th), AV_OPT_TYPE_DOUBLE, {.dbl=.98}, 0, 1},
54
+    { "pixel_black_th", "set the pixel black threshold", OFFSET(pixel_black_th), AV_OPT_TYPE_DOUBLE, {.dbl=.10}, 0, 1},
55
+    { "pix_th",         "set the pixel black threshold", OFFSET(pixel_black_th), AV_OPT_TYPE_DOUBLE, {.dbl=.10}, 0, 1},
56
+    { NULL },
57
+};
58
+
59
+static const char *blackdetect_get_name(void *ctx)
60
+{
61
+    return "blackdetect";
62
+}
63
+
64
+static const AVClass blackdetect_class = {
65
+    .class_name = "BlackDetectContext",
66
+    .item_name  = blackdetect_get_name,
67
+    .option     = blackdetect_options,
68
+};
69
+
70
+#define YUVJ_FORMATS \
71
+    PIX_FMT_YUVJ420P, PIX_FMT_YUVJ422P, PIX_FMT_YUVJ444P, PIX_FMT_YUVJ440P
72
+
73
+static enum PixelFormat yuvj_formats[] = {
74
+    YUVJ_FORMATS, PIX_FMT_NONE
75
+};
76
+
77
+static int query_formats(AVFilterContext *ctx)
78
+{
79
+    static const enum PixelFormat pix_fmts[] = {
80
+        PIX_FMT_YUV410P, PIX_FMT_YUV420P, PIX_FMT_GRAY8, PIX_FMT_NV12,
81
+        PIX_FMT_NV21, PIX_FMT_YUV444P, PIX_FMT_YUV422P, PIX_FMT_YUV411P,
82
+        YUVJ_FORMATS,
83
+        PIX_FMT_NONE
84
+    };
85
+
86
+    avfilter_set_common_pixel_formats(ctx, avfilter_make_format_list(pix_fmts));
87
+    return 0;
88
+}
89
+
90
+static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque)
91
+{
92
+    int ret;
93
+    BlackDetectContext *blackdetect = ctx->priv;
94
+
95
+    blackdetect->class = &blackdetect_class;
96
+    av_opt_set_defaults(blackdetect);
97
+
98
+    if ((ret = av_set_options_string(blackdetect, args, "=", ":")) < 0) {
99
+        av_log(ctx, AV_LOG_ERROR, "Error parsing options string: '%s'\n", args);
100
+        return ret;
101
+    }
102
+
103
+    return 0;
104
+}
105
+
106
+static int config_input(AVFilterLink *inlink)
107
+{
108
+    AVFilterContext *ctx = inlink->dst;
109
+    BlackDetectContext *blackdetect = ctx->priv;
110
+
111
+    blackdetect->black_min_duration =
112
+        blackdetect->black_min_duration_time / av_q2d(inlink->time_base);
113
+
114
+    blackdetect->pixel_black_th_i = ff_fmt_is_in(inlink->format, yuvj_formats) ?
115
+        // luminance_minimum_value + pixel_black_th * luminance_range_size
116
+             blackdetect->pixel_black_th *  255 :
117
+        16 + blackdetect->pixel_black_th * (235 - 16);
118
+
119
+    av_log(blackdetect, AV_LOG_INFO,
120
+           "black_min_duration:%s pixel_black_th:%f pixel_black_th_i:%d picture_black_ratio_th:%f\n",
121
+           av_ts2timestr(blackdetect->black_min_duration, &inlink->time_base),
122
+           blackdetect->pixel_black_th, blackdetect->pixel_black_th_i,
123
+           blackdetect->picture_black_ratio_th);
124
+    return 0;
125
+}
126
+
127
+static void draw_slice(AVFilterLink *inlink, int y, int h, int slice_dir)
128
+{
129
+    AVFilterContext *ctx = inlink->dst;
130
+    BlackDetectContext *blackdetect = ctx->priv;
131
+    AVFilterBufferRef *picref = inlink->cur_buf;
132
+    int x, i;
133
+    const uint8_t *p = picref->data[0] + y * picref->linesize[0];
134
+
135
+    for (i = 0; i < h; i++) {
136
+        for (x = 0; x < inlink->w; x++)
137
+            blackdetect->nb_black_pixels += p[x] <= blackdetect->pixel_black_th_i;
138
+        p += picref->linesize[0];
139
+    }
140
+
141
+    avfilter_draw_slice(ctx->outputs[0], y, h, slice_dir);
142
+}
143
+
144
+static void end_frame(AVFilterLink *inlink)
145
+{
146
+    AVFilterContext *ctx = inlink->dst;
147
+    BlackDetectContext *blackdetect = ctx->priv;
148
+    AVFilterBufferRef *picref = inlink->cur_buf;
149
+    double picture_black_ratio = 0;
150
+
151
+    picture_black_ratio = (double)blackdetect->nb_black_pixels / (inlink->w * inlink->h);
152
+
153
+    av_log(ctx, AV_LOG_DEBUG,
154
+           "frame:%u picture_black_ratio:%f pos:%"PRId64" pts:%s t:%s type:%c\n",
155
+           blackdetect->frame_count, picture_black_ratio,
156
+           picref->pos, av_ts2str(picref->pts),
157
+           av_ts2timestr(blackdetect->black_start, &inlink->time_base),
158
+           av_get_picture_type_char(picref->video->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
+        }
166
+    } else if (blackdetect->black_started) {
167
+        /* black ends here */
168
+        blackdetect->black_started = 0;
169
+        blackdetect->black_end = picref->pts;
170
+
171
+        if ((blackdetect->black_end - blackdetect->black_start) >= blackdetect->black_min_duration) {
172
+            av_log(blackdetect, AV_LOG_INFO,
173
+                   "black_start:%s black_end:%s black_duration:%s\n",
174
+                   av_ts2timestr(blackdetect->black_start, &inlink->time_base),
175
+                   av_ts2timestr(blackdetect->black_end, &inlink->time_base),
176
+                   av_ts2timestr(blackdetect->black_end - blackdetect->black_start, &inlink->time_base));
177
+        }
178
+    }
179
+
180
+    blackdetect->frame_count++;
181
+    blackdetect->nb_black_pixels = 0;
182
+    avfilter_end_frame(inlink->dst->outputs[0]);
183
+}
184
+
185
+AVFilter avfilter_vf_blackdetect = {
186
+    .name          = "blackdetect",
187
+    .description   = NULL_IF_CONFIG_SMALL("Detect video intervals that are (almost) black."),
188
+    .priv_size     = sizeof(BlackDetectContext),
189
+    .init          = init,
190
+    .query_formats = query_formats,
191
+
192
+    .inputs = (const AVFilterPad[]) {
193
+        { .name             = "default",
194
+          .type             = AVMEDIA_TYPE_VIDEO,
195
+          .config_props     = config_input,
196
+          .draw_slice       = draw_slice,
197
+          .get_video_buffer = avfilter_null_get_video_buffer,
198
+          .start_frame      = avfilter_null_start_frame,
199
+          .end_frame        = end_frame, },
200
+        { .name = NULL }
201
+    },
202
+
203
+    .outputs = (const AVFilterPad[]) {
204
+        { .name             = "default",
205
+          .type             = AVMEDIA_TYPE_VIDEO },
206
+        { .name = NULL }
207
+    },
208
+};