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 s->quality = av_clipf(avctx->global_quality / (float)FF_QP2LAMBDA,
72 if (avctx->compression_level < 0 || avctx->compression_level > 6) {
73 av_log(avctx, AV_LOG_WARNING, "invalid compression level: %d\n",
74 avctx->compression_level);
75 avctx->compression_level = av_clip(avctx->compression_level, 0, 6);
78 if (s->preset >= WEBP_PRESET_DEFAULT) {
79 ret = WebPConfigPreset(&s->config, s->preset, s->quality);
81 return AVERROR_UNKNOWN;
82 s->lossless = s->config.lossless;
83 s->quality = s->config.quality;
84 avctx->compression_level = s->config.method;
86 ret = WebPConfigInit(&s->config);
88 return AVERROR_UNKNOWN;
90 s->config.lossless = s->lossless;
91 s->config.quality = s->quality;
92 s->config.method = avctx->compression_level;
94 ret = WebPValidateConfig(&s->config);
96 return AVERROR(EINVAL);
99 av_log(avctx, AV_LOG_DEBUG, "%s - quality=%.1f method=%d\n",
100 s->lossless ? "Lossless" : "Lossy", s->quality,
101 avctx->compression_level);
106 static int libwebp_encode_frame(AVCodecContext *avctx, AVPacket *pkt,
107 const AVFrame *frame, int *got_packet)
109 LibWebPContext *s = avctx->priv_data;
110 AVFrame *alt_frame = NULL;
111 WebPPicture *pic = NULL;
112 WebPMemoryWriter mw = { 0 };
115 if (avctx->width > WEBP_MAX_DIMENSION || avctx->height > WEBP_MAX_DIMENSION) {
116 av_log(avctx, AV_LOG_ERROR, "Picture size is too large. Max is %dx%d.\n",
117 WEBP_MAX_DIMENSION, WEBP_MAX_DIMENSION);
118 return AVERROR(EINVAL);
121 pic = av_malloc(sizeof(*pic));
123 return AVERROR(ENOMEM);
125 ret = WebPPictureInit(pic);
127 ret = AVERROR_UNKNOWN;
130 pic->width = avctx->width;
131 pic->height = avctx->height;
133 if (avctx->pix_fmt == AV_PIX_FMT_RGB32) {
135 /* libwebp will automatically convert RGB input to YUV when
137 if (!s->conversion_warning) {
138 av_log(avctx, AV_LOG_WARNING,
139 "Using libwebp for RGB-to-YUV conversion. You may want "
140 "to consider passing in YUV instead for lossy "
142 s->conversion_warning = 1;
146 pic->argb = (uint32_t *)frame->data[0];
147 pic->argb_stride = frame->linesize[0] / 4;
149 if (frame->linesize[1] != frame->linesize[2] || s->cr_threshold) {
150 if (!s->chroma_warning && !s->cr_threshold) {
151 av_log(avctx, AV_LOG_WARNING,
152 "Copying frame due to differing chroma linesizes.\n");
153 s->chroma_warning = 1;
155 alt_frame = av_frame_alloc();
157 ret = AVERROR(ENOMEM);
160 alt_frame->width = frame->width;
161 alt_frame->height = frame->height;
162 alt_frame->format = frame->format;
164 alt_frame->format = AV_PIX_FMT_YUVA420P;
165 ret = av_frame_get_buffer(alt_frame, 32);
168 alt_frame->format = frame->format;
169 av_frame_copy(alt_frame, frame);
171 if (s->cr_threshold) {
176 s->ref = av_frame_clone(frame);
178 ret = AVERROR(ENOMEM);
183 alt_frame->format = AV_PIX_FMT_YUVA420P;
184 for (y = 0; y < frame->height; y+= bs) {
185 for (x = 0; x < frame->width; x+= bs) {
188 for (p = 0; p < 3; p++) {
190 int w = FF_CEIL_RSHIFT(frame->width , !!p);
191 int h = FF_CEIL_RSHIFT(frame->height, !!p);
194 for (y2 = ys; y2 < FFMIN(ys + bs2, h); y2++) {
195 for (x2 = xs; x2 < FFMIN(xs + bs2, w); x2++) {
196 int diff = frame->data[p][frame->linesize[p] * y2 + x2]
197 -s->ref->data[p][frame->linesize[p] * y2 + x2];
202 skip = sse < s->cr_threshold && frame->data[3] != s->ref->data[3];
204 for (p = 0; p < 3; p++) {
206 int w = FF_CEIL_RSHIFT(frame->width , !!p);
207 int h = FF_CEIL_RSHIFT(frame->height, !!p);
210 for (y2 = ys; y2 < FFMIN(ys + bs2, h); y2++) {
211 memcpy(&s->ref->data[p][frame->linesize[p] * y2 + xs],
212 & frame->data[p][frame->linesize[p] * y2 + xs], FFMIN(bs2, w-xs));
215 for (y2 = y; y2 < FFMIN(y+bs, frame->height); y2++) {
216 memset(&frame->data[3][frame->linesize[3] * y2 + x],
218 FFMIN(bs, frame->width-x));
226 pic->y = frame->data[0];
227 pic->u = frame->data[1];
228 pic->v = frame->data[2];
229 pic->y_stride = frame->linesize[0];
230 pic->uv_stride = frame->linesize[1];
231 if (frame->format == AV_PIX_FMT_YUVA420P) {
232 pic->colorspace = WEBP_YUV420A;
233 pic->a = frame->data[3];
234 pic->a_stride = frame->linesize[3];
236 WebPCleanupTransparentArea(pic);
238 pic->colorspace = WEBP_YUV420;
242 /* We do not have a way to automatically prioritize RGB over YUV
243 in automatic pixel format conversion based on whether we're
244 encoding lossless or lossy, so we do conversion with libwebp as
246 if (!s->conversion_warning) {
247 av_log(avctx, AV_LOG_WARNING,
248 "Using libwebp for YUV-to-RGB conversion. You may want "
249 "to consider passing in RGB instead for lossless "
251 s->conversion_warning = 1;
254 #if (WEBP_ENCODER_ABI_VERSION <= 0x201)
255 /* libwebp should do the conversion automatically, but there is a
256 bug that causes it to return an error instead, so a work-around
258 See https://code.google.com/p/webp/issues/detail?id=178 */
259 pic->memory_ = (void*)1; /* something non-null */
260 ret = WebPPictureYUVAToARGB(pic);
262 av_log(avctx, AV_LOG_ERROR,
263 "WebPPictureYUVAToARGB() failed with error: %d\n",
265 ret = libwebp_error_to_averror(pic->error_code);
268 pic->memory_ = NULL; /* restore pointer */
273 WebPMemoryWriterInit(&mw);
274 pic->custom_ptr = &mw;
275 pic->writer = WebPMemoryWrite;
277 ret = WebPEncode(&s->config, pic);
279 av_log(avctx, AV_LOG_ERROR, "WebPEncode() failed with error: %d\n",
281 ret = libwebp_error_to_averror(pic->error_code);
285 ret = ff_alloc_packet(pkt, mw.size);
288 memcpy(pkt->data, mw.mem, mw.size);
290 pkt->flags |= AV_PKT_FLAG_KEY;
294 #if (WEBP_ENCODER_ABI_VERSION > 0x0203)
295 WebPMemoryWriterClear(&mw);
297 free(mw.mem); /* must use free() according to libwebp documentation */
299 WebPPictureFree(pic);
301 av_frame_free(&alt_frame);
306 static int libwebp_encode_close(AVCodecContext *avctx)
308 LibWebPContext *s = avctx->priv_data;
310 av_frame_free(&s->ref);
315 #define OFFSET(x) offsetof(LibWebPContext, x)
316 #define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
317 static const AVOption options[] = {
318 { "lossless", "Use lossless mode", OFFSET(lossless), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, VE },
319 { "preset", "Configuration preset", OFFSET(preset), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, WEBP_PRESET_TEXT, VE, "preset" },
320 { "none", "do not use a preset", 0, AV_OPT_TYPE_CONST, { .i64 = -1 }, 0, 0, VE, "preset" },
321 { "default", "default preset", 0, AV_OPT_TYPE_CONST, { .i64 = WEBP_PRESET_DEFAULT }, 0, 0, VE, "preset" },
322 { "picture", "digital picture, like portrait, inner shot", 0, AV_OPT_TYPE_CONST, { .i64 = WEBP_PRESET_PICTURE }, 0, 0, VE, "preset" },
323 { "photo", "outdoor photograph, with natural lighting", 0, AV_OPT_TYPE_CONST, { .i64 = WEBP_PRESET_PHOTO }, 0, 0, VE, "preset" },
324 { "drawing", "hand or line drawing, with high-contrast details", 0, AV_OPT_TYPE_CONST, { .i64 = WEBP_PRESET_DRAWING }, 0, 0, VE, "preset" },
325 { "icon", "small-sized colorful images", 0, AV_OPT_TYPE_CONST, { .i64 = WEBP_PRESET_ICON }, 0, 0, VE, "preset" },
326 { "text", "text-like", 0, AV_OPT_TYPE_CONST, { .i64 = WEBP_PRESET_TEXT }, 0, 0, VE, "preset" },
327 { "cr_threshold","Conditional replenishment threshold", OFFSET(cr_threshold), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, VE },
328 { "cr_size" ,"Conditional replenishment block size", OFFSET(cr_size) , AV_OPT_TYPE_INT, { .i64 = 16 }, 0, 256, VE },
329 { "quality" ,"Quality", OFFSET(quality), AV_OPT_TYPE_FLOAT, { .dbl = 75 }, 0, 100, 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,