LCOV - code coverage report
Current view: top level - libavfilter - vf_fps.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 102 115 88.7 %
Date: 2018-05-20 11:54:08 Functions: 8 8 100.0 %

          Line data    Source code
       1             : /*
       2             :  * Copyright 2007 Bobby Bingham
       3             :  * Copyright 2012 Robert Nagy <ronag89 gmail com>
       4             :  * Copyright 2012 Anton Khirnov <anton khirnov net>
       5             :  * Copyright 2018 Calvin Walton <calvin.walton@kepstin.ca>
       6             :  *
       7             :  * This file is part of FFmpeg.
       8             :  *
       9             :  * FFmpeg is free software; you can redistribute it and/or
      10             :  * modify it under the terms of the GNU Lesser General Public
      11             :  * License as published by the Free Software Foundation; either
      12             :  * version 2.1 of the License, or (at your option) any later version.
      13             :  *
      14             :  * FFmpeg is distributed in the hope that it will be useful,
      15             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      17             :  * Lesser General Public License for more details.
      18             :  *
      19             :  * You should have received a copy of the GNU Lesser General Public
      20             :  * License along with FFmpeg; if not, write to the Free Software
      21             :  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
      22             :  */
      23             : 
      24             : /**
      25             :  * @file
      26             :  * a filter enforcing given constant framerate
      27             :  */
      28             : 
      29             : #include <float.h>
      30             : #include <stdint.h>
      31             : 
      32             : #include "libavutil/avassert.h"
      33             : #include "libavutil/mathematics.h"
      34             : #include "libavutil/opt.h"
      35             : #include "avfilter.h"
      36             : #include "filters.h"
      37             : #include "internal.h"
      38             : 
      39             : enum EOFAction {
      40             :     EOF_ACTION_ROUND,
      41             :     EOF_ACTION_PASS,
      42             :     EOF_ACTION_NB
      43             : };
      44             : 
      45             : typedef struct FPSContext {
      46             :     const AVClass *class;
      47             : 
      48             :     double start_time;      ///< pts, in seconds, of the expected first frame
      49             : 
      50             :     AVRational framerate;   ///< target framerate
      51             :     int rounding;           ///< AVRounding method for timestamps
      52             :     int eof_action;         ///< action performed for last frame in FIFO
      53             : 
      54             :     /* Set during outlink configuration */
      55             :     int64_t  in_pts_off;    ///< input frame pts offset for start_time handling
      56             :     int64_t  out_pts_off;   ///< output frame pts offset for start_time handling
      57             : 
      58             :     /* Runtime state */
      59             :     int      status;        ///< buffered input status
      60             :     int64_t  status_pts;    ///< buffered input status timestamp
      61             : 
      62             :     AVFrame *frames[2];     ///< buffered frames
      63             :     int      frames_count;  ///< number of buffered frames
      64             : 
      65             :     int64_t  next_pts;      ///< pts of the next frame to output
      66             : 
      67             :     /* statistics */
      68             :     int cur_frame_out;         ///< number of times current frame has been output
      69             :     int frames_in;             ///< number of frames on input
      70             :     int frames_out;            ///< number of frames on output
      71             :     int dup;                   ///< number of frames duplicated
      72             :     int drop;                  ///< number of framed dropped
      73             : } FPSContext;
      74             : 
      75             : #define OFFSET(x) offsetof(FPSContext, x)
      76             : #define V AV_OPT_FLAG_VIDEO_PARAM
      77             : #define F AV_OPT_FLAG_FILTERING_PARAM
      78             : static const AVOption fps_options[] = {
      79             :     { "fps", "A string describing desired output framerate", OFFSET(framerate), AV_OPT_TYPE_VIDEO_RATE, { .str = "25" }, 0, INT_MAX, V|F },
      80             :     { "start_time", "Assume the first PTS should be this value.", OFFSET(start_time), AV_OPT_TYPE_DOUBLE, { .dbl = DBL_MAX}, -DBL_MAX, DBL_MAX, V|F },
      81             :     { "round", "set rounding method for timestamps", OFFSET(rounding), AV_OPT_TYPE_INT, { .i64 = AV_ROUND_NEAR_INF }, 0, 5, V|F, "round" },
      82             :         { "zero", "round towards 0",                 0, AV_OPT_TYPE_CONST, { .i64 = AV_ROUND_ZERO     }, 0, 0, V|F, "round" },
      83             :         { "inf",  "round away from 0",               0, AV_OPT_TYPE_CONST, { .i64 = AV_ROUND_INF      }, 0, 0, V|F, "round" },
      84             :         { "down", "round towards -infty",            0, AV_OPT_TYPE_CONST, { .i64 = AV_ROUND_DOWN     }, 0, 0, V|F, "round" },
      85             :         { "up",   "round towards +infty",            0, AV_OPT_TYPE_CONST, { .i64 = AV_ROUND_UP       }, 0, 0, V|F, "round" },
      86             :         { "near", "round to nearest",                0, AV_OPT_TYPE_CONST, { .i64 = AV_ROUND_NEAR_INF }, 0, 0, V|F, "round" },
      87             :     { "eof_action", "action performed for last frame", OFFSET(eof_action), AV_OPT_TYPE_INT, { .i64 = EOF_ACTION_ROUND }, 0, EOF_ACTION_NB-1, V|F, "eof_action" },
      88             :         { "round", "round similar to other frames",  0, AV_OPT_TYPE_CONST, { .i64 = EOF_ACTION_ROUND }, 0, 0, V|F, "eof_action" },
      89             :         { "pass",  "pass through last frame",        0, AV_OPT_TYPE_CONST, { .i64 = EOF_ACTION_PASS  }, 0, 0, V|F, "eof_action" },
      90             :     { NULL }
      91             : };
      92             : 
      93             : AVFILTER_DEFINE_CLASS(fps);
      94             : 
      95          23 : static av_cold int init(AVFilterContext *ctx)
      96             : {
      97          23 :     FPSContext *s = ctx->priv;
      98             : 
      99          23 :     s->status_pts   = AV_NOPTS_VALUE;
     100          23 :     s->next_pts     = AV_NOPTS_VALUE;
     101             : 
     102          23 :     av_log(ctx, AV_LOG_VERBOSE, "fps=%d/%d\n", s->framerate.num, s->framerate.den);
     103          23 :     return 0;
     104             : }
     105             : 
     106             : /* Remove the first frame from the buffer, returning it */
     107         226 : static AVFrame *shift_frame(AVFilterContext *ctx, FPSContext *s)
     108             : {
     109             :     AVFrame *frame;
     110             : 
     111             :     /* Must only be called when there are frames in the buffer */
     112             :     av_assert1(s->frames_count > 0);
     113             : 
     114         226 :     frame = s->frames[0];
     115         226 :     s->frames[0] = s->frames[1];
     116         226 :     s->frames[1] = NULL;
     117         226 :     s->frames_count--;
     118             : 
     119             :     /* Update statistics counters */
     120         226 :     s->frames_out += s->cur_frame_out;
     121         226 :     if (s->cur_frame_out > 1) {
     122          36 :         av_log(ctx, AV_LOG_DEBUG, "Duplicated frame with pts %"PRId64" %d times\n",
     123          36 :                frame->pts, s->cur_frame_out - 1);
     124          36 :         s->dup += s->cur_frame_out - 1;
     125         190 :     } else if (s->cur_frame_out == 0) {
     126          88 :         av_log(ctx, AV_LOG_DEBUG, "Dropping frame with pts %"PRId64"\n",
     127             :                frame->pts);
     128          88 :         s->drop++;
     129             :     }
     130         226 :     s->cur_frame_out = 0;
     131             : 
     132         226 :     return frame;
     133             : }
     134             : 
     135          23 : static av_cold void uninit(AVFilterContext *ctx)
     136             : {
     137          23 :     FPSContext *s = ctx->priv;
     138             : 
     139             :     AVFrame *frame;
     140             : 
     141          46 :     while (s->frames_count > 0) {
     142           0 :         frame = shift_frame(ctx, s);
     143           0 :         av_frame_free(&frame);
     144             :     }
     145             : 
     146          23 :     av_log(ctx, AV_LOG_VERBOSE, "%d frames in, %d frames out; %d frames dropped, "
     147             :            "%d frames duplicated.\n", s->frames_in, s->frames_out, s->drop, s->dup);
     148          23 : }
     149             : 
     150          13 : static int config_props(AVFilterLink* outlink)
     151             : {
     152          13 :     AVFilterContext *ctx    = outlink->src;
     153          13 :     AVFilterLink    *inlink = ctx->inputs[0];
     154          13 :     FPSContext      *s      = ctx->priv;
     155             : 
     156          13 :     outlink->time_base  = av_inv_q(s->framerate);
     157          13 :     outlink->frame_rate = s->framerate;
     158             : 
     159             :     /* Calculate the input and output pts offsets for start_time */
     160          13 :     if (s->start_time != DBL_MAX && s->start_time != AV_NOPTS_VALUE) {
     161           2 :         double first_pts = s->start_time * AV_TIME_BASE;
     162           2 :         if (first_pts < INT64_MIN || first_pts > INT64_MAX) {
     163           0 :             av_log(ctx, AV_LOG_ERROR, "Start time %f cannot be represented in internal time base\n",
     164             :                    s->start_time);
     165           0 :             return AVERROR(EINVAL);
     166             :         }
     167           2 :         s->in_pts_off  = av_rescale_q_rnd(first_pts, AV_TIME_BASE_Q, inlink->time_base,
     168           2 :                                           s->rounding | AV_ROUND_PASS_MINMAX);
     169           2 :         s->out_pts_off = av_rescale_q_rnd(first_pts, AV_TIME_BASE_Q, outlink->time_base,
     170           2 :                                           s->rounding | AV_ROUND_PASS_MINMAX);
     171           2 :         s->next_pts = s->out_pts_off;
     172           2 :         av_log(ctx, AV_LOG_VERBOSE, "Set first pts to (in:%"PRId64" out:%"PRId64") from start time %f\n",
     173             :                s->in_pts_off, s->out_pts_off, s->start_time);
     174             :     }
     175             : 
     176          13 :     return 0;
     177             : }
     178             : 
     179             : /* Read a frame from the input and save it in the buffer */
     180         226 : static int read_frame(AVFilterContext *ctx, FPSContext *s, AVFilterLink *inlink, AVFilterLink *outlink)
     181             : {
     182             :     AVFrame *frame;
     183             :     int ret;
     184             :     int64_t in_pts;
     185             : 
     186             :     /* Must only be called when we have buffer room available */
     187             :     av_assert1(s->frames_count < 2);
     188             : 
     189         226 :     ret = ff_inlink_consume_frame(inlink, &frame);
     190             :     /* Caller must have run ff_inlink_check_available_frame first */
     191             :     av_assert1(ret);
     192         226 :     if (ret < 0)
     193           0 :         return ret;
     194             : 
     195             :     /* Convert frame pts to output timebase.
     196             :      * The dance with offsets is required to match the rounding behaviour of the
     197             :      * previous version of the fps filter when using the start_time option. */
     198         226 :     in_pts = frame->pts;
     199         452 :     frame->pts = s->out_pts_off + av_rescale_q_rnd(in_pts - s->in_pts_off,
     200             :                                                    inlink->time_base, outlink->time_base,
     201         226 :                                                    s->rounding | AV_ROUND_PASS_MINMAX);
     202             : 
     203         226 :     av_log(ctx, AV_LOG_DEBUG, "Read frame with in pts %"PRId64", out pts %"PRId64"\n",
     204         226 :            in_pts, frame->pts);
     205             : 
     206         226 :     s->frames[s->frames_count++] = frame;
     207         226 :     s->frames_in++;
     208             : 
     209         226 :     return 1;
     210             : }
     211             : 
     212             : /* Write a frame to the output */
     213         546 : static int write_frame(AVFilterContext *ctx, FPSContext *s, AVFilterLink *outlink, int *again)
     214             : {
     215             :     AVFrame *frame;
     216             : 
     217             :     av_assert1(s->frames_count == 2 || (s->status && s->frames_count == 1));
     218             : 
     219             :     /* We haven't yet determined the pts of the first frame */
     220         546 :     if (s->next_pts == AV_NOPTS_VALUE) {
     221          11 :         if (s->frames[0]->pts != AV_NOPTS_VALUE) {
     222          11 :             s->next_pts = s->frames[0]->pts;
     223          11 :             av_log(ctx, AV_LOG_VERBOSE, "Set first pts to %"PRId64"\n", s->next_pts);
     224             :         } else {
     225           0 :             av_log(ctx, AV_LOG_WARNING, "Discarding initial frame(s) with no "
     226             :                    "timestamp.\n");
     227           0 :             frame = shift_frame(ctx, s);
     228           0 :             av_frame_free(&frame);
     229           0 :             *again = 1;
     230           0 :             return 0;
     231             :         }
     232             :     }
     233             : 
     234             :     /* There are two conditions where we want to drop a frame:
     235             :      * - If we have two buffered frames and the second frame is acceptable
     236             :      *   as the next output frame, then drop the first buffered frame.
     237             :      * - If we have status (EOF) set, drop frames when we hit the
     238             :      *   status timestamp. */
     239         879 :     if ((s->frames_count == 2 && s->frames[1]->pts <= s->next_pts) ||
     240         359 :         (s->status            && s->status_pts     <= s->next_pts)) {
     241             : 
     242         226 :         frame = shift_frame(ctx, s);
     243         226 :         av_frame_free(&frame);
     244         226 :         *again = 1;
     245         226 :         return 0;
     246             : 
     247             :     /* Output a copy of the first buffered frame */
     248             :     } else {
     249         320 :         frame = av_frame_clone(s->frames[0]);
     250         320 :         if (!frame)
     251           0 :             return AVERROR(ENOMEM);
     252         320 :         frame->pts = s->next_pts++;
     253             : 
     254         640 :         av_log(ctx, AV_LOG_DEBUG, "Writing frame with pts %"PRId64" to pts %"PRId64"\n",
     255         640 :                s->frames[0]->pts, frame->pts);
     256         320 :         s->cur_frame_out++;
     257             : 
     258         320 :         return ff_filter_frame(outlink, frame);
     259             :     }
     260             : }
     261             : 
     262             : /* Convert status_pts to outlink timebase */
     263          13 : static void update_eof_pts(AVFilterContext *ctx, FPSContext *s, AVFilterLink *inlink, AVFilterLink *outlink, int64_t status_pts)
     264             : {
     265          13 :     int eof_rounding = (s->eof_action == EOF_ACTION_PASS) ? AV_ROUND_UP : s->rounding;
     266          13 :     s->status_pts = av_rescale_q_rnd(status_pts, inlink->time_base, outlink->time_base,
     267          13 :                                      eof_rounding | AV_ROUND_PASS_MINMAX);
     268             : 
     269          13 :     av_log(ctx, AV_LOG_DEBUG, "EOF is at pts %"PRId64"\n", s->status_pts);
     270          13 : }
     271             : 
     272         801 : static int activate(AVFilterContext *ctx)
     273             : {
     274         801 :     FPSContext   *s       = ctx->priv;
     275         801 :     AVFilterLink *inlink  = ctx->inputs[0];
     276         801 :     AVFilterLink *outlink = ctx->outputs[0];
     277             : 
     278             :     int ret;
     279         801 :     int again = 0;
     280             :     int64_t status_pts;
     281             : 
     282         801 :     FF_FILTER_FORWARD_STATUS_BACK(outlink, inlink);
     283             : 
     284             :     /* No buffered status: normal operation */
     285         798 :     if (!s->status) {
     286             : 
     287             :         /* Read available input frames if we have room */
     288        1770 :         while (s->frames_count < 2 && ff_inlink_check_available_frame(inlink)) {
     289         226 :             ret = read_frame(ctx, s, inlink, outlink);
     290         226 :             if (ret < 0)
     291           0 :                 return ret;
     292             :         }
     293             : 
     294             :         /* We do not yet have enough frames to produce output */
     295         772 :         if (s->frames_count < 2) {
     296             :             /* Check if we've hit EOF (or otherwise that an error status is set) */
     297         252 :             ret = ff_inlink_acknowledge_status(inlink, &s->status, &status_pts);
     298         252 :             if (ret > 0)
     299          13 :                 update_eof_pts(ctx, s, inlink, outlink, status_pts);
     300             : 
     301         252 :             if (!ret) {
     302             :                 /* If someone wants us to output, we'd better ask for more input */
     303         239 :                 FF_FILTER_FORWARD_WANTED(outlink, inlink);
     304           3 :                 return 0;
     305             :             }
     306             :         }
     307             :     }
     308             : 
     309             :     /* Buffered frames are available, so generate an output frame */
     310         559 :     if (s->frames_count > 0) {
     311         546 :         ret = write_frame(ctx, s, outlink, &again);
     312             :         /* Couldn't generate a frame, so schedule us to perform another step */
     313         546 :         if (again)
     314         226 :             ff_filter_set_ready(ctx, 100);
     315         546 :         return ret;
     316             :     }
     317             : 
     318             :     /* No frames left, so forward the status */
     319          13 :     if (s->status && s->frames_count == 0) {
     320          13 :         ff_outlink_set_status(outlink, s->status, s->next_pts);
     321          13 :         return 0;
     322             :     }
     323             : 
     324           0 :     return FFERROR_NOT_READY;
     325             : }
     326             : 
     327             : static const AVFilterPad avfilter_vf_fps_inputs[] = {
     328             :     {
     329             :         .name         = "default",
     330             :         .type         = AVMEDIA_TYPE_VIDEO,
     331             :     },
     332             :     { NULL }
     333             : };
     334             : 
     335             : static const AVFilterPad avfilter_vf_fps_outputs[] = {
     336             :     {
     337             :         .name          = "default",
     338             :         .type          = AVMEDIA_TYPE_VIDEO,
     339             :         .config_props  = config_props,
     340             :     },
     341             :     { NULL }
     342             : };
     343             : 
     344             : AVFilter ff_vf_fps = {
     345             :     .name        = "fps",
     346             :     .description = NULL_IF_CONFIG_SMALL("Force constant framerate."),
     347             :     .init        = init,
     348             :     .uninit      = uninit,
     349             :     .priv_size   = sizeof(FPSContext),
     350             :     .priv_class  = &fps_class,
     351             :     .activate    = activate,
     352             :     .inputs      = avfilter_vf_fps_inputs,
     353             :     .outputs     = avfilter_vf_fps_outputs,
     354             : };

Generated by: LCOV version 1.13