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

Paul B Mahol onemda at gmail.com
Sun Sep 12 23:21:42 EEST 2021


On Sun, Sep 12, 2021 at 10:21 PM Martin Reboredo <yakoyoku at 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 at 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 at 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 at ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request at ffmpeg.org with subject "unsubscribe".
>


More information about the ffmpeg-devel mailing list