Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

244 lines (215 sloc) 8.152 kb
/*
* Copyright (c) 2011 Smartjog S.A.S, Clément Bœsch <clement.boesch@smartjog.com>
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file
* Potential thumbnail lookup filter to reduce the risk of an inappropriate
* selection (such as a black frame) we could get with an absolute seek.
*
* Simplified version of algorithm by Vadim Zaliva <lord@crocodile.org>.
* @see http://notbrainsurgery.livejournal.com/29773.html
*/
#include "avfilter.h"
#define HIST_SIZE (3*256)
struct thumb_frame {
AVFilterBufferRef *buf; ///< cached frame
int histogram[HIST_SIZE]; ///< RGB color distribution histogram of the frame
};
typedef struct {
int n; ///< current frame
int n_frames; ///< number of frames for analysis
struct thumb_frame *frames; ///< the n_frames frames
} ThumbContext;
static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque)
{
ThumbContext *thumb = ctx->priv;
if (!args) {
thumb->n_frames = 100;
} else {
int n = sscanf(args, "%d", &thumb->n_frames);
if (n != 1 || thumb->n_frames < 2) {
thumb->n_frames = 0;
av_log(ctx, AV_LOG_ERROR,
"Invalid number of frames specified (minimum is 2).\n");
return AVERROR(EINVAL);
}
}
thumb->frames = av_calloc(thumb->n_frames, sizeof(*thumb->frames));
if (!thumb->frames) {
av_log(ctx, AV_LOG_ERROR,
"Allocation failure, try to lower the number of frames\n");
return AVERROR(ENOMEM);
}
av_log(ctx, AV_LOG_INFO, "batch size: %d frames\n", thumb->n_frames);
return 0;
}
static void draw_slice(AVFilterLink *inlink, int y, int h, int slice_dir)
{
int i, j;
AVFilterContext *ctx = inlink->dst;
ThumbContext *thumb = ctx->priv;
int *hist = thumb->frames[thumb->n].histogram;
AVFilterBufferRef *picref = inlink->cur_buf;
const uint8_t *p = picref->data[0] + y * picref->linesize[0];
// update current frame RGB histogram
for (j = 0; j < h; j++) {
for (i = 0; i < inlink->w; i++) {
hist[0*256 + p[i*3 ]]++;
hist[1*256 + p[i*3 + 1]]++;
hist[2*256 + p[i*3 + 2]]++;
}
p += picref->linesize[0];
}
}
/**
* @brief Compute Sum-square deviation to estimate "closeness".
* @param hist color distribution histogram
* @param median average color distribution histogram
* @return sum of squared errors
*/
static double frame_sum_square_err(const int *hist, const double *median)
{
int i;
double err, sum_sq_err = 0;
for (i = 0; i < HIST_SIZE; i++) {
err = median[i] - (double)hist[i];
sum_sq_err += err*err;
}
return sum_sq_err;
}
static void end_frame(AVFilterLink *inlink)
{
int i, j, best_frame_idx = 0;
double avg_hist[HIST_SIZE] = {0}, sq_err, min_sq_err = -1;
AVFilterLink *outlink = inlink->dst->outputs[0];
ThumbContext *thumb = inlink->dst->priv;
AVFilterContext *ctx = inlink->dst;
AVFilterBufferRef *picref;
// keep a reference of each frame
thumb->frames[thumb->n].buf = inlink->cur_buf;
// no selection until the buffer of N frames is filled up
if (thumb->n < thumb->n_frames - 1) {
thumb->n++;
return;
}
// average histogram of the N frames
for (j = 0; j < FF_ARRAY_ELEMS(avg_hist); j++) {
for (i = 0; i < thumb->n_frames; i++)
avg_hist[j] += (double)thumb->frames[i].histogram[j];
avg_hist[j] /= thumb->n_frames;
}
// find the frame closer to the average using the sum of squared errors
for (i = 0; i < thumb->n_frames; i++) {
sq_err = frame_sum_square_err(thumb->frames[i].histogram, avg_hist);
if (i == 0 || sq_err < min_sq_err)
best_frame_idx = i, min_sq_err = sq_err;
}
// free and reset everything (except the best frame buffer)
for (i = 0; i < thumb->n_frames; i++) {
memset(thumb->frames[i].histogram, 0, sizeof(thumb->frames[i].histogram));
if (i == best_frame_idx)
continue;
avfilter_unref_buffer(thumb->frames[i].buf);
thumb->frames[i].buf = NULL;
}
thumb->n = 0;
// raise the chosen one
picref = thumb->frames[best_frame_idx].buf;
av_log(ctx, AV_LOG_INFO, "frame id #%d (pts_time=%f) selected\n",
best_frame_idx, picref->pts * av_q2d(inlink->time_base));
avfilter_start_frame(outlink, picref);
thumb->frames[best_frame_idx].buf = NULL;
avfilter_draw_slice(outlink, 0, inlink->h, 1);
avfilter_end_frame(outlink);
}
static av_cold void uninit(AVFilterContext *ctx)
{
int i;
ThumbContext *thumb = ctx->priv;
for (i = 0; i < thumb->n_frames && thumb->frames[i].buf; i++) {
avfilter_unref_buffer(thumb->frames[i].buf);
thumb->frames[i].buf = NULL;
}
av_freep(&thumb->frames);
}
static void null_start_frame(AVFilterLink *link, AVFilterBufferRef *picref) { }
static int request_frame(AVFilterLink *link)
{
ThumbContext *thumb = link->src->priv;
/* loop until a frame thumbnail is available (when a frame is queued,
* thumb->n is reset to zero) */
do {
int ret = avfilter_request_frame(link->src->inputs[0]);
if (ret < 0)
return ret;
} while (thumb->n);
return 0;
}
static int poll_frame(AVFilterLink *link)
{
ThumbContext *thumb = link->src->priv;
AVFilterLink *inlink = link->src->inputs[0];
int ret, available_frames = avfilter_poll_frame(inlink);
/* If the input link is not able to provide any frame, we can't do anything
* at the moment and thus have zero thumbnail available. */
if (!available_frames)
return 0;
/* Since at least one frame is available and the next frame will allow us
* to compute a thumbnail, we can return 1 frame. */
if (thumb->n == thumb->n_frames - 1)
return 1;
/* we have some frame(s) available in the input link, but not yet enough to
* output a thumbnail, so we request more */
ret = avfilter_request_frame(inlink);
return ret < 0 ? ret : 0;
}
static int query_formats(AVFilterContext *ctx)
{
static const enum PixelFormat pix_fmts[] = {
PIX_FMT_RGB24, PIX_FMT_BGR24,
PIX_FMT_NONE
};
avfilter_set_common_pixel_formats(ctx, avfilter_make_format_list(pix_fmts));
return 0;
}
AVFilter avfilter_vf_thumbnail = {
.name = "thumbnail",
.description = NULL_IF_CONFIG_SMALL("Select the most representative frame in a given sequence of consecutive frames."),
.priv_size = sizeof(ThumbContext),
.init = init,
.uninit = uninit,
.query_formats = query_formats,
.inputs = (const AVFilterPad[]) {
{ .name = "default",
.type = AVMEDIA_TYPE_VIDEO,
.get_video_buffer = avfilter_null_get_video_buffer,
.start_frame = null_start_frame,
.draw_slice = draw_slice,
.end_frame = end_frame,
},{ .name = NULL }
},
.outputs = (const AVFilterPad[]) {
{ .name = "default",
.type = AVMEDIA_TYPE_VIDEO,
.request_frame = request_frame,
.poll_frame = poll_frame,
.rej_perms = AV_PERM_REUSE2,
},{ .name = NULL }
},
};
Jump to Line
Something went wrong with that request. Please try again.