FFmpeg
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
libwebpenc.c
Go to the documentation of this file.
1 /*
2  * WebP encoding support via libwebp
3  * Copyright (c) 2013 Justin Ruggles <justin.ruggles@gmail.com>
4  *
5  * This file is part of FFmpeg.
6  *
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.
11  *
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.
16  *
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
20  */
21 
22 /**
23  * @file
24  * WebP encoder using libwebp
25  */
26 
27 #include <webp/encode.h>
28 
29 #include "libavutil/common.h"
30 #include "libavutil/frame.h"
31 #include "libavutil/imgutils.h"
32 #include "libavutil/opt.h"
33 #include "avcodec.h"
34 #include "internal.h"
35 
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
45  int cr_size;
48 
49 static int libwebp_error_to_averror(int err)
50 {
51  switch (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);
59  }
60  return AVERROR_UNKNOWN;
61 }
62 
64 {
65  LibWebPContext *s = avctx->priv_data;
66  int ret;
67 
68  if (avctx->global_quality >= 0)
69  s->quality = av_clipf(avctx->global_quality / (float)FF_QP2LAMBDA,
70  0.0f, 100.0f);
71 
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);
76  }
77 
78  if (s->preset >= WEBP_PRESET_DEFAULT) {
79  ret = WebPConfigPreset(&s->config, s->preset, s->quality);
80  if (!ret)
81  return AVERROR_UNKNOWN;
82  s->lossless = s->config.lossless;
83  s->quality = s->config.quality;
84  avctx->compression_level = s->config.method;
85  } else {
86  ret = WebPConfigInit(&s->config);
87  if (!ret)
88  return AVERROR_UNKNOWN;
89 
90  s->config.lossless = s->lossless;
91  s->config.quality = s->quality;
92  s->config.method = avctx->compression_level;
93 
94  ret = WebPValidateConfig(&s->config);
95  if (!ret)
96  return AVERROR(EINVAL);
97  }
98 
99  av_log(avctx, AV_LOG_DEBUG, "%s - quality=%.1f method=%d\n",
100  s->lossless ? "Lossless" : "Lossy", s->quality,
101  avctx->compression_level);
102 
103  return 0;
104 }
105 
107  const AVFrame *frame, int *got_packet)
108 {
109  LibWebPContext *s = avctx->priv_data;
110  AVFrame *alt_frame = NULL;
111  WebPPicture *pic = NULL;
112  WebPMemoryWriter mw = { 0 };
113  int ret;
114 
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);
119  }
120 
121  pic = av_malloc(sizeof(*pic));
122  if (!pic)
123  return AVERROR(ENOMEM);
124 
125  ret = WebPPictureInit(pic);
126  if (!ret) {
127  ret = AVERROR_UNKNOWN;
128  goto end;
129  }
130  pic->width = avctx->width;
131  pic->height = avctx->height;
132 
133  if (avctx->pix_fmt == AV_PIX_FMT_RGB32) {
134  if (!s->lossless) {
135  /* libwebp will automatically convert RGB input to YUV when
136  encoding lossy. */
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 "
141  "encoding.\n");
142  s->conversion_warning = 1;
143  }
144  }
145  pic->use_argb = 1;
146  pic->argb = (uint32_t *)frame->data[0];
147  pic->argb_stride = frame->linesize[0] / 4;
148  } else {
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;
154  }
155  alt_frame = av_frame_alloc();
156  if (!alt_frame) {
157  ret = AVERROR(ENOMEM);
158  goto end;
159  }
160  alt_frame->width = frame->width;
161  alt_frame->height = frame->height;
162  alt_frame->format = frame->format;
163  if (s->cr_threshold)
164  alt_frame->format = AV_PIX_FMT_YUVA420P;
165  ret = av_frame_get_buffer(alt_frame, 32);
166  if (ret < 0)
167  goto end;
168  alt_frame->format = frame->format;
169  av_frame_copy(alt_frame, frame);
170  frame = alt_frame;
171  if (s->cr_threshold) {
172  int x,y, x2, y2, p;
173  int bs = s->cr_size;
174 
175  if (!s->ref) {
176  s->ref = av_frame_clone(frame);
177  if (!s->ref) {
178  ret = AVERROR(ENOMEM);
179  goto end;
180  }
181  }
182 
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) {
186  int skip;
187  int sse = 0;
188  for (p = 0; p < 3; p++) {
189  int bs2 = bs >> !!p;
190  int w = FF_CEIL_RSHIFT(frame->width , !!p);
191  int h = FF_CEIL_RSHIFT(frame->height, !!p);
192  int xs = x >> !!p;
193  int ys = y >> !!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];
198  sse += diff*diff;
199  }
200  }
201  }
202  skip = sse < s->cr_threshold && frame->data[3] != s->ref->data[3];
203  if (!skip)
204  for (p = 0; p < 3; p++) {
205  int bs2 = bs >> !!p;
206  int w = FF_CEIL_RSHIFT(frame->width , !!p);
207  int h = FF_CEIL_RSHIFT(frame->height, !!p);
208  int xs = x >> !!p;
209  int ys = y >> !!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));
213  }
214  }
215  for (y2 = y; y2 < FFMIN(y+bs, frame->height); y2++) {
216  memset(&frame->data[3][frame->linesize[3] * y2 + x],
217  skip ? 0 : 255,
218  FFMIN(bs, frame->width-x));
219  }
220  }
221  }
222  }
223  }
224 
225  pic->use_argb = 0;
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];
235  if (alt_frame)
236  WebPCleanupTransparentArea(pic);
237  } else {
238  pic->colorspace = WEBP_YUV420;
239  }
240 
241  if (s->lossless) {
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
245  a convenience. */
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 "
250  "encoding.\n");
251  s->conversion_warning = 1;
252  }
253 
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
257  is required.
258  See https://code.google.com/p/webp/issues/detail?id=178 */
259  pic->memory_ = (void*)1; /* something non-null */
260  ret = WebPPictureYUVAToARGB(pic);
261  if (!ret) {
262  av_log(avctx, AV_LOG_ERROR,
263  "WebPPictureYUVAToARGB() failed with error: %d\n",
264  pic->error_code);
265  ret = libwebp_error_to_averror(pic->error_code);
266  goto end;
267  }
268  pic->memory_ = NULL; /* restore pointer */
269 #endif
270  }
271  }
272 
273  WebPMemoryWriterInit(&mw);
274  pic->custom_ptr = &mw;
275  pic->writer = WebPMemoryWrite;
276 
277  ret = WebPEncode(&s->config, pic);
278  if (!ret) {
279  av_log(avctx, AV_LOG_ERROR, "WebPEncode() failed with error: %d\n",
280  pic->error_code);
281  ret = libwebp_error_to_averror(pic->error_code);
282  goto end;
283  }
284 
285  ret = ff_alloc_packet(pkt, mw.size);
286  if (ret < 0)
287  goto end;
288  memcpy(pkt->data, mw.mem, mw.size);
289 
290  pkt->flags |= AV_PKT_FLAG_KEY;
291  *got_packet = 1;
292 
293 end:
294 #if (WEBP_ENCODER_ABI_VERSION > 0x0203)
295  WebPMemoryWriterClear(&mw);
296 #else
297  free(mw.mem); /* must use free() according to libwebp documentation */
298 #endif
299  WebPPictureFree(pic);
300  av_freep(&pic);
301  av_frame_free(&alt_frame);
302 
303  return ret;
304 }
305 
307 {
308  LibWebPContext *s = avctx->priv_data;
309 
310  av_frame_free(&s->ref);
311 
312  return 0;
313 }
314 
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 },
330  { NULL },
331 };
332 
333 
334 static const AVClass class = {
335  .class_name = "libwebp",
336  .item_name = av_default_item_name,
337  .option = options,
339 };
340 
342  { "compression_level", "4" },
343  { "global_quality", "-1" },
344  { NULL },
345 };
346 
348  .name = "libwebp",
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),
354  .encode2 = libwebp_encode_frame,
355  .close = libwebp_encode_close,
356  .pix_fmts = (const enum AVPixelFormat[]) {
360  },
361  .priv_class = &class,
362  .defaults = libwebp_defaults,
363 };