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