mirror of https://git.ffmpeg.org/ffmpeg.git
lavfi/thumbnail: support flushing.
This makes possible the raise of one picture if the input has less than the number of frames to analyze. Fixes Ticket1992.
This commit is contained in:
parent
ed69c69a01
commit
a95a38793c
|
@ -30,7 +30,7 @@
|
||||||
|
|
||||||
#define LIBAVFILTER_VERSION_MAJOR 3
|
#define LIBAVFILTER_VERSION_MAJOR 3
|
||||||
#define LIBAVFILTER_VERSION_MINOR 45
|
#define LIBAVFILTER_VERSION_MINOR 45
|
||||||
#define LIBAVFILTER_VERSION_MICRO 102
|
#define LIBAVFILTER_VERSION_MICRO 103
|
||||||
|
|
||||||
#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
|
#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
|
||||||
LIBAVFILTER_VERSION_MINOR, \
|
LIBAVFILTER_VERSION_MINOR, \
|
||||||
|
|
|
@ -41,6 +41,7 @@ typedef struct {
|
||||||
int n; ///< current frame
|
int n; ///< current frame
|
||||||
int n_frames; ///< number of frames for analysis
|
int n_frames; ///< number of frames for analysis
|
||||||
struct thumb_frame *frames; ///< the n_frames frames
|
struct thumb_frame *frames; ///< the n_frames frames
|
||||||
|
AVRational tb; ///< copy of the input timebase to ease access
|
||||||
} ThumbContext;
|
} ThumbContext;
|
||||||
|
|
||||||
static av_cold int init(AVFilterContext *ctx, const char *args)
|
static av_cold int init(AVFilterContext *ctx, const char *args)
|
||||||
|
@ -86,14 +87,52 @@ static double frame_sum_square_err(const int *hist, const double *median)
|
||||||
return sum_sq_err;
|
return sum_sq_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static AVFrame *get_best_frame(AVFilterContext *ctx)
|
||||||
|
{
|
||||||
|
AVFrame *picref;
|
||||||
|
ThumbContext *thumb = ctx->priv;
|
||||||
|
int i, j, best_frame_idx = 0;
|
||||||
|
int nb_frames = thumb->n;
|
||||||
|
double avg_hist[HIST_SIZE] = {0}, sq_err, min_sq_err = -1;
|
||||||
|
|
||||||
|
// average histogram of the N frames
|
||||||
|
for (j = 0; j < FF_ARRAY_ELEMS(avg_hist); j++) {
|
||||||
|
for (i = 0; i < nb_frames; i++)
|
||||||
|
avg_hist[j] += (double)thumb->frames[i].histogram[j];
|
||||||
|
avg_hist[j] /= nb_frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the frame closer to the average using the sum of squared errors
|
||||||
|
for (i = 0; i < nb_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 < nb_frames; i++) {
|
||||||
|
memset(thumb->frames[i].histogram, 0, sizeof(thumb->frames[i].histogram));
|
||||||
|
if (i != best_frame_idx)
|
||||||
|
av_frame_unref(thumb->frames[i].buf);
|
||||||
|
}
|
||||||
|
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 "
|
||||||
|
"from a set of %d images\n", best_frame_idx,
|
||||||
|
picref->pts * av_q2d(thumb->tb), nb_frames);
|
||||||
|
thumb->frames[best_frame_idx].buf = NULL;
|
||||||
|
|
||||||
|
return picref;
|
||||||
|
}
|
||||||
|
|
||||||
static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
|
static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
|
||||||
{
|
{
|
||||||
int i, j, best_frame_idx = 0;
|
int i, j;
|
||||||
double avg_hist[HIST_SIZE] = {0}, sq_err, min_sq_err = -1;
|
|
||||||
AVFilterContext *ctx = inlink->dst;
|
AVFilterContext *ctx = inlink->dst;
|
||||||
ThumbContext *thumb = ctx->priv;
|
ThumbContext *thumb = ctx->priv;
|
||||||
AVFilterLink *outlink = ctx->outputs[0];
|
AVFilterLink *outlink = ctx->outputs[0];
|
||||||
AVFrame *picref;
|
|
||||||
int *hist = thumb->frames[thumb->n].histogram;
|
int *hist = thumb->frames[thumb->n].histogram;
|
||||||
const uint8_t *p = frame->data[0];
|
const uint8_t *p = frame->data[0];
|
||||||
|
|
||||||
|
@ -111,40 +150,11 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
// no selection until the buffer of N frames is filled up
|
// no selection until the buffer of N frames is filled up
|
||||||
if (thumb->n < thumb->n_frames - 1) {
|
thumb->n++;
|
||||||
thumb->n++;
|
if (thumb->n < thumb->n_frames)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
|
||||||
|
|
||||||
// average histogram of the N frames
|
return ff_filter_frame(outlink, get_best_frame(ctx));
|
||||||
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;
|
|
||||||
av_frame_unref(thumb->frames[i].buf);
|
|
||||||
}
|
|
||||||
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));
|
|
||||||
thumb->frames[best_frame_idx].buf = NULL;
|
|
||||||
return ff_filter_frame(outlink, picref);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static av_cold void uninit(AVFilterContext *ctx)
|
static av_cold void uninit(AVFilterContext *ctx)
|
||||||
|
@ -158,18 +168,34 @@ static av_cold void uninit(AVFilterContext *ctx)
|
||||||
|
|
||||||
static int request_frame(AVFilterLink *link)
|
static int request_frame(AVFilterLink *link)
|
||||||
{
|
{
|
||||||
ThumbContext *thumb = link->src->priv;
|
AVFilterContext *ctx = link->src;
|
||||||
|
ThumbContext *thumb = ctx->priv;
|
||||||
|
|
||||||
/* loop until a frame thumbnail is available (when a frame is queued,
|
/* loop until a frame thumbnail is available (when a frame is queued,
|
||||||
* thumb->n is reset to zero) */
|
* thumb->n is reset to zero) */
|
||||||
do {
|
do {
|
||||||
int ret = ff_request_frame(link->src->inputs[0]);
|
int ret = ff_request_frame(ctx->inputs[0]);
|
||||||
|
if (ret == AVERROR_EOF && thumb->n) {
|
||||||
|
ret = ff_filter_frame(link, get_best_frame(ctx));
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
ret = AVERROR_EOF;
|
||||||
|
}
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
} while (thumb->n);
|
} while (thumb->n);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int config_props(AVFilterLink *inlink)
|
||||||
|
{
|
||||||
|
AVFilterContext *ctx = inlink->dst;
|
||||||
|
ThumbContext *thumb = ctx->priv;
|
||||||
|
|
||||||
|
thumb->tb = inlink->time_base;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int query_formats(AVFilterContext *ctx)
|
static int query_formats(AVFilterContext *ctx)
|
||||||
{
|
{
|
||||||
static const enum AVPixelFormat pix_fmts[] = {
|
static const enum AVPixelFormat pix_fmts[] = {
|
||||||
|
@ -184,6 +210,7 @@ static const AVFilterPad thumbnail_inputs[] = {
|
||||||
{
|
{
|
||||||
.name = "default",
|
.name = "default",
|
||||||
.type = AVMEDIA_TYPE_VIDEO,
|
.type = AVMEDIA_TYPE_VIDEO,
|
||||||
|
.config_props = config_props,
|
||||||
.get_video_buffer = ff_null_get_video_buffer,
|
.get_video_buffer = ff_null_get_video_buffer,
|
||||||
.filter_frame = filter_frame,
|
.filter_frame = filter_frame,
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue