On Sun, Sep 12, 2021 at 10:21 PM Martin Reboredo <yakoy...@gmail.com> wrote:
> Followup of the webp demuxer implementation. As the demuxer sends RIFF > packets, the decoder choses what to do with the arriving chunks. > > Completely fixes #4907. > I really doubt that. > > Signed-off-by: Martin Reboredo <yakoy...@gmail.com> > --- > configure | 4 +- > libavcodec/Makefile | 1 + > libavcodec/allcodecs.c | 1 + > libavcodec/libwebpdec.c | 419 ++++++++++++++++++++++++++++++++++++++++ > 4 files changed, 424 insertions(+), 1 deletion(-) > create mode 100644 libavcodec/libwebpdec.c > > diff --git a/configure b/configure > index 98987ed186..73dc45fb0d 100755 > --- a/configure > +++ b/configure > @@ -285,7 +285,7 @@ External library support: > --enable-libvorbis enable Vorbis en/decoding via libvorbis, > native implementation exists [no] > --enable-libvpx enable VP8 and VP9 de/encoding via libvpx [no] > - --enable-libwebp enable WebP encoding via libwebp [no] > + --enable-libwebp enable WebP de/encoding via libwebp [no] > --enable-libx264 enable H.264 encoding via x264 [no] > --enable-libx265 enable HEVC encoding via x265 [no] > --enable-libxavs enable AVS encoding via xavs [no] > @@ -3314,6 +3314,7 @@ libvpx_vp8_decoder_deps="libvpx" > libvpx_vp8_encoder_deps="libvpx" > libvpx_vp9_decoder_deps="libvpx" > libvpx_vp9_encoder_deps="libvpx" > +libwebp_decoder_deps="libwebpdecoder" > libwebp_encoder_deps="libwebp" > libwebp_anim_encoder_deps="libwebp" > libx262_encoder_deps="libx262" > @@ -6518,6 +6519,7 @@ enabled libvpx && { > } > > enabled libwebp && { > + enabled libwebp_decoder && require_pkg_config libwebpdecoder > "libwebpdecoder >= 0.2.0" webp/decode.h WebPGetDecoderVersion > enabled libwebp_encoder && require_pkg_config libwebp "libwebp > >= 0.2.0" webp/encode.h WebPGetEncoderVersion > enabled libwebp_anim_encoder && check_pkg_config libwebp_anim_encoder > "libwebpmux >= 0.4.0" webp/mux.h WebPAnimEncoderOptionsInit; } > enabled libx264 && { check_pkg_config libx264 x264 "stdint.h > x264.h" x264_encoder_encode || > diff --git a/libavcodec/Makefile b/libavcodec/Makefile > index 11873eecae..81936b9828 100644 > --- a/libavcodec/Makefile > +++ b/libavcodec/Makefile > @@ -1062,6 +1062,7 @@ OBJS-$(CONFIG_LIBVPX_VP8_DECODER) += > libvpxdec.o > OBJS-$(CONFIG_LIBVPX_VP8_ENCODER) += libvpxenc.o > OBJS-$(CONFIG_LIBVPX_VP9_DECODER) += libvpxdec.o libvpx.o > OBJS-$(CONFIG_LIBVPX_VP9_ENCODER) += libvpxenc.o libvpx.o > +OBJS-$(CONFIG_LIBWEBP_DECODER) += libwebpdec.o > OBJS-$(CONFIG_LIBWEBP_ENCODER) += libwebpenc_common.o > libwebpenc.o > OBJS-$(CONFIG_LIBWEBP_ANIM_ENCODER) += libwebpenc_common.o > libwebpenc_animencoder.o > OBJS-$(CONFIG_LIBX262_ENCODER) += libx264.o > diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c > index c42aba140d..223f8bbf15 100644 > --- a/libavcodec/allcodecs.c > +++ b/libavcodec/allcodecs.c > @@ -768,6 +768,7 @@ extern AVCodec ff_libvpx_vp9_decoder; > /* preferred over libwebp */ > extern const AVCodec ff_libwebp_anim_encoder; > extern const AVCodec ff_libwebp_encoder; > +extern const AVCodec ff_libwebp_decoder; > extern const AVCodec ff_libx262_encoder; > #if CONFIG_LIBX264_ENCODER > #include <x264.h> > diff --git a/libavcodec/libwebpdec.c b/libavcodec/libwebpdec.c > new file mode 100644 > index 0000000000..c583f919e0 > --- /dev/null > +++ b/libavcodec/libwebpdec.c > @@ -0,0 +1,419 @@ > +/* > + * WebP decoding support via libwebp > + * Copyright (c) 2021 Martin Reboredo <yakoy...@gmail.com> > + * > + * This file is part of FFmpeg. > + * > + * FFmpeg is free software; you can redistribute it and/or > + * modify it under the terms of the GNU Lesser General Public > + * License as published by the Free Software Foundation; either > + * version 2.1 of the License, or (at your option) any later version. > + * > + * FFmpeg is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * Lesser General Public License for more details. > + * > + * You should have received a copy of the GNU Lesser General Public > + * License along with FFmpeg; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA > 02110-1301 USA > + */ > + > +/** > + * @file > + * WebP decoder using libwebp (WebPDecode API) > + */ > + > +#include <stdint.h> > +#include <webp/decode.h> > + > +#include "decode.h" > +#include "internal.h" > +#include "libavutil/colorspace.h" > +#include "libavutil/intreadwrite.h" > +#include "libavutil/error.h" > +#include "libavutil/opt.h" > + > +struct WebPDecBgColor { > + uint8_t y; > + uint8_t u; > + uint8_t v; > + uint8_t a; > +}; > + > +typedef struct LibWebPDecContext { > + AVClass *class; // class for AVOptions > + struct WebPDecBgColor bg_color; // background color for frame > disposals > + int loop; // number of times to loop all the > pictures (0 means indefinitely) > + int bypass_filter; // bypass filtering for decoded frames > + int flip; // flip output images vertically > + int dither_strength; // dithering strength applied to the > images > + int dispose; // dispose the previous frame with > background color > + int blend; // alpha blend with the previous frame > + int chunk_unread; // chunk read is not yet complete > + WebPDecoderConfig config; // libwebpdecoder configuration > + AVFrame *frame; // current decoded frame > + AVFrame *prev; // previously decoded frame > + int prev_alpha; // previous frame had an alpha channel > +} LibWebPDecContext; > + > +static int ff_libwebpdec_error_to_averror(int err) > +{ > + switch (err) { > + case VP8_STATUS_OUT_OF_MEMORY: > + return AVERROR(ENOMEM); > + case VP8_STATUS_UNSUPPORTED_FEATURE: > + case VP8_STATUS_BITSTREAM_ERROR: > + case VP8_STATUS_INVALID_PARAM: > + return AVERROR_INVALIDDATA; > + case VP8_STATUS_NOT_ENOUGH_DATA: > + return AVERROR(EAGAIN); > + } > + return AVERROR_UNKNOWN; > +} > + > +static struct WebPDecBgColor ff_libwebpdec_bgra2yuv(int bgra) > +{ > + uint8_t r = (bgra >> 8) & 0xFF; > + uint8_t g = (bgra >> 16) & 0xFF; > + uint8_t b = bgra >> 24; > + return (struct WebPDecBgColor) { > + RGB_TO_Y_JPEG(r, g, b), > + RGB_TO_U_JPEG(r, g, b), > + RGB_TO_V_JPEG(r, g, b), > + bgra & 0xFF, > + }; > +} > + > +static int ff_libwebpdec_parse_animation_frame(AVCodecContext *avctx, > uint8_t *chunk) > +{ > + LibWebPDecContext *w = avctx->priv_data; > + int flags = 0; > + > + flags = *(chunk + 23); > + w->dispose = flags & 0x01; > + w->blend = (flags & 0x02) == 0; > + > + return 0; > +} > + > +// divide by 255 and round to nearest > +// apply a fast variant: (X+127)/255 = ((X+127)*257+257)>>16 = > ((X+128)*257)>>16 > +#define FAST_DIV255(x) ((((x) + 128) * 257) >> 16) > + > +static void ff_libwebpdec_alpha_blend_frames(AVFrame *dst, const AVFrame > *src, int alpha_bg) > +{ > + const uint8_t *y_src = src->data[0]; > + const uint8_t *u_src = src->data[1]; > + const uint8_t *v_src = src->data[2]; > + const uint8_t *a_src = src->data[3]; > + uint8_t *y_dst = dst->data[0]; > + uint8_t *u_dst = dst->data[1]; > + uint8_t *v_dst = dst->data[2]; > + uint8_t *a_dst = dst->data[3]; > + > + for (int y = 0; y < src->height; y++) { > + if ((y & 1) == 0) { > + for (int x = 0; x < (src->width >> 1); x++) { > + const uint8_t *a_bgp = (y + 1 == src->height) ? a_src : > a_src + src->linesize[3]; > + uint8_t *a_fgp = (y + 1 == src->height) ? a_dst : a_dst + > dst->linesize[3]; > + uint8_t a_bg = (alpha_bg) ? (a_src[2 * x] + a_src[2 * x + > 1] + a_bgp[2 * x] + a_bgp[2 * x + 1]) >> 2 : 255; > + uint8_t a_fg = (a_dst[2 * x] + a_dst[2 * x + 1] + a_fgp[2 > * x] + a_fgp[2 * x + 1]) >> 2; > + uint8_t out_uv_alpha = a_fg + FAST_DIV255((255 - a_fg) * > a_bg); > + > + if (out_uv_alpha == 0) { > + u_dst[x] = 0; > + v_dst[x] = 0; > + } else if (out_uv_alpha >= 255) { > + u_dst[x] = FAST_DIV255(a_fg * u_dst[x] + (255 - a_fg) > * u_src[x]); > + v_dst[x] = FAST_DIV255(a_fg * v_dst[x] + (255 - a_fg) > * v_src[x]); > + } else { > + u_dst[x] = (255 * a_fg * u_dst[x] + (255 - a_fg) * > a_bg * u_src[x]) / (255 * out_uv_alpha); > + v_dst[x] = (255 * a_fg * v_dst[x] + (255 - a_fg) * > a_bg * v_src[x]) / (255 * out_uv_alpha); > + } > + } > + u_src += src->linesize[1]; > + v_src += src->linesize[2]; > + u_dst += dst->linesize[1]; > + v_dst += dst->linesize[2]; > + } > + for (int x = 0; x < src->width; x++) { > + uint8_t a_bg = (alpha_bg) ? a_src[x] : 255; > + uint8_t a_fg = a_dst[x]; > + uint8_t out_y_alpha = a_fg + FAST_DIV255((255 - a_fg) * a_bg); > + > + if (out_y_alpha == 0) { > + y_dst[x] = 0; > + } else if (out_y_alpha == 255) { > + y_dst[x] = FAST_DIV255(a_fg * y_dst[x] + (255 - a_fg) * > y_src[x]); > + } else { > + y_dst[x] = (255 * a_fg * y_dst[x] + (255 - a_fg) * a_bg * > y_src[x]) / (255 * out_y_alpha); > + } > + > + a_dst[x] = out_y_alpha; > + } > + y_src += src->linesize[0]; > + a_src += src->linesize[3]; > + y_dst += dst->linesize[0]; > + a_dst += dst->linesize[3]; > + } > +} > + > +static av_cold int libwebp_decode_init(AVCodecContext *avctx) > +{ > + LibWebPDecContext *s = avctx->priv_data; > + int ret; > + > + if (!WebPInitDecoderConfig(&s->config)) { > + return AVERROR_INVALIDDATA; > + } > + > + s->config.options.bypass_filtering = s->bypass_filter; > + s->config.options.dithering_strength = s->dither_strength; > + s->config.options.alpha_dithering_strength = s->dither_strength; > + s->config.options.flip = s->flip; > + s->config.options.use_threads = avctx->thread_count; > + > + s->loop = -1; > + s->chunk_unread = 0; > + > + s->frame = av_frame_alloc(); > + s->prev = av_frame_alloc(); > + if (s->frame == NULL || s->prev == NULL) { > + av_frame_free(&s->frame); > + av_frame_free(&s->prev); > + return AVERROR(ENOMEM); > + } > + > + ret = ff_get_buffer(avctx, s->frame, 0); > + if (ret < 0) > + return ret; > + > + s->frame->format = s->prev->format = avctx->pix_fmt; > + s->frame->width = s->prev->width = avctx->width; > + s->frame->height = s->prev->height = avctx->height; > + > + ret = av_frame_get_buffer(s->frame, 0); > + if (ret < 0) > + return ret; > + > + ret = av_frame_get_buffer(s->prev, 0); > + if (ret < 0) > + return ret; > + > + s->config.output.is_external_memory = 1; > + s->config.output.width = avctx->width; > + s->config.output.height = avctx->height; > + > + if (s->frame->format == AV_PIX_FMT_YUVA420P || s->frame->format == > AV_PIX_FMT_YUV420P) { > + s->config.output.u.YUVA.y = s->frame->data[0]; > + s->config.output.u.YUVA.u = s->frame->data[1]; > + s->config.output.u.YUVA.v = s->frame->data[2]; > + s->config.output.u.YUVA.a = s->frame->data[3]; > + s->config.output.u.YUVA.y_stride = s->frame->linesize[0]; > + s->config.output.u.YUVA.u_stride = s->frame->linesize[1]; > + s->config.output.u.YUVA.v_stride = s->frame->linesize[2]; > + s->config.output.u.YUVA.a_stride = s->frame->linesize[3]; > + s->config.output.u.YUVA.y_size = s->frame->linesize[0] * > avctx->height; > + s->config.output.u.YUVA.u_size = s->frame->linesize[1] * > (avctx->height / 2); > + s->config.output.u.YUVA.v_size = s->frame->linesize[2] * > (avctx->height / 2); > + s->config.output.u.YUVA.a_size = s->frame->linesize[3] * > avctx->height; > + if (s->frame->format == AV_PIX_FMT_YUVA420P) { > + s->prev_alpha = 1; > + s->config.output.colorspace = MODE_YUVA; > + } else { > + s->prev_alpha = 0; > + s->config.output.colorspace = MODE_YUV; > + } > + } else { > + return AVERROR_INVALIDDATA; > + } > + > + return 0; > +} > + > +static void ff_libwebpdec_dispose_frame(AVCodecContext *avctx) > +{ > + LibWebPDecContext *s = avctx->priv_data; > + > + if (avctx->pix_fmt == AV_PIX_FMT_YUVA420P) { > + memset(s->prev->data[0], s->bg_color.y, > s->config.output.u.YUVA.y_size); > + memset(s->prev->data[1], s->bg_color.u, > s->config.output.u.YUVA.u_size); > + memset(s->prev->data[2], s->bg_color.v, > s->config.output.u.YUVA.v_size); > + memset(s->prev->data[3], s->bg_color.a, > s->config.output.u.YUVA.a_size); > + } > +} > + > +static int libwebp_decode_frame(AVCodecContext *avctx, void *data, > + int *got_frame, AVPacket *pkt) > +{ > + LibWebPDecContext *s = avctx->priv_data; > + AVFrame *picture = data; > + uint8_t *chunk = pkt->data, *alpha_chunk = NULL; > + int chunk_size = 0, alpha_chunk_size = 0, offset = 0; > + int ret = 0, cont = 1, alpha = 0; > + > + if (s->dispose) { > + ff_libwebpdec_dispose_frame(avctx); > + } > + > + while (cont && ret >= 0) { > + int skip = 1; > + int fourcc = AV_RL32(chunk); > + int size = AV_RL32(chunk + 4); > + int padded_size = size + (size & 1); > + chunk_size = padded_size + 8; > + > + cont = 0; > + > + switch (fourcc) { > + case MKTAG('R', 'I', 'F', 'F'): > + chunk_size = 12; > + cont = 1; > + break; > + case MKTAG('V', 'P', '8', 'X'): > + chunk_size = 18; > + cont = 1; > + break; > + case MKTAG('A', 'N', 'I', 'M'): > + if (s->loop == -1) { > + s->bg_color = ff_libwebpdec_bgra2yuv(AV_RL32(chunk + 8)); > + ff_libwebpdec_dispose_frame(avctx); > + > + s->loop = AV_RL16(chunk + 12); > + } > + > + chunk_size = 14; > + cont = 1; > + break; > + case MKTAG('A', 'N', 'M', 'F'): > + ret = ff_libwebpdec_parse_animation_frame(avctx, chunk); > + if (ret < 0) > + return ret; > + > + chunk_size = 24; > + if (s->chunk_unread) > + return AVERROR_INVALIDDATA; > + s->chunk_unread = 1; > + cont = 1; > + break; > + case MKTAG('A', 'L', 'P', 'H'): > + if (avctx->pix_fmt == AV_PIX_FMT_YUV420P) > + return AVERROR_INVALIDDATA; > + if (pkt->size < offset + chunk_size) > + return AVERROR(EAGAIN); > + alpha_chunk = chunk; > + alpha_chunk_size = chunk_size + AV_RL32(chunk + chunk_size + > 4) + 8; > + alpha = 1; > + cont = 1; > + break; > + case MKTAG('V', 'P', '8', 'L'): > + if (*(chunk + 12) & 0x10) { > + if (avctx->pix_fmt == AV_PIX_FMT_YUV420P) > + return AVERROR_INVALIDDATA; > + alpha_chunk = chunk; > + alpha_chunk_size = chunk_size; > + alpha = 1; > + } > + case MKTAG('V', 'P', '8', ' '): > + s->config.output.colorspace = (alpha_chunk != NULL) ? > MODE_YUVA : MODE_YUV; > + s->chunk_unread = 0; > + skip = 0; > + break; > + default: > + cont = 1; > + break; > + } > + > + offset += chunk_size; > + if (skip) > + chunk += chunk_size; > + > + if (cont && offset > pkt->size) > + return AVERROR(EAGAIN); > + } > + > + if (alpha_chunk != NULL) { > + chunk = alpha_chunk; > + chunk_size = alpha_chunk_size; > + } > + ret = WebPDecode(chunk, chunk_size, &s->config); > + if (ret != VP8_STATUS_OK) { > + av_log(avctx, AV_LOG_ERROR, "WebPDecode() failed with error: > %d\n", > + ret); > + > + if (ret == VP8_STATUS_NOT_ENOUGH_DATA) > + return AVERROR_INVALIDDATA; > + > + ret = ff_libwebpdec_error_to_averror(ret); > + return ret; > + } > + > + if (avctx->pix_fmt == AV_PIX_FMT_YUVA420P) { > + if (!alpha) > + memset(s->frame->data[3], 0xFF, > s->config.output.u.YUVA.a_size); > + if (s->blend) > + ff_libwebpdec_alpha_blend_frames(s->frame, s->prev, > s->prev_alpha); > + } > + > + s->prev_alpha = alpha; > + > + ret = av_frame_copy(s->prev, s->frame); > + if (ret < 0) > + return ret; > + > + ret = av_frame_ref(picture, s->frame); > + if (ret < 0) > + return ret; > + > + *got_frame = 1; > + > + return pkt->size; > +} > + > +static int libwebp_decode_close(AVCodecContext *avctx) > +{ > + LibWebPDecContext *s = avctx->priv_data; > + av_frame_unref(s->frame); > + av_frame_free(&s->frame); > + av_frame_free(&s->prev); > + > + return 0; > +} > + > +const enum AVPixelFormat ff_libwebpdec_pix_fmts[] = { > + AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUVA420P, > + AV_PIX_FMT_NONE > +}; > + > +#define OFFSET(x) offsetof(LibWebPDecContext, x) > +#define VD AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_DECODING_PARAM > +static const AVOption options[] = { > + { "bypass", "Bypass filter", OFFSET(bypass_filter), > AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, VD }, > + { "dither_strength", "Dithering strength", > OFFSET(dither_strength), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 100, VD }, > + { "flip", "Flip decoded pictures", OFFSET(flip), > AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, VD }, > + { NULL }, > +}; > + > +const AVClass ff_libwebpdec_class = { > + .class_name = "libwebp decoder", > + .item_name = av_default_item_name, > + .option = options, > + .version = LIBAVUTIL_VERSION_INT, > +}; > + > +const AVCodec ff_libwebp_decoder = { > + .name = "libwebp", > + .long_name = NULL_IF_CONFIG_SMALL("libwebp WebP image"), > + .type = AVMEDIA_TYPE_VIDEO, > + .id = AV_CODEC_ID_WEBP, > + .capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_OTHER_THREADS, > + .caps_internal = AV_CODEC_CAP_AUTO_THREADS, > + .pix_fmts = ff_libwebpdec_pix_fmts, > + .priv_class = &ff_libwebpdec_class, > + .priv_data_size = sizeof(LibWebPDecContext), > + .init = libwebp_decode_init, > + .decode = libwebp_decode_frame, > + .close = libwebp_decode_close, > + .wrapper_name = "libwebp", > +}; > -- > 2.32.0 > > _______________________________________________ > ffmpeg-devel mailing list > ffmpeg-devel@ffmpeg.org > https://ffmpeg.org/mailman/listinfo/ffmpeg-devel > > To unsubscribe, visit link above, or email > ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe". > _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".