2 * WebP encoding support via libwebp
3 * Copyright (c) 2013 Justin Ruggles <justin.ruggles@gmail.com>
5 * This file is part of FFmpeg.
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * FFmpeg is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
24 * WebP encoder using libwebp
27 #include <webp/encode.h>
29 #include "libavutil/common.h"
30 #include "libavutil/frame.h"
31 #include "libavutil/imgutils.h"
32 #include "libavutil/opt.h"
36 typedef struct LibWebPContext {
37 AVClass *class; // class for AVOptions
38 float quality; // lossy quality 0 - 100
39 int lossless; // use lossless encoding
40 int preset; // configuration preset
41 int chroma_warning; // chroma linesize mismatch warning has been printed
42 int conversion_warning; // pixel format conversion warning has been printed
43 WebPConfig config; // libwebp configuration
49 static int libwebp_error_to_averror(int err)
52 case VP8_ENC_ERROR_OUT_OF_MEMORY:
53 case VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY:
54 return AVERROR(ENOMEM);
55 case VP8_ENC_ERROR_NULL_PARAMETER:
56 case VP8_ENC_ERROR_INVALID_CONFIGURATION:
57 case VP8_ENC_ERROR_BAD_DIMENSION:
58 return AVERROR(EINVAL);
60 return AVERROR_UNKNOWN;
63 static av_cold int libwebp_encode_init(AVCodecContext *avctx)
65 LibWebPContext *s = avctx->priv_data;
68 if (avctx->global_quality < 0)
69 avctx->global_quality = 75 * FF_QP2LAMBDA;
70 s->quality = av_clipf(avctx->global_quality / (float)FF_QP2LAMBDA,
73 if (avctx->compression_level < 0 || avctx->compression_level > 6) {
74 av_log(avctx, AV_LOG_WARNING, "invalid compression level: %d\n",
75 avctx->compression_level);
76 avctx->compression_level = av_clip(avctx->compression_level, 0, 6);
79 if (s->preset >= WEBP_PRESET_DEFAULT) {
80 ret = WebPConfigPreset(&s->config, s->preset, s->quality);
82 return AVERROR_UNKNOWN;
83 s->lossless = s->config.lossless;
84 s->quality = s->config.quality;
85 avctx->compression_level = s->config.method;
87 ret = WebPConfigInit(&s->config);
89 return AVERROR_UNKNOWN;
91 s->config.lossless = s->lossless;
92 s->config.quality = s->quality;
93 s->config.method = avctx->compression_level;
95 ret = WebPValidateConfig(&s->config);
97 return AVERROR(EINVAL);
100 av_log(avctx, AV_LOG_DEBUG, "%s - quality=%.1f method=%d\n",
101 s->lossless ? "Lossless" : "Lossy", s->quality,
102 avctx->compression_level);
107 static int libwebp_encode_frame(AVCodecContext *avctx, AVPacket *pkt,
108 const AVFrame *frame, int *got_packet)
110 LibWebPContext *s = avctx->priv_data;
111 AVFrame *alt_frame = NULL;
112 WebPPicture *pic = NULL;
113 WebPMemoryWriter mw = { 0 };
116 if (avctx->width > WEBP_MAX_DIMENSION || avctx->height > WEBP_MAX_DIMENSION) {
117 av_log(avctx, AV_LOG_ERROR, "Picture size is too large. Max is %dx%d.\n",
118 WEBP_MAX_DIMENSION, WEBP_MAX_DIMENSION);
119 return AVERROR(EINVAL);
122 pic = av_malloc(sizeof(*pic));
124 return AVERROR(ENOMEM);
126 ret = WebPPictureInit(pic);
128 ret = AVERROR_UNKNOWN;
131 pic->width = avctx->width;
132 pic->height = avctx->height;
134 if (avctx->pix_fmt == AV_PIX_FMT_RGB32) {
136 /* libwebp will automatically convert RGB input to YUV when
138 if (!s->conversion_warning) {
139 av_log(avctx, AV_LOG_WARNING,
140 "Using libwebp for RGB-to-YUV conversion. You may want "
141 "to consider passing in YUV instead for lossy "
143 s->conversion_warning = 1;
147 pic->argb = (uint32_t *)frame->data[0];
148 pic->argb_stride = frame->linesize[0] / 4;
150 if (frame->linesize[1] != frame->linesize[2] || s->cr_threshold) {
151 if (!s->chroma_warning && !s->cr_threshold) {
152 av_log(avctx, AV_LOG_WARNING,
153 "Copying frame due to differing chroma linesizes.\n");
154 s->chroma_warning = 1;
156 alt_frame = av_frame_alloc();
158 ret = AVERROR(ENOMEM);
161 alt_frame->width = frame->width;
162 alt_frame->height = frame->height;
163 alt_frame->format = frame->format;
165 alt_frame->format = AV_PIX_FMT_YUVA420P;
166 ret = av_frame_get_buffer(alt_frame, 32);
169 alt_frame->format = frame->format;
170 av_frame_copy(alt_frame, frame);
172 if (s->cr_threshold) {
177 s->ref = av_frame_clone(frame);
179 ret = AVERROR(ENOMEM);
184 alt_frame->format = AV_PIX_FMT_YUVA420P;
185 for (y = 0; y < frame->height; y+= bs) {
186 for (x = 0; x < frame->width; x+= bs) {
189 for (p = 0; p < 3; p++) {
191 int w = FF_CEIL_RSHIFT(frame->width , !!p);
192 int h = FF_CEIL_RSHIFT(frame->height, !!p);
195 for (y2 = ys; y2 < FFMIN(ys + bs2, h); y2++) {
196 for (x2 = xs; x2 < FFMIN(xs + bs2, w); x2++) {
197 int diff = frame->data[p][frame->linesize[p] * y2 + x2]
198 -s->ref->data[p][frame->linesize[p] * y2 + x2];
203 skip = sse < s->cr_threshold && frame->data[3] != s->ref->data[3];
205 for (p = 0; p < 3; p++) {
207 int w = FF_CEIL_RSHIFT(frame->width , !!p);
208 int h = FF_CEIL_RSHIFT(frame->height, !!p);
211 for (y2 = ys; y2 < FFMIN(ys + bs2, h); y2++) {
212 memcpy(&s->ref->data[p][frame->linesize[p] * y2 + xs],
213 & frame->data[p][frame->linesize[p] * y2 + xs], FFMIN(bs2, w-xs));
216 for (y2 = y; y2 < FFMIN(y+bs, frame->height); y2++) {
217 memset(&frame->data[3][frame->linesize[3] * y2 + x],
219 FFMIN(bs, frame->width-x));
227 pic->y = frame->data[0];
228 pic->u = frame->data[1];
229 pic->v = frame->data[2];
230 pic->y_stride = frame->linesize[0];
231 pic->uv_stride = frame->linesize[1];
232 if (frame->format == AV_PIX_FMT_YUVA420P) {
233 pic->colorspace = WEBP_YUV420A;
234 pic->a = frame->data[3];
235 pic->a_stride = frame->linesize[3];
237 WebPCleanupTransparentArea(pic);
239 pic->colorspace = WEBP_YUV420;
243 /* We do not have a way to automatically prioritize RGB over YUV
244 in automatic pixel format conversion based on whether we're
245 encoding lossless or lossy, so we do conversion with libwebp as
247 if (!s->conversion_warning) {
248 av_log(avctx, AV_LOG_WARNING,
249 "Using libwebp for YUV-to-RGB conversion. You may want "
250 "to consider passing in RGB instead for lossless "
252 s->conversion_warning = 1;
255 #if (WEBP_ENCODER_ABI_VERSION <= 0x201)
256 /* libwebp should do the conversion automatically, but there is a
257 bug that causes it to return an error instead, so a work-around
259 See https://code.google.com/p/webp/issues/detail?id=178 */
260 pic->memory_ = (void*)1; /* something non-null */
261 ret = WebPPictureYUVAToARGB(pic);
263 av_log(avctx, AV_LOG_ERROR,
264 "WebPPictureYUVAToARGB() failed with error: %d\n",
266 ret = libwebp_error_to_averror(pic->error_code);
269 pic->memory_ = NULL; /* restore pointer */
274 WebPMemoryWriterInit(&mw);
275 pic->custom_ptr = &mw;
276 pic->writer = WebPMemoryWrite;
278 ret = WebPEncode(&s->config, pic);
280 av_log(avctx, AV_LOG_ERROR, "WebPEncode() failed with error: %d\n",
282 ret = libwebp_error_to_averror(pic->error_code);
286 ret = ff_alloc_packet(pkt, mw.size);
289 memcpy(pkt->data, mw.mem, mw.size);
291 pkt->flags |= AV_PKT_FLAG_KEY;
295 #if (WEBP_ENCODER_ABI_VERSION > 0x0203)
296 WebPMemoryWriterClear(&mw);
298 free(mw.mem); /* must use free() according to libwebp documentation */
300 WebPPictureFree(pic);
302 av_frame_free(&alt_frame);
307 static int libwebp_encode_close(AVCodecContext *avctx)
309 LibWebPContext *s = avctx->priv_data;
311 av_frame_free(&s->ref);
316 #define OFFSET(x) offsetof(LibWebPContext, x)
317 #define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
318 static const AVOption options[] = {
319 { "lossless", "Use lossless mode", OFFSET(lossless), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, VE },
320 { "preset", "Configuration preset", OFFSET(preset), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, WEBP_PRESET_TEXT, VE, "preset" },
321 { "none", "do not use a preset", 0, AV_OPT_TYPE_CONST, { .i64 = -1 }, 0, 0, VE, "preset" },
322 { "default", "default preset", 0, AV_OPT_TYPE_CONST, { .i64 = WEBP_PRESET_DEFAULT }, 0, 0, VE, "preset" },
323 { "picture", "digital picture, like portrait, inner shot", 0, AV_OPT_TYPE_CONST, { .i64 = WEBP_PRESET_PICTURE }, 0, 0, VE, "preset" },
324 { "photo", "outdoor photograph, with natural lighting", 0, AV_OPT_TYPE_CONST, { .i64 = WEBP_PRESET_PHOTO }, 0, 0, VE, "preset" },
325 { "drawing", "hand or line drawing, with high-contrast details", 0, AV_OPT_TYPE_CONST, { .i64 = WEBP_PRESET_DRAWING }, 0, 0, VE, "preset" },
326 { "icon", "small-sized colorful images", 0, AV_OPT_TYPE_CONST, { .i64 = WEBP_PRESET_ICON }, 0, 0, VE, "preset" },
327 { "text", "text-like", 0, AV_OPT_TYPE_CONST, { .i64 = WEBP_PRESET_TEXT }, 0, 0, VE, "preset" },
328 { "cr_threshold","Conditional replenishment threshold", OFFSET(cr_threshold), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, VE },
329 { "cr_size" ,"Conditional replenishment block size", OFFSET(cr_size) , AV_OPT_TYPE_INT, { .i64 = 16 }, 0, 256, VE },
334 static const AVClass class = {
335 .class_name = "libwebp",
336 .item_name = av_default_item_name,
338 .version = LIBAVUTIL_VERSION_INT,
341 static const AVCodecDefault libwebp_defaults[] = {
342 { "compression_level", "4" },
343 { "global_quality", "-1" },
347 AVCodec ff_libwebp_encoder = {
349 .long_name = NULL_IF_CONFIG_SMALL("libwebp WebP image"),
350 .type = AVMEDIA_TYPE_VIDEO,
351 .id = AV_CODEC_ID_WEBP,
352 .priv_data_size = sizeof(LibWebPContext),
353 .init = libwebp_encode_init,
354 .encode2 = libwebp_encode_frame,
355 .close = libwebp_encode_close,
356 .pix_fmts = (const enum AVPixelFormat[]) {
358 AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUVA420P,
361 .priv_class = &class,
362 .defaults = libwebp_defaults,