From: heipa <[email protected]>

Fix several issues in the decode loop related to looping and timing:

- Properly handle infinite loop (loop=0) by tracking when the file has
  an infinite loop setting and only honoring it when ignore_loop is not set
- Fix timestamp accumulation across loop iterations by tracking a
  cumulative timestamp offset, which prevented incorrect frame timestamps
  and frame drops
- Fix decoder reset logic that caused an extra iteration through
  the loop; move loop count check before decoder reset
- Remove hardcoded 1000 FPS framerate setting; animated WebP timestamps
  are in milliseconds and frame rate is not constant
- Add AV_CODEC_CAP_DELAY capability flag for proper frame reordering
- Fix flush packet handling to not incorrectly return 0 when a flush
  occurs during decoding

Based on an initial patch by Peter Xia <[email protected]>

Signed-off-by: heipa <[email protected]>
---
 libavcodec/libwebpdec.c | 119 ++++++++++++++++++++++++++++++----------
 1 file changed, 89 insertions(+), 30 deletions(-)

diff --git a/libavcodec/libwebpdec.c b/libavcodec/libwebpdec.c
index 0b820aa..8f0b687 100644
--- a/libavcodec/libwebpdec.c
+++ b/libavcodec/libwebpdec.c
@@ -1,6 +1,7 @@
 /*
  * LibWebP decoder
  * Copyright (c) 2025 Peter Xia
+ * Copyright (c) 2026 heipa
  *
  * This file is part of FFmpeg.
  *
@@ -34,22 +35,40 @@
 #include <webp/demux.h>
 #include <webp/decode.h>
 
+/**
+ * Private context for the libwebp animated WebP decoder.
+ *
+ * Maintains decoder state, per-loop tracking, and timestamp management
+ * across multiple animation loops.
+ */
 typedef struct AnimatedWebPContext {
-    const AVClass *class;
-    WebPAnimDecoderOptions dec_options;
-    WebPAnimDecoder *dec;
-    AVBufferRef *file_content;
-    WebPData webp_data;
-    uint32_t loop_count;
-    uint32_t loop_sent;
-    uint32_t frame_count;
-    uint32_t frame_sent;
-    int prev_timestamp_ms;
-    int ignore_loop;
-    int infinite_loop;
-    int first_frame_pts;
+    const AVClass *class;                        /**< AVClass for option 
handling */
+    WebPAnimDecoderOptions dec_options;          /**< decoder configuration 
options */
+    WebPAnimDecoder *dec;                        /**< libwebp animation 
decoder instance */
+    AVBufferRef *file_content;                   /**< reference to the input 
packet buffer */
+    WebPData webp_data;                          /**< libwebp data wrapper for 
the input */
+    uint32_t loop_count;                         /**< effective loop count to 
play */
+    uint32_t loop_sent;                          /**< number of completed 
loops */
+    uint32_t frame_count;                        /**< total frames in the 
animation */
+    uint32_t frame_sent;                         /**< frames output so far in 
current loop */
+    int prev_timestamp_ms;                       /**< timestamp of the 
previous frame in ms */
+    int ignore_loop;                             /**< user option to ignore 
file loop setting */
+    int infinite_loop;                           /**< effective infinite loop 
mode */
+    int file_has_infinite_loop;                  /**< loop_count == 0 in the 
source file */
+    int first_frame_pts;                         /**< PTS of the first frame 
for duration calc */
+    int64_t timestamp_offset;                    /**< accumulated offset 
across loop restarts */
 } AnimatedWebPContext;
 
+/**
+ * Initialize the libwebp decoder.
+ *
+ * Configures WebPAnimDecoderOptions (RGBA color mode, threaded decoding)
+ * and sets the output pixel format to AV_PIX_FMT_RGBA with millisecond
+ * packet timebase.
+ *
+ * @param avctx  codec context to initialize
+ * @return 0 on success, negative AVERROR on failure
+ */
 static av_cold int libwebp_decode_init(AVCodecContext *avctx)
 {
     AnimatedWebPContext *s = avctx->priv_data;
@@ -66,7 +85,9 @@ static av_cold int libwebp_decode_init(AVCodecContext *avctx)
     s->frame_sent = 0;
     s->prev_timestamp_ms = 0;
     s->infinite_loop = 0;
+    s->file_has_infinite_loop = 0;
     s->first_frame_pts = -1;
+    s->timestamp_offset = 0;
 
     avctx->pix_fmt = AV_PIX_FMT_RGBA;
     avctx->pkt_timebase = av_make_q(1, 1000);
@@ -74,6 +95,23 @@ static av_cold int libwebp_decode_init(AVCodecContext *avctx)
     return 0;
 }
 
+/**
+ * Decode one frame from the animated WebP stream.
+ *
+ * On the first call, initializes the WebPAnimDecoder from the input
+ * packet and reads animation info. On subsequent calls, advances to
+ * the next frame. Automatically handles loop iteration: when the
+ * decoder runs out of frames, it resets and increments the loop
+ * counter unless the effective loop count has been reached.
+ * Frame RGBA data is copied into the supplied AVFrame.
+ *
+ * @param avctx      codec context
+ * @param p          output AVFrame to fill
+ * @param got_frame  set to 1 if a frame was produced, 0 otherwise
+ * @param avpkt      input packet (used on first call only)
+ * @return 0 on success or when no more frames are available,
+ *         negative AVERROR on failure
+ */
 static int libwebp_decode_frame(AVCodecContext *avctx, AVFrame *p,
                                 int *got_frame, AVPacket *avpkt)
 {
@@ -109,29 +147,34 @@ static int libwebp_decode_frame(AVCodecContext *avctx, 
AVFrame *p,
             return AVERROR_EXTERNAL;
         }
 
-        av_log(avctx, AV_LOG_DEBUG,
-               "WebP: %ux%u, %u frames, loop_count=%u\n",
-               anim_info.canvas_width, anim_info.canvas_height,
-               anim_info.frame_count, anim_info.loop_count);
-
         s->loop_count = anim_info.loop_count;
         s->frame_count = anim_info.frame_count;
-        s->infinite_loop = (anim_info.loop_count == 0) && !s->ignore_loop;
+        s->file_has_infinite_loop = (anim_info.loop_count == 0);
+        s->infinite_loop = s->file_has_infinite_loop && !s->ignore_loop;
+        if (s->file_has_infinite_loop && s->ignore_loop)
+            s->loop_count = 1;
+
+        av_log(avctx, AV_LOG_DEBUG,
+               "WebP: %ux%u, %u frames, loop_count=%u (effective=%u, 
infinite=%d)\n",
+               anim_info.canvas_width, anim_info.canvas_height,
+               anim_info.frame_count, anim_info.loop_count, s->loop_count, 
s->infinite_loop);
 
         avctx->width = anim_info.canvas_width;
         avctx->coded_width = anim_info.canvas_width;
         avctx->height = anim_info.canvas_height;
         avctx->coded_height = anim_info.canvas_height;
-
-        if (anim_info.frame_count > 0)
-            avctx->framerate = av_make_q(1000, 1);
     } else if (!avpkt || avpkt->size <= 0) {
         if (!WebPAnimDecoderHasMoreFrames(s->dec)) {
             if (!s->infinite_loop && s->loop_sent >= s->loop_count) {
                 *got_frame = 0;
                 return 0;
             }
+            s->timestamp_offset += s->prev_timestamp_ms;
             s->loop_sent++;
+            if (!s->infinite_loop && s->loop_sent >= s->loop_count) {
+                *got_frame = 0;
+                return 0;
+            }
             WebPAnimDecoderReset(s->dec);
             s->frame_sent = 0;
             s->prev_timestamp_ms = 0;
@@ -142,16 +185,16 @@ static int libwebp_decode_frame(AVCodecContext *avctx, 
AVFrame *p,
     }
 
     if (!WebPAnimDecoderHasMoreFrames(s->dec)) {
+        s->timestamp_offset += s->prev_timestamp_ms;
         s->loop_sent++;
-        WebPAnimDecoderReset(s->dec);
-        s->frame_sent = 0;
-        s->prev_timestamp_ms = 0;
-        s->first_frame_pts = -1;
-
         if (!s->infinite_loop && s->loop_sent >= s->loop_count) {
             *got_frame = 0;
             return 0;
         }
+        WebPAnimDecoderReset(s->dec);
+        s->frame_sent = 0;
+        s->prev_timestamp_ms = 0;
+        s->first_frame_pts = -1;
         av_log(avctx, AV_LOG_DEBUG, "Loop %u/%u\n", s->loop_sent + 1,
                s->infinite_loop ? 0 : s->loop_count);
     }
@@ -174,8 +217,8 @@ static int libwebp_decode_frame(AVCodecContext *avctx, 
AVFrame *p,
     p->width = avctx->width;
     p->height = avctx->height;
     p->format = AV_PIX_FMT_RGBA;
-    p->pts = timestamp_ms;
-    p->pkt_dts = timestamp_ms;
+    p->pts = timestamp_ms + s->timestamp_offset;
+    p->pkt_dts = timestamp_ms + s->timestamp_offset;
     p->pict_type = AV_PICTURE_TYPE_I;
     p->flags |= AV_FRAME_FLAG_KEY;
 
@@ -208,6 +251,12 @@ static int libwebp_decode_frame(AVCodecContext *avctx, 
AVFrame *p,
     return avpkt ? avpkt->size : 0;
 }
 
+/**
+ * Close the libwebp decoder and release all resources.
+ *
+ * @param avctx  codec context
+ * @return 0 on success
+ */
 static av_cold int libwebp_decode_close(AVCodecContext *avctx)
 {
     AnimatedWebPContext *s = avctx->priv_data;
@@ -221,6 +270,15 @@ static av_cold int libwebp_decode_close(AVCodecContext 
*avctx)
     return 0;
 }
 
+/**
+ * Flush the decoder, resetting all playback state.
+ *
+ * Resets the WebPAnimDecoder to the beginning of the animation
+ * and clears loop/frame counters and timestamp offsets so that
+ * subsequent decoding starts from the first frame.
+ *
+ * @param avctx  codec context
+ */
 static void libwebp_decode_flush(AVCodecContext *avctx)
 {
     AnimatedWebPContext *s = avctx->priv_data;
@@ -231,6 +289,7 @@ static void libwebp_decode_flush(AVCodecContext *avctx)
         s->frame_sent = 0;
         s->prev_timestamp_ms = 0;
         s->first_frame_pts = -1;
+        s->timestamp_offset = 0;
     }
 }
 
@@ -259,5 +318,5 @@ const FFCodec ff_libwebp_decoder = {
     FF_CODEC_DECODE_CB(libwebp_decode_frame),
     .close          = libwebp_decode_close,
     .flush          = libwebp_decode_flush,
-    .p.capabilities = AV_CODEC_CAP_DR1,
+    .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY,
 };
-- 
2.43.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to