1cabdff1aSopenharmony_ci/* 2cabdff1aSopenharmony_ci * JPEG XL encoding support via libjxl 3cabdff1aSopenharmony_ci * Copyright (c) 2021 Leo Izen <leo.izen@gmail.com> 4cabdff1aSopenharmony_ci * 5cabdff1aSopenharmony_ci * This file is part of FFmpeg. 6cabdff1aSopenharmony_ci * 7cabdff1aSopenharmony_ci * FFmpeg is free software; you can redistribute it and/or 8cabdff1aSopenharmony_ci * modify it under the terms of the GNU Lesser General Public 9cabdff1aSopenharmony_ci * License as published by the Free Software Foundation; either 10cabdff1aSopenharmony_ci * version 2.1 of the License, or (at your option) any later version. 11cabdff1aSopenharmony_ci * 12cabdff1aSopenharmony_ci * FFmpeg is distributed in the hope that it will be useful, 13cabdff1aSopenharmony_ci * but WITHOUT ANY WARRANTY; without even the implied warranty of 14cabdff1aSopenharmony_ci * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15cabdff1aSopenharmony_ci * Lesser General Public License for more details. 16cabdff1aSopenharmony_ci * 17cabdff1aSopenharmony_ci * You should have received a copy of the GNU Lesser General Public 18cabdff1aSopenharmony_ci * License along with FFmpeg; if not, write to the Free Software 19cabdff1aSopenharmony_ci * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20cabdff1aSopenharmony_ci */ 21cabdff1aSopenharmony_ci 22cabdff1aSopenharmony_ci/** 23cabdff1aSopenharmony_ci * @file 24cabdff1aSopenharmony_ci * JPEG XL encoder using libjxl 25cabdff1aSopenharmony_ci */ 26cabdff1aSopenharmony_ci 27cabdff1aSopenharmony_ci#include <string.h> 28cabdff1aSopenharmony_ci 29cabdff1aSopenharmony_ci#include "libavutil/avutil.h" 30cabdff1aSopenharmony_ci#include "libavutil/csp.h" 31cabdff1aSopenharmony_ci#include "libavutil/error.h" 32cabdff1aSopenharmony_ci#include "libavutil/frame.h" 33cabdff1aSopenharmony_ci#include "libavutil/libm.h" 34cabdff1aSopenharmony_ci#include "libavutil/opt.h" 35cabdff1aSopenharmony_ci#include "libavutil/pixdesc.h" 36cabdff1aSopenharmony_ci#include "libavutil/pixfmt.h" 37cabdff1aSopenharmony_ci#include "libavutil/version.h" 38cabdff1aSopenharmony_ci 39cabdff1aSopenharmony_ci#include "avcodec.h" 40cabdff1aSopenharmony_ci#include "encode.h" 41cabdff1aSopenharmony_ci#include "codec_internal.h" 42cabdff1aSopenharmony_ci 43cabdff1aSopenharmony_ci#include <jxl/encode.h> 44cabdff1aSopenharmony_ci#include <jxl/thread_parallel_runner.h> 45cabdff1aSopenharmony_ci#include "libjxl.h" 46cabdff1aSopenharmony_ci 47cabdff1aSopenharmony_citypedef struct LibJxlEncodeContext { 48cabdff1aSopenharmony_ci AVClass *class; 49cabdff1aSopenharmony_ci void *runner; 50cabdff1aSopenharmony_ci JxlEncoder *encoder; 51cabdff1aSopenharmony_ci JxlEncoderFrameSettings *options; 52cabdff1aSopenharmony_ci int effort; 53cabdff1aSopenharmony_ci float distance; 54cabdff1aSopenharmony_ci int modular; 55cabdff1aSopenharmony_ci uint8_t *buffer; 56cabdff1aSopenharmony_ci size_t buffer_size; 57cabdff1aSopenharmony_ci} LibJxlEncodeContext; 58cabdff1aSopenharmony_ci 59cabdff1aSopenharmony_ci/** 60cabdff1aSopenharmony_ci * Map a quality setting for -qscale roughly from libjpeg 61cabdff1aSopenharmony_ci * quality numbers to libjxl's butteraugli distance for 62cabdff1aSopenharmony_ci * photographic content. 63cabdff1aSopenharmony_ci * 64cabdff1aSopenharmony_ci * Setting distance explicitly is preferred, but this will 65cabdff1aSopenharmony_ci * allow qscale to be used as a fallback. 66cabdff1aSopenharmony_ci * 67cabdff1aSopenharmony_ci * This function is continuous and injective on [0, 100] which 68cabdff1aSopenharmony_ci * makes it monotonic. 69cabdff1aSopenharmony_ci * 70cabdff1aSopenharmony_ci * @param quality 0.0 to 100.0 quality setting, libjpeg quality 71cabdff1aSopenharmony_ci * @return Butteraugli distance between 0.0 and 15.0 72cabdff1aSopenharmony_ci */ 73cabdff1aSopenharmony_cistatic float quality_to_distance(float quality) 74cabdff1aSopenharmony_ci{ 75cabdff1aSopenharmony_ci if (quality >= 100.0) 76cabdff1aSopenharmony_ci return 0.0; 77cabdff1aSopenharmony_ci else if (quality >= 90.0) 78cabdff1aSopenharmony_ci return (100.0 - quality) * 0.10; 79cabdff1aSopenharmony_ci else if (quality >= 30.0) 80cabdff1aSopenharmony_ci return 0.1 + (100.0 - quality) * 0.09; 81cabdff1aSopenharmony_ci else if (quality > 0.0) 82cabdff1aSopenharmony_ci return 15.0 + (59.0 * quality - 4350.0) * quality / 9000.0; 83cabdff1aSopenharmony_ci else 84cabdff1aSopenharmony_ci return 15.0; 85cabdff1aSopenharmony_ci} 86cabdff1aSopenharmony_ci 87cabdff1aSopenharmony_ci/** 88cabdff1aSopenharmony_ci * Initalize the decoder on a per-frame basis. All of these need to be set 89cabdff1aSopenharmony_ci * once each time the decoder is reset, which it must be each frame to make 90cabdff1aSopenharmony_ci * the image2 muxer work. 91cabdff1aSopenharmony_ci * 92cabdff1aSopenharmony_ci * @return 0 upon success, negative on failure. 93cabdff1aSopenharmony_ci */ 94cabdff1aSopenharmony_cistatic int libjxl_init_jxl_encoder(AVCodecContext *avctx) 95cabdff1aSopenharmony_ci{ 96cabdff1aSopenharmony_ci LibJxlEncodeContext *ctx = avctx->priv_data; 97cabdff1aSopenharmony_ci 98cabdff1aSopenharmony_ci /* reset the encoder every frame for image2 muxer */ 99cabdff1aSopenharmony_ci JxlEncoderReset(ctx->encoder); 100cabdff1aSopenharmony_ci 101cabdff1aSopenharmony_ci ctx->options = JxlEncoderFrameSettingsCreate(ctx->encoder, NULL); 102cabdff1aSopenharmony_ci if (!ctx->options) { 103cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_ERROR, "Failed to create JxlEncoderOptions\n"); 104cabdff1aSopenharmony_ci return AVERROR_EXTERNAL; 105cabdff1aSopenharmony_ci } 106cabdff1aSopenharmony_ci 107cabdff1aSopenharmony_ci /* This needs to be set each time the decoder is reset */ 108cabdff1aSopenharmony_ci if (JxlEncoderSetParallelRunner(ctx->encoder, JxlThreadParallelRunner, ctx->runner) 109cabdff1aSopenharmony_ci != JXL_ENC_SUCCESS) { 110cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_ERROR, "Failed to set JxlThreadParallelRunner\n"); 111cabdff1aSopenharmony_ci return AVERROR_EXTERNAL; 112cabdff1aSopenharmony_ci } 113cabdff1aSopenharmony_ci 114cabdff1aSopenharmony_ci /* these shouldn't fail, libjxl bug notwithstanding */ 115cabdff1aSopenharmony_ci if (JxlEncoderFrameSettingsSetOption(ctx->options, JXL_ENC_FRAME_SETTING_EFFORT, ctx->effort) 116cabdff1aSopenharmony_ci != JXL_ENC_SUCCESS) { 117cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_ERROR, "Failed to set effort to: %d\n", ctx->effort); 118cabdff1aSopenharmony_ci return AVERROR_EXTERNAL; 119cabdff1aSopenharmony_ci } 120cabdff1aSopenharmony_ci 121cabdff1aSopenharmony_ci /* check for negative, our default */ 122cabdff1aSopenharmony_ci if (ctx->distance < 0.0) { 123cabdff1aSopenharmony_ci /* use ffmpeg.c -q option if passed */ 124cabdff1aSopenharmony_ci if (avctx->flags & AV_CODEC_FLAG_QSCALE) 125cabdff1aSopenharmony_ci ctx->distance = quality_to_distance((float)avctx->global_quality / FF_QP2LAMBDA); 126cabdff1aSopenharmony_ci else 127cabdff1aSopenharmony_ci /* default 1.0 matches cjxl */ 128cabdff1aSopenharmony_ci ctx->distance = 1.0; 129cabdff1aSopenharmony_ci } 130cabdff1aSopenharmony_ci 131cabdff1aSopenharmony_ci /* 132cabdff1aSopenharmony_ci * 0.01 is the minimum distance accepted for lossy 133cabdff1aSopenharmony_ci * interpreting any positive value less than this as minimum 134cabdff1aSopenharmony_ci */ 135cabdff1aSopenharmony_ci if (ctx->distance > 0.0 && ctx->distance < 0.01) 136cabdff1aSopenharmony_ci ctx->distance = 0.01; 137cabdff1aSopenharmony_ci if (JxlEncoderSetFrameDistance(ctx->options, ctx->distance) != JXL_ENC_SUCCESS) { 138cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_ERROR, "Failed to set distance: %f\n", ctx->distance); 139cabdff1aSopenharmony_ci return AVERROR_EXTERNAL; 140cabdff1aSopenharmony_ci } 141cabdff1aSopenharmony_ci 142cabdff1aSopenharmony_ci /* 143cabdff1aSopenharmony_ci * In theory the library should automatically enable modular if necessary, 144cabdff1aSopenharmony_ci * but it appears it won't at the moment due to a bug. This will still 145cabdff1aSopenharmony_ci * work even if that is patched. 146cabdff1aSopenharmony_ci */ 147cabdff1aSopenharmony_ci if (JxlEncoderFrameSettingsSetOption(ctx->options, JXL_ENC_FRAME_SETTING_MODULAR, 148cabdff1aSopenharmony_ci ctx->modular || ctx->distance <= 0.0 ? 1 : -1) != JXL_ENC_SUCCESS) { 149cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_ERROR, "Failed to set modular\n"); 150cabdff1aSopenharmony_ci return AVERROR_EXTERNAL; 151cabdff1aSopenharmony_ci } 152cabdff1aSopenharmony_ci 153cabdff1aSopenharmony_ci return 0; 154cabdff1aSopenharmony_ci} 155cabdff1aSopenharmony_ci 156cabdff1aSopenharmony_ci/** 157cabdff1aSopenharmony_ci * Global encoder initialization. This only needs to be run once, 158cabdff1aSopenharmony_ci * not every frame. 159cabdff1aSopenharmony_ci */ 160cabdff1aSopenharmony_cistatic av_cold int libjxl_encode_init(AVCodecContext *avctx) 161cabdff1aSopenharmony_ci{ 162cabdff1aSopenharmony_ci LibJxlEncodeContext *ctx = avctx->priv_data; 163cabdff1aSopenharmony_ci JxlMemoryManager manager; 164cabdff1aSopenharmony_ci 165cabdff1aSopenharmony_ci ff_libjxl_init_memory_manager(&manager); 166cabdff1aSopenharmony_ci ctx->encoder = JxlEncoderCreate(&manager); 167cabdff1aSopenharmony_ci if (!ctx->encoder) { 168cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_ERROR, "Failed to create JxlEncoder\n"); 169cabdff1aSopenharmony_ci return AVERROR_EXTERNAL; 170cabdff1aSopenharmony_ci } 171cabdff1aSopenharmony_ci 172cabdff1aSopenharmony_ci ctx->runner = JxlThreadParallelRunnerCreate(&manager, ff_libjxl_get_threadcount(avctx->thread_count)); 173cabdff1aSopenharmony_ci if (!ctx->runner) { 174cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_ERROR, "Failed to create JxlThreadParallelRunner\n"); 175cabdff1aSopenharmony_ci return AVERROR_EXTERNAL; 176cabdff1aSopenharmony_ci } 177cabdff1aSopenharmony_ci 178cabdff1aSopenharmony_ci ctx->buffer_size = 4096; 179cabdff1aSopenharmony_ci ctx->buffer = av_realloc(NULL, ctx->buffer_size); 180cabdff1aSopenharmony_ci 181cabdff1aSopenharmony_ci if (!ctx->buffer) { 182cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_ERROR, "Could not allocate encoding buffer\n"); 183cabdff1aSopenharmony_ci return AVERROR(ENOMEM); 184cabdff1aSopenharmony_ci } 185cabdff1aSopenharmony_ci 186cabdff1aSopenharmony_ci return 0; 187cabdff1aSopenharmony_ci} 188cabdff1aSopenharmony_ci 189cabdff1aSopenharmony_ci/** 190cabdff1aSopenharmony_ci * Populate a JxlColorEncoding with the given enum AVColorPrimaries. 191cabdff1aSopenharmony_ci * @return < 0 upon failure, >= 0 upon success 192cabdff1aSopenharmony_ci */ 193cabdff1aSopenharmony_cistatic int libjxl_populate_primaries(void *avctx, JxlColorEncoding *jxl_color, enum AVColorPrimaries prm) 194cabdff1aSopenharmony_ci{ 195cabdff1aSopenharmony_ci const AVColorPrimariesDesc *desc; 196cabdff1aSopenharmony_ci 197cabdff1aSopenharmony_ci switch (prm) { 198cabdff1aSopenharmony_ci case AVCOL_PRI_BT709: 199cabdff1aSopenharmony_ci jxl_color->primaries = JXL_PRIMARIES_SRGB; 200cabdff1aSopenharmony_ci jxl_color->white_point = JXL_WHITE_POINT_D65; 201cabdff1aSopenharmony_ci return 0; 202cabdff1aSopenharmony_ci case AVCOL_PRI_BT2020: 203cabdff1aSopenharmony_ci jxl_color->primaries = JXL_PRIMARIES_2100; 204cabdff1aSopenharmony_ci jxl_color->white_point = JXL_WHITE_POINT_D65; 205cabdff1aSopenharmony_ci return 0; 206cabdff1aSopenharmony_ci case AVCOL_PRI_SMPTE431: 207cabdff1aSopenharmony_ci jxl_color->primaries = JXL_PRIMARIES_P3; 208cabdff1aSopenharmony_ci jxl_color->white_point = JXL_WHITE_POINT_DCI; 209cabdff1aSopenharmony_ci return 0; 210cabdff1aSopenharmony_ci case AVCOL_PRI_SMPTE432: 211cabdff1aSopenharmony_ci jxl_color->primaries = JXL_PRIMARIES_P3; 212cabdff1aSopenharmony_ci jxl_color->white_point = JXL_WHITE_POINT_D65; 213cabdff1aSopenharmony_ci return 0; 214cabdff1aSopenharmony_ci case AVCOL_PRI_UNSPECIFIED: 215cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_WARNING, "Unknown primaries, assuming BT.709/sRGB. Colors may be wrong.\n"); 216cabdff1aSopenharmony_ci jxl_color->primaries = JXL_PRIMARIES_SRGB; 217cabdff1aSopenharmony_ci jxl_color->white_point = JXL_WHITE_POINT_D65; 218cabdff1aSopenharmony_ci return 0; 219cabdff1aSopenharmony_ci } 220cabdff1aSopenharmony_ci 221cabdff1aSopenharmony_ci desc = av_csp_primaries_desc_from_id(prm); 222cabdff1aSopenharmony_ci if (!desc) 223cabdff1aSopenharmony_ci return AVERROR(EINVAL); 224cabdff1aSopenharmony_ci 225cabdff1aSopenharmony_ci jxl_color->primaries = JXL_PRIMARIES_CUSTOM; 226cabdff1aSopenharmony_ci jxl_color->white_point = JXL_WHITE_POINT_CUSTOM; 227cabdff1aSopenharmony_ci 228cabdff1aSopenharmony_ci jxl_color->primaries_red_xy[0] = av_q2d(desc->prim.r.x); 229cabdff1aSopenharmony_ci jxl_color->primaries_red_xy[1] = av_q2d(desc->prim.r.y); 230cabdff1aSopenharmony_ci jxl_color->primaries_green_xy[0] = av_q2d(desc->prim.g.x); 231cabdff1aSopenharmony_ci jxl_color->primaries_green_xy[1] = av_q2d(desc->prim.g.y); 232cabdff1aSopenharmony_ci jxl_color->primaries_blue_xy[0] = av_q2d(desc->prim.b.x); 233cabdff1aSopenharmony_ci jxl_color->primaries_blue_xy[1] = av_q2d(desc->prim.b.y); 234cabdff1aSopenharmony_ci jxl_color->white_point_xy[0] = av_q2d(desc->wp.x); 235cabdff1aSopenharmony_ci jxl_color->white_point_xy[1] = av_q2d(desc->wp.y); 236cabdff1aSopenharmony_ci 237cabdff1aSopenharmony_ci return 0; 238cabdff1aSopenharmony_ci} 239cabdff1aSopenharmony_ci 240cabdff1aSopenharmony_ci/** 241cabdff1aSopenharmony_ci * Encode an entire frame. Currently animation, is not supported by 242cabdff1aSopenharmony_ci * this encoder, so this will always reinitialize a new still image 243cabdff1aSopenharmony_ci * and encode a one-frame image (for image2 and image2pipe). 244cabdff1aSopenharmony_ci */ 245cabdff1aSopenharmony_cistatic int libjxl_encode_frame(AVCodecContext *avctx, AVPacket *pkt, const AVFrame *frame, int *got_packet) 246cabdff1aSopenharmony_ci{ 247cabdff1aSopenharmony_ci LibJxlEncodeContext *ctx = avctx->priv_data; 248cabdff1aSopenharmony_ci AVFrameSideData *sd; 249cabdff1aSopenharmony_ci const AVPixFmtDescriptor *pix_desc = av_pix_fmt_desc_get(frame->format); 250cabdff1aSopenharmony_ci JxlBasicInfo info; 251cabdff1aSopenharmony_ci JxlColorEncoding jxl_color; 252cabdff1aSopenharmony_ci JxlPixelFormat jxl_fmt; 253cabdff1aSopenharmony_ci JxlEncoderStatus jret; 254cabdff1aSopenharmony_ci int ret; 255cabdff1aSopenharmony_ci size_t available = ctx->buffer_size; 256cabdff1aSopenharmony_ci size_t bytes_written = 0; 257cabdff1aSopenharmony_ci uint8_t *next_out = ctx->buffer; 258cabdff1aSopenharmony_ci 259cabdff1aSopenharmony_ci ret = libjxl_init_jxl_encoder(avctx); 260cabdff1aSopenharmony_ci if (ret) { 261cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_ERROR, "Error frame-initializing JxlEncoder\n"); 262cabdff1aSopenharmony_ci return ret; 263cabdff1aSopenharmony_ci } 264cabdff1aSopenharmony_ci 265cabdff1aSopenharmony_ci /* populate the basic info settings */ 266cabdff1aSopenharmony_ci JxlEncoderInitBasicInfo(&info); 267cabdff1aSopenharmony_ci jxl_fmt.num_channels = pix_desc->nb_components; 268cabdff1aSopenharmony_ci info.xsize = frame->width; 269cabdff1aSopenharmony_ci info.ysize = frame->height; 270cabdff1aSopenharmony_ci info.num_extra_channels = (jxl_fmt.num_channels + 1) % 2; 271cabdff1aSopenharmony_ci info.num_color_channels = jxl_fmt.num_channels - info.num_extra_channels; 272cabdff1aSopenharmony_ci info.bits_per_sample = av_get_bits_per_pixel(pix_desc) / jxl_fmt.num_channels; 273cabdff1aSopenharmony_ci info.alpha_bits = (info.num_extra_channels > 0) * info.bits_per_sample; 274cabdff1aSopenharmony_ci if (pix_desc->flags & AV_PIX_FMT_FLAG_FLOAT) { 275cabdff1aSopenharmony_ci info.exponent_bits_per_sample = info.bits_per_sample > 16 ? 8 : 5; 276cabdff1aSopenharmony_ci info.alpha_exponent_bits = info.alpha_bits ? info.exponent_bits_per_sample : 0; 277cabdff1aSopenharmony_ci jxl_fmt.data_type = info.bits_per_sample > 16 ? JXL_TYPE_FLOAT : JXL_TYPE_FLOAT16; 278cabdff1aSopenharmony_ci } else { 279cabdff1aSopenharmony_ci info.exponent_bits_per_sample = 0; 280cabdff1aSopenharmony_ci info.alpha_exponent_bits = 0; 281cabdff1aSopenharmony_ci jxl_fmt.data_type = info.bits_per_sample <= 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16; 282cabdff1aSopenharmony_ci } 283cabdff1aSopenharmony_ci 284cabdff1aSopenharmony_ci /* JPEG XL format itself does not support limited range */ 285cabdff1aSopenharmony_ci if (avctx->color_range == AVCOL_RANGE_MPEG || 286cabdff1aSopenharmony_ci avctx->color_range == AVCOL_RANGE_UNSPECIFIED && frame->color_range == AVCOL_RANGE_MPEG) 287cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_WARNING, "This encoder does not support limited (tv) range, colors will be wrong!\n"); 288cabdff1aSopenharmony_ci else if (avctx->color_range != AVCOL_RANGE_JPEG && frame->color_range != AVCOL_RANGE_JPEG) 289cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_WARNING, "Unknown color range, assuming full (pc)\n"); 290cabdff1aSopenharmony_ci 291cabdff1aSopenharmony_ci /* bitexact lossless requires there to be no XYB transform */ 292cabdff1aSopenharmony_ci info.uses_original_profile = ctx->distance == 0.0; 293cabdff1aSopenharmony_ci 294cabdff1aSopenharmony_ci if (JxlEncoderSetBasicInfo(ctx->encoder, &info) != JXL_ENC_SUCCESS) { 295cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_ERROR, "Failed to set JxlBasicInfo\n"); 296cabdff1aSopenharmony_ci return AVERROR_EXTERNAL; 297cabdff1aSopenharmony_ci } 298cabdff1aSopenharmony_ci 299cabdff1aSopenharmony_ci /* rendering intent doesn't matter here 300cabdff1aSopenharmony_ci * but libjxl will whine if we don't set it */ 301cabdff1aSopenharmony_ci jxl_color.rendering_intent = JXL_RENDERING_INTENT_RELATIVE; 302cabdff1aSopenharmony_ci 303cabdff1aSopenharmony_ci switch (frame->color_trc && frame->color_trc != AVCOL_TRC_UNSPECIFIED 304cabdff1aSopenharmony_ci ? frame->color_trc : avctx->color_trc) { 305cabdff1aSopenharmony_ci case AVCOL_TRC_BT709: 306cabdff1aSopenharmony_ci jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_709; 307cabdff1aSopenharmony_ci break; 308cabdff1aSopenharmony_ci case AVCOL_TRC_LINEAR: 309cabdff1aSopenharmony_ci jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR; 310cabdff1aSopenharmony_ci break; 311cabdff1aSopenharmony_ci case AVCOL_TRC_IEC61966_2_1: 312cabdff1aSopenharmony_ci jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_SRGB; 313cabdff1aSopenharmony_ci break; 314cabdff1aSopenharmony_ci case AVCOL_TRC_SMPTE428: 315cabdff1aSopenharmony_ci jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_DCI; 316cabdff1aSopenharmony_ci break; 317cabdff1aSopenharmony_ci case AVCOL_TRC_SMPTE2084: 318cabdff1aSopenharmony_ci jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_PQ; 319cabdff1aSopenharmony_ci break; 320cabdff1aSopenharmony_ci case AVCOL_TRC_ARIB_STD_B67: 321cabdff1aSopenharmony_ci jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_HLG; 322cabdff1aSopenharmony_ci break; 323cabdff1aSopenharmony_ci case AVCOL_TRC_GAMMA22: 324cabdff1aSopenharmony_ci jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA; 325cabdff1aSopenharmony_ci jxl_color.gamma = 2.2; 326cabdff1aSopenharmony_ci break; 327cabdff1aSopenharmony_ci case AVCOL_TRC_GAMMA28: 328cabdff1aSopenharmony_ci jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA; 329cabdff1aSopenharmony_ci jxl_color.gamma = 2.8; 330cabdff1aSopenharmony_ci break; 331cabdff1aSopenharmony_ci default: 332cabdff1aSopenharmony_ci if (pix_desc->flags & AV_PIX_FMT_FLAG_FLOAT) { 333cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_WARNING, "Unknown transfer function, assuming Linear Light. Colors may be wrong.\n"); 334cabdff1aSopenharmony_ci jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR; 335cabdff1aSopenharmony_ci } else { 336cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_WARNING, "Unknown transfer function, assuming IEC61966-2-1/sRGB. Colors may be wrong.\n"); 337cabdff1aSopenharmony_ci jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_SRGB; 338cabdff1aSopenharmony_ci } 339cabdff1aSopenharmony_ci } 340cabdff1aSopenharmony_ci 341cabdff1aSopenharmony_ci /* This should be implied to be honest 342cabdff1aSopenharmony_ci * but a libjxl bug makes it fail otherwise */ 343cabdff1aSopenharmony_ci if (info.num_color_channels == 1) 344cabdff1aSopenharmony_ci jxl_color.color_space = JXL_COLOR_SPACE_GRAY; 345cabdff1aSopenharmony_ci else 346cabdff1aSopenharmony_ci jxl_color.color_space = JXL_COLOR_SPACE_RGB; 347cabdff1aSopenharmony_ci 348cabdff1aSopenharmony_ci ret = libjxl_populate_primaries(avctx, &jxl_color, 349cabdff1aSopenharmony_ci frame->color_primaries && frame->color_primaries != AVCOL_PRI_UNSPECIFIED 350cabdff1aSopenharmony_ci ? frame->color_primaries : avctx->color_primaries); 351cabdff1aSopenharmony_ci if (ret < 0) 352cabdff1aSopenharmony_ci return ret; 353cabdff1aSopenharmony_ci 354cabdff1aSopenharmony_ci sd = av_frame_get_side_data(frame, AV_FRAME_DATA_ICC_PROFILE); 355cabdff1aSopenharmony_ci if (sd && sd->size && JxlEncoderSetICCProfile(ctx->encoder, sd->data, sd->size) != JXL_ENC_SUCCESS) 356cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_WARNING, "Could not set ICC Profile\n"); 357cabdff1aSopenharmony_ci if (JxlEncoderSetColorEncoding(ctx->encoder, &jxl_color) != JXL_ENC_SUCCESS) 358cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_WARNING, "Failed to set JxlColorEncoding\n"); 359cabdff1aSopenharmony_ci 360cabdff1aSopenharmony_ci /* depending on basic info, level 10 might 361cabdff1aSopenharmony_ci * be required instead of level 5 */ 362cabdff1aSopenharmony_ci if (JxlEncoderGetRequiredCodestreamLevel(ctx->encoder) > 5) { 363cabdff1aSopenharmony_ci if (JxlEncoderSetCodestreamLevel(ctx->encoder, 10) != JXL_ENC_SUCCESS) 364cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_WARNING, "Could not increase codestream level\n"); 365cabdff1aSopenharmony_ci } 366cabdff1aSopenharmony_ci 367cabdff1aSopenharmony_ci jxl_fmt.endianness = JXL_NATIVE_ENDIAN; 368cabdff1aSopenharmony_ci jxl_fmt.align = frame->linesize[0]; 369cabdff1aSopenharmony_ci 370cabdff1aSopenharmony_ci if (JxlEncoderAddImageFrame(ctx->options, &jxl_fmt, frame->data[0], jxl_fmt.align * info.ysize) != JXL_ENC_SUCCESS) { 371cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_ERROR, "Failed to add Image Frame\n"); 372cabdff1aSopenharmony_ci return AVERROR_EXTERNAL; 373cabdff1aSopenharmony_ci } 374cabdff1aSopenharmony_ci 375cabdff1aSopenharmony_ci /* 376cabdff1aSopenharmony_ci * Run this after the last frame in the image has been passed. 377cabdff1aSopenharmony_ci * TODO support animation 378cabdff1aSopenharmony_ci */ 379cabdff1aSopenharmony_ci JxlEncoderCloseInput(ctx->encoder); 380cabdff1aSopenharmony_ci 381cabdff1aSopenharmony_ci while (1) { 382cabdff1aSopenharmony_ci jret = JxlEncoderProcessOutput(ctx->encoder, &next_out, &available); 383cabdff1aSopenharmony_ci if (jret == JXL_ENC_ERROR) { 384cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_ERROR, "Unspecified libjxl error occurred\n"); 385cabdff1aSopenharmony_ci return AVERROR_EXTERNAL; 386cabdff1aSopenharmony_ci } 387cabdff1aSopenharmony_ci bytes_written = ctx->buffer_size - available; 388cabdff1aSopenharmony_ci /* all data passed has been encoded */ 389cabdff1aSopenharmony_ci if (jret == JXL_ENC_SUCCESS) 390cabdff1aSopenharmony_ci break; 391cabdff1aSopenharmony_ci if (jret == JXL_ENC_NEED_MORE_OUTPUT) { 392cabdff1aSopenharmony_ci /* 393cabdff1aSopenharmony_ci * at the moment, libjxl has no way to 394cabdff1aSopenharmony_ci * tell us how much space it actually needs 395cabdff1aSopenharmony_ci * so we need to malloc loop 396cabdff1aSopenharmony_ci */ 397cabdff1aSopenharmony_ci uint8_t *temp; 398cabdff1aSopenharmony_ci size_t new_size = ctx->buffer_size * 2; 399cabdff1aSopenharmony_ci temp = av_realloc(ctx->buffer, new_size); 400cabdff1aSopenharmony_ci if (!temp) 401cabdff1aSopenharmony_ci return AVERROR(ENOMEM); 402cabdff1aSopenharmony_ci ctx->buffer = temp; 403cabdff1aSopenharmony_ci ctx->buffer_size = new_size; 404cabdff1aSopenharmony_ci next_out = ctx->buffer + bytes_written; 405cabdff1aSopenharmony_ci available = new_size - bytes_written; 406cabdff1aSopenharmony_ci continue; 407cabdff1aSopenharmony_ci } 408cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_ERROR, "Bad libjxl event: %d\n", jret); 409cabdff1aSopenharmony_ci return AVERROR_EXTERNAL; 410cabdff1aSopenharmony_ci } 411cabdff1aSopenharmony_ci 412cabdff1aSopenharmony_ci ret = ff_get_encode_buffer(avctx, pkt, bytes_written, 0); 413cabdff1aSopenharmony_ci if (ret < 0) 414cabdff1aSopenharmony_ci return ret; 415cabdff1aSopenharmony_ci 416cabdff1aSopenharmony_ci memcpy(pkt->data, ctx->buffer, bytes_written); 417cabdff1aSopenharmony_ci *got_packet = 1; 418cabdff1aSopenharmony_ci 419cabdff1aSopenharmony_ci return 0; 420cabdff1aSopenharmony_ci} 421cabdff1aSopenharmony_ci 422cabdff1aSopenharmony_cistatic av_cold int libjxl_encode_close(AVCodecContext *avctx) 423cabdff1aSopenharmony_ci{ 424cabdff1aSopenharmony_ci LibJxlEncodeContext *ctx = avctx->priv_data; 425cabdff1aSopenharmony_ci 426cabdff1aSopenharmony_ci if (ctx->runner) 427cabdff1aSopenharmony_ci JxlThreadParallelRunnerDestroy(ctx->runner); 428cabdff1aSopenharmony_ci ctx->runner = NULL; 429cabdff1aSopenharmony_ci 430cabdff1aSopenharmony_ci /* 431cabdff1aSopenharmony_ci * destroying the decoder also frees 432cabdff1aSopenharmony_ci * ctx->options so we don't need to 433cabdff1aSopenharmony_ci */ 434cabdff1aSopenharmony_ci if (ctx->encoder) 435cabdff1aSopenharmony_ci JxlEncoderDestroy(ctx->encoder); 436cabdff1aSopenharmony_ci ctx->encoder = NULL; 437cabdff1aSopenharmony_ci 438cabdff1aSopenharmony_ci av_freep(&ctx->buffer); 439cabdff1aSopenharmony_ci 440cabdff1aSopenharmony_ci return 0; 441cabdff1aSopenharmony_ci} 442cabdff1aSopenharmony_ci 443cabdff1aSopenharmony_ci#define OFFSET(x) offsetof(LibJxlEncodeContext, x) 444cabdff1aSopenharmony_ci#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM 445cabdff1aSopenharmony_ci 446cabdff1aSopenharmony_cistatic const AVOption libjxl_encode_options[] = { 447cabdff1aSopenharmony_ci { "effort", "Encoding effort", OFFSET(effort), AV_OPT_TYPE_INT, { .i64 = 7 }, 1, 9, VE }, 448cabdff1aSopenharmony_ci { "distance", "Maximum Butteraugli distance (quality setting, " 449cabdff1aSopenharmony_ci "lower = better, zero = lossless, default 1.0)", OFFSET(distance), AV_OPT_TYPE_FLOAT, { .dbl = -1.0 }, -1.0, 15.0, VE }, 450cabdff1aSopenharmony_ci { "modular", "Force modular mode", OFFSET(modular), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, VE }, 451cabdff1aSopenharmony_ci { NULL }, 452cabdff1aSopenharmony_ci}; 453cabdff1aSopenharmony_ci 454cabdff1aSopenharmony_cistatic const AVClass libjxl_encode_class = { 455cabdff1aSopenharmony_ci .class_name = "libjxl", 456cabdff1aSopenharmony_ci .item_name = av_default_item_name, 457cabdff1aSopenharmony_ci .option = libjxl_encode_options, 458cabdff1aSopenharmony_ci .version = LIBAVUTIL_VERSION_INT, 459cabdff1aSopenharmony_ci}; 460cabdff1aSopenharmony_ci 461cabdff1aSopenharmony_ciconst FFCodec ff_libjxl_encoder = { 462cabdff1aSopenharmony_ci .p.name = "libjxl", 463cabdff1aSopenharmony_ci .p.long_name = NULL_IF_CONFIG_SMALL("libjxl JPEG XL"), 464cabdff1aSopenharmony_ci .p.type = AVMEDIA_TYPE_VIDEO, 465cabdff1aSopenharmony_ci .p.id = AV_CODEC_ID_JPEGXL, 466cabdff1aSopenharmony_ci .priv_data_size = sizeof(LibJxlEncodeContext), 467cabdff1aSopenharmony_ci .init = libjxl_encode_init, 468cabdff1aSopenharmony_ci FF_CODEC_ENCODE_CB(libjxl_encode_frame), 469cabdff1aSopenharmony_ci .close = libjxl_encode_close, 470cabdff1aSopenharmony_ci .p.capabilities = AV_CODEC_CAP_OTHER_THREADS, 471cabdff1aSopenharmony_ci .caps_internal = FF_CODEC_CAP_AUTO_THREADS | FF_CODEC_CAP_INIT_CLEANUP, 472cabdff1aSopenharmony_ci .p.pix_fmts = (const enum AVPixelFormat[]) { 473cabdff1aSopenharmony_ci AV_PIX_FMT_RGB24, AV_PIX_FMT_RGBA, 474cabdff1aSopenharmony_ci AV_PIX_FMT_RGB48, AV_PIX_FMT_RGBA64, 475cabdff1aSopenharmony_ci AV_PIX_FMT_GRAY8, AV_PIX_FMT_YA8, 476cabdff1aSopenharmony_ci AV_PIX_FMT_GRAY16, AV_PIX_FMT_YA16, 477cabdff1aSopenharmony_ci AV_PIX_FMT_GRAYF32, 478cabdff1aSopenharmony_ci AV_PIX_FMT_NONE 479cabdff1aSopenharmony_ci }, 480cabdff1aSopenharmony_ci .p.priv_class = &libjxl_encode_class, 481cabdff1aSopenharmony_ci .p.wrapper_name = "libjxl", 482cabdff1aSopenharmony_ci}; 483