Hi, $subject
$attached says: ------------------------------------------------------------------------ This API is available at head (MUX_ABI_VERSION >= 0x0104), is stable, and will be made official in future release. For compatibility, the previous code is left as fallback. Dont' forget to use --enable-libwebp as ./configure option. This new code handles video, so that something like ffmpeg -i video.mp4 -y anim.webp should just work. -loop option is also supported. ------------------------------------------------------------------------ Comments welcome! skal/
From dcad4a82df4d0e63f187e51c858038ea4508e4b6 Mon Sep 17 00:00:00 2001 From: Urvang Joshi <urv...@google.com> Date: Fri, 27 Mar 2015 10:42:57 +0100 Subject: [PATCH] use WebPAnimEncoder API to generate animated WebP This API is available at head (MUX_ABI_VERSION >= 0x0104), is stable, and will be made official in future release. For compatibility, the previous code is left as fallback. Dont' forget to use --enable-libwebp as ./configure option. This new code handles video, so that something like ffmpeg -i video.mp4 -y anim.webp should just work. -loop option is also supported. --- configure | 4 +- libavcodec/libwebpenc.c | 399 +++++++++++++++++++++++++++++------------------- libavformat/webpenc.c | 41 ++++- 3 files changed, 281 insertions(+), 163 deletions(-) diff --git a/configure b/configure index a96bfb7..4843079 100755 --- a/configure +++ b/configure @@ -4985,7 +4985,9 @@ enabled libvpx && { enabled libvpx_vp9_decoder && { check_lib2 "vpx/vpx_decoder.h vpx/vp8dx.h" "vpx_codec_vp9_dx" -lvpx || disable libvpx_vp9_decoder; } enabled libvpx_vp9_encoder && { check_lib2 "vpx/vpx_encoder.h vpx/vp8cx.h" "vpx_codec_vp9_cx VP9E_SET_AQ_MODE" -lvpx || disable libvpx_vp9_encoder; } } enabled libwavpack && require libwavpack wavpack/wavpack.h WavpackOpenFileOutput -lwavpack -enabled libwebp && require_pkg_config "libwebp >= 0.2.0" webp/encode.h WebPGetEncoderVersion +enabled libwebp && require_pkg_config "libwebp >= 0.2.0" webp/encode.h WebPGetEncoderVersion && + { use_pkg_config "libwebpmux >= 0.3.0" webp/mux.h WebPGetMuxVersion || + warn "using libwebp without libwebpmux"; } enabled libx264 && { use_pkg_config x264 "stdint.h x264.h" x264_encoder_encode || { require libx264 x264.h x264_encoder_encode -lx264 && warn "using libx264 without pkg-config"; } } && diff --git a/libavcodec/libwebpenc.c b/libavcodec/libwebpenc.c index 95d56ac..0726d1f 100644 --- a/libavcodec/libwebpenc.c +++ b/libavcodec/libwebpenc.c @@ -26,6 +26,13 @@ #include <webp/encode.h> +#if (WEBP_ENCODER_ABI_VERSION >= 0x0206) +#include <webp/mux.h> +#if (WEBP_MUX_ABI_VERSION >= 0x0104) +#define USE_WEBP_ANIMENCODER +#endif +#endif + #include "libavutil/common.h" #include "libavutil/frame.h" #include "libavutil/imgutils.h" @@ -44,6 +51,11 @@ typedef struct LibWebPContext { AVFrame *ref; int cr_size; int cr_threshold; +#ifdef USE_WEBP_ANIMENCODER + WebPAnimEncoder *enc; // the main AnimEncoder object + int64_t prev_frame_pts; // pts of the previously encoded frame. + int done; // If true, we have assembled the bitstream already +#endif } LibWebPContext; static int libwebp_error_to_averror(int err) @@ -96,6 +108,20 @@ static av_cold int libwebp_encode_init(AVCodecContext *avctx) return AVERROR(EINVAL); } +#ifdef USE_WEBP_ANIMENCODER + { + WebPAnimEncoderOptions enc_options; + WebPAnimEncoderOptionsInit(&enc_options); + // TODO(urvang): Expose some options on command-line perhaps. + s->enc = WebPAnimEncoderNew(avctx->width, avctx->height, &enc_options); + if (!s->enc) + return AVERROR(EINVAL); + // Ensures 'pts - prev_frame_pts' works correctly for first frame, too. + s->prev_frame_pts = -1; + s->done = 0; + } +#endif + av_log(avctx, AV_LOG_DEBUG, "%s - quality=%.1f method=%d\n", s->lossless ? "Lossless" : "Lossy", s->quality, avctx->compression_level); @@ -107,100 +133,114 @@ static int libwebp_encode_frame(AVCodecContext *avctx, AVPacket *pkt, const AVFrame *frame, int *got_packet) { LibWebPContext *s = avctx->priv_data; - AVFrame *alt_frame = NULL; - WebPPicture *pic = NULL; - WebPMemoryWriter mw = { 0 }; int ret; - if (avctx->width > WEBP_MAX_DIMENSION || avctx->height > WEBP_MAX_DIMENSION) { - av_log(avctx, AV_LOG_ERROR, "Picture size is too large. Max is %dx%d.\n", - WEBP_MAX_DIMENSION, WEBP_MAX_DIMENSION); - return AVERROR(EINVAL); - } - - pic = av_malloc(sizeof(*pic)); - if (!pic) - return AVERROR(ENOMEM); - - ret = WebPPictureInit(pic); - if (!ret) { - ret = AVERROR_UNKNOWN; - goto end; - } - pic->width = avctx->width; - pic->height = avctx->height; - - if (avctx->pix_fmt == AV_PIX_FMT_RGB32) { - if (!s->lossless) { - /* libwebp will automatically convert RGB input to YUV when - encoding lossy. */ - if (!s->conversion_warning) { - av_log(avctx, AV_LOG_WARNING, - "Using libwebp for RGB-to-YUV conversion. You may want " - "to consider passing in YUV instead for lossy " - "encoding.\n"); - s->conversion_warning = 1; +#ifdef USE_WEBP_ANIMENCODER + if (!frame) { + if (s->done) { // Second flush: return empty package to denote finish. + *got_packet = 0; + return 0; + } else { // First flush: assemble bitstream and return it. + WebPData assembled_data = { 0 }; + ret = WebPAnimEncoderAssemble(s->enc, &assembled_data); + if (ret) { + ret = ff_alloc_packet(pkt, assembled_data.size); + if (ret < 0) + return ret; + memcpy(pkt->data, assembled_data.bytes, assembled_data.size); + s->done = 1; + pkt->flags |= AV_PKT_FLAG_KEY; + pkt->pts = pkt->dts = s->prev_frame_pts + 1; + *got_packet = 1; + return 0; + } else { + av_log(s, AV_LOG_ERROR, + "WebPAnimEncoderAssemble() failed with error: %d\n", + VP8_ENC_ERROR_OUT_OF_MEMORY); + return AVERROR(ENOMEM); } } - pic->use_argb = 1; - pic->argb = (uint32_t *)frame->data[0]; - pic->argb_stride = frame->linesize[0] / 4; } else { - if (frame->linesize[1] != frame->linesize[2] || s->cr_threshold) { - if (!s->chroma_warning && !s->cr_threshold) { - av_log(avctx, AV_LOG_WARNING, - "Copying frame due to differing chroma linesizes.\n"); - s->chroma_warning = 1; - } - alt_frame = av_frame_alloc(); - if (!alt_frame) { - ret = AVERROR(ENOMEM); - goto end; +#endif + AVFrame *alt_frame = NULL; + WebPPicture *pic = NULL; +#ifndef USE_WEBP_ANIMENCODER + WebPMemoryWriter mw = { 0 }; +#endif + + if (avctx->width > WEBP_MAX_DIMENSION || avctx->height > WEBP_MAX_DIMENSION) { + av_log(avctx, AV_LOG_ERROR, "Picture size is too large. Max is %dx%d.\n", + WEBP_MAX_DIMENSION, WEBP_MAX_DIMENSION); + return AVERROR(EINVAL); + } + + pic = av_malloc(sizeof(*pic)); + if (!pic) + return AVERROR(ENOMEM); + + ret = WebPPictureInit(pic); + if (!ret) { + ret = AVERROR_UNKNOWN; + goto end; + } + pic->width = avctx->width; + pic->height = avctx->height; + + if (avctx->pix_fmt == AV_PIX_FMT_RGB32) { + if (!s->lossless) { + /* libwebp will automatically convert RGB input to YUV when + encoding lossy. */ + if (!s->conversion_warning) { + av_log(avctx, AV_LOG_WARNING, + "Using libwebp for RGB-to-YUV conversion. You may want " + "to consider passing in YUV instead for lossy " + "encoding.\n"); + s->conversion_warning = 1; + } } - alt_frame->width = frame->width; - alt_frame->height = frame->height; - alt_frame->format = frame->format; - if (s->cr_threshold) - alt_frame->format = AV_PIX_FMT_YUVA420P; - ret = av_frame_get_buffer(alt_frame, 32); - if (ret < 0) - goto end; - alt_frame->format = frame->format; - av_frame_copy(alt_frame, frame); - frame = alt_frame; - if (s->cr_threshold) { - int x,y, x2, y2, p; - int bs = s->cr_size; - - if (!s->ref) { - s->ref = av_frame_clone(frame); - if (!s->ref) { - ret = AVERROR(ENOMEM); - goto end; - } + pic->use_argb = 1; + pic->argb = (uint32_t *)frame->data[0]; + pic->argb_stride = frame->linesize[0] / 4; + } else { + if (frame->linesize[1] != frame->linesize[2] || s->cr_threshold) { + if (!s->chroma_warning && !s->cr_threshold) { + av_log(avctx, AV_LOG_WARNING, + "Copying frame due to differing chroma linesizes.\n"); + s->chroma_warning = 1; + } + alt_frame = av_frame_alloc(); + if (!alt_frame) { + ret = AVERROR(ENOMEM); + goto end; } + alt_frame->width = frame->width; + alt_frame->height = frame->height; + alt_frame->format = frame->format; + if (s->cr_threshold) + alt_frame->format = AV_PIX_FMT_YUVA420P; + ret = av_frame_get_buffer(alt_frame, 32); + if (ret < 0) + goto end; + alt_frame->format = frame->format; + av_frame_copy(alt_frame, frame); + frame = alt_frame; + if (s->cr_threshold) { + int x,y, x2, y2, p; + int bs = s->cr_size; - alt_frame->format = AV_PIX_FMT_YUVA420P; - for (y = 0; y < frame->height; y+= bs) { - for (x = 0; x < frame->width; x+= bs) { - int skip; - int sse = 0; - for (p = 0; p < 3; p++) { - int bs2 = bs >> !!p; - int w = FF_CEIL_RSHIFT(frame->width , !!p); - int h = FF_CEIL_RSHIFT(frame->height, !!p); - int xs = x >> !!p; - int ys = y >> !!p; - for (y2 = ys; y2 < FFMIN(ys + bs2, h); y2++) { - for (x2 = xs; x2 < FFMIN(xs + bs2, w); x2++) { - int diff = frame->data[p][frame->linesize[p] * y2 + x2] - -s->ref->data[p][frame->linesize[p] * y2 + x2]; - sse += diff*diff; - } - } + if (!s->ref) { + s->ref = av_frame_clone(frame); + if (!s->ref) { + ret = AVERROR(ENOMEM); + goto end; } - skip = sse < s->cr_threshold && frame->data[3] != s->ref->data[3]; - if (!skip) + } + + alt_frame->format = AV_PIX_FMT_YUVA420P; + for (y = 0; y < frame->height; y+= bs) { + for (x = 0; x < frame->width; x+= bs) { + int skip; + int sse = 0; for (p = 0; p < 3; p++) { int bs2 = bs >> !!p; int w = FF_CEIL_RSHIFT(frame->width , !!p); @@ -208,107 +248,141 @@ static int libwebp_encode_frame(AVCodecContext *avctx, AVPacket *pkt, int xs = x >> !!p; int ys = y >> !!p; for (y2 = ys; y2 < FFMIN(ys + bs2, h); y2++) { - memcpy(&s->ref->data[p][frame->linesize[p] * y2 + xs], - & frame->data[p][frame->linesize[p] * y2 + xs], FFMIN(bs2, w-xs)); + for (x2 = xs; x2 < FFMIN(xs + bs2, w); x2++) { + int diff = frame->data[p][frame->linesize[p] * y2 + x2] + -s->ref->data[p][frame->linesize[p] * y2 + x2]; + sse += diff*diff; + } + } + } + skip = sse < s->cr_threshold && frame->data[3] != s->ref->data[3]; + if (!skip) + for (p = 0; p < 3; p++) { + int bs2 = bs >> !!p; + int w = FF_CEIL_RSHIFT(frame->width , !!p); + int h = FF_CEIL_RSHIFT(frame->height, !!p); + int xs = x >> !!p; + int ys = y >> !!p; + for (y2 = ys; y2 < FFMIN(ys + bs2, h); y2++) { + memcpy(&s->ref->data[p][frame->linesize[p] * y2 + xs], + & frame->data[p][frame->linesize[p] * y2 + xs], FFMIN(bs2, w-xs)); + } } + for (y2 = y; y2 < FFMIN(y+bs, frame->height); y2++) { + memset(&frame->data[3][frame->linesize[3] * y2 + x], + skip ? 0 : 255, + FFMIN(bs, frame->width-x)); } - for (y2 = y; y2 < FFMIN(y+bs, frame->height); y2++) { - memset(&frame->data[3][frame->linesize[3] * y2 + x], - skip ? 0 : 255, - FFMIN(bs, frame->width-x)); } } } } - } - - pic->use_argb = 0; - pic->y = frame->data[0]; - pic->u = frame->data[1]; - pic->v = frame->data[2]; - pic->y_stride = frame->linesize[0]; - pic->uv_stride = frame->linesize[1]; - if (frame->format == AV_PIX_FMT_YUVA420P) { - pic->colorspace = WEBP_YUV420A; - pic->a = frame->data[3]; - pic->a_stride = frame->linesize[3]; - if (alt_frame) - WebPCleanupTransparentArea(pic); - } else { - pic->colorspace = WEBP_YUV420; - } - if (s->lossless) { - /* We do not have a way to automatically prioritize RGB over YUV - in automatic pixel format conversion based on whether we're - encoding lossless or lossy, so we do conversion with libwebp as - a convenience. */ - if (!s->conversion_warning) { - av_log(avctx, AV_LOG_WARNING, - "Using libwebp for YUV-to-RGB conversion. You may want " - "to consider passing in RGB instead for lossless " - "encoding.\n"); - s->conversion_warning = 1; + pic->use_argb = 0; + pic->y = frame->data[0]; + pic->u = frame->data[1]; + pic->v = frame->data[2]; + pic->y_stride = frame->linesize[0]; + pic->uv_stride = frame->linesize[1]; + if (frame->format == AV_PIX_FMT_YUVA420P) { + pic->colorspace = WEBP_YUV420A; + pic->a = frame->data[3]; + pic->a_stride = frame->linesize[3]; + if (alt_frame) + WebPCleanupTransparentArea(pic); + } else { + pic->colorspace = WEBP_YUV420; } + if (s->lossless) { + /* We do not have a way to automatically prioritize RGB over YUV + in automatic pixel format conversion based on whether we're + encoding lossless or lossy, so we do conversion with libwebp as + a convenience. */ + if (!s->conversion_warning) { + av_log(avctx, AV_LOG_WARNING, + "Using libwebp for YUV-to-RGB conversion. You may want " + "to consider passing in RGB instead for lossless " + "encoding.\n"); + s->conversion_warning = 1; + } + #if (WEBP_ENCODER_ABI_VERSION <= 0x201) - /* libwebp should do the conversion automatically, but there is a - bug that causes it to return an error instead, so a work-around - is required. - See https://code.google.com/p/webp/issues/detail?id=178 */ - pic->memory_ = (void*)1; /* something non-null */ - ret = WebPPictureYUVAToARGB(pic); - if (!ret) { - av_log(avctx, AV_LOG_ERROR, - "WebPPictureYUVAToARGB() failed with error: %d\n", - pic->error_code); - ret = libwebp_error_to_averror(pic->error_code); - goto end; + /* libwebp should do the conversion automatically, but there is a + bug that causes it to return an error instead, so a work-around + is required. + See https://code.google.com/p/webp/issues/detail?id=178 */ + pic->memory_ = (void*)1; /* something non-null */ + ret = WebPPictureYUVAToARGB(pic); + if (!ret) { + av_log(avctx, AV_LOG_ERROR, + "WebPPictureYUVAToARGB() failed with error: %d\n", + pic->error_code); + ret = libwebp_error_to_averror(pic->error_code); + goto end; + } + pic->memory_ = NULL; /* restore pointer */ +#endif } - pic->memory_ = NULL; /* restore pointer */ + } +#ifdef USE_WEBP_ANIMENCODER + { + const int pts_diff = frame->pts - s->prev_frame_pts; + const int duration = + avctx->time_base.num * pts_diff * 1000 / avctx->time_base.den; + ret = WebPAnimEncoderAdd(s->enc, pic, duration, &s->config); + } +#else + WebPMemoryWriterInit(&mw); + pic->custom_ptr = &mw; + pic->writer = WebPMemoryWrite; + ret = WebPEncode(&s->config, pic); #endif + if (!ret) { + av_log(avctx, AV_LOG_ERROR, + "Encoding WebP frame failed with error: %d\n", + pic->error_code); + ret = libwebp_error_to_averror(pic->error_code); + goto end; } - } - - WebPMemoryWriterInit(&mw); - pic->custom_ptr = &mw; - pic->writer = WebPMemoryWrite; - - ret = WebPEncode(&s->config, pic); - if (!ret) { - av_log(avctx, AV_LOG_ERROR, "WebPEncode() failed with error: %d\n", - pic->error_code); - ret = libwebp_error_to_averror(pic->error_code); - goto end; - } - ret = ff_alloc_packet(pkt, mw.size); - if (ret < 0) - goto end; - memcpy(pkt->data, mw.mem, mw.size); - - pkt->flags |= AV_PKT_FLAG_KEY; - *got_packet = 1; +#ifdef USE_WEBP_ANIMENCODER + pkt->pts = pkt->dts = frame->pts; + s->prev_frame_pts = frame->pts; // Save for next frame. + ret = 0; +#else + ret = ff_alloc_packet(pkt, mw.size); + if (ret < 0) + goto end; + memcpy(pkt->data, mw.mem, mw.size); + pkt->flags |= AV_PKT_FLAG_KEY; +#endif + *got_packet = 1; end: +#ifndef USE_WEBP_ANIMENCODER #if (WEBP_ENCODER_ABI_VERSION > 0x0203) - WebPMemoryWriterClear(&mw); + WebPMemoryWriterClear(&mw); #else - free(mw.mem); /* must use free() according to libwebp documentation */ + free(mw.mem); /* must use free() according to libwebp documentation */ +#endif +#endif + WebPPictureFree(pic); + av_freep(&pic); + av_frame_free(&alt_frame); + return ret; +#ifdef USE_WEBP_ANIMENCODER + } #endif - WebPPictureFree(pic); - av_freep(&pic); - av_frame_free(&alt_frame); - - return ret; } static int libwebp_encode_close(AVCodecContext *avctx) { LibWebPContext *s = avctx->priv_data; - +#ifdef USE_WEBP_ANIMENCODER + WebPAnimEncoderDelete(s->enc); +#endif av_frame_free(&s->ref); - return 0; } @@ -353,6 +427,9 @@ AVCodec ff_libwebp_encoder = { .init = libwebp_encode_init, .encode2 = libwebp_encode_frame, .close = libwebp_encode_close, +#ifdef USE_WEBP_ANIMENCODER + .capabilities = CODEC_CAP_DELAY, +#endif .pix_fmts = (const enum AVPixelFormat[]) { AV_PIX_FMT_RGB32, AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUVA420P, diff --git a/libavformat/webpenc.c b/libavformat/webpenc.c index ee110de..d4411a9 100644 --- a/libavformat/webpenc.c +++ b/libavformat/webpenc.c @@ -24,10 +24,20 @@ #include "avformat.h" #include "internal.h" +#include <webp/encode.h> +#if (WEBP_ENCODER_ABI_VERSION >= 0x0206) +#include <webp/mux.h> +#if (WEBP_MUX_ABI_VERSION >= 0x0104) +#define USE_WEBP_ANIMENCODER +#endif +#endif + typedef struct WebpContext{ AVClass *class; int frame_count; +#ifndef USE_WEBP_ANIMENCODER AVPacket last_pkt; +#endif int loop; } WebpContext; @@ -44,13 +54,40 @@ static int webp_write_header(AVFormatContext *s) av_log(s, AV_LOG_ERROR, "Only WebP is supported\n"); return AVERROR(EINVAL); } - avpriv_set_pts_info(st, 24, 1, 1000); +#ifndef USE_WEBP_ANIMENCODER + avpriv_set_pts_info(st, 24, 1, 1000); avio_write(s->pb, "RIFF\0\0\0\0WEBP", 12); +#endif + + return 0; +} + +#ifdef USE_WEBP_ANIMENCODER +static int webp_write_packet(AVFormatContext *s, AVPacket *pkt) +{ + if (pkt->size) { + avio_write(s->pb, pkt->data, pkt->size); + } else { + WebpContext *w = s->priv_data; + ++w->frame_count; + } return 0; } +static int webp_write_trailer(AVFormatContext *s) +{ + WebpContext *w = s->priv_data; + if (w->frame_count > 1 && w->loop != 0) { // Write loop count. + avio_seek(s->pb, 42, SEEK_SET); + avio_wl16(s->pb, w->loop); + } + return 0; +} + +#else + static int flush(AVFormatContext *s, int trailer, int64_t pts) { WebpContext *w = s->priv_data; @@ -141,6 +178,8 @@ static int webp_write_trailer(AVFormatContext *s) return 0; } +#endif + #define OFFSET(x) offsetof(WebpContext, x) #define ENC AV_OPT_FLAG_ENCODING_PARAM static const AVOption options[] = { -- 2.2.0.rc0.207.ga3a616c
_______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel