00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00027 #include <float.h>
00028 #include "libavutil/opt.h"
00029 #include "libavutil/timestamp.h"
00030 #include "avfilter.h"
00031 #include "internal.h"
00032
00033 typedef struct {
00034 const AVClass *class;
00035 double black_min_duration_time;
00036 int64_t black_min_duration;
00037 int64_t black_start;
00038 int64_t black_end;
00039 int64_t last_picref_pts;
00040 int black_started;
00041
00042 double picture_black_ratio_th;
00043 double pixel_black_th;
00044 unsigned int pixel_black_th_i;
00045
00046 unsigned int frame_count;
00047 unsigned int nb_black_pixels;
00048 } BlackDetectContext;
00049
00050 #define OFFSET(x) offsetof(BlackDetectContext, x)
00051 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
00052
00053 static const AVOption blackdetect_options[] = {
00054 { "d", "set minimum detected black duration in seconds", OFFSET(black_min_duration_time), AV_OPT_TYPE_DOUBLE, {.dbl=2}, 0, DBL_MAX, FLAGS },
00055 { "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 },
00056 { "picture_black_ratio_th", "set the picture black ratio threshold", OFFSET(picture_black_ratio_th), AV_OPT_TYPE_DOUBLE, {.dbl=.98}, 0, 1, FLAGS },
00057 { "pic_th", "set the picture black ratio threshold", OFFSET(picture_black_ratio_th), AV_OPT_TYPE_DOUBLE, {.dbl=.98}, 0, 1, FLAGS },
00058 { "pixel_black_th", "set the pixel black threshold", OFFSET(pixel_black_th), AV_OPT_TYPE_DOUBLE, {.dbl=.10}, 0, 1, FLAGS },
00059 { "pix_th", "set the pixel black threshold", OFFSET(pixel_black_th), AV_OPT_TYPE_DOUBLE, {.dbl=.10}, 0, 1, FLAGS },
00060 { NULL },
00061 };
00062
00063 AVFILTER_DEFINE_CLASS(blackdetect);
00064
00065 #define YUVJ_FORMATS \
00066 PIX_FMT_YUVJ420P, PIX_FMT_YUVJ422P, PIX_FMT_YUVJ444P, PIX_FMT_YUVJ440P
00067
00068 static enum PixelFormat yuvj_formats[] = {
00069 YUVJ_FORMATS, PIX_FMT_NONE
00070 };
00071
00072 static int query_formats(AVFilterContext *ctx)
00073 {
00074 static const enum PixelFormat pix_fmts[] = {
00075 PIX_FMT_YUV410P, PIX_FMT_YUV420P, PIX_FMT_GRAY8, PIX_FMT_NV12,
00076 PIX_FMT_NV21, PIX_FMT_YUV444P, PIX_FMT_YUV422P, PIX_FMT_YUV411P,
00077 YUVJ_FORMATS,
00078 PIX_FMT_NONE
00079 };
00080
00081 ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
00082 return 0;
00083 }
00084
00085 static av_cold int init(AVFilterContext *ctx, const char *args)
00086 {
00087 int ret;
00088 BlackDetectContext *blackdetect = ctx->priv;
00089
00090 blackdetect->class = &blackdetect_class;
00091 av_opt_set_defaults(blackdetect);
00092
00093 if ((ret = av_set_options_string(blackdetect, args, "=", ":")) < 0)
00094 return ret;
00095
00096 return 0;
00097 }
00098
00099 static int config_input(AVFilterLink *inlink)
00100 {
00101 AVFilterContext *ctx = inlink->dst;
00102 BlackDetectContext *blackdetect = ctx->priv;
00103
00104 blackdetect->black_min_duration =
00105 blackdetect->black_min_duration_time / av_q2d(inlink->time_base);
00106
00107 blackdetect->pixel_black_th_i = ff_fmt_is_in(inlink->format, yuvj_formats) ?
00108
00109 blackdetect->pixel_black_th * 255 :
00110 16 + blackdetect->pixel_black_th * (235 - 16);
00111
00112 av_log(blackdetect, AV_LOG_VERBOSE,
00113 "black_min_duration:%s pixel_black_th:%f pixel_black_th_i:%d picture_black_ratio_th:%f\n",
00114 av_ts2timestr(blackdetect->black_min_duration, &inlink->time_base),
00115 blackdetect->pixel_black_th, blackdetect->pixel_black_th_i,
00116 blackdetect->picture_black_ratio_th);
00117 return 0;
00118 }
00119
00120 static void check_black_end(AVFilterContext *ctx)
00121 {
00122 BlackDetectContext *blackdetect = ctx->priv;
00123 AVFilterLink *inlink = ctx->inputs[0];
00124
00125 if ((blackdetect->black_end - blackdetect->black_start) >= blackdetect->black_min_duration) {
00126 av_log(blackdetect, AV_LOG_INFO,
00127 "black_start:%s black_end:%s black_duration:%s\n",
00128 av_ts2timestr(blackdetect->black_start, &inlink->time_base),
00129 av_ts2timestr(blackdetect->black_end, &inlink->time_base),
00130 av_ts2timestr(blackdetect->black_end - blackdetect->black_start, &inlink->time_base));
00131 }
00132 }
00133
00134 static int request_frame(AVFilterLink *outlink)
00135 {
00136 AVFilterContext *ctx = outlink->src;
00137 BlackDetectContext *blackdetect = ctx->priv;
00138 AVFilterLink *inlink = ctx->inputs[0];
00139 int ret = ff_request_frame(inlink);
00140
00141 if (ret == AVERROR_EOF && blackdetect->black_started) {
00142
00143 blackdetect->black_end = blackdetect->last_picref_pts;
00144 check_black_end(ctx);
00145 }
00146 return ret;
00147 }
00148
00149 static int draw_slice(AVFilterLink *inlink, int y, int h, int slice_dir)
00150 {
00151 AVFilterContext *ctx = inlink->dst;
00152 BlackDetectContext *blackdetect = ctx->priv;
00153 AVFilterBufferRef *picref = inlink->cur_buf;
00154 int x, i;
00155 const uint8_t *p = picref->data[0] + y * picref->linesize[0];
00156
00157 for (i = 0; i < h; i++) {
00158 for (x = 0; x < inlink->w; x++)
00159 blackdetect->nb_black_pixels += p[x] <= blackdetect->pixel_black_th_i;
00160 p += picref->linesize[0];
00161 }
00162
00163 return ff_draw_slice(ctx->outputs[0], y, h, slice_dir);
00164 }
00165
00166 static int end_frame(AVFilterLink *inlink)
00167 {
00168 AVFilterContext *ctx = inlink->dst;
00169 BlackDetectContext *blackdetect = ctx->priv;
00170 AVFilterBufferRef *picref = inlink->cur_buf;
00171 double picture_black_ratio = 0;
00172
00173 picture_black_ratio = (double)blackdetect->nb_black_pixels / (inlink->w * inlink->h);
00174
00175 av_log(ctx, AV_LOG_DEBUG,
00176 "frame:%u picture_black_ratio:%f pos:%"PRId64" pts:%s t:%s type:%c\n",
00177 blackdetect->frame_count, picture_black_ratio,
00178 picref->pos, av_ts2str(picref->pts), av_ts2timestr(picref->pts, &inlink->time_base),
00179 av_get_picture_type_char(picref->video->pict_type));
00180
00181 if (picture_black_ratio >= blackdetect->picture_black_ratio_th) {
00182 if (!blackdetect->black_started) {
00183
00184 blackdetect->black_started = 1;
00185 blackdetect->black_start = picref->pts;
00186 }
00187 } else if (blackdetect->black_started) {
00188
00189 blackdetect->black_started = 0;
00190 blackdetect->black_end = picref->pts;
00191 check_black_end(ctx);
00192 }
00193
00194 blackdetect->last_picref_pts = picref->pts;
00195 blackdetect->frame_count++;
00196 blackdetect->nb_black_pixels = 0;
00197 return ff_end_frame(inlink->dst->outputs[0]);
00198 }
00199
00200 AVFilter avfilter_vf_blackdetect = {
00201 .name = "blackdetect",
00202 .description = NULL_IF_CONFIG_SMALL("Detect video intervals that are (almost) black."),
00203 .priv_size = sizeof(BlackDetectContext),
00204 .init = init,
00205 .query_formats = query_formats,
00206
00207 .inputs = (const AVFilterPad[]) {
00208 { .name = "default",
00209 .type = AVMEDIA_TYPE_VIDEO,
00210 .config_props = config_input,
00211 .draw_slice = draw_slice,
00212 .get_video_buffer = ff_null_get_video_buffer,
00213 .start_frame = ff_null_start_frame,
00214 .end_frame = end_frame, },
00215 { .name = NULL }
00216 },
00217
00218 .outputs = (const AVFilterPad[]) {
00219 { .name = "default",
00220 .type = AVMEDIA_TYPE_VIDEO,
00221 .request_frame = request_frame, },
00222 { .name = NULL }
00223 },
00224 .priv_class = &blackdetect_class,
00225 };