[FFmpeg-devel,PATCHv2,2/2] avfilter/vf_framerate: simplify filter

Submitted by Marton Balint on Jan. 4, 2018, 10:14 p.m.

Details

Message ID 20180104221424.1117-1-cus@passwd.hu
State New
Headers show

Commit Message

Marton Balint Jan. 4, 2018, 10:14 p.m.
The framerate filter was quite convoluted with some filter_frame /
request_frame logic bugs. It seemed easier to rewrite the whole filter_frame /
request_frame part and also the frame interpolation ratio calculation part in
one step.

Notable changes:
- The filter now only stores 2 frames instead of 3
- filter_frame outputs all the frames it can to be able to handle consecutive
  filter_frame calls which previously caused early drops of buffered frames.
- because of this, request_frame is largely simplified and it only outputs
  frames on flush. Previously consecuitve request_frame calls could cause the
  filter to think it is in flush mode filling its buffer with the same frames
  causing a "ghost" effect on the output.
- PTS discontinuities are handled better
- frames with unknown PTS values are now dropped

Fixes ticket #4870.
Probably fixes ticket #5493.

Signed-off-by: Marton Balint <cus@passwd.hu>
---
 libavfilter/vf_framerate.c               | 359 ++++++++-----------------------
 tests/ref/fate/filter-framerate-12bit-up |   1 +
 2 files changed, 95 insertions(+), 265 deletions(-)

Comments

Marton Balint Jan. 8, 2018, 11:40 p.m.
On Thu, 4 Jan 2018, Marton Balint wrote:

> The framerate filter was quite convoluted with some filter_frame /
> request_frame logic bugs. It seemed easier to rewrite the whole filter_frame /
> request_frame part and also the frame interpolation ratio calculation part in
> one step.
>
> Notable changes:
> - The filter now only stores 2 frames instead of 3
> - filter_frame outputs all the frames it can to be able to handle consecutive
>  filter_frame calls which previously caused early drops of buffered frames.
> - because of this, request_frame is largely simplified and it only outputs
>  frames on flush. Previously consecuitve request_frame calls could cause the
>  filter to think it is in flush mode filling its buffer with the same frames
>  causing a "ghost" effect on the output.
> - PTS discontinuities are handled better
> - frames with unknown PTS values are now dropped
>
> Fixes ticket #4870.
> Probably fixes ticket #5493.
>

Will push this soon.

Regards,
Marton
Marton Balint Jan. 12, 2018, 10:53 p.m.
On Tue, 9 Jan 2018, Marton Balint wrote:

>
>
> On Thu, 4 Jan 2018, Marton Balint wrote:
>
>> The framerate filter was quite convoluted with some filter_frame /
>> request_frame logic bugs. It seemed easier to rewrite the whole 
> filter_frame /
>> request_frame part and also the frame interpolation ratio calculation part 
> in
>> one step.
>>
>> Notable changes:
>> - The filter now only stores 2 frames instead of 3
>> - filter_frame outputs all the frames it can to be able to handle 
> consecutive
>>  filter_frame calls which previously caused early drops of buffered frames.
>> - because of this, request_frame is largely simplified and it only outputs
>>  frames on flush. Previously consecuitve request_frame calls could cause 
> the
>>  filter to think it is in flush mode filling its buffer with the same 
> frames
>>  causing a "ghost" effect on the output.
>> - PTS discontinuities are handled better
>> - frames with unknown PTS values are now dropped
>>
>> Fixes ticket #4870.
>> Probably fixes ticket #5493.
>>
>
> Will push this soon.

Pushed.

Regards,
Marton

Patch hide | download patch | download mbox

diff --git a/libavfilter/vf_framerate.c b/libavfilter/vf_framerate.c
index 38f45a8033..750a430020 100644
--- a/libavfilter/vf_framerate.c
+++ b/libavfilter/vf_framerate.c
@@ -55,30 +55,25 @@  typedef struct FrameRateContext {
     int line_size[4];                   ///< bytes of pixel data per line for each plane
     int vsub;
 
-    int frst, next, prev, crnt, last;
-    int pending_srce_frames;            ///< how many input frames are still waiting to be processed
-    int flush;                          ///< are we flushing final frames
-    int pending_end_frame;              ///< flag indicating we are waiting to call filter_frame()
-
     AVRational srce_time_base;          ///< timebase of source
-
     AVRational dest_time_base;          ///< timebase of destination
-    int32_t dest_frame_num;
-    int64_t last_dest_frame_pts;        ///< pts of the last frame output
-    int64_t average_srce_pts_dest_delta;///< average input pts delta converted from input rate to output rate
-    int64_t average_dest_pts_delta;     ///< calculated average output pts delta
 
     av_pixelutils_sad_fn sad;           ///< Sum of the absolute difference function (scene detect only)
     double prev_mafd;                   ///< previous MAFD                           (scene detect only)
 
-    AVFrame *srce[N_SRCE];              ///< buffered source frames
-    int64_t srce_pts_dest[N_SRCE];      ///< pts for source frames scaled to output timebase
-    double srce_score[N_SRCE];          ///< scene change score compared to the next srce frame
-    int64_t pts;                        ///< pts of frame we are working on
-
     int max;
     int bitdepth;
     AVFrame *work;
+
+    AVFrame *f0;                        ///< last frame
+    AVFrame *f1;                        ///< current frame
+    int64_t pts0;                       ///< last frame pts in dest_time_base
+    int64_t pts1;                       ///< current frame pts in dest_time_base
+    int64_t delta;                      ///< pts1 to pts0 delta
+    double score;                       ///< scene change score (f0 to f1)
+    int flush;                          ///< 1 if the filter is being flushed
+    int64_t start_pts;                  ///< pts of the first output frame
+    int64_t n;                          ///< output frame counter
 } FrameRateContext;
 
 #define OFFSET(x) offsetof(FrameRateContext, x)
@@ -102,27 +97,6 @@  static const AVOption framerate_options[] = {
 
 AVFILTER_DEFINE_CLASS(framerate);
 
-static void next_source(AVFilterContext *ctx)
-{
-    FrameRateContext *s = ctx->priv;
-    int i;
-
-    ff_dlog(ctx,  "next_source()\n");
-
-    if (s->srce[s->last] && s->srce[s->last] != s->srce[s->last-1]) {
-        ff_dlog(ctx, "next_source() unlink %d\n", s->last);
-        av_frame_free(&s->srce[s->last]);
-    }
-    for (i = s->last; i > s->frst; i--) {
-        ff_dlog(ctx, "next_source() copy %d to %d\n", i - 1, i);
-        s->srce[i] = s->srce[i - 1];
-        s->srce_score[i] = s->srce_score[i - 1];
-    }
-    ff_dlog(ctx, "next_source() make %d null\n", s->frst);
-    s->srce[s->frst] = NULL;
-    s->srce_score[s->frst] = -1.0;
-}
-
 static av_always_inline int64_t sad_8x8_16(const uint16_t *src1, ptrdiff_t stride1,
                                            const uint16_t *src2, ptrdiff_t stride2)
 {
@@ -307,28 +281,25 @@  static int filter_slice16(AVFilterContext *ctx, void *arg, int job, int nb_jobs)
     return 0;
 }
 
-static int blend_frames(AVFilterContext *ctx, int interpolate,
-                        int src1, int src2)
+static int blend_frames(AVFilterContext *ctx, int interpolate)
 {
     FrameRateContext *s = ctx->priv;
     AVFilterLink *outlink = ctx->outputs[0];
     double interpolate_scene_score = 0;
 
-    if ((s->flags & FRAMERATE_FLAG_SCD) && s->srce[src1] && s->srce[src2]) {
-        int i1 = src1 < src2 ? src1 : src2;
-        int i2 = src1 < src2 ? src2 : src1;
-        if (i2 == i1 + 1 && s->srce_score[i1] >= 0.0)
-            interpolate_scene_score = s->srce_score[i1];
+    if ((s->flags & FRAMERATE_FLAG_SCD)) {
+        if (s->score >= 0.0)
+            interpolate_scene_score = s->score;
         else
-            interpolate_scene_score = s->srce_score[i1] = get_scene_score(ctx, s->srce[i1], s->srce[i2]);
+            interpolate_scene_score = s->score = get_scene_score(ctx, s->f0, s->f1);
         ff_dlog(ctx, "blend_frames() interpolate scene score:%f\n", interpolate_scene_score);
     }
     // decide if the shot-change detection allows us to blend two frames
-    if (interpolate_scene_score < s->scene_score && s->srce[src2]) {
+    if (interpolate_scene_score < s->scene_score) {
         ThreadData td;
-        td.copy_src1 = s->srce[src1];
-        td.copy_src2 = s->srce[src2];
-        td.src2_factor = FFABS(interpolate);
+        td.copy_src1 = s->f0;
+        td.copy_src2 = s->f1;
+        td.src2_factor = interpolate;
         td.src1_factor = s->max - td.src2_factor;
 
         // get work-space for output frame
@@ -336,7 +307,7 @@  static int blend_frames(AVFilterContext *ctx, int interpolate,
         if (!s->work)
             return AVERROR(ENOMEM);
 
-        av_frame_copy_props(s->work, s->srce[s->crnt]);
+        av_frame_copy_props(s->work, s->f0);
 
         ff_dlog(ctx, "blend_frames() INTERPOLATE to create work frame\n");
         ctx->internal->execute(ctx, s->bitdepth == 8 ? filter_slice8 : filter_slice16, &td, NULL, FFMIN(outlink->h, ff_filter_get_nb_threads(ctx)));
@@ -345,198 +316,65 @@  static int blend_frames(AVFilterContext *ctx, int interpolate,
     return 0;
 }
 
-static int process_work_frame(AVFilterContext *ctx, int stop)
+static int process_work_frame(AVFilterContext *ctx)
 {
     FrameRateContext *s = ctx->priv;
-    int64_t work_next_pts;
+    int64_t work_pts;
     int interpolate;
-    int src1, src2;
-
-    ff_dlog(ctx, "process_work_frame()\n");
-
-    ff_dlog(ctx, "process_work_frame() pending_input_frames %d\n", s->pending_srce_frames);
-
-    if (s->srce[s->prev]) ff_dlog(ctx, "process_work_frame() srce prev pts:%"PRId64"\n", s->srce[s->prev]->pts);
-    if (s->srce[s->crnt]) ff_dlog(ctx, "process_work_frame() srce crnt pts:%"PRId64"\n", s->srce[s->crnt]->pts);
-    if (s->srce[s->next]) ff_dlog(ctx, "process_work_frame() srce next pts:%"PRId64"\n", s->srce[s->next]->pts);
+    int ret;
 
-    if (!s->srce[s->crnt]) {
-        // the filter cannot do anything
-        ff_dlog(ctx, "process_work_frame() no current frame cached: move on to next frame, do not output a frame\n");
-        next_source(ctx);
+    if (!s->f1)
         return 0;
-    }
-
-    work_next_pts = s->pts + s->average_dest_pts_delta;
-
-    ff_dlog(ctx, "process_work_frame() work crnt pts:%"PRId64"\n", s->pts);
-    ff_dlog(ctx, "process_work_frame() work next pts:%"PRId64"\n", work_next_pts);
-    if (s->srce[s->prev])
-        ff_dlog(ctx, "process_work_frame() srce prev pts:%"PRId64" at dest time base:%u/%u\n",
-            s->srce_pts_dest[s->prev], s->dest_time_base.num, s->dest_time_base.den);
-    if (s->srce[s->crnt])
-        ff_dlog(ctx, "process_work_frame() srce crnt pts:%"PRId64" at dest time base:%u/%u\n",
-            s->srce_pts_dest[s->crnt], s->dest_time_base.num, s->dest_time_base.den);
-    if (s->srce[s->next])
-        ff_dlog(ctx, "process_work_frame() srce next pts:%"PRId64" at dest time base:%u/%u\n",
-            s->srce_pts_dest[s->next], s->dest_time_base.num, s->dest_time_base.den);
-
-    av_assert0(s->srce[s->next]);
-
-    // should filter be skipping input frame (output frame rate is lower than input frame rate)
-    if (!s->flush && s->pts >= s->srce_pts_dest[s->next]) {
-        ff_dlog(ctx, "process_work_frame() work crnt pts >= srce next pts: SKIP FRAME, move on to next frame, do not output a frame\n");
-        next_source(ctx);
-        s->pending_srce_frames--;
+    if (!s->f0 && !s->flush)
         return 0;
-    }
 
-    // calculate interpolation
-    interpolate = av_rescale(s->pts - s->srce_pts_dest[s->crnt], s->max, s->average_srce_pts_dest_delta);
-    ff_dlog(ctx, "process_work_frame() interpolate:%d/%d\n", interpolate, s->max);
-    src1 = s->crnt;
-    if (interpolate > s->interp_end) {
-        ff_dlog(ctx, "process_work_frame() source is:NEXT\n");
-        src1 = s->next;
-    }
-    if (s->srce[s->prev] && interpolate < -s->interp_end) {
-        ff_dlog(ctx, "process_work_frame() source is:PREV\n");
-        src1 = s->prev;
-    }
+    work_pts = s->start_pts + av_rescale_q(s->n, av_inv_q(s->dest_frame_rate), s->dest_time_base);
 
-    // decide whether to blend two frames
-    if ((interpolate >= s->interp_start && interpolate <= s->interp_end) || (interpolate <= -s->interp_start && interpolate >= -s->interp_end)) {
-        if (interpolate > 0) {
-            ff_dlog(ctx, "process_work_frame() interpolate source is:NEXT\n");
-            src2 = s->next;
+    if (work_pts >= s->pts1 && !s->flush)
+        return 0;
+
+    if (!s->f0) {
+        s->work = av_frame_clone(s->f1);
+    } else {
+        if (work_pts >= s->pts1 + s->delta && s->flush)
+            return 0;
+
+        interpolate = av_rescale(work_pts - s->pts0, s->max, s->delta);
+        ff_dlog(ctx, "process_work_frame() interpolate:%d/%d\n", interpolate, s->max);
+        if (interpolate > s->interp_end) {
+            s->work = av_frame_clone(s->f1);
+        } else if (interpolate < s->interp_start) {
+            s->work = av_frame_clone(s->f0);
         } else {
-            ff_dlog(ctx, "process_work_frame() interpolate source is:PREV\n");
-            src2 = s->prev;
+            ret = blend_frames(ctx, interpolate);
+            if (ret < 0)
+                return ret;
+            if (ret == 0)
+                s->work = av_frame_clone(interpolate > (s->max >> 1) ? s->f1 : s->f0);
         }
-        if (blend_frames(ctx, interpolate, src1, src2))
-            goto copy_done;
-        else
-            ff_dlog(ctx, "process_work_frame() CUT - DON'T INTERPOLATE\n");
     }
 
-    ff_dlog(ctx, "process_work_frame() COPY to the work frame\n");
-    // copy the frame we decided is our base source
-    s->work = av_frame_clone(s->srce[src1]);
     if (!s->work)
         return AVERROR(ENOMEM);
 
-copy_done:
-    s->work->pts = s->pts;
-
-    // should filter be re-using input frame (output frame rate is higher than input frame rate)
-    if (!s->flush && (work_next_pts + s->average_dest_pts_delta) < (s->srce_pts_dest[s->crnt] + s->average_srce_pts_dest_delta)) {
-        ff_dlog(ctx, "process_work_frame() REPEAT FRAME\n");
-    } else {
-        ff_dlog(ctx, "process_work_frame() CONSUME FRAME, move to next frame\n");
-        s->pending_srce_frames--;
-        next_source(ctx);
-    }
-    ff_dlog(ctx, "process_work_frame() output a frame\n");
-    s->dest_frame_num++;
-    if (stop)
-        s->pending_end_frame = 0;
-    s->last_dest_frame_pts = s->work->pts;
+    s->work->pts = work_pts;
+    s->n++;
 
     return 1;
 }
 
-static void set_srce_frame_dest_pts(AVFilterContext *ctx)
-{
-    FrameRateContext *s = ctx->priv;
-
-    ff_dlog(ctx, "set_srce_frame_output_pts()\n");
-
-    // scale the input pts from the timebase difference between input and output
-    if (s->srce[s->prev])
-        s->srce_pts_dest[s->prev] = av_rescale_q(s->srce[s->prev]->pts, s->srce_time_base, s->dest_time_base);
-    if (s->srce[s->crnt])
-        s->srce_pts_dest[s->crnt] = av_rescale_q(s->srce[s->crnt]->pts, s->srce_time_base, s->dest_time_base);
-    if (s->srce[s->next])
-        s->srce_pts_dest[s->next] = av_rescale_q(s->srce[s->next]->pts, s->srce_time_base, s->dest_time_base);
-}
-
-static void set_work_frame_pts(AVFilterContext *ctx)
-{
-    FrameRateContext *s = ctx->priv;
-    int64_t pts, average_srce_pts_delta = 0;
-
-    ff_dlog(ctx, "set_work_frame_pts()\n");
-
-    av_assert0(s->srce[s->next]);
-    av_assert0(s->srce[s->crnt]);
-
-    ff_dlog(ctx, "set_work_frame_pts() srce crnt pts:%"PRId64"\n", s->srce[s->crnt]->pts);
-    ff_dlog(ctx, "set_work_frame_pts() srce next pts:%"PRId64"\n", s->srce[s->next]->pts);
-    if (s->srce[s->prev])
-        ff_dlog(ctx, "set_work_frame_pts() srce prev pts:%"PRId64"\n", s->srce[s->prev]->pts);
-
-    average_srce_pts_delta = s->average_srce_pts_dest_delta;
-    ff_dlog(ctx, "set_work_frame_pts() initial average srce pts:%"PRId64"\n", average_srce_pts_delta);
-
-    set_srce_frame_dest_pts(ctx);
-
-    // calculate the PTS delta
-    if ((pts = (s->srce_pts_dest[s->next] - s->srce_pts_dest[s->crnt]))) {
-        average_srce_pts_delta = average_srce_pts_delta?((average_srce_pts_delta+pts)>>1):pts;
-    } else if (s->srce[s->prev] && (pts = (s->srce_pts_dest[s->crnt] - s->srce_pts_dest[s->prev]))) {
-        average_srce_pts_delta = average_srce_pts_delta?((average_srce_pts_delta+pts)>>1):pts;
-    }
-
-    s->average_srce_pts_dest_delta = average_srce_pts_delta;
-    ff_dlog(ctx, "set_work_frame_pts() average srce pts:%"PRId64"\n", average_srce_pts_delta);
-    ff_dlog(ctx, "set_work_frame_pts() average srce pts:%"PRId64" at dest time base:%u/%u\n",
-            s->average_srce_pts_dest_delta, s->dest_time_base.num, s->dest_time_base.den);
-
-    if (ctx->inputs[0] && !s->average_dest_pts_delta) {
-        int64_t d = av_q2d(av_inv_q(av_mul_q(s->dest_time_base, s->dest_frame_rate)));
-        s->average_dest_pts_delta = d;
-        ff_dlog(ctx, "set_work_frame_pts() average dest pts delta:%"PRId64"\n", s->average_dest_pts_delta);
-    }
-
-    if (!s->dest_frame_num) {
-        s->pts = s->last_dest_frame_pts = s->srce_pts_dest[s->crnt];
-    } else {
-        s->pts = s->last_dest_frame_pts + s->average_dest_pts_delta;
-    }
-
-    ff_dlog(ctx, "set_work_frame_pts() calculated pts:%"PRId64" at dest time base:%u/%u\n",
-            s->pts, s->dest_time_base.num, s->dest_time_base.den);
-}
-
 static av_cold int init(AVFilterContext *ctx)
 {
     FrameRateContext *s = ctx->priv;
-    int i;
-
-    s->dest_frame_num = 0;
-
-    s->crnt = (N_SRCE)>>1;
-    s->last = N_SRCE - 1;
-
-    s->next = s->crnt - 1;
-    s->prev = s->crnt + 1;
-
-    for (i = 0; i < N_SRCE; i++)
-        s->srce_score[i] = -1.0;
-
+    s->start_pts = AV_NOPTS_VALUE;
     return 0;
 }
 
 static av_cold void uninit(AVFilterContext *ctx)
 {
     FrameRateContext *s = ctx->priv;
-    int i;
-
-    for (i = s->frst; i < s->last; i++) {
-        if (s->srce[i] && (s->srce[i] != s->srce[i + 1]))
-            av_frame_free(&s->srce[i]);
-    }
-    av_frame_free(&s->srce[s->last]);
+    av_frame_free(&s->f0);
+    av_frame_free(&s->f1);
 }
 
 static int query_formats(AVFilterContext *ctx)
@@ -593,28 +431,48 @@  static int filter_frame(AVFilterLink *inlink, AVFrame *inpicref)
     int ret;
     AVFilterContext *ctx = inlink->dst;
     FrameRateContext *s = ctx->priv;
-
-    // we have one new frame
-    s->pending_srce_frames++;
+    int64_t pts;
 
     if (inpicref->interlaced_frame)
         av_log(ctx, AV_LOG_WARNING, "Interlaced frame found - the output will not be correct.\n");
 
-    // store the pointer to the new frame
-    av_frame_free(&s->srce[s->frst]);
-    s->srce[s->frst] = inpicref;
+    if (inpicref->pts == AV_NOPTS_VALUE) {
+        av_log(ctx, AV_LOG_WARNING, "Ignoring frame without PTS.\n");
+        return 0;
+    }
 
-    if (!s->pending_end_frame && s->srce[s->crnt]) {
-        set_work_frame_pts(ctx);
-        s->pending_end_frame = 1;
-    } else {
-        set_srce_frame_dest_pts(ctx);
+    pts = av_rescale_q(inpicref->pts, s->srce_time_base, s->dest_time_base);
+    if (s->f1 && pts == s->pts1) {
+        av_log(ctx, AV_LOG_WARNING, "Ignoring frame with same PTS.\n");
+        return 0;
     }
 
-    ret = process_work_frame(ctx, 1);
-    if (ret < 0)
-        return ret;
-    return ret ? ff_filter_frame(ctx->outputs[0], s->work) : 0;
+    av_frame_free(&s->f0);
+    s->f0 = s->f1;
+    s->pts0 = s->pts1;
+    s->f1 = inpicref;
+    s->pts1 = pts;
+    s->delta = s->pts1 - s->pts0;
+    s->score = -1.0;
+
+    if (s->delta < 0) {
+        av_log(ctx, AV_LOG_WARNING, "PTS discontinuity.\n");
+        s->start_pts = s->pts1;
+        s->n = 0;
+        av_frame_free(&s->f0);
+    }
+
+    if (s->start_pts == AV_NOPTS_VALUE)
+        s->start_pts = s->pts1;
+
+    do {
+        ret = process_work_frame(ctx);
+        if (ret <= 0)
+            return ret;
+        ret = ff_filter_frame(ctx->outputs[0], s->work);
+    } while (ret >= 0);
+
+    return ret;
 }
 
 static int config_output(AVFilterLink *outlink)
@@ -666,50 +524,21 @@  static int request_frame(AVFilterLink *outlink)
 {
     AVFilterContext *ctx = outlink->src;
     FrameRateContext *s = ctx->priv;
-    int ret, i;
+    int ret;
 
     ff_dlog(ctx, "request_frame()\n");
 
-    // if there is no "next" frame AND we are not in flush then get one from our input filter
-    if (!s->srce[s->frst] && !s->flush)
-        goto request;
-
-    ff_dlog(ctx, "request_frame() REPEAT or FLUSH\n");
-
-    if (s->pending_srce_frames <= 0) {
-        ff_dlog(ctx, "request_frame() nothing else to do, return:EOF\n");
-        return AVERROR_EOF;
-    }
-
-    // otherwise, make brand-new frame and pass to our output filter
-    ff_dlog(ctx, "request_frame() FLUSH\n");
-
-    // back fill at end of file when source has no more frames
-    for (i = s->last; i > s->frst; i--) {
-        if (!s->srce[i - 1] && s->srce[i]) {
-            ff_dlog(ctx, "request_frame() copy:%d to:%d\n", i, i - 1);
-            s->srce[i - 1] = s->srce[i];
-        }
-    }
-
-    set_work_frame_pts(ctx);
-    ret = process_work_frame(ctx, 0);
-    if (ret < 0)
-        return ret;
-    if (ret)
-        return ff_filter_frame(ctx->outputs[0], s->work);
-
-request:
-    ff_dlog(ctx, "request_frame() call source's request_frame()\n");
     ret = ff_request_frame(ctx->inputs[0]);
-    if (ret < 0 && (ret != AVERROR_EOF)) {
-        ff_dlog(ctx, "request_frame() source's request_frame() returned error:%d\n", ret);
-        return ret;
-    } else if (ret == AVERROR_EOF) {
+    if (ret == AVERROR_EOF && s->f1 && !s->flush) {
         s->flush = 1;
+        ret = process_work_frame(ctx);
+        if (ret < 0)
+            return ret;
+        ret = ret ? ff_filter_frame(ctx->outputs[0], s->work) : AVERROR_EOF;
     }
+
     ff_dlog(ctx, "request_frame() source's request_frame() returned:%d\n", ret);
-    return 0;
+    return ret;
 }
 
 static const AVFilterPad framerate_inputs[] = {
diff --git a/tests/ref/fate/filter-framerate-12bit-up b/tests/ref/fate/filter-framerate-12bit-up
index 686fe8e82b..ef709a8fc8 100644
--- a/tests/ref/fate/filter-framerate-12bit-up
+++ b/tests/ref/fate/filter-framerate-12bit-up
@@ -62,3 +62,4 @@ 
 0,         56,         56,        1,   307200, 0x8cf55128
 0,         57,         57,        1,   307200, 0x4e740b42
 0,         58,         58,        1,   307200, 0x8e7e705c
+0,         59,         59,        1,   307200, 0xe73f29ef