You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

364 lines
14KB

  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. * @file
  23. * WebP encoder using libwebp
  24. */
  25. #include <webp/encode.h>
  26. #include "libavutil/common.h"
  27. #include "libavutil/frame.h"
  28. #include "libavutil/imgutils.h"
  29. #include "libavutil/opt.h"
  30. #include "avcodec.h"
  31. #include "internal.h"
  32. typedef struct LibWebPContext {
  33. AVClass *class; // class for AVOptions
  34. float quality; // lossy quality 0 - 100
  35. int lossless; // use lossless encoding
  36. int preset; // configuration preset
  37. int chroma_warning; // chroma linesize mismatch warning has been printed
  38. int conversion_warning; // pixel format conversion warning has been printed
  39. WebPConfig config; // libwebp configuration
  40. AVFrame *ref;
  41. int cr_size;
  42. int cr_threshold;
  43. } LibWebPContext;
  44. static int libwebp_error_to_averror(int err)
  45. {
  46. switch (err) {
  47. case VP8_ENC_ERROR_OUT_OF_MEMORY:
  48. case VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY:
  49. return AVERROR(ENOMEM);
  50. case VP8_ENC_ERROR_NULL_PARAMETER:
  51. case VP8_ENC_ERROR_INVALID_CONFIGURATION:
  52. case VP8_ENC_ERROR_BAD_DIMENSION:
  53. return AVERROR(EINVAL);
  54. }
  55. return AVERROR_UNKNOWN;
  56. }
  57. static av_cold int libwebp_encode_init(AVCodecContext *avctx)
  58. {
  59. LibWebPContext *s = avctx->priv_data;
  60. int ret;
  61. if (avctx->global_quality < 0)
  62. avctx->global_quality = 75 * FF_QP2LAMBDA;
  63. s->quality = av_clipf(avctx->global_quality / (float)FF_QP2LAMBDA,
  64. 0.0f, 100.0f);
  65. if (avctx->compression_level < 0 || avctx->compression_level > 6) {
  66. av_log(avctx, AV_LOG_WARNING, "invalid compression level: %d\n",
  67. avctx->compression_level);
  68. avctx->compression_level = av_clip(avctx->compression_level, 0, 6);
  69. }
  70. if (s->preset >= WEBP_PRESET_DEFAULT) {
  71. ret = WebPConfigPreset(&s->config, s->preset, s->quality);
  72. if (!ret)
  73. return AVERROR_UNKNOWN;
  74. s->lossless = s->config.lossless;
  75. s->quality = s->config.quality;
  76. avctx->compression_level = s->config.method;
  77. } else {
  78. ret = WebPConfigInit(&s->config);
  79. if (!ret)
  80. return AVERROR_UNKNOWN;
  81. s->config.lossless = s->lossless;
  82. s->config.quality = s->quality;
  83. s->config.method = avctx->compression_level;
  84. ret = WebPValidateConfig(&s->config);
  85. if (!ret)
  86. return AVERROR(EINVAL);
  87. }
  88. av_log(avctx, AV_LOG_DEBUG, "%s - quality=%.1f method=%d\n",
  89. s->lossless ? "Lossless" : "Lossy", s->quality,
  90. avctx->compression_level);
  91. return 0;
  92. }
  93. static int libwebp_encode_frame(AVCodecContext *avctx, AVPacket *pkt,
  94. const AVFrame *frame, int *got_packet)
  95. {
  96. LibWebPContext *s = avctx->priv_data;
  97. AVFrame *alt_frame = NULL;
  98. WebPPicture *pic = NULL;
  99. WebPMemoryWriter mw = { 0 };
  100. int ret;
  101. if (avctx->width > WEBP_MAX_DIMENSION || avctx->height > WEBP_MAX_DIMENSION) {
  102. av_log(avctx, AV_LOG_ERROR, "Picture size is too large. Max is %dx%d.\n",
  103. WEBP_MAX_DIMENSION, WEBP_MAX_DIMENSION);
  104. return AVERROR(EINVAL);
  105. }
  106. pic = av_malloc(sizeof(*pic));
  107. if (!pic)
  108. return AVERROR(ENOMEM);
  109. ret = WebPPictureInit(pic);
  110. if (!ret) {
  111. ret = AVERROR_UNKNOWN;
  112. goto end;
  113. }
  114. pic->width = avctx->width;
  115. pic->height = avctx->height;
  116. if (avctx->pix_fmt == AV_PIX_FMT_RGB32) {
  117. if (!s->lossless) {
  118. /* libwebp will automatically convert RGB input to YUV when
  119. encoding lossy. */
  120. if (!s->conversion_warning) {
  121. av_log(avctx, AV_LOG_WARNING,
  122. "Using libwebp for RGB-to-YUV conversion. You may want "
  123. "to consider passing in YUV instead for lossy "
  124. "encoding.\n");
  125. s->conversion_warning = 1;
  126. }
  127. }
  128. pic->use_argb = 1;
  129. pic->argb = (uint32_t *)frame->data[0];
  130. pic->argb_stride = frame->linesize[0] / 4;
  131. } else {
  132. if (frame->linesize[1] != frame->linesize[2] || s->cr_threshold) {
  133. if (!s->chroma_warning && !s->cr_threshold) {
  134. av_log(avctx, AV_LOG_WARNING,
  135. "Copying frame due to differing chroma linesizes.\n");
  136. s->chroma_warning = 1;
  137. }
  138. alt_frame = av_frame_alloc();
  139. if (!alt_frame) {
  140. ret = AVERROR(ENOMEM);
  141. goto end;
  142. }
  143. alt_frame->width = frame->width;
  144. alt_frame->height = frame->height;
  145. alt_frame->format = frame->format;
  146. if (s->cr_threshold)
  147. alt_frame->format = AV_PIX_FMT_YUVA420P;
  148. ret = av_frame_get_buffer(alt_frame, 32);
  149. if (ret < 0)
  150. goto end;
  151. alt_frame->format = frame->format;
  152. av_frame_copy(alt_frame, frame);
  153. frame = alt_frame;
  154. if (s->cr_threshold) {
  155. int x,y, x2, y2, p;
  156. int bs = s->cr_size;
  157. if (!s->ref) {
  158. s->ref = av_frame_clone(frame);
  159. if (!s->ref) {
  160. ret = AVERROR(ENOMEM);
  161. goto end;
  162. }
  163. }
  164. alt_frame->format = AV_PIX_FMT_YUVA420P;
  165. for (y = 0; y < frame->height; y+= bs) {
  166. for (x = 0; x < frame->width; x+= bs) {
  167. int skip;
  168. int sse = 0;
  169. for (p = 0; p < 3; p++) {
  170. int bs2 = bs >> !!p;
  171. int w = FF_CEIL_RSHIFT(frame->width , !!p);
  172. int h = FF_CEIL_RSHIFT(frame->height, !!p);
  173. int xs = x >> !!p;
  174. int ys = y >> !!p;
  175. for (y2 = ys; y2 < FFMIN(ys + bs2, h); y2++) {
  176. for (x2 = xs; x2 < FFMIN(xs + bs2, w); x2++) {
  177. int diff = frame->data[p][frame->linesize[p] * y2 + x2]
  178. -s->ref->data[p][frame->linesize[p] * y2 + x2];
  179. sse += diff*diff;
  180. }
  181. }
  182. }
  183. skip = sse < s->cr_threshold && frame->data[3] != s->ref->data[3];
  184. if (!skip)
  185. for (p = 0; p < 3; p++) {
  186. int bs2 = bs >> !!p;
  187. int w = FF_CEIL_RSHIFT(frame->width , !!p);
  188. int h = FF_CEIL_RSHIFT(frame->height, !!p);
  189. int xs = x >> !!p;
  190. int ys = y >> !!p;
  191. for (y2 = ys; y2 < FFMIN(ys + bs2, h); y2++) {
  192. memcpy(&s->ref->data[p][frame->linesize[p] * y2 + xs],
  193. & frame->data[p][frame->linesize[p] * y2 + xs], FFMIN(bs2, w-xs));
  194. }
  195. }
  196. for (y2 = y; y2 < FFMIN(y+bs, frame->height); y2++) {
  197. memset(&frame->data[3][frame->linesize[3] * y2 + x],
  198. skip ? 0 : 255,
  199. FFMIN(bs, frame->width-x));
  200. }
  201. }
  202. }
  203. }
  204. }
  205. pic->use_argb = 0;
  206. pic->y = frame->data[0];
  207. pic->u = frame->data[1];
  208. pic->v = frame->data[2];
  209. pic->y_stride = frame->linesize[0];
  210. pic->uv_stride = frame->linesize[1];
  211. if (frame->format == AV_PIX_FMT_YUVA420P) {
  212. pic->colorspace = WEBP_YUV420A;
  213. pic->a = frame->data[3];
  214. pic->a_stride = frame->linesize[3];
  215. if (alt_frame)
  216. WebPCleanupTransparentArea(pic);
  217. } else {
  218. pic->colorspace = WEBP_YUV420;
  219. }
  220. if (s->lossless) {
  221. /* We do not have a way to automatically prioritize RGB over YUV
  222. in automatic pixel format conversion based on whether we're
  223. encoding lossless or lossy, so we do conversion with libwebp as
  224. a convenience. */
  225. if (!s->conversion_warning) {
  226. av_log(avctx, AV_LOG_WARNING,
  227. "Using libwebp for YUV-to-RGB conversion. You may want "
  228. "to consider passing in RGB instead for lossless "
  229. "encoding.\n");
  230. s->conversion_warning = 1;
  231. }
  232. #if (WEBP_ENCODER_ABI_VERSION <= 0x201)
  233. /* libwebp should do the conversion automatically, but there is a
  234. bug that causes it to return an error instead, so a work-around
  235. is required.
  236. See https://code.google.com/p/webp/issues/detail?id=178 */
  237. pic->memory_ = (void*)1; /* something non-null */
  238. ret = WebPPictureYUVAToARGB(pic);
  239. if (!ret) {
  240. av_log(avctx, AV_LOG_ERROR,
  241. "WebPPictureYUVAToARGB() failed with error: %d\n",
  242. pic->error_code);
  243. ret = libwebp_error_to_averror(pic->error_code);
  244. goto end;
  245. }
  246. pic->memory_ = NULL; /* restore pointer */
  247. #endif
  248. }
  249. }
  250. WebPMemoryWriterInit(&mw);
  251. pic->custom_ptr = &mw;
  252. pic->writer = WebPMemoryWrite;
  253. ret = WebPEncode(&s->config, pic);
  254. if (!ret) {
  255. av_log(avctx, AV_LOG_ERROR, "WebPEncode() failed with error: %d\n",
  256. pic->error_code);
  257. ret = libwebp_error_to_averror(pic->error_code);
  258. goto end;
  259. }
  260. ret = ff_alloc_packet(pkt, mw.size);
  261. if (ret < 0)
  262. goto end;
  263. memcpy(pkt->data, mw.mem, mw.size);
  264. pkt->flags |= AV_PKT_FLAG_KEY;
  265. *got_packet = 1;
  266. end:
  267. #if (WEBP_ENCODER_ABI_VERSION > 0x0203)
  268. WebPMemoryWriterClear(&mw);
  269. #else
  270. free(mw.mem); /* must use free() according to libwebp documentation */
  271. #endif
  272. WebPPictureFree(pic);
  273. av_freep(&pic);
  274. av_frame_free(&alt_frame);
  275. return ret;
  276. }
  277. static int libwebp_encode_close(AVCodecContext *avctx)
  278. {
  279. LibWebPContext *s = avctx->priv_data;
  280. av_frame_free(&s->ref);
  281. return 0;
  282. }
  283. #define OFFSET(x) offsetof(LibWebPContext, x)
  284. #define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
  285. static const AVOption options[] = {
  286. { "lossless", "Use lossless mode", OFFSET(lossless), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, VE },
  287. { "preset", "Configuration preset", OFFSET(preset), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, WEBP_PRESET_TEXT, VE, "preset" },
  288. { "none", "do not use a preset", 0, AV_OPT_TYPE_CONST, { .i64 = -1 }, 0, 0, VE, "preset" },
  289. { "default", "default preset", 0, AV_OPT_TYPE_CONST, { .i64 = WEBP_PRESET_DEFAULT }, 0, 0, VE, "preset" },
  290. { "picture", "digital picture, like portrait, inner shot", 0, AV_OPT_TYPE_CONST, { .i64 = WEBP_PRESET_PICTURE }, 0, 0, VE, "preset" },
  291. { "photo", "outdoor photograph, with natural lighting", 0, AV_OPT_TYPE_CONST, { .i64 = WEBP_PRESET_PHOTO }, 0, 0, VE, "preset" },
  292. { "drawing", "hand or line drawing, with high-contrast details", 0, AV_OPT_TYPE_CONST, { .i64 = WEBP_PRESET_DRAWING }, 0, 0, VE, "preset" },
  293. { "icon", "small-sized colorful images", 0, AV_OPT_TYPE_CONST, { .i64 = WEBP_PRESET_ICON }, 0, 0, VE, "preset" },
  294. { "text", "text-like", 0, AV_OPT_TYPE_CONST, { .i64 = WEBP_PRESET_TEXT }, 0, 0, VE, "preset" },
  295. { "cr_threshold","Conditional replenishment threshold", OFFSET(cr_threshold), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, VE },
  296. { "cr_size" ,"Conditional replenishment block size", OFFSET(cr_size) , AV_OPT_TYPE_INT, { .i64 = 16 }, 0, 256, VE },
  297. { NULL },
  298. };
  299. static const AVClass class = {
  300. .class_name = "libwebp",
  301. .item_name = av_default_item_name,
  302. .option = options,
  303. .version = LIBAVUTIL_VERSION_INT,
  304. };
  305. static const AVCodecDefault libwebp_defaults[] = {
  306. { "compression_level", "4" },
  307. { "global_quality", "-1" },
  308. { NULL },
  309. };
  310. AVCodec ff_libwebp_encoder = {
  311. .name = "libwebp",
  312. .long_name = NULL_IF_CONFIG_SMALL("libwebp WebP image"),
  313. .type = AVMEDIA_TYPE_VIDEO,
  314. .id = AV_CODEC_ID_WEBP,
  315. .priv_data_size = sizeof(LibWebPContext),
  316. .init = libwebp_encode_init,
  317. .encode2 = libwebp_encode_frame,
  318. .close = libwebp_encode_close,
  319. .pix_fmts = (const enum AVPixelFormat[]) {
  320. AV_PIX_FMT_RGB32,
  321. AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUVA420P,
  322. AV_PIX_FMT_NONE
  323. },
  324. .priv_class = &class,
  325. .defaults = libwebp_defaults,
  326. };