This is an automated email from the git hooks/post-receive script. Git pushed a commit to branch master in repository ffmpeg.
commit a3d8ba6613dace8ad725808286bf82934e21512b Author: Ramiro Polla <[email protected]> AuthorDate: Wed Apr 22 18:09:26 2026 +0200 Commit: Ramiro Polla <[email protected]> CommitDate: Tue May 19 11:36:10 2026 +0200 avcodec/webp: add support for Animated WebP decoding Fixes: 4907 Adds Animated WebP feature according to spec: https://developers.google.com/speed/webp/docs/riff_container#animation Original work by Josef Zlomek <[email protected]> and Thilo Borgmann <[email protected]> Signed-off-by: Ramiro Polla <[email protected]> --- Changelog | 1 + configure | 1 + libavcodec/Makefile | 1 + libavcodec/allcodecs.c | 1 + libavcodec/webp.c | 631 ++++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 633 insertions(+), 2 deletions(-) diff --git a/Changelog b/Changelog index f4171592db..9834f78261 100644 --- a/Changelog +++ b/Changelog @@ -12,6 +12,7 @@ version <next>: - SMPTE 2094-50 metadata support and passthrough - ProRes RAW VideoToolbox hwaccel - APV Vulkan hwaccel +- Animated WebP decoder version 8.1: diff --git a/configure b/configure index 514f1723d6..96bbeb372b 100755 --- a/configure +++ b/configure @@ -3366,6 +3366,7 @@ vp9_decoder_select="videodsp vp9_parser cbs_vp9 vp9_superframe_split_bsf" vvc_decoder_select="cabac cbs_h266 golomb videodsp vvc_sei" wcmv_decoder_select="inflate_wrapper" webp_decoder_select="vp8_decoder" +webp_anim_decoder_select="vp8_decoder" wmalossless_decoder_select="llauddsp" wmapro_decoder_select="sinewin wma_freqs" wmav1_decoder_select="sinewin wma_freqs" diff --git a/libavcodec/Makefile b/libavcodec/Makefile index 4935cfc3b3..2312e7b040 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -846,6 +846,7 @@ OBJS-$(CONFIG_WBMP_DECODER) += wbmpdec.o OBJS-$(CONFIG_WBMP_ENCODER) += wbmpenc.o OBJS-$(CONFIG_WCMV_DECODER) += wcmv.o OBJS-$(CONFIG_WEBP_DECODER) += webp.o +OBJS-$(CONFIG_WEBP_ANIM_DECODER) += webp.o OBJS-$(CONFIG_WEBVTT_DECODER) += webvttdec.o ass.o OBJS-$(CONFIG_WEBVTT_ENCODER) += webvttenc.o ass_split.o OBJS-$(CONFIG_WMALOSSLESS_DECODER) += wmalosslessdec.o wma_common.o diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index 5e798c1430..e29a3a4020 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -396,6 +396,7 @@ extern const FFCodec ff_vqc_decoder; extern const FFCodec ff_vvc_decoder; extern const FFCodec ff_wbmp_decoder; extern const FFCodec ff_wbmp_encoder; +extern const FFCodec ff_webp_anim_decoder; extern const FFCodec ff_webp_decoder; extern const FFCodec ff_wcmv_decoder; extern const FFCodec ff_wrapped_avframe_encoder; diff --git a/libavcodec/webp.c b/libavcodec/webp.c index b34ed5e44d..b5af60c673 100644 --- a/libavcodec/webp.c +++ b/libavcodec/webp.c @@ -2,6 +2,7 @@ * WebP (.webp) image decoder * Copyright (c) 2013 Aneesh Dogra <[email protected]> * Copyright (c) 2013 Justin Ruggles <[email protected]> + * Copyright (c) 2020 Pexeso Inc. * * This file is part of FFmpeg. * @@ -38,10 +39,13 @@ * @author Thilo Borgmann <thilo.borgmann _at_ mail.de> * XMP metadata * - * Unimplemented: - * - Animation + * @author Josef Zlomek, Pexeso Inc. <[email protected]> + * Animation */ +#include "config_components.h" + +#include "libavutil/colorspace.h" #include "libavutil/imgutils.h" #include "libavutil/mem.h" @@ -1593,3 +1597,626 @@ const FFCodec ff_webp_decoder = { .caps_internal = FF_CODEC_CAP_ICC_PROFILES | FF_CODEC_CAP_USES_PROGRESSFRAMES, }; + +#if CONFIG_WEBP_ANIM_DECODER + +#define ANMF_FLAG_DISPOSE (1 << 0) +#define ANMF_FLAG_NO_BLEND (1 << 1) + +typedef struct AnimatedWebPContext { + WebPContext w; + + AVFrame *canvas; /* AVFrame for canvas */ + AVFrame *subframe; /* AVFrame for subframe */ + int canvas_width; /* canvas width */ + int canvas_height; /* canvas height */ + int anmf_flags; /* frame flags from ANMF chunk */ + int pos_x; /* frame position X */ + int pos_y; /* frame position Y */ + int duration; /* frame duration */ + int prev_anmf_flags; /* previous frame flags from ANMF chunk */ + int prev_width; /* previous frame width */ + int prev_height; /* previous frame height */ + int prev_pos_x; /* previous frame position X */ + int prev_pos_y; /* previous frame position Y */ + uint8_t background_argb[4]; /* background color in ARGB format */ + uint8_t background_yuva[4]; /* background color in YUVA format */ +} AnimatedWebPContext; + +/* + * Blend src (foreground) into dst (background), in ARGB format. + * pos_x, pos_y is the position in dst. + */ +static void blend_alpha_argb(AVFrame *dst, AVFrame *src, int pos_x, int pos_y) +{ + for (int y = 0; y < src->height; y++) { + const uint8_t *src_argb = src->data[0] + y * src->linesize[0]; + uint8_t *dst_argb = dst->data[0] + (pos_y + y) * dst->linesize[0] + pos_x * sizeof(uint32_t); + for (int x = 0; x < src->width; x++) { + int src_alpha = src_argb[0]; + int dst_alpha = dst_argb[0]; + + if (src_alpha == 255) { + memcpy(dst_argb, src_argb, sizeof(uint32_t)); + } else if (src_alpha == 0) { + // no-op + } else { + int tmp_alpha = (dst_alpha * (256 - src_alpha)) >> 8; + int blend_alpha = src_alpha + tmp_alpha; + int scale = (1UL << 24) / blend_alpha; + + dst_argb[0] = blend_alpha; + dst_argb[1] = (((uint32_t) (src_argb[1] * src_alpha + dst_argb[1] * tmp_alpha)) * scale) >> 24; + dst_argb[2] = (((uint32_t) (src_argb[2] * src_alpha + dst_argb[2] * tmp_alpha)) * scale) >> 24; + dst_argb[3] = (((uint32_t) (src_argb[3] * src_alpha + dst_argb[3] * tmp_alpha)) * scale) >> 24; + } + src_argb += sizeof(uint32_t); + dst_argb += sizeof(uint32_t); + } + } +} + +/* + * Blend src (foreground) into dst (background), in YUVA format. + * pos_x, pos_y is the position in dst. + */ +static void blend_alpha_yuva(AVFrame *dst, AVFrame *src, int pos_x, int pos_y) +{ + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(src->format); + + int plane_y = desc->comp[0].plane; + int plane_u = desc->comp[1].plane; + int plane_v = desc->comp[2].plane; + int plane_a = desc->comp[3].plane; + + // blend U & V planes first, because the later step may modify alpha plane + for (int y = 0; y < AV_CEIL_RSHIFT(src->height, 1); y++) { + int tile_h = FFMIN(src->height - y * 2, 2); + const uint8_t *src_u = src->data[plane_u] + y * src->linesize[plane_u]; + const uint8_t *src_v = src->data[plane_v] + y * src->linesize[plane_v]; + uint8_t *dst_u = dst->data[plane_u] + ((pos_y >> 1) + y) * dst->linesize[plane_u] + (pos_x >> 1); + uint8_t *dst_v = dst->data[plane_v] + ((pos_y >> 1) + y) * dst->linesize[plane_v] + (pos_x >> 1); + for (int x = 0; x < AV_CEIL_RSHIFT(src->width, 1); x++) { + int tile_w = FFMIN(src->width - x * 2, 2); + // calculate the average alpha of the tile + int src_alpha = 0; + int dst_alpha = 0; + for (int yy = 0; yy < tile_h; yy++) { + for (int xx = 0; xx < tile_w; xx++) { + src_alpha += src->data[plane_a][(y * 2 + yy) * src->linesize[plane_a] + + (x * 2 + xx)]; + dst_alpha += dst->data[plane_a][(((pos_y >> 1) + y) * 2 + yy) * dst->linesize[plane_a] + + (((pos_x >> 1) + x) * 2 + xx)]; + } + } + int shift = (tile_h == 2) + (tile_w == 2); + src_alpha = AV_CEIL_RSHIFT(src_alpha, shift); + dst_alpha = AV_CEIL_RSHIFT(dst_alpha, shift); + + if (src_alpha == 255) { + *dst_u = *src_u; + *dst_v = *src_v; + } else if (src_alpha == 0) { + // no-op + } else { + int tmp_alpha = (dst_alpha * (256 - src_alpha)) >> 8; + int blend_alpha = src_alpha + tmp_alpha; + int scale = (1UL << 24) / blend_alpha; + *dst_u = (((uint32_t) (*src_u * src_alpha + *dst_u * tmp_alpha)) * scale) >> 24; + *dst_v = (((uint32_t) (*src_v * src_alpha + *dst_v * tmp_alpha)) * scale) >> 24; + } + src_u += 1; + src_v += 1; + dst_u += 1; + dst_v += 1; + } + } + + // blend Y & A planes + for (int y = 0; y < src->height; y++) { + const uint8_t *src_y = src->data[plane_y] + y * src->linesize[plane_y]; + const uint8_t *src_a = src->data[plane_a] + y * src->linesize[plane_a]; + uint8_t *dst_y = dst->data[plane_y] + (pos_y + y) * dst->linesize[plane_y] + pos_x; + uint8_t *dst_a = dst->data[plane_a] + (pos_y + y) * dst->linesize[plane_a] + pos_x; + for (int x = 0; x < src->width; x++) { + int src_alpha = *src_a; + int dst_alpha = *dst_a; + + if (src_alpha == 255) { + *dst_y = *src_y; + *dst_a = 255; + } else if (src_alpha == 0) { + // no-op + } else { + int tmp_alpha = (dst_alpha * (256 - src_alpha)) >> 8; + int blend_alpha = src_alpha + tmp_alpha; + int scale = (1UL << 24) / blend_alpha; + *dst_y = (((uint32_t) (*src_y * src_alpha + *dst_y * tmp_alpha)) * scale) >> 24; + *dst_a = blend_alpha; + } + src_y += 1; + src_a += 1; + dst_y += 1; + dst_a += 1; + } + } +} + +static av_always_inline void webp_yuva2argb(uint8_t *out, int Y, int U, int V, int A) +{ + // variables used in macros + const uint8_t *cm = ff_crop_tab + MAX_NEG_CROP; + uint8_t r, g, b; + int y, cb, cr; + int r_add, g_add, b_add; + + YUV_TO_RGB1_CCIR(U, V); + YUV_TO_RGB2_CCIR(r, g, b, Y); + + out[0] = av_clip_uint8(A); + out[1] = av_clip_uint8(r); + out[2] = av_clip_uint8(g); + out[3] = av_clip_uint8(b); +} + +static void copy_yuva2argb(AVFrame *dst, AVFrame *src, int pos_x, int pos_y) +{ + const AVPixFmtDescriptor *src_desc = av_pix_fmt_desc_get(src->format); + + int alpha = src_desc->nb_components > 3; + int plane_y = src_desc->comp[0].plane; + int plane_u = src_desc->comp[1].plane; + int plane_v = src_desc->comp[2].plane; + int plane_a = src_desc->comp[3].plane; + + for (int y = 0; y < src->height; y++) { + const uint8_t *src_y = src->data[plane_y] + y * src->linesize[plane_y]; + const uint8_t *src_u = src->data[plane_u] + (y >> 1) * src->linesize[plane_u]; + const uint8_t *src_v = src->data[plane_v] + (y >> 1) * src->linesize[plane_v]; + const uint8_t *src_a = NULL; + uint8_t *dst_argb = dst->data[0] + (pos_y + y) * dst->linesize[0] + pos_x * 4; + if (alpha) + src_a = src->data[plane_a] + y * src->linesize[plane_a]; + + for (int x = 0; x < src->width; x++) { + webp_yuva2argb(dst_argb, *src_y, *src_u, *src_v, (alpha ? *src_a : 255)); + src_y += 1; + src_u += x & 1; + src_v += x & 1; + if (alpha) + src_a += 1; + dst_argb += sizeof(uint32_t); + } + } +} + +static void blend_yuva2argb(AVFrame *dst, AVFrame *src, int pos_x, int pos_y) +{ + const AVPixFmtDescriptor *src_desc = av_pix_fmt_desc_get(src->format); + + int plane_y = src_desc->comp[0].plane; + int plane_u = src_desc->comp[1].plane; + int plane_v = src_desc->comp[2].plane; + int plane_a = src_desc->comp[3].plane; + + for (int y = 0; y < src->height; y++) { + const uint8_t *src_y = src->data[plane_y] + y * src->linesize[plane_y]; + const uint8_t *src_u = src->data[plane_u] + (y >> 1) * src->linesize[plane_u]; + const uint8_t *src_v = src->data[plane_v] + (y >> 1) * src->linesize[plane_v]; + const uint8_t *src_a = src->data[plane_a] + y * src->linesize[plane_a]; + uint8_t *dst_argb = dst->data[0] + (pos_y + y) * dst->linesize[0] + pos_x * 4; + + for (int x = 0; x < src->width; x++) { + int src_alpha = *src_a; + int dst_alpha = dst_argb[0]; + + if (src_alpha == 255) { + webp_yuva2argb(dst_argb, *src_y, *src_u, *src_v, src_alpha); + } else if (src_alpha == 0) { + // no-op + } else { + uint8_t tmp[4]; + int tmp_alpha = (dst_alpha * (256 - src_alpha)) >> 8; + int blend_alpha = src_alpha + tmp_alpha; + int scale = (1UL << 24) / blend_alpha; + + webp_yuva2argb(tmp, *src_y, *src_u, *src_v, src_alpha); + + dst_argb[0] = blend_alpha; + dst_argb[1] = (((uint32_t) (tmp[1] * src_alpha + dst_argb[1] * tmp_alpha)) * scale) >> 24; + dst_argb[2] = (((uint32_t) (tmp[2] * src_alpha + dst_argb[2] * tmp_alpha)) * scale) >> 24; + dst_argb[3] = (((uint32_t) (tmp[3] * src_alpha + dst_argb[3] * tmp_alpha)) * scale) >> 24; + } + + src_y += 1; + src_u += x & 1; + src_v += x & 1; + src_a += 1; + dst_argb += sizeof(uint32_t); + } + } +} + +static int blend_subframe_into_canvas(AnimatedWebPContext *s) +{ + AVFrame *canvas = s->canvas; + AVFrame *frame = s->subframe; + + if ((s->anmf_flags & ANMF_FLAG_NO_BLEND) + || frame->format == AV_PIX_FMT_YUV420P) { + // do not blend, overwrite + + if (canvas->format == AV_PIX_FMT_ARGB) { + if (canvas->format == frame->format) { + const uint8_t *src = frame->data[0]; + uint8_t *dst = canvas->data[0] + + s->pos_y * canvas->linesize[0] + + s->pos_x * sizeof(uint32_t); + for (int y = 0; y < s->w.height; y++) { + memcpy(dst, src, s->w.width * sizeof(uint32_t)); + src += frame->linesize[0]; + dst += canvas->linesize[0]; + } + } else { + copy_yuva2argb(canvas, frame, s->pos_x, s->pos_y); + } + } else /* if (canvas->format == AV_PIX_FMT_YUVA420P) */ { + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format); + + for (int comp = 0; comp < desc->nb_components; comp++) { + int plane = desc->comp[comp].plane; + int shift = (comp == 1 || comp == 2) ? 1 : 0; + const uint8_t *src = frame->data[plane]; + uint8_t *dst = canvas->data[plane] + + (s->pos_y >> shift) * canvas->linesize[plane] + + (s->pos_x >> shift); + for (int y = 0; y < AV_CEIL_RSHIFT(s->w.height, shift); y++) { + memcpy(dst, src, AV_CEIL_RSHIFT(s->w.width, shift)); + src += frame->linesize[plane]; + dst += canvas->linesize[plane]; + } + } + + if (canvas->format == AV_PIX_FMT_YUVA420P && desc->nb_components < 4) { + // frame does not have alpha, set alpha to 255 + const AVPixFmtDescriptor *canvas_desc = av_pix_fmt_desc_get(canvas->format); + int plane = canvas_desc->comp[3].plane; + uint8_t *dst = canvas->data[plane] + s->pos_y * canvas->linesize[plane] + s->pos_x; + for (int y = 0; y < s->w.height; y++) { + memset(dst, 255, s->w.width); + dst += canvas->linesize[plane]; + } + } + } + } else { + // alpha blending + + if (canvas->format == AV_PIX_FMT_ARGB) { + if (canvas->format == frame->format) { + blend_alpha_argb(canvas, frame, s->pos_x, s->pos_y); + } else { + blend_yuva2argb(canvas, frame, s->pos_x, s->pos_y); + } + } else /* if (canvas->format == AV_PIX_FMT_YUVA420P) */ { + blend_alpha_yuva(canvas, frame, s->pos_x, s->pos_y); + } + } + + return 0; +} + +/** + * Fill a rectangle on the canvas with the background color (transparent black + * by default, or the color from the ANIM chunk if provided by the demuxer). + */ +static void fill_canvas_rect(AnimatedWebPContext *s, int pos_x, int pos_y, int width, int height) +{ + AVFrame *canvas = s->canvas; + + if (canvas->format == AV_PIX_FMT_ARGB) { + uint32_t bg_color = AV_RN32(s->background_argb); + int is_repeatable = (bg_color == ((bg_color & 0xff) * 0x01010101)); + for (int y = 0; y < height; y++) { + uint32_t *dst = (uint32_t *) (canvas->data[0] + (pos_y + y) * canvas->linesize[0]) + pos_x; + if (is_repeatable) { + memset(dst, bg_color, width * sizeof(uint32_t)); + } else { + for (int x = 0; x < width; x++) + dst[x] = bg_color; + } + } + } else /* if (canvas->format == AV_PIX_FMT_YUVA420P) */ { + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(canvas->format); + for (int comp = 0; comp < desc->nb_components; comp++) { + int shift = (comp == 1 || comp == 2) ? 1 : 0; + int plane = desc->comp[comp].plane; + uint8_t *dst = canvas->data[plane] + (pos_y >> shift) * canvas->linesize[plane] + (pos_x >> shift); + for (int y = 0; y < AV_CEIL_RSHIFT(height, shift); y++) { + memset(dst, s->background_yuva[plane], AV_CEIL_RSHIFT(width, shift)); + dst += canvas->linesize[plane]; + } + } + } +} + +static int allocate_canvas(AnimatedWebPContext *s, int format) +{ + s->w.avctx->pix_fmt = format; + int ret = ff_set_dimensions(s->w.avctx, s->canvas_width, s->canvas_height); + if (ret < 0) + return ret; + return ff_reget_buffer(s->w.avctx, s->canvas, 0); +} + +static int prepare_canvas(AnimatedWebPContext *s, int key_frame, int format) +{ + int ret; + + /** + * Clear the canvas on keyframes and frames that overwrite the entire + * canvas. + */ + if (key_frame || + ((s->anmf_flags & ANMF_FLAG_NO_BLEND) && + (s->pos_x == 0) && (s->pos_x + s->w.width == s->canvas_width) && + (s->pos_y == 0) && (s->pos_y + s->w.height == s->canvas_height))) + av_frame_unref(s->canvas); + + if (!s->canvas->buf[0]) { + /* Allocate new canvas frame */ + ret = allocate_canvas(s, format); + if (ret < 0) + return ret; + /* ... and initialize it. */ + fill_canvas_rect(s, 0, 0, s->canvas->width, s->canvas->height); + } else { + if (format == AV_PIX_FMT_ARGB && s->canvas->format == AV_PIX_FMT_YUVA420P) { + /** + * If we have a lossless frame following a lossy frame, we upgrade + * the canvas to ARGB, but we don't convert the canvas back to YUVA + * if there is a lossy frame following a lossless frame. + */ + AVFrame *yuva_canvas = av_frame_clone(s->canvas); + if (!yuva_canvas) + return AVERROR(ENOMEM); + av_frame_unref(s->canvas); + ret = allocate_canvas(s, AV_PIX_FMT_ARGB); + if (ret < 0) { + av_frame_free(&yuva_canvas); + return ret; + } + copy_yuva2argb(s->canvas, yuva_canvas, 0, 0); + av_frame_free(&yuva_canvas); + } else { + /** + * The decode frame function returns a reference to the canvas, + * therefore we have to ensure it is writable before using it + * for a new frame. + */ + ret = av_frame_make_writable(s->canvas); + if (ret < 0) + return ret; + } + /* Dispose of previous frame if needed. */ + if (s->prev_anmf_flags & ANMF_FLAG_DISPOSE) + fill_canvas_rect(s, s->prev_pos_x, s->prev_pos_y, s->prev_width, s->prev_height); + } + + return 0; +} + +static int webp_anim_decode_frame(AVCodecContext *avctx, AVFrame *p, + int *got_frame, AVPacket *avpkt) +{ + AnimatedWebPContext *s = avctx->priv_data; + int key_frame = (avpkt->flags & AV_PKT_FLAG_KEY); + int ret; + + GetByteContext gb; + bytestream2_init(&gb, avpkt->data, avpkt->size); + + /* Parse ANMF header. */ + s->pos_x = bytestream2_get_le24(&gb) * 2; + s->pos_y = bytestream2_get_le24(&gb) * 2; + s->w.width = bytestream2_get_le24(&gb) + 1; + s->w.height = bytestream2_get_le24(&gb) + 1; + s->duration = bytestream2_get_le24(&gb); + s->anmf_flags = bytestream2_get_byte(&gb); + + av_log(avctx, AV_LOG_DEBUG, + "ANMF frame pos: %dx%d size: %dx%d duration: %d\n", + s->pos_x, s->pos_y, s->w.width, s->w.height, s->duration); + + /* Reset alpha field from previous frame. */ + s->w.has_alpha = 0; + + /* Parse ANMF subchunks. */ + while (bytestream2_get_bytes_left(&gb) > 8) { + uint32_t chunk_type = bytestream2_get_le32(&gb); + uint32_t chunk_size = bytestream2_get_le32(&gb); + + if (chunk_size == UINT32_MAX) { + ret = AVERROR_INVALIDDATA; + goto end; + } + chunk_size += chunk_size & 1; + + if (bytestream2_get_bytes_left(&gb) < chunk_size) { + /* we seem to be running out of data, but it could also be that the + * bitstream has trailing junk leading to bogus chunk_size. */ + break; + } + + switch (chunk_type) { + case MKTAG('A', 'L', 'P', 'H'): { + if (chunk_size == 0) { + av_log(avctx, AV_LOG_ERROR, "invalid ALPHA chunk size\n"); + ret = AVERROR_INVALIDDATA; + goto end; + } + int alpha_header = bytestream2_get_byte(&gb); + s->w.alpha_data = avpkt->data + bytestream2_tell(&gb); + s->w.alpha_data_size = chunk_size - 1; + bytestream2_skip(&gb, s->w.alpha_data_size); + + int filter_m = (alpha_header >> 2) & 0x03; + int compression = alpha_header & 0x03; + + if (compression > ALPHA_COMPRESSION_VP8L) { + av_log(avctx, AV_LOG_VERBOSE, + "skipping unsupported ALPHA chunk\n"); + } else { + s->w.has_alpha = 1; + s->w.alpha_compression = compression; + s->w.alpha_filter = filter_m; + } + + break; + } + case MKTAG('V', 'P', '8', ' '): + if (*got_frame) { + av_log(avctx, AV_LOG_VERBOSE, "Ignoring extra VP8 chunk\n"); + bytestream2_skip(&gb, chunk_size); + break; + } + ret = vp8_lossy_decode_frame(avctx, s->subframe, got_frame, + avpkt->data + bytestream2_tell(&gb), + chunk_size); + if (ret < 0) + goto end; + ret = prepare_canvas(s, key_frame, AV_PIX_FMT_YUVA420P); + if (ret < 0) + goto end; + bytestream2_skip(&gb, chunk_size); + break; + case MKTAG('V', 'P', '8', 'L'): + if (*got_frame) { + av_log(avctx, AV_LOG_VERBOSE, "Ignoring extra VP8L chunk\n"); + bytestream2_skip(&gb, chunk_size); + break; + } + ret = vp8_lossless_decode_frame(avctx, s->subframe, got_frame, + avpkt->data + bytestream2_tell(&gb), + chunk_size, 0); + if (ret < 0) + goto end; + ret = prepare_canvas(s, key_frame, AV_PIX_FMT_ARGB); + if (ret < 0) + goto end; +#if FF_API_CODEC_PROPS +FF_DISABLE_DEPRECATION_WARNINGS + avctx->properties |= FF_CODEC_PROPERTY_LOSSLESS; +FF_ENABLE_DEPRECATION_WARNINGS +#endif + bytestream2_skip(&gb, chunk_size); + break; + default: + av_log(avctx, AV_LOG_VERBOSE, "skipping unknown chunk: %s\n", + av_fourcc2str(chunk_type)); + bytestream2_skip(&gb, chunk_size); + break; + } + } + + if (!*got_frame) { + av_log(avctx, AV_LOG_ERROR, "image data not found\n"); + ret = AVERROR_INVALIDDATA; + goto end; + } + + /* The subframe dimensions may have been modified by update_canvas_size() */ + if (s->pos_x + s->w.width > s->canvas_width || + s->pos_y + s->w.height > s->canvas_height) { + av_log(avctx, AV_LOG_ERROR, + "Frame (%dx%d at pos %dx%d) does not fit into canvas (%dx%d)\n", + s->w.width, s->w.height, s->pos_x, s->pos_y, + s->canvas_width, s->canvas_height); + ret = AVERROR_INVALIDDATA; + goto end; + } + + ret = blend_subframe_into_canvas(s); + if (ret < 0) + goto end; + + ret = av_frame_ref(p, s->canvas); + if (ret < 0) + goto end; + + p->pict_type = key_frame ? AV_PICTURE_TYPE_I : AV_PICTURE_TYPE_P; + p->pts = avpkt->pts; + p->duration = s->duration; + + s->prev_anmf_flags = s->anmf_flags; + s->prev_width = s->w.width; + s->prev_height = s->w.height; + s->prev_pos_x = s->pos_x; + s->prev_pos_y = s->pos_y; + + ret = avpkt->size; + +end: + av_frame_unref(s->subframe); + return ret; +} + +static av_cold int webp_anim_decode_init(AVCodecContext *avctx) +{ + AnimatedWebPContext *s = avctx->priv_data; + + s->w.avctx = avctx; + s->canvas_width = avctx->width; + s->canvas_height = avctx->height; + + s->canvas = av_frame_alloc(); + if (!s->canvas) + return AVERROR(ENOMEM); + + s->subframe = av_frame_alloc(); + if (!s->subframe) + return AVERROR(ENOMEM); + + /** + * Use background color if it was provided by the demuxer. Otherwise, the + * background color will be 0x00000000 (transparent black). + */ + if (avctx->extradata_size >= 4) { + s->background_argb[0] = avctx->extradata[3]; + s->background_argb[1] = avctx->extradata[2]; + s->background_argb[2] = avctx->extradata[1]; + s->background_argb[3] = avctx->extradata[0]; + } + + /* Convert background color to YUVA. */ + const uint8_t *argb = s->background_argb; + s->background_yuva[0] = RGB_TO_Y_CCIR(argb[1], argb[2], argb[3]); + s->background_yuva[1] = RGB_TO_U_CCIR(argb[1], argb[2], argb[3], 0); + s->background_yuva[2] = RGB_TO_V_CCIR(argb[1], argb[2], argb[3], 0); + s->background_yuva[3] = argb[0]; + + return webp_decode_init(avctx); +} + +static av_cold int webp_anim_decode_close(AVCodecContext *avctx) +{ + AnimatedWebPContext *s = avctx->priv_data; + + av_frame_free(&s->canvas); + av_frame_free(&s->subframe); + + return webp_decode_close(avctx); +} + +const FFCodec ff_webp_anim_decoder = { + .p.name = "webp_anim", + CODEC_LONG_NAME("Animated WebP image"), + .p.type = AVMEDIA_TYPE_VIDEO, + .p.id = AV_CODEC_ID_WEBP_ANIM, + .priv_data_size = sizeof(AnimatedWebPContext), + .init = webp_anim_decode_init, + FF_CODEC_DECODE_CB(webp_anim_decode_frame), + .close = webp_anim_decode_close, + .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_SLICE_THREADS, + .caps_internal = FF_CODEC_CAP_USES_PROGRESSFRAMES, +}; +#endif /* CONFIG_WEBP_ANIM_DECODER */ _______________________________________________ ffmpeg-cvslog mailing list -- [email protected] To unsubscribe send an email to [email protected]
