[FFmpeg-devel] [PATCH v3 4/4] avcodec/libwebpdec: libwebp decoder implementation
Martin Reboredo
yakoyoku at gmail.com
Sun Sep 12 23:20:10 EEST 2021
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 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
More information about the ffmpeg-devel
mailing list