diff mbox series

[FFmpeg-devel,v3,4/4] avcodec/libwebpdec: libwebp decoder implementation

Message ID 20210912202010.1542872-5-yakoyoku@gmail.com
State New
Headers show
Series [FFmpeg-devel,v3,1/4] avcodec/webp: compatibilize with avformat/webpdec | expand

Checks

Context Check Description
andriy/make_x86 success Make finished
andriy/make_fate_x86 fail Make fate failed
andriy/make_ppc success Make finished
andriy/make_fate_ppc fail Make fate failed

Commit Message

Martin Reboredo Sept. 12, 2021, 8:20 p.m. UTC
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.

Signed-off-by: Martin Reboredo <yakoyoku@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

Comments

Paul B Mahol Sept. 12, 2021, 8:21 p.m. UTC | #1
On Sun, Sep 12, 2021 at 10:21 PM Martin Reboredo <yakoyoku@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 <yakoyoku@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 <yakoyoku@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-request@ffmpeg.org with subject "unsubscribe".
>
Lynne Sept. 13, 2021, 12:14 a.m. UTC | #2
12 Sept 2021, 22:20 by yakoyoku@gmail.com:

> 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'd really rather not merge this. Not when we have patches from last year[1] that implement
animated webp decoding. I approved them, but wanted another dev to take a look at them
as well, since it's not a small patchset.
I tried to find the original developer two or so months ago, but he's missing since the
Freenode death.

If it comes to it, I'll take over the patchset and make changes myself.

[1] - http://ffmpeg.org/pipermail/ffmpeg-devel/2020-August/268820.html-
diff mbox series

Patch

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 <yakoyoku@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",
+};