1 /*
2 * JPEG XL encoding support via libjxl
3 * Copyright (c) 2021 Leo Izen <leo.izen@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 * JPEG XL encoder using libjxl
25 */
26
27 #include <string.h>
28
40
45
46 #include <jxl/encode.h>
47 #include <jxl/thread_parallel_runner.h>
49
62
63 /* animation stuff */
68
69 /**
70 * Map a quality setting for -qscale roughly from libjpeg
71 * quality numbers to libjxl's butteraugli distance for
72 * photographic content.
73 *
74 * Setting distance explicitly is preferred, but this will
75 * allow qscale to be used as a fallback.
76 *
77 * This function is continuous and injective on [0, 100] which
78 * makes it monotonic.
79 *
80 * @param quality 0.0 to 100.0 quality setting, libjpeg quality
81 * @return Butteraugli distance between 0.0 and 15.0
82 */
84 {
86 return 0.0;
88 return (100.0 -
quality) * 0.10;
90 return 0.1 + (100.0 -
quality) * 0.09;
93 else
94 return 15.0;
95 }
96
97 /**
98 * Initialize the encoder on a per-file basis. All of these need to be set
99 * once each time the encoder is reset, which is each frame for still
100 * images, to make the image2 muxer work. For animation this is run once.
101 *
102 * @return 0 upon success, negative on failure.
103 */
105 {
107
108 /* reset the encoder every frame for image2 muxer */
109 JxlEncoderReset(
ctx->encoder);
110
111 /* This needs to be set each time the encoder is reset */
112 if (JxlEncoderSetParallelRunner(
ctx->encoder, JxlThreadParallelRunner,
ctx->runner)
113 != JXL_ENC_SUCCESS) {
116 }
117
118 ctx->options = JxlEncoderFrameSettingsCreate(
ctx->encoder,
NULL);
122 }
123
124 return 0;
125 }
126
127 /**
128 * Global encoder initialization. This only needs to be run once,
129 * not every frame.
130 */
132 {
134 JxlMemoryManager manager;
135
137 ctx->encoder = JxlEncoderCreate(&manager);
141 }
142
147 }
148
149 ctx->buffer_size = 4096;
151
155 }
156
157 /* check for negative, our default */
158 if (
ctx->distance < 0.0) {
159 /* use ffmpeg.c -q option if passed */
162 else
163 /* default 1.0 matches cjxl */
165 }
166 /*
167 * 0.01 is the minimum distance accepted for lossy
168 * interpreting any positive value less than this as minimum
169 */
170 if (
ctx->distance > 0.0 &&
ctx->distance < 0.01)
171 ctx->distance = 0.01;
172
173 return 0;
174 }
175
176 /**
177 * Initializer for the animation encoder. This calls the other initializers
178 * to prevent code duplication and also allocates the prev-frame used in the
179 * encoder.
180 */
182 {
185
189
193
197
198 return 0;
199 }
200
201 /**
202 * Populate a JxlColorEncoding with the given enum AVColorPrimaries.
203 * @return < 0 upon failure, >= 0 upon success
204 */
206 {
208
209 switch (prm) {
211 jxl_color->primaries = JXL_PRIMARIES_SRGB;
212 jxl_color->white_point = JXL_WHITE_POINT_D65;
213 return 0;
215 jxl_color->primaries = JXL_PRIMARIES_2100;
216 jxl_color->white_point = JXL_WHITE_POINT_D65;
217 return 0;
219 jxl_color->primaries = JXL_PRIMARIES_P3;
220 jxl_color->white_point = JXL_WHITE_POINT_DCI;
221 return 0;
223 jxl_color->primaries = JXL_PRIMARIES_P3;
224 jxl_color->white_point = JXL_WHITE_POINT_D65;
225 return 0;
227 av_log(avctx,
AV_LOG_WARNING,
"Unknown primaries, assuming BT.709/sRGB. Colors may be wrong.\n");
228 jxl_color->primaries = JXL_PRIMARIES_SRGB;
229 jxl_color->white_point = JXL_WHITE_POINT_D65;
230 return 0;
231 }
232
236
237 jxl_color->primaries = JXL_PRIMARIES_CUSTOM;
238 jxl_color->white_point = JXL_WHITE_POINT_CUSTOM;
239
240 jxl_color->primaries_red_xy[0] =
av_q2d(
desc->prim.r.x);
241 jxl_color->primaries_red_xy[1] =
av_q2d(
desc->prim.r.y);
242 jxl_color->primaries_green_xy[0] =
av_q2d(
desc->prim.g.x);
243 jxl_color->primaries_green_xy[1] =
av_q2d(
desc->prim.g.y);
244 jxl_color->primaries_blue_xy[0] =
av_q2d(
desc->prim.b.x);
245 jxl_color->primaries_blue_xy[1] =
av_q2d(
desc->prim.b.y);
246 jxl_color->white_point_xy[0] =
av_q2d(
desc->wp.x);
247 jxl_color->white_point_xy[1] =
av_q2d(
desc->wp.y);
248
249 return 0;
250 }
251
254 {
255 JxlColorEncoding jxl_color;
258
262 jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_709;
263 break;
265 jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR;
266 break;
268 jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
269 break;
271 jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_DCI;
272 break;
274 jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_PQ;
275 break;
277 jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_HLG;
278 break;
280 jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
281 jxl_color.gamma = 1/2.2f;
282 break;
284 jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
285 jxl_color.gamma = 1/2.8f;
286 break;
287 default:
290 "Unknown transfer function, assuming Linear Light. Colors may be wrong.\n");
291 jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR;
292 } else {
294 "Unknown transfer function, assuming IEC61966-2-1/sRGB. Colors may be wrong.\n");
295 jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
296 }
297 }
298
299 jxl_color.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;
300 if (
info->num_color_channels == 1)
301 jxl_color.color_space = JXL_COLOR_SPACE_GRAY;
302 else
303 jxl_color.color_space = JXL_COLOR_SPACE_RGB;
304
310
311 if (JxlEncoderSetColorEncoding(
ctx->encoder, &jxl_color) != JXL_ENC_SUCCESS) {
314 }
315
316 return 0;
317 }
318
319 /**
320 * Sends metadata to libjxl based on the first frame of the stream, such as pixel format,
321 * orientation, bit depth, and that sort of thing.
322 */
324 {
331 JxlPixelFormat *jxl_fmt = &
ctx->jxl_fmt;
332 int bits_per_sample;
333 int orientation;
335 #if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 8, 0)
336 JxlBitDepth jxl_bit_depth;
337 #endif
338
339 /* populate the basic info settings */
340 JxlEncoderInitBasicInfo(&
info);
344 info.num_extra_channels = (jxl_fmt->num_channels + 1) & 0x1;
345 info.num_color_channels = jxl_fmt->num_channels -
info.num_extra_channels;
349 info.alpha_bits = (
info.num_extra_channels > 0) *
info.bits_per_sample;
351 info.exponent_bits_per_sample =
info.bits_per_sample > 16 ? 8 : 5;
352 info.alpha_exponent_bits =
info.alpha_bits ?
info.exponent_bits_per_sample : 0;
353 jxl_fmt->data_type =
info.bits_per_sample > 16 ? JXL_TYPE_FLOAT : JXL_TYPE_FLOAT16;
354 } else {
355 info.exponent_bits_per_sample = 0;
356 info.alpha_exponent_bits = 0;
357 jxl_fmt->data_type =
info.bits_per_sample <= 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16;
358 }
359
360 if (
info.alpha_bits) {
363 info.alpha_premultiplied = 1;
366 }
367 }
368
369 #if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 8, 0)
370 jxl_bit_depth.bits_per_sample = bits_per_sample;
371 jxl_bit_depth.type = JXL_BIT_DEPTH_FROM_PIXEL_FORMAT;
373 info.exponent_bits_per_sample : 0;
374 #endif
375
376 /* JPEG XL format itself does not support limited range */
379 av_log(avctx,
AV_LOG_WARNING,
"This encoder does not support limited (tv) range, colors will be wrong!\n");
382
383 /* bitexact lossless requires there to be no XYB transform */
384 info.uses_original_profile =
ctx->distance == 0.0 || !
ctx->xyb;
385
387 if (sd) {
399 } else {
401 }
407 } else {
409 if (sd)
411 else
413 }
414
415 /* av_display_matrix_flip is a right-multipilcation */
416 /* i.e. flip is applied before the previous matrix */
417 if (
frame->linesize < 0)
419
421 /* JPEG XL orientation flag agrees with EXIF for values 1-8 */
422 if (orientation) {
423 info.orientation = orientation;
424 } else {
426 info.orientation =
frame->linesize[0] >= 0 ? JXL_ORIENT_IDENTITY : JXL_ORIENT_FLIP_VERTICAL;
427 }
428
429 /* restore the previous value */
430 if (
frame->linesize < 0)
432
433 if (animated) {
434 info.have_animation = 1;
435 info.animation.have_timecodes = 0;
436 info.animation.num_loops = 0;
437 /* avctx->timebase is in seconds per tick, so we take the reciprocol */
440 }
441
442 if (JxlEncoderSetBasicInfo(
ctx->encoder, &
info) != JXL_ENC_SUCCESS) {
445 goto end;
446 }
447
448 if (
info.alpha_bits) {
449 JxlExtraChannelInfo extra_info;
450 JxlEncoderInitExtraChannelInfo(JXL_CHANNEL_ALPHA, &extra_info);
451 extra_info.bits_per_sample =
info.alpha_bits;
452 extra_info.exponent_bits_per_sample =
info.alpha_exponent_bits;
453 extra_info.alpha_premultiplied =
info.alpha_premultiplied;
454
455 if (JxlEncoderSetExtraChannelInfo(
ctx->encoder, 0, &extra_info) != JXL_ENC_SUCCESS) {
458 }
459 }
460
462 if (sd && sd->
size && JxlEncoderSetICCProfile(
ctx->encoder, sd->
data, sd->
size) != JXL_ENC_SUCCESS) {
465 }
466
467 if (!sd || !sd->
size)
469
470 #if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 8, 0)
471 if (JxlEncoderSetFrameBitDepth(
ctx->options, &jxl_bit_depth) != JXL_ENC_SUCCESS)
473 #endif
474
475 if (exif_buffer) {
476 if (JxlEncoderUseBoxes(
ctx->encoder) != JXL_ENC_SUCCESS)
478 }
479
480 /* depending on basic info, level 10 might
481 * be required instead of level 5 */
482 if (JxlEncoderGetRequiredCodestreamLevel(
ctx->encoder) > 5) {
483 if (JxlEncoderSetCodestreamLevel(
ctx->encoder, 10) != JXL_ENC_SUCCESS)
485 }
486
487 end:
490 }
491
492 /**
493 * Sends frame information to libjxl on a per-frame basis. If this is a still image,
494 * this is evaluated once per output file. If this is an animated JPEG XL encode, it
495 * is called once per frame.
496 *
497 * This returns a buffer to the data that should be passed to libjxl (via the
498 * argument **data). If the linesize is nonnegative, this will be frame->data[0],
499 * although if the linesize is negative, it will be the start of the buffer
500 * instead. *data is just a pointer to a location in frame->data so it should not be
501 * freed directly.
502 */
504 {
506 JxlPixelFormat *jxl_fmt = &
ctx->jxl_fmt;
507
508 /* these shouldn't fail, libjxl bug notwithstanding */
509 if (JxlEncoderFrameSettingsSetOption(
ctx->options, JXL_ENC_FRAME_SETTING_EFFORT,
ctx->effort)
510 != JXL_ENC_SUCCESS) {
513 }
514
515 if (JxlEncoderSetFrameDistance(
ctx->options,
ctx->distance) != JXL_ENC_SUCCESS) {
518 }
519
520 /*
521 * In theory the library should automatically enable modular if necessary,
522 * but it appears it won't at the moment due to a bug. This will still
523 * work even if that is patched.
524 */
525 if (JxlEncoderFrameSettingsSetOption(
ctx->options, JXL_ENC_FRAME_SETTING_MODULAR,
526 ctx->modular ||
ctx->distance <= 0.0 ? 1 : -1) != JXL_ENC_SUCCESS) {
529 }
530
531 jxl_fmt->endianness = JXL_NATIVE_ENDIAN;
532 if (
frame->linesize[0] >= 0) {
533 jxl_fmt->align =
frame->linesize[0];
535 } else {
536 jxl_fmt->align = -
frame->linesize[0];
538 }
539
540 return 0;
541 }
542
543 /**
544 * Run libjxl's output processing loop, reallocating the packet as necessary
545 * if libjxl needs more space to work with.
546 */
548 {
550 JxlEncoderStatus jret;
552 uint8_t *next_out =
ctx->buffer;
553
554 while (1) {
555 jret = JxlEncoderProcessOutput(
ctx->encoder, &next_out, &
available);
556 if (jret == JXL_ENC_ERROR) {
559 }
561 /* all data passed has been encoded */
562 if (jret == JXL_ENC_SUCCESS)
563 break;
564 if (jret == JXL_ENC_NEED_MORE_OUTPUT) {
565 /*
566 * at the moment, libjxl has no way to
567 * tell us how much space it actually needs
568 * so we need to malloc loop
569 */
571 size_t new_size =
ctx->buffer_size * 2;
576 ctx->buffer_size = new_size;
577 next_out =
ctx->buffer + *bytes_written;
579 continue;
580 }
583 }
584
585 return 0;
586 }
587
588 /**
589 * Encode an entire frame. This will always reinitialize a new still image
590 * and encode a one-frame image (for image2 and image2pipe).
591 */
593 {
594
597 size_t bytes_written = 0;
599
604 }
605
609
613
614 if (JxlEncoderAddImageFrame(
ctx->options, &
ctx->jxl_fmt,
data,
ctx->jxl_fmt.align *
frame->height)
615 != JXL_ENC_SUCCESS) {
618 }
619
620 /*
621 * Run this after the last frame in the image has been passed.
622 */
623 JxlEncoderCloseInput(
ctx->encoder);
624
628
632
633 memcpy(
pkt->
data,
ctx->buffer, bytes_written);
634 *got_packet = 1;
635
636 return 0;
637 }
638
639 /**
640 * Encode one frame of the animation. libjxl requires us to set duration of the frame
641 * but we're only promised the PTS, not the duration. As a result we have to buffer
642 * a frame and subtract the PTS from the last PTS. The last frame uses the previous
643 * frame's calculated duration as a fallback if its duration field is unset.
644 *
645 * We also need to tell libjxl if our frame is the last one, which we won't know upon
646 * receiving a single frame, so we have to buffer a frame as well and send the "last frame"
647 * upon receiving the special EOF frame.
648 */
650 {
654 size_t bytes_written = 0;
656
667 }
668
673 }
676
678
680 ctx->frame ?
ctx->frame->pts -
ctx->prev->pts :
682 1;
683
688
689 if (JxlEncoderSetFrameHeader(
ctx->options, &
frame_header) != JXL_ENC_SUCCESS) {
692 }
693
697
698 if (JxlEncoderAddImageFrame(
ctx->options, &
ctx->jxl_fmt,
data,
ctx->jxl_fmt.align *
ctx->prev->height)
699 != JXL_ENC_SUCCESS) {
702 }
703
705 JxlEncoderCloseInput(
ctx->encoder);
706
710
714
715 memcpy(
pkt->
data,
ctx->buffer, bytes_written);
716
720 } else {
722 }
723
725 }
726
728 {
730
732 JxlThreadParallelRunnerDestroy(
ctx->runner);
734
735 /*
736 * destroying the encoder also frees
737 * ctx->options so we don't need to
738 */
740 JxlEncoderDestroy(
ctx->encoder);
742
746
747 return 0;
748 }
749
750 #define OFFSET(x) offsetof(LibJxlEncodeContext, x)
751 #define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
752
755 { "distance", "Maximum Butteraugli distance (quality setting, "
758 {
"xyb",
"Use XYB-encoding for lossy images",
OFFSET(xyb),
761 };
762
768 };
769
778 };
779
798 .p.wrapper_name = "libjxl",
799 };
800
802 .
p.
name =
"libjxl_anim",
819 .p.wrapper_name = "libjxl",
820 };