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