00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00030 #include "avfilter.h"
00031
00032 #define HIST_SIZE (3*256)
00033
00034 struct thumb_frame {
00035 AVFilterBufferRef *buf;
00036 int histogram[HIST_SIZE];
00037 };
00038
00039 typedef struct {
00040 int n;
00041 int n_frames;
00042 struct thumb_frame *frames;
00043 } ThumbContext;
00044
00045 static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque)
00046 {
00047 ThumbContext *thumb = ctx->priv;
00048
00049 if (!args) {
00050 thumb->n_frames = 100;
00051 } else {
00052 int n = sscanf(args, "%d", &thumb->n_frames);
00053 if (n != 1 || thumb->n_frames < 2) {
00054 thumb->n_frames = 0;
00055 av_log(ctx, AV_LOG_ERROR,
00056 "Invalid number of frames specified (minimum is 2).\n");
00057 return AVERROR(EINVAL);
00058 }
00059 }
00060 thumb->frames = av_calloc(thumb->n_frames, sizeof(*thumb->frames));
00061 if (!thumb->frames) {
00062 av_log(ctx, AV_LOG_ERROR,
00063 "Allocation failure, try to lower the number of frames\n");
00064 return AVERROR(ENOMEM);
00065 }
00066 av_log(ctx, AV_LOG_INFO, "batch size: %d frames\n", thumb->n_frames);
00067 return 0;
00068 }
00069
00070 static void draw_slice(AVFilterLink *inlink, int y, int h, int slice_dir)
00071 {
00072 int i, j;
00073 AVFilterContext *ctx = inlink->dst;
00074 ThumbContext *thumb = ctx->priv;
00075 int *hist = thumb->frames[thumb->n].histogram;
00076 AVFilterBufferRef *picref = inlink->cur_buf;
00077 const uint8_t *p = picref->data[0] + y * picref->linesize[0];
00078
00079
00080 for (j = 0; j < h; j++) {
00081 for (i = 0; i < inlink->w; i++) {
00082 hist[0*256 + p[i*3 ]]++;
00083 hist[1*256 + p[i*3 + 1]]++;
00084 hist[2*256 + p[i*3 + 2]]++;
00085 }
00086 p += picref->linesize[0];
00087 }
00088 }
00089
00096 static double frame_sum_square_err(const int *hist, const double *median)
00097 {
00098 int i;
00099 double err, sum_sq_err = 0;
00100
00101 for (i = 0; i < HIST_SIZE; i++) {
00102 err = median[i] - (double)hist[i];
00103 sum_sq_err += err*err;
00104 }
00105 return sum_sq_err;
00106 }
00107
00108 static void end_frame(AVFilterLink *inlink)
00109 {
00110 int i, j, best_frame_idx = 0;
00111 double avg_hist[HIST_SIZE] = {0}, sq_err, min_sq_err = -1;
00112 AVFilterLink *outlink = inlink->dst->outputs[0];
00113 ThumbContext *thumb = inlink->dst->priv;
00114 AVFilterContext *ctx = inlink->dst;
00115 AVFilterBufferRef *picref;
00116
00117
00118 thumb->frames[thumb->n].buf = inlink->cur_buf;
00119
00120
00121 if (thumb->n < thumb->n_frames - 1) {
00122 thumb->n++;
00123 return;
00124 }
00125
00126
00127 for (j = 0; j < FF_ARRAY_ELEMS(avg_hist); j++) {
00128 for (i = 0; i < thumb->n_frames; i++)
00129 avg_hist[j] += (double)thumb->frames[i].histogram[j];
00130 avg_hist[j] /= thumb->n_frames;
00131 }
00132
00133
00134 for (i = 0; i < thumb->n_frames; i++) {
00135 sq_err = frame_sum_square_err(thumb->frames[i].histogram, avg_hist);
00136 if (i == 0 || sq_err < min_sq_err)
00137 best_frame_idx = i, min_sq_err = sq_err;
00138 }
00139
00140
00141 for (i = 0; i < thumb->n_frames; i++) {
00142 memset(thumb->frames[i].histogram, 0, sizeof(thumb->frames[i].histogram));
00143 if (i == best_frame_idx)
00144 continue;
00145 avfilter_unref_buffer(thumb->frames[i].buf);
00146 thumb->frames[i].buf = NULL;
00147 }
00148 thumb->n = 0;
00149
00150
00151 picref = thumb->frames[best_frame_idx].buf;
00152 av_log(ctx, AV_LOG_INFO, "frame id #%d (pts_time=%f) selected\n",
00153 best_frame_idx, picref->pts * av_q2d(inlink->time_base));
00154 avfilter_start_frame(outlink, picref);
00155 thumb->frames[best_frame_idx].buf = NULL;
00156 avfilter_draw_slice(outlink, 0, inlink->h, 1);
00157 avfilter_end_frame(outlink);
00158 }
00159
00160 static av_cold void uninit(AVFilterContext *ctx)
00161 {
00162 int i;
00163 ThumbContext *thumb = ctx->priv;
00164 for (i = 0; i < thumb->n_frames && thumb->frames[i].buf; i++) {
00165 avfilter_unref_buffer(thumb->frames[i].buf);
00166 thumb->frames[i].buf = NULL;
00167 }
00168 av_freep(&thumb->frames);
00169 }
00170
00171 static void null_start_frame(AVFilterLink *link, AVFilterBufferRef *picref) { }
00172
00173 static int request_frame(AVFilterLink *link)
00174 {
00175 ThumbContext *thumb = link->src->priv;
00176
00177
00178
00179 while (thumb->n) {
00180 int ret = avfilter_request_frame(link->src->inputs[0]);
00181 if (ret < 0)
00182 return ret;
00183 }
00184 return 0;
00185 }
00186
00187 static int poll_frame(AVFilterLink *link)
00188 {
00189 ThumbContext *thumb = link->src->priv;
00190 AVFilterLink *inlink = link->src->inputs[0];
00191 int ret, available_frames = avfilter_poll_frame(inlink);
00192
00193
00194
00195 if (!available_frames)
00196 return 0;
00197
00198
00199
00200 if (thumb->n == thumb->n_frames - 1)
00201 return 1;
00202
00203
00204
00205 ret = avfilter_request_frame(inlink);
00206 return ret < 0 ? ret : 0;
00207 }
00208
00209 static int query_formats(AVFilterContext *ctx)
00210 {
00211 static const enum PixelFormat pix_fmts[] = {
00212 PIX_FMT_RGB24, PIX_FMT_BGR24,
00213 PIX_FMT_NONE
00214 };
00215 avfilter_set_common_pixel_formats(ctx, avfilter_make_format_list(pix_fmts));
00216 return 0;
00217 }
00218
00219 AVFilter avfilter_vf_thumbnail = {
00220 .name = "thumbnail",
00221 .description = NULL_IF_CONFIG_SMALL("Select the most representative frame in a given sequence of consecutive frames."),
00222 .priv_size = sizeof(ThumbContext),
00223 .init = init,
00224 .uninit = uninit,
00225 .query_formats = query_formats,
00226 .inputs = (const AVFilterPad[]) {
00227 { .name = "default",
00228 .type = AVMEDIA_TYPE_VIDEO,
00229 .get_video_buffer = avfilter_null_get_video_buffer,
00230 .start_frame = null_start_frame,
00231 .draw_slice = draw_slice,
00232 .end_frame = end_frame,
00233 },{ .name = NULL }
00234 },
00235 .outputs = (const AVFilterPad[]) {
00236 { .name = "default",
00237 .type = AVMEDIA_TYPE_VIDEO,
00238 .request_frame = request_frame,
00239 .poll_frame = poll_frame,
00240 .rej_perms = AV_PERM_REUSE2,
00241 },{ .name = NULL }
00242 },
00243 };