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]
