Subversion Repositories Kolibri OS

Rev

Go to most recent revision | Blame | Last modification | View Log | RSS feed

  1. /*
  2.  * Copyright (c) 2011 Smartjog S.A.S, Clément Bœsch <clement.boesch@smartjog.com>
  3.  *
  4.  * This file is part of FFmpeg.
  5.  *
  6.  * FFmpeg is free software; you can redistribute it and/or
  7.  * modify it under the terms of the GNU Lesser General Public
  8.  * License as published by the Free Software Foundation; either
  9.  * version 2.1 of the License, or (at your option) any later version.
  10.  *
  11.  * FFmpeg is distributed in the hope that it will be useful,
  12.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14.  * Lesser General Public License for more details.
  15.  *
  16.  * You should have received a copy of the GNU Lesser General Public
  17.  * License along with FFmpeg; if not, write to the Free Software
  18.  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  19.  */
  20.  
  21. /**
  22.  * @file
  23.  * Potential thumbnail lookup filter to reduce the risk of an inappropriate
  24.  * selection (such as a black frame) we could get with an absolute seek.
  25.  *
  26.  * Simplified version of algorithm by Vadim Zaliva <lord@crocodile.org>.
  27.  * @see http://notbrainsurgery.livejournal.com/29773.html
  28.  */
  29.  
  30. #include "libavutil/opt.h"
  31. #include "avfilter.h"
  32. #include "internal.h"
  33.  
  34. #define HIST_SIZE (3*256)
  35.  
  36. struct thumb_frame {
  37.     AVFrame *buf;               ///< cached frame
  38.     int histogram[HIST_SIZE];   ///< RGB color distribution histogram of the frame
  39. };
  40.  
  41. typedef struct {
  42.     const AVClass *class;
  43.     int n;                      ///< current frame
  44.     int n_frames;               ///< number of frames for analysis
  45.     struct thumb_frame *frames; ///< the n_frames frames
  46.     AVRational tb;              ///< copy of the input timebase to ease access
  47. } ThumbContext;
  48.  
  49. #define OFFSET(x) offsetof(ThumbContext, x)
  50. #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
  51.  
  52. static const AVOption thumbnail_options[] = {
  53.     { "n", "set the frames batch size", OFFSET(n_frames), AV_OPT_TYPE_INT, {.i64=100}, 2, INT_MAX, FLAGS },
  54.     { NULL }
  55. };
  56.  
  57. AVFILTER_DEFINE_CLASS(thumbnail);
  58.  
  59. static av_cold int init(AVFilterContext *ctx)
  60. {
  61.     ThumbContext *thumb = ctx->priv;
  62.  
  63.     thumb->frames = av_calloc(thumb->n_frames, sizeof(*thumb->frames));
  64.     if (!thumb->frames) {
  65.         av_log(ctx, AV_LOG_ERROR,
  66.                "Allocation failure, try to lower the number of frames\n");
  67.         return AVERROR(ENOMEM);
  68.     }
  69.     av_log(ctx, AV_LOG_VERBOSE, "batch size: %d frames\n", thumb->n_frames);
  70.     return 0;
  71. }
  72.  
  73. /**
  74.  * @brief        Compute Sum-square deviation to estimate "closeness".
  75.  * @param hist   color distribution histogram
  76.  * @param median average color distribution histogram
  77.  * @return       sum of squared errors
  78.  */
  79. static double frame_sum_square_err(const int *hist, const double *median)
  80. {
  81.     int i;
  82.     double err, sum_sq_err = 0;
  83.  
  84.     for (i = 0; i < HIST_SIZE; i++) {
  85.         err = median[i] - (double)hist[i];
  86.         sum_sq_err += err*err;
  87.     }
  88.     return sum_sq_err;
  89. }
  90.  
  91. static AVFrame *get_best_frame(AVFilterContext *ctx)
  92. {
  93.     AVFrame *picref;
  94.     ThumbContext *thumb = ctx->priv;
  95.     int i, j, best_frame_idx = 0;
  96.     int nb_frames = thumb->n;
  97.     double avg_hist[HIST_SIZE] = {0}, sq_err, min_sq_err = -1;
  98.  
  99.     // average histogram of the N frames
  100.     for (j = 0; j < FF_ARRAY_ELEMS(avg_hist); j++) {
  101.         for (i = 0; i < nb_frames; i++)
  102.             avg_hist[j] += (double)thumb->frames[i].histogram[j];
  103.         avg_hist[j] /= nb_frames;
  104.     }
  105.  
  106.     // find the frame closer to the average using the sum of squared errors
  107.     for (i = 0; i < nb_frames; i++) {
  108.         sq_err = frame_sum_square_err(thumb->frames[i].histogram, avg_hist);
  109.         if (i == 0 || sq_err < min_sq_err)
  110.             best_frame_idx = i, min_sq_err = sq_err;
  111.     }
  112.  
  113.     // free and reset everything (except the best frame buffer)
  114.     for (i = 0; i < nb_frames; i++) {
  115.         memset(thumb->frames[i].histogram, 0, sizeof(thumb->frames[i].histogram));
  116.         if (i != best_frame_idx)
  117.             av_frame_free(&thumb->frames[i].buf);
  118.     }
  119.     thumb->n = 0;
  120.  
  121.     // raise the chosen one
  122.     picref = thumb->frames[best_frame_idx].buf;
  123.     av_log(ctx, AV_LOG_INFO, "frame id #%d (pts_time=%f) selected "
  124.            "from a set of %d images\n", best_frame_idx,
  125.            picref->pts * av_q2d(thumb->tb), nb_frames);
  126.     thumb->frames[best_frame_idx].buf = NULL;
  127.  
  128.     return picref;
  129. }
  130.  
  131. static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
  132. {
  133.     int i, j;
  134.     AVFilterContext *ctx  = inlink->dst;
  135.     ThumbContext *thumb   = ctx->priv;
  136.     AVFilterLink *outlink = ctx->outputs[0];
  137.     int *hist = thumb->frames[thumb->n].histogram;
  138.     const uint8_t *p = frame->data[0];
  139.  
  140.     // keep a reference of each frame
  141.     thumb->frames[thumb->n].buf = frame;
  142.  
  143.     // update current frame RGB histogram
  144.     for (j = 0; j < inlink->h; j++) {
  145.         for (i = 0; i < inlink->w; i++) {
  146.             hist[0*256 + p[i*3    ]]++;
  147.             hist[1*256 + p[i*3 + 1]]++;
  148.             hist[2*256 + p[i*3 + 2]]++;
  149.         }
  150.         p += frame->linesize[0];
  151.     }
  152.  
  153.     // no selection until the buffer of N frames is filled up
  154.     thumb->n++;
  155.     if (thumb->n < thumb->n_frames)
  156.         return 0;
  157.  
  158.     return ff_filter_frame(outlink, get_best_frame(ctx));
  159. }
  160.  
  161. static av_cold void uninit(AVFilterContext *ctx)
  162. {
  163.     int i;
  164.     ThumbContext *thumb = ctx->priv;
  165.     for (i = 0; i < thumb->n_frames && thumb->frames[i].buf; i++)
  166.         av_frame_free(&thumb->frames[i].buf);
  167.     av_freep(&thumb->frames);
  168. }
  169.  
  170. static int request_frame(AVFilterLink *link)
  171. {
  172.     AVFilterContext *ctx = link->src;
  173.     ThumbContext *thumb = ctx->priv;
  174.  
  175.     /* loop until a frame thumbnail is available (when a frame is queued,
  176.      * thumb->n is reset to zero) */
  177.     do {
  178.         int ret = ff_request_frame(ctx->inputs[0]);
  179.         if (ret == AVERROR_EOF && thumb->n) {
  180.             ret = ff_filter_frame(link, get_best_frame(ctx));
  181.             if (ret < 0)
  182.                 return ret;
  183.             ret = AVERROR_EOF;
  184.         }
  185.         if (ret < 0)
  186.             return ret;
  187.     } while (thumb->n);
  188.     return 0;
  189. }
  190.  
  191. static int config_props(AVFilterLink *inlink)
  192. {
  193.     AVFilterContext *ctx = inlink->dst;
  194.     ThumbContext *thumb = ctx->priv;
  195.  
  196.     thumb->tb = inlink->time_base;
  197.     return 0;
  198. }
  199.  
  200. static int query_formats(AVFilterContext *ctx)
  201. {
  202.     static const enum AVPixelFormat pix_fmts[] = {
  203.         AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24,
  204.         AV_PIX_FMT_NONE
  205.     };
  206.     ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
  207.     return 0;
  208. }
  209.  
  210. static const AVFilterPad thumbnail_inputs[] = {
  211.     {
  212.         .name         = "default",
  213.         .type         = AVMEDIA_TYPE_VIDEO,
  214.         .config_props = config_props,
  215.         .filter_frame = filter_frame,
  216.     },
  217.     { NULL }
  218. };
  219.  
  220. static const AVFilterPad thumbnail_outputs[] = {
  221.     {
  222.         .name          = "default",
  223.         .type          = AVMEDIA_TYPE_VIDEO,
  224.         .request_frame = request_frame,
  225.     },
  226.     { NULL }
  227. };
  228.  
  229. AVFilter avfilter_vf_thumbnail = {
  230.     .name          = "thumbnail",
  231.     .description   = NULL_IF_CONFIG_SMALL("Select the most representative frame in a given sequence of consecutive frames."),
  232.     .priv_size     = sizeof(ThumbContext),
  233.     .init          = init,
  234.     .uninit        = uninit,
  235.     .query_formats = query_formats,
  236.     .inputs        = thumbnail_inputs,
  237.     .outputs       = thumbnail_outputs,
  238.     .priv_class    = &thumbnail_class,
  239. };
  240.