This is an automated email from the git hooks/post-receive script.

Git pushed a commit to branch master
in repository ffmpeg.

The following commit(s) were added to refs/heads/master by this push:
     new 361174e5ea avfilter/vf_fps: preserve first output frame at EOF
361174e5ea is described below

commit 361174e5ea6bd65e34554afa1d098dda2a9bcbba
Author:     Skanda Vyas <[email protected]>
AuthorDate: Wed Sep 16 19:15:04 2026 -0700
Commit:     michaelni <[email protected]>
CommitDate: Sat Oct 3 15:53:40 2026 +0000

    avfilter/vf_fps: preserve first output frame at EOF
    
    When the input duration rounds to zero in the output timebase, the
    rounded EOF timestamp can equal next_pts before any frame has been
    output. In that case, do not discard a buffered frame whose timestamp
    matches next_pts.
    
    Keep the selected EOF timestamp rounding, but allow this first-frame
    exception to extend output by one output frame period. Preserve start_time
    trimming by checking the last input frame and EOF before rounding. A frame
    located at start_time remains eligible even when its duration is zero.
    
    Document the first-frame exception and add FATE coverage for a short
    single-frame input and trimming at start_time, including a frame spanning
    the start and a zero-duration frame at the start.
    
    Fixes ticket #7361.
    
    Signed-off-by: Skanda Vyas <[email protected]>
    
    Assisted-by: Fairy
---
 doc/filters.texi                                   |  6 ++++++
 libavfilter/vf_fps.c                               | 25 +++++++++++++++++++---
 tests/fate/filter-video.mak                        | 13 ++++++++++-
 .../{filter-palettegen-1 => filter-fps-single}     |  4 ++--
 ...palettegen-1 => filter-fps-single-before-start} |  3 +--
 ...palettegen-1 => filter-fps-single-end-at-start} |  3 +--
 6 files changed, 44 insertions(+), 10 deletions(-)

diff --git a/doc/filters.texi b/doc/filters.texi
index 81e9e280ed..406ead7198 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -14818,6 +14818,12 @@ Pass through last frame if input duration has not been 
reached yet.
 @end table
 The default is @code{round}.
 
+If timestamp rounding would otherwise discard every frame, one frame is
+retained when its rounded timestamp equals both the first output timestamp
+and the rounded end of input. This exception extends the output beyond the
+rounded end of input by one output frame period. Frames whose entire interval
+is trimmed by @option{start_time} remain discarded.
+
 @end table
 
 Alternatively, the options can be specified as a flat string:
diff --git a/libavfilter/vf_fps.c b/libavfilter/vf_fps.c
index 0257a8ef78..e81d7b1668 100644
--- a/libavfilter/vf_fps.c
+++ b/libavfilter/vf_fps.c
@@ -83,6 +83,8 @@ typedef struct FPSContext {
     /* Runtime state */
     int      status;        ///< buffered input status
     int64_t  status_pts;    ///< buffered input status timestamp
+    int64_t  last_in_pts;   ///< last frame timestamp in input timebase
+    int      last_frame_trimmed; ///< last frame ends at or before start_time
 
     AVFrame *frames[2];     ///< buffered frames
     int      frames_count;  ///< number of buffered frames
@@ -245,7 +247,7 @@ static int read_frame(AVFilterContext *ctx, FPSContext *s, 
AVFilterLink *inlink,
     /* Convert frame pts to output timebase.
      * The dance with offsets is required to match the rounding behaviour of 
the
      * previous version of the fps filter when using the start_time option. */
-    in_pts = frame->pts;
+    in_pts = s->last_in_pts = frame->pts;
     frame->pts = s->out_pts_off + av_rescale_q_rnd(in_pts - s->in_pts_off,
                                                    inlink->time_base, 
outlink->time_base,
                                                    s->rounding | 
AV_ROUND_PASS_MINMAX);
@@ -264,6 +266,8 @@ static int read_frame(AVFilterContext *ctx, FPSContext *s, 
AVFilterLink *inlink,
 static int write_frame(AVFilterContext *ctx, FPSContext *s, AVFilterLink 
*outlink, int *again)
 {
     AVFrame *frame;
+    int eof_before_or_at_next;
+    int first_output_due;
 
     av_assert1(s->frames_count == 2 || (s->status && s->frames_count == 1));
 
@@ -282,13 +286,20 @@ static int write_frame(AVFilterContext *ctx, FPSContext 
*s, AVFilterLink *outlin
         }
     }
 
+    eof_before_or_at_next = s->status && s->status_pts <= s->next_pts;
+    first_output_due      = !s->frames_out && !s->cur_frame_out &&
+                            !s->last_frame_trimmed &&
+                            s->frames[0]->pts == s->next_pts &&
+                            s->status_pts == s->next_pts;
+
     /* There are two conditions where we want to drop a frame:
      * - If we have two buffered frames and the second frame is acceptable
      *   as the next output frame, then drop the first buffered frame.
      * - If we have status (EOF) set, drop frames when we hit the
-     *   status timestamp. */
+     *   status timestamp, unless no frame has been output yet and the
+     *   buffered frame is due at the next output timestamp. */
     if ((s->frames_count == 2 && s->frames[1]->pts <= s->next_pts) ||
-        (s->status            && s->status_pts     <= s->next_pts)) {
+        (eof_before_or_at_next && !first_output_due)) {
 
         frame = shift_frame(ctx, s);
         av_frame_free(&frame);
@@ -317,6 +328,14 @@ static int write_frame(AVFilterContext *ctx, FPSContext 
*s, AVFilterLink *outlin
 static void update_eof_pts(AVFilterContext *ctx, FPSContext *s, AVFilterLink 
*inlink, AVFilterLink *outlink, int64_t status_pts)
 {
     int eof_rounding = (s->eof_action == EOF_ACTION_PASS) ? AV_ROUND_UP : 
s->rounding;
+
+    /* Check trimming before rounding can collapse a frame before start_time
+     * and the end of its interval onto the first output timestamp. A frame
+     * starting at start_time is still eligible even with zero duration. */
+    s->last_frame_trimmed = s->start_time != DBL_MAX &&
+                            s->start_time != AV_NOPTS_VALUE &&
+                            s->last_in_pts < s->in_pts_off &&
+                            status_pts <= s->in_pts_off;
     s->status_pts = av_rescale_q_rnd(status_pts, inlink->time_base, 
outlink->time_base,
                                      eof_rounding | AV_ROUND_PASS_MINMAX);
 
diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak
index b746f6a0e8..564c970e60 100644
--- a/tests/fate/filter-video.mak
+++ b/tests/fate/filter-video.mak
@@ -501,7 +501,7 @@ fate-filter-mpdecimate-mode-1: CMD = framecrc -lavfi 
testsrc2=r=4:d=5,fps=5,mpde
 FATE_FILTER-$(call FILTERFRAMECRC, TESTSRC2 FPS MPDECIMATE) += 
fate-filter-mpdecimate-mode-1-min-3
 fate-filter-mpdecimate-mode-1-min-3: CMD = framecrc -lavfi 
testsrc2=r=2:d=7,fps=7,mpdecimate=mode=1:min=3 -pix_fmt yuv420p
 
-FATE_FILTER-$(call FILTERFRAMECRC, FPS TESTSRC2) += $(addprefix 
fate-filter-fps-, up up-round-down up-round-up down down-round-down 
down-round-up down-eof-pass start-drop start-fill)
+FATE_FILTER-$(call FILTERFRAMECRC, FPS TESTSRC2) += $(addprefix 
fate-filter-fps-, up up-round-down up-round-up down down-round-down 
down-round-up down-eof-pass start-drop start-fill single)
 fate-filter-fps-up: CMD = framecrc -lavfi testsrc2=r=3:d=2,fps=7
 fate-filter-fps-up-round-down: CMD = framecrc -lavfi 
testsrc2=r=3:d=2,fps=7:round=down
 fate-filter-fps-up-round-up: CMD = framecrc -lavfi 
testsrc2=r=3:d=2,fps=7:round=up
@@ -511,6 +511,17 @@ fate-filter-fps-down-round-up: CMD = framecrc -lavfi 
testsrc2=r=7:d=3.5,fps=3:ro
 fate-filter-fps-down-eof-pass: CMD = framecrc -lavfi 
testsrc2=r=7:d=3.5,fps=3:eof_action=pass
 fate-filter-fps-start-drop: CMD = framecrc -lavfi 
testsrc2=r=7:d=3.5,fps=3:start_time=1.5
 fate-filter-fps-start-fill: CMD = framecrc -lavfi 
testsrc2=r=7:d=1.5,setpts=PTS+14,fps=3:start_time=1.5
+fate-filter-fps-single: CMD = framecrc -lavfi 
testsrc2=s=16x16:r=25:d=0.04,fps=1
+
+FATE_FILTER-$(call FILTERFRAMECRC, FPS TESTSRC2 SETPTS) += 
fate-filter-fps-single-before-start fate-filter-fps-single-end-at-start 
fate-filter-fps-single-zero-duration
+fate-filter-fps-single-before-start: CMD = framecrc -lavfi 
testsrc2=s=16x16:r=25:d=0.04,setpts=PTS-2,fps=1:start_time=0
+fate-filter-fps-single-end-at-start: CMD = framecrc -lavfi 
testsrc2=s=16x16:r=25:d=0.04,setpts=PTS-1,fps=1:start_time=0
+fate-filter-fps-single-zero-duration: CMD = framecrc -lavfi 
testsrc2=s=16x16:r=25:d=0.04,setpts=0:strip_fps=1,fps=1:start_time=0
+fate-filter-fps-single-zero-duration: REF = 
$(SRC_PATH)/tests/ref/fate/filter-fps-single
+
+FATE_FILTER-$(call FILTERFRAMECRC, FPS TESTSRC2 SETPTS SETTB) += 
fate-filter-fps-single-cross-start
+fate-filter-fps-single-cross-start: CMD = framecrc -lavfi 
testsrc2=s=16x16:r=25/2:d=0.08,settb=1/25,setpts=PTS-1,fps=1:start_time=0
+fate-filter-fps-single-cross-start: REF = 
$(SRC_PATH)/tests/ref/fate/filter-fps-single
 
 FATE_LIBAVFILTER-yes += fate-filter-drawutils
 fate-filter-drawutils: libavfilter/tests/drawutils$(EXESUF)
diff --git a/tests/ref/fate/filter-palettegen-1 
b/tests/ref/fate/filter-fps-single
similarity index 50%
copy from tests/ref/fate/filter-palettegen-1
copy to tests/ref/fate/filter-fps-single
index 1e5c9ee002..d315556861 100644
--- a/tests/ref/fate/filter-palettegen-1
+++ b/tests/ref/fate/filter-fps-single
@@ -1,6 +1,6 @@
-#tb 0: 1001/24000
+#tb 0: 1/1
 #media_type 0: video
 #codec_id 0: rawvideo
 #dimensions 0: 16x16
 #sar 0: 1/1
-0,          0,          0,        1,     1024, 0xa285dd77
+0,          0,          0,        1,      384, 0x92e591ca
diff --git a/tests/ref/fate/filter-palettegen-1 
b/tests/ref/fate/filter-fps-single-before-start
similarity index 50%
copy from tests/ref/fate/filter-palettegen-1
copy to tests/ref/fate/filter-fps-single-before-start
index 1e5c9ee002..2d09a982e2 100644
--- a/tests/ref/fate/filter-palettegen-1
+++ b/tests/ref/fate/filter-fps-single-before-start
@@ -1,6 +1,5 @@
-#tb 0: 1001/24000
+#tb 0: 1/1
 #media_type 0: video
 #codec_id 0: rawvideo
 #dimensions 0: 16x16
 #sar 0: 1/1
-0,          0,          0,        1,     1024, 0xa285dd77
diff --git a/tests/ref/fate/filter-palettegen-1 
b/tests/ref/fate/filter-fps-single-end-at-start
similarity index 50%
copy from tests/ref/fate/filter-palettegen-1
copy to tests/ref/fate/filter-fps-single-end-at-start
index 1e5c9ee002..2d09a982e2 100644
--- a/tests/ref/fate/filter-palettegen-1
+++ b/tests/ref/fate/filter-fps-single-end-at-start
@@ -1,6 +1,5 @@
-#tb 0: 1001/24000
+#tb 0: 1/1
 #media_type 0: video
 #codec_id 0: rawvideo
 #dimensions 0: 16x16
 #sar 0: 1/1
-0,          0,          0,        1,     1024, 0xa285dd77

-- 
To stop receiving notification emails like this one, please contact
[email protected].
_______________________________________________
ffmpeg-cvslog mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to