1cabdff1aSopenharmony_ci/* 2cabdff1aSopenharmony_ci * JPEG XL decoding 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 decoder using libjxl 25cabdff1aSopenharmony_ci */ 26cabdff1aSopenharmony_ci 27cabdff1aSopenharmony_ci#include "libavutil/avassert.h" 28cabdff1aSopenharmony_ci#include "libavutil/buffer.h" 29cabdff1aSopenharmony_ci#include "libavutil/common.h" 30cabdff1aSopenharmony_ci#include "libavutil/csp.h" 31cabdff1aSopenharmony_ci#include "libavutil/error.h" 32cabdff1aSopenharmony_ci#include "libavutil/mem.h" 33cabdff1aSopenharmony_ci#include "libavutil/pixdesc.h" 34cabdff1aSopenharmony_ci#include "libavutil/pixfmt.h" 35cabdff1aSopenharmony_ci#include "libavutil/frame.h" 36cabdff1aSopenharmony_ci 37cabdff1aSopenharmony_ci#include "avcodec.h" 38cabdff1aSopenharmony_ci#include "codec_internal.h" 39cabdff1aSopenharmony_ci#include "internal.h" 40cabdff1aSopenharmony_ci 41cabdff1aSopenharmony_ci#include <jxl/decode.h> 42cabdff1aSopenharmony_ci#include <jxl/thread_parallel_runner.h> 43cabdff1aSopenharmony_ci#include "libjxl.h" 44cabdff1aSopenharmony_ci 45cabdff1aSopenharmony_citypedef struct LibJxlDecodeContext { 46cabdff1aSopenharmony_ci void *runner; 47cabdff1aSopenharmony_ci JxlDecoder *decoder; 48cabdff1aSopenharmony_ci JxlBasicInfo basic_info; 49cabdff1aSopenharmony_ci JxlPixelFormat jxl_pixfmt; 50cabdff1aSopenharmony_ci JxlDecoderStatus events; 51cabdff1aSopenharmony_ci AVBufferRef *iccp; 52cabdff1aSopenharmony_ci} LibJxlDecodeContext; 53cabdff1aSopenharmony_ci 54cabdff1aSopenharmony_cistatic int libjxl_init_jxl_decoder(AVCodecContext *avctx) 55cabdff1aSopenharmony_ci{ 56cabdff1aSopenharmony_ci LibJxlDecodeContext *ctx = avctx->priv_data; 57cabdff1aSopenharmony_ci 58cabdff1aSopenharmony_ci ctx->events = JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE | JXL_DEC_COLOR_ENCODING; 59cabdff1aSopenharmony_ci if (JxlDecoderSubscribeEvents(ctx->decoder, ctx->events) != JXL_DEC_SUCCESS) { 60cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_ERROR, "Error subscribing to JXL events\n"); 61cabdff1aSopenharmony_ci return AVERROR_EXTERNAL; 62cabdff1aSopenharmony_ci } 63cabdff1aSopenharmony_ci 64cabdff1aSopenharmony_ci if (JxlDecoderSetParallelRunner(ctx->decoder, JxlThreadParallelRunner, ctx->runner) != JXL_DEC_SUCCESS) { 65cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_ERROR, "Failed to set JxlThreadParallelRunner\n"); 66cabdff1aSopenharmony_ci return AVERROR_EXTERNAL; 67cabdff1aSopenharmony_ci } 68cabdff1aSopenharmony_ci 69cabdff1aSopenharmony_ci memset(&ctx->basic_info, 0, sizeof(JxlBasicInfo)); 70cabdff1aSopenharmony_ci memset(&ctx->jxl_pixfmt, 0, sizeof(JxlPixelFormat)); 71cabdff1aSopenharmony_ci 72cabdff1aSopenharmony_ci return 0; 73cabdff1aSopenharmony_ci} 74cabdff1aSopenharmony_ci 75cabdff1aSopenharmony_cistatic av_cold int libjxl_decode_init(AVCodecContext *avctx) 76cabdff1aSopenharmony_ci{ 77cabdff1aSopenharmony_ci LibJxlDecodeContext *ctx = avctx->priv_data; 78cabdff1aSopenharmony_ci JxlMemoryManager manager; 79cabdff1aSopenharmony_ci 80cabdff1aSopenharmony_ci ff_libjxl_init_memory_manager(&manager); 81cabdff1aSopenharmony_ci ctx->decoder = JxlDecoderCreate(&manager); 82cabdff1aSopenharmony_ci if (!ctx->decoder) { 83cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_ERROR, "Failed to create JxlDecoder\n"); 84cabdff1aSopenharmony_ci return AVERROR_EXTERNAL; 85cabdff1aSopenharmony_ci } 86cabdff1aSopenharmony_ci 87cabdff1aSopenharmony_ci ctx->runner = JxlThreadParallelRunnerCreate(&manager, ff_libjxl_get_threadcount(avctx->thread_count)); 88cabdff1aSopenharmony_ci if (!ctx->runner) { 89cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_ERROR, "Failed to create JxlThreadParallelRunner\n"); 90cabdff1aSopenharmony_ci return AVERROR_EXTERNAL; 91cabdff1aSopenharmony_ci } 92cabdff1aSopenharmony_ci 93cabdff1aSopenharmony_ci return libjxl_init_jxl_decoder(avctx); 94cabdff1aSopenharmony_ci} 95cabdff1aSopenharmony_ci 96cabdff1aSopenharmony_cistatic enum AVPixelFormat libjxl_get_pix_fmt(void *avctx, const JxlBasicInfo *basic_info, JxlPixelFormat *format) 97cabdff1aSopenharmony_ci{ 98cabdff1aSopenharmony_ci format->endianness = JXL_NATIVE_ENDIAN; 99cabdff1aSopenharmony_ci format->num_channels = basic_info->num_color_channels + (basic_info->alpha_bits > 0); 100cabdff1aSopenharmony_ci /* Gray */ 101cabdff1aSopenharmony_ci if (basic_info->num_color_channels == 1) { 102cabdff1aSopenharmony_ci if (basic_info->bits_per_sample <= 8) { 103cabdff1aSopenharmony_ci format->data_type = JXL_TYPE_UINT8; 104cabdff1aSopenharmony_ci return basic_info->alpha_bits ? AV_PIX_FMT_YA8 : AV_PIX_FMT_GRAY8; 105cabdff1aSopenharmony_ci } 106cabdff1aSopenharmony_ci if (basic_info->exponent_bits_per_sample || basic_info->bits_per_sample > 16) { 107cabdff1aSopenharmony_ci if (basic_info->alpha_bits) 108cabdff1aSopenharmony_ci return AV_PIX_FMT_NONE; 109cabdff1aSopenharmony_ci format->data_type = JXL_TYPE_FLOAT; 110cabdff1aSopenharmony_ci return AV_PIX_FMT_GRAYF32; 111cabdff1aSopenharmony_ci } 112cabdff1aSopenharmony_ci format->data_type = JXL_TYPE_UINT16; 113cabdff1aSopenharmony_ci return basic_info->alpha_bits ? AV_PIX_FMT_YA16 : AV_PIX_FMT_GRAY16; 114cabdff1aSopenharmony_ci } 115cabdff1aSopenharmony_ci /* rgb only */ 116cabdff1aSopenharmony_ci /* libjxl only supports packed RGB and gray output at the moment */ 117cabdff1aSopenharmony_ci if (basic_info->num_color_channels == 3) { 118cabdff1aSopenharmony_ci if (basic_info->bits_per_sample <= 8) { 119cabdff1aSopenharmony_ci format->data_type = JXL_TYPE_UINT8; 120cabdff1aSopenharmony_ci return basic_info->alpha_bits ? AV_PIX_FMT_RGBA : AV_PIX_FMT_RGB24; 121cabdff1aSopenharmony_ci } 122cabdff1aSopenharmony_ci if (basic_info->bits_per_sample > 16) 123cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_WARNING, "Downsampling larger integer to 16-bit via libjxl\n"); 124cabdff1aSopenharmony_ci if (basic_info->exponent_bits_per_sample) 125cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_WARNING, "Downsampling float to 16-bit integer via libjxl\n"); 126cabdff1aSopenharmony_ci format->data_type = JXL_TYPE_UINT16; 127cabdff1aSopenharmony_ci return basic_info->alpha_bits ? AV_PIX_FMT_RGBA64 : AV_PIX_FMT_RGB48; 128cabdff1aSopenharmony_ci } 129cabdff1aSopenharmony_ci 130cabdff1aSopenharmony_ci return AV_PIX_FMT_NONE; 131cabdff1aSopenharmony_ci} 132cabdff1aSopenharmony_ci 133cabdff1aSopenharmony_cistatic enum AVColorPrimaries libjxl_get_primaries(void *avctx, const JxlColorEncoding *jxl_color) 134cabdff1aSopenharmony_ci{ 135cabdff1aSopenharmony_ci AVColorPrimariesDesc desc; 136cabdff1aSopenharmony_ci enum AVColorPrimaries prim; 137cabdff1aSopenharmony_ci 138cabdff1aSopenharmony_ci /* libjxl populates these double values even if it uses an enum space */ 139cabdff1aSopenharmony_ci desc.prim.r.x = av_d2q(jxl_color->primaries_red_xy[0], 300000); 140cabdff1aSopenharmony_ci desc.prim.r.y = av_d2q(jxl_color->primaries_red_xy[1], 300000); 141cabdff1aSopenharmony_ci desc.prim.g.x = av_d2q(jxl_color->primaries_green_xy[0], 300000); 142cabdff1aSopenharmony_ci desc.prim.g.y = av_d2q(jxl_color->primaries_green_xy[1], 300000); 143cabdff1aSopenharmony_ci desc.prim.b.x = av_d2q(jxl_color->primaries_blue_xy[0], 300000); 144cabdff1aSopenharmony_ci desc.prim.b.y = av_d2q(jxl_color->primaries_blue_xy[1], 300000); 145cabdff1aSopenharmony_ci desc.wp.x = av_d2q(jxl_color->white_point_xy[0], 300000); 146cabdff1aSopenharmony_ci desc.wp.y = av_d2q(jxl_color->white_point_xy[1], 300000); 147cabdff1aSopenharmony_ci 148cabdff1aSopenharmony_ci prim = av_csp_primaries_id_from_desc(&desc); 149cabdff1aSopenharmony_ci if (prim == AVCOL_PRI_UNSPECIFIED) { 150cabdff1aSopenharmony_ci /* try D65 with the same primaries */ 151cabdff1aSopenharmony_ci /* BT.709 uses D65 white point */ 152cabdff1aSopenharmony_ci desc.wp = av_csp_primaries_desc_from_id(AVCOL_PRI_BT709)->wp; 153cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_WARNING, "Changing unknown white point to D65\n"); 154cabdff1aSopenharmony_ci prim = av_csp_primaries_id_from_desc(&desc); 155cabdff1aSopenharmony_ci } 156cabdff1aSopenharmony_ci 157cabdff1aSopenharmony_ci return prim; 158cabdff1aSopenharmony_ci} 159cabdff1aSopenharmony_ci 160cabdff1aSopenharmony_cistatic enum AVColorTransferCharacteristic libjxl_get_trc(void *avctx, const JxlColorEncoding *jxl_color) 161cabdff1aSopenharmony_ci{ 162cabdff1aSopenharmony_ci switch (jxl_color->transfer_function) { 163cabdff1aSopenharmony_ci case JXL_TRANSFER_FUNCTION_709: return AVCOL_TRC_BT709; 164cabdff1aSopenharmony_ci case JXL_TRANSFER_FUNCTION_LINEAR: return AVCOL_TRC_LINEAR; 165cabdff1aSopenharmony_ci case JXL_TRANSFER_FUNCTION_SRGB: return AVCOL_TRC_IEC61966_2_1; 166cabdff1aSopenharmony_ci case JXL_TRANSFER_FUNCTION_PQ: return AVCOL_TRC_SMPTE2084; 167cabdff1aSopenharmony_ci case JXL_TRANSFER_FUNCTION_DCI: return AVCOL_TRC_SMPTE428; 168cabdff1aSopenharmony_ci case JXL_TRANSFER_FUNCTION_HLG: return AVCOL_TRC_ARIB_STD_B67; 169cabdff1aSopenharmony_ci case JXL_TRANSFER_FUNCTION_GAMMA: 170cabdff1aSopenharmony_ci if (jxl_color->gamma > 0.45355 && jxl_color->gamma < 0.45555) 171cabdff1aSopenharmony_ci return AVCOL_TRC_GAMMA22; 172cabdff1aSopenharmony_ci else if (jxl_color->gamma > 0.35614 && jxl_color->gamma < 0.35814) 173cabdff1aSopenharmony_ci return AVCOL_TRC_GAMMA28; 174cabdff1aSopenharmony_ci else 175cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_WARNING, "Unsupported gamma transfer: %f\n", jxl_color->gamma); 176cabdff1aSopenharmony_ci break; 177cabdff1aSopenharmony_ci default: 178cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_WARNING, "Unknown transfer function: %d\n", jxl_color->transfer_function); 179cabdff1aSopenharmony_ci } 180cabdff1aSopenharmony_ci 181cabdff1aSopenharmony_ci return AVCOL_TRC_UNSPECIFIED; 182cabdff1aSopenharmony_ci} 183cabdff1aSopenharmony_ci 184cabdff1aSopenharmony_cistatic int libjxl_get_icc(AVCodecContext *avctx) 185cabdff1aSopenharmony_ci{ 186cabdff1aSopenharmony_ci LibJxlDecodeContext *ctx = avctx->priv_data; 187cabdff1aSopenharmony_ci size_t icc_len; 188cabdff1aSopenharmony_ci JxlDecoderStatus jret; 189cabdff1aSopenharmony_ci /* an ICC profile is present, and we can meaningfully get it, 190cabdff1aSopenharmony_ci * because the pixel data is not XYB-encoded */ 191cabdff1aSopenharmony_ci jret = JxlDecoderGetICCProfileSize(ctx->decoder, &ctx->jxl_pixfmt, JXL_COLOR_PROFILE_TARGET_DATA, &icc_len); 192cabdff1aSopenharmony_ci if (jret == JXL_DEC_SUCCESS && icc_len > 0) { 193cabdff1aSopenharmony_ci av_buffer_unref(&ctx->iccp); 194cabdff1aSopenharmony_ci ctx->iccp = av_buffer_alloc(icc_len); 195cabdff1aSopenharmony_ci if (!ctx->iccp) 196cabdff1aSopenharmony_ci return AVERROR(ENOMEM); 197cabdff1aSopenharmony_ci jret = JxlDecoderGetColorAsICCProfile(ctx->decoder, &ctx->jxl_pixfmt, JXL_COLOR_PROFILE_TARGET_DATA, 198cabdff1aSopenharmony_ci ctx->iccp->data, icc_len); 199cabdff1aSopenharmony_ci if (jret != JXL_DEC_SUCCESS) { 200cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_WARNING, "Unable to obtain ICC Profile\n"); 201cabdff1aSopenharmony_ci av_buffer_unref(&ctx->iccp); 202cabdff1aSopenharmony_ci } 203cabdff1aSopenharmony_ci } 204cabdff1aSopenharmony_ci 205cabdff1aSopenharmony_ci return 0; 206cabdff1aSopenharmony_ci} 207cabdff1aSopenharmony_ci 208cabdff1aSopenharmony_ci/* 209cabdff1aSopenharmony_ci * There's generally four cases when it comes to decoding a libjxl image 210cabdff1aSopenharmony_ci * with regard to color encoding: 211cabdff1aSopenharmony_ci * (a) There is an embedded ICC profile in the image, and the image is XYB-encoded. 212cabdff1aSopenharmony_ci * (b) There is an embedded ICC profile in the image, and the image is not XYB-encoded. 213cabdff1aSopenharmony_ci * (c) There is no embedded ICC profile, and FFmpeg supports the tagged colorspace. 214cabdff1aSopenharmony_ci * (d) There is no embedded ICC profile, and FFmpeg does not support the tagged colorspace. 215cabdff1aSopenharmony_ci * 216cabdff1aSopenharmony_ci * In case (b), we forward the pixel data as is and forward the ICC Profile as-is. 217cabdff1aSopenharmony_ci * In case (c), we request the pixel data in the space it's tagged as, 218cabdff1aSopenharmony_ci * and tag the space accordingly. 219cabdff1aSopenharmony_ci * In case (a), libjxl does not support getting the pixel data in the space described by the ICC 220cabdff1aSopenharmony_ci * profile, so instead we request the pixel data in BT.2020/PQ as it is the widest 221cabdff1aSopenharmony_ci * space that FFmpeg supports. 222cabdff1aSopenharmony_ci * In case (d), we also request wide-gamut pixel data as a fallback since FFmpeg doesn't support 223cabdff1aSopenharmony_ci * the custom primaries tagged in the space. 224cabdff1aSopenharmony_ci */ 225cabdff1aSopenharmony_cistatic int libjxl_color_encoding_event(AVCodecContext *avctx, AVFrame *frame) 226cabdff1aSopenharmony_ci{ 227cabdff1aSopenharmony_ci LibJxlDecodeContext *ctx = avctx->priv_data; 228cabdff1aSopenharmony_ci JxlDecoderStatus jret; 229cabdff1aSopenharmony_ci int ret; 230cabdff1aSopenharmony_ci JxlColorEncoding jxl_color; 231cabdff1aSopenharmony_ci /* set this flag if we need to fall back on wide gamut */ 232cabdff1aSopenharmony_ci int fallback = 0; 233cabdff1aSopenharmony_ci 234cabdff1aSopenharmony_ci jret = JxlDecoderGetColorAsEncodedProfile(ctx->decoder, NULL, JXL_COLOR_PROFILE_TARGET_ORIGINAL, &jxl_color); 235cabdff1aSopenharmony_ci if (jret == JXL_DEC_SUCCESS) { 236cabdff1aSopenharmony_ci /* enum values describe the colors of this image */ 237cabdff1aSopenharmony_ci jret = JxlDecoderSetPreferredColorProfile(ctx->decoder, &jxl_color); 238cabdff1aSopenharmony_ci if (jret == JXL_DEC_SUCCESS) 239cabdff1aSopenharmony_ci jret = JxlDecoderGetColorAsEncodedProfile(ctx->decoder, &ctx->jxl_pixfmt, JXL_COLOR_PROFILE_TARGET_DATA, &jxl_color); 240cabdff1aSopenharmony_ci /* if we couldn't successfully request the pixel data space, we fall back on wide gamut */ 241cabdff1aSopenharmony_ci /* this code path is very unlikely to happen in practice */ 242cabdff1aSopenharmony_ci if (jret != JXL_DEC_SUCCESS) 243cabdff1aSopenharmony_ci fallback = 1; 244cabdff1aSopenharmony_ci } else { 245cabdff1aSopenharmony_ci /* an ICC Profile is present in the stream */ 246cabdff1aSopenharmony_ci if (ctx->basic_info.uses_original_profile) { 247cabdff1aSopenharmony_ci /* uses_original_profile is the same as !xyb_encoded */ 248cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_VERBOSE, "Using embedded ICC Profile\n"); 249cabdff1aSopenharmony_ci if ((ret = libjxl_get_icc(avctx)) < 0) 250cabdff1aSopenharmony_ci return ret; 251cabdff1aSopenharmony_ci } else { 252cabdff1aSopenharmony_ci /* 253cabdff1aSopenharmony_ci * an XYB-encoded image with an embedded ICC profile can't always have the 254cabdff1aSopenharmony_ci * pixel data requested in the original space, so libjxl has no feature 255cabdff1aSopenharmony_ci * to allow this to happen, so we fall back on wide gamut 256cabdff1aSopenharmony_ci */ 257cabdff1aSopenharmony_ci fallback = 1; 258cabdff1aSopenharmony_ci } 259cabdff1aSopenharmony_ci } 260cabdff1aSopenharmony_ci 261cabdff1aSopenharmony_ci avctx->color_range = frame->color_range = AVCOL_RANGE_JPEG; 262cabdff1aSopenharmony_ci if (ctx->jxl_pixfmt.num_channels >= 3) 263cabdff1aSopenharmony_ci avctx->colorspace = AVCOL_SPC_RGB; 264cabdff1aSopenharmony_ci avctx->color_primaries = AVCOL_PRI_UNSPECIFIED; 265cabdff1aSopenharmony_ci avctx->color_trc = AVCOL_TRC_UNSPECIFIED; 266cabdff1aSopenharmony_ci 267cabdff1aSopenharmony_ci if (!ctx->iccp) { 268cabdff1aSopenharmony_ci /* checking enum values */ 269cabdff1aSopenharmony_ci if (!fallback) { 270cabdff1aSopenharmony_ci if (avctx->colorspace == AVCOL_SPC_RGB) 271cabdff1aSopenharmony_ci avctx->color_primaries = libjxl_get_primaries(avctx, &jxl_color); 272cabdff1aSopenharmony_ci avctx->color_trc = libjxl_get_trc(avctx, &jxl_color); 273cabdff1aSopenharmony_ci } 274cabdff1aSopenharmony_ci /* fall back on wide gamut if enum values fail */ 275cabdff1aSopenharmony_ci if (avctx->color_primaries == AVCOL_PRI_UNSPECIFIED) { 276cabdff1aSopenharmony_ci if (avctx->colorspace == AVCOL_SPC_RGB) { 277cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_WARNING, "Falling back on wide gamut output\n"); 278cabdff1aSopenharmony_ci jxl_color.primaries = JXL_PRIMARIES_2100; 279cabdff1aSopenharmony_ci avctx->color_primaries = AVCOL_PRI_BT2020; 280cabdff1aSopenharmony_ci } 281cabdff1aSopenharmony_ci /* libjxl requires this set even for grayscale */ 282cabdff1aSopenharmony_ci jxl_color.white_point = JXL_WHITE_POINT_D65; 283cabdff1aSopenharmony_ci } 284cabdff1aSopenharmony_ci if (avctx->color_trc == AVCOL_TRC_UNSPECIFIED) { 285cabdff1aSopenharmony_ci if (ctx->jxl_pixfmt.data_type == JXL_TYPE_FLOAT 286cabdff1aSopenharmony_ci || ctx->jxl_pixfmt.data_type == JXL_TYPE_FLOAT16) { 287cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_WARNING, "Falling back on Linear Light transfer\n"); 288cabdff1aSopenharmony_ci jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR; 289cabdff1aSopenharmony_ci avctx->color_trc = AVCOL_TRC_LINEAR; 290cabdff1aSopenharmony_ci } else { 291cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_WARNING, "Falling back on iec61966-2-1/sRGB transfer\n"); 292cabdff1aSopenharmony_ci jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_SRGB; 293cabdff1aSopenharmony_ci avctx->color_trc = AVCOL_TRC_IEC61966_2_1; 294cabdff1aSopenharmony_ci } 295cabdff1aSopenharmony_ci } 296cabdff1aSopenharmony_ci /* all colors will be in-gamut so we want accurate colors */ 297cabdff1aSopenharmony_ci jxl_color.rendering_intent = JXL_RENDERING_INTENT_RELATIVE; 298cabdff1aSopenharmony_ci jxl_color.color_space = avctx->colorspace == AVCOL_SPC_RGB ? JXL_COLOR_SPACE_RGB : JXL_COLOR_SPACE_GRAY; 299cabdff1aSopenharmony_ci jret = JxlDecoderSetPreferredColorProfile(ctx->decoder, &jxl_color); 300cabdff1aSopenharmony_ci if (jret != JXL_DEC_SUCCESS) { 301cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_WARNING, "Unable to set fallback color encoding\n"); 302cabdff1aSopenharmony_ci /* 303cabdff1aSopenharmony_ci * This should only happen if there's a non-XYB encoded image with custom primaries 304cabdff1aSopenharmony_ci * embedded as enums and no embedded ICC Profile. 305cabdff1aSopenharmony_ci * In this case, libjxl will synthesize an ICC Profile for us. 306cabdff1aSopenharmony_ci */ 307cabdff1aSopenharmony_ci avctx->color_trc = AVCOL_TRC_UNSPECIFIED; 308cabdff1aSopenharmony_ci avctx->color_primaries = AVCOL_PRI_UNSPECIFIED; 309cabdff1aSopenharmony_ci if ((ret = libjxl_get_icc(avctx)) < 0) 310cabdff1aSopenharmony_ci return ret; 311cabdff1aSopenharmony_ci } 312cabdff1aSopenharmony_ci } 313cabdff1aSopenharmony_ci 314cabdff1aSopenharmony_ci frame->color_trc = avctx->color_trc; 315cabdff1aSopenharmony_ci frame->color_primaries = avctx->color_primaries; 316cabdff1aSopenharmony_ci frame->colorspace = avctx->colorspace; 317cabdff1aSopenharmony_ci 318cabdff1aSopenharmony_ci return 0; 319cabdff1aSopenharmony_ci} 320cabdff1aSopenharmony_ci 321cabdff1aSopenharmony_cistatic int libjxl_decode_frame(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacket *avpkt) 322cabdff1aSopenharmony_ci{ 323cabdff1aSopenharmony_ci LibJxlDecodeContext *ctx = avctx->priv_data; 324cabdff1aSopenharmony_ci const uint8_t *buf = avpkt->data; 325cabdff1aSopenharmony_ci size_t remaining = avpkt->size; 326cabdff1aSopenharmony_ci JxlDecoderStatus jret; 327cabdff1aSopenharmony_ci int ret; 328cabdff1aSopenharmony_ci *got_frame = 0; 329cabdff1aSopenharmony_ci 330cabdff1aSopenharmony_ci while (1) { 331cabdff1aSopenharmony_ci 332cabdff1aSopenharmony_ci jret = JxlDecoderSetInput(ctx->decoder, buf, remaining); 333cabdff1aSopenharmony_ci 334cabdff1aSopenharmony_ci if (jret == JXL_DEC_ERROR) { 335cabdff1aSopenharmony_ci /* this should never happen here unless there's a bug in libjxl */ 336cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_ERROR, "Unknown libjxl decode error\n"); 337cabdff1aSopenharmony_ci return AVERROR_EXTERNAL; 338cabdff1aSopenharmony_ci } 339cabdff1aSopenharmony_ci 340cabdff1aSopenharmony_ci jret = JxlDecoderProcessInput(ctx->decoder); 341cabdff1aSopenharmony_ci /* 342cabdff1aSopenharmony_ci * JxlDecoderReleaseInput returns the number 343cabdff1aSopenharmony_ci * of bytes remaining to be read, rather than 344cabdff1aSopenharmony_ci * the number of bytes that it did read 345cabdff1aSopenharmony_ci */ 346cabdff1aSopenharmony_ci remaining = JxlDecoderReleaseInput(ctx->decoder); 347cabdff1aSopenharmony_ci buf = avpkt->data + avpkt->size - remaining; 348cabdff1aSopenharmony_ci 349cabdff1aSopenharmony_ci switch(jret) { 350cabdff1aSopenharmony_ci case JXL_DEC_ERROR: 351cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_ERROR, "Unknown libjxl decode error\n"); 352cabdff1aSopenharmony_ci return AVERROR_INVALIDDATA; 353cabdff1aSopenharmony_ci case JXL_DEC_NEED_MORE_INPUT: 354cabdff1aSopenharmony_ci if (remaining == 0) { 355cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_ERROR, "Unexpected end of JXL codestream\n"); 356cabdff1aSopenharmony_ci return AVERROR_INVALIDDATA; 357cabdff1aSopenharmony_ci } 358cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_DEBUG, "NEED_MORE_INPUT event emitted\n"); 359cabdff1aSopenharmony_ci continue; 360cabdff1aSopenharmony_ci case JXL_DEC_BASIC_INFO: 361cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_DEBUG, "BASIC_INFO event emitted\n"); 362cabdff1aSopenharmony_ci if (JxlDecoderGetBasicInfo(ctx->decoder, &ctx->basic_info) != JXL_DEC_SUCCESS) { 363cabdff1aSopenharmony_ci /* 364cabdff1aSopenharmony_ci * this should never happen 365cabdff1aSopenharmony_ci * if it does it is likely a libjxl decoder bug 366cabdff1aSopenharmony_ci */ 367cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_ERROR, "Bad libjxl basic info event\n"); 368cabdff1aSopenharmony_ci return AVERROR_EXTERNAL; 369cabdff1aSopenharmony_ci } 370cabdff1aSopenharmony_ci avctx->pix_fmt = libjxl_get_pix_fmt(avctx, &ctx->basic_info, &ctx->jxl_pixfmt); 371cabdff1aSopenharmony_ci if (avctx->pix_fmt == AV_PIX_FMT_NONE) { 372cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_ERROR, "Bad libjxl pixel format\n"); 373cabdff1aSopenharmony_ci return AVERROR_EXTERNAL; 374cabdff1aSopenharmony_ci } 375cabdff1aSopenharmony_ci if ((ret = ff_set_dimensions(avctx, ctx->basic_info.xsize, ctx->basic_info.ysize)) < 0) 376cabdff1aSopenharmony_ci return ret; 377cabdff1aSopenharmony_ci continue; 378cabdff1aSopenharmony_ci case JXL_DEC_COLOR_ENCODING: 379cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_DEBUG, "COLOR_ENCODING event emitted\n"); 380cabdff1aSopenharmony_ci if ((ret = libjxl_color_encoding_event(avctx, frame)) < 0) 381cabdff1aSopenharmony_ci return ret; 382cabdff1aSopenharmony_ci continue; 383cabdff1aSopenharmony_ci case JXL_DEC_NEED_IMAGE_OUT_BUFFER: 384cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_DEBUG, "NEED_IMAGE_OUT_BUFFER event emitted\n"); 385cabdff1aSopenharmony_ci if ((ret = ff_get_buffer(avctx, frame, 0)) < 0) 386cabdff1aSopenharmony_ci return ret; 387cabdff1aSopenharmony_ci ctx->jxl_pixfmt.align = frame->linesize[0]; 388cabdff1aSopenharmony_ci if (JxlDecoderSetImageOutBuffer(ctx->decoder, &ctx->jxl_pixfmt, frame->data[0], frame->buf[0]->size) 389cabdff1aSopenharmony_ci != JXL_DEC_SUCCESS) { 390cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_ERROR, "Bad libjxl dec need image out buffer event\n"); 391cabdff1aSopenharmony_ci return AVERROR_EXTERNAL; 392cabdff1aSopenharmony_ci } 393cabdff1aSopenharmony_ci continue; 394cabdff1aSopenharmony_ci case JXL_DEC_FULL_IMAGE: 395cabdff1aSopenharmony_ci /* full image is one frame, even if animated */ 396cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_DEBUG, "FULL_IMAGE event emitted\n"); 397cabdff1aSopenharmony_ci frame->pict_type = AV_PICTURE_TYPE_I; 398cabdff1aSopenharmony_ci frame->key_frame = 1; 399cabdff1aSopenharmony_ci if (ctx->iccp) { 400cabdff1aSopenharmony_ci AVFrameSideData *sd = av_frame_new_side_data_from_buf(frame, AV_FRAME_DATA_ICC_PROFILE, ctx->iccp); 401cabdff1aSopenharmony_ci if (!sd) 402cabdff1aSopenharmony_ci return AVERROR(ENOMEM); 403cabdff1aSopenharmony_ci /* ownership is transfered, and it is not ref-ed */ 404cabdff1aSopenharmony_ci ctx->iccp = NULL; 405cabdff1aSopenharmony_ci } 406cabdff1aSopenharmony_ci *got_frame = 1; 407cabdff1aSopenharmony_ci return avpkt->size - remaining; 408cabdff1aSopenharmony_ci case JXL_DEC_SUCCESS: 409cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_DEBUG, "SUCCESS event emitted\n"); 410cabdff1aSopenharmony_ci /* 411cabdff1aSopenharmony_ci * The SUCCESS event isn't fired until after JXL_DEC_FULL_IMAGE. If this 412cabdff1aSopenharmony_ci * stream only contains one JXL image then JXL_DEC_SUCCESS will never fire. 413cabdff1aSopenharmony_ci * If the image2 sequence being decoded contains several JXL files, then 414cabdff1aSopenharmony_ci * libjxl will fire this event after the next AVPacket has been passed, 415cabdff1aSopenharmony_ci * which means the current packet is actually the next image in the sequence. 416cabdff1aSopenharmony_ci * This is why we reset the decoder and populate the packet data now, since 417cabdff1aSopenharmony_ci * this is the next packet and it has not been decoded yet. The decoder does 418cabdff1aSopenharmony_ci * have to be reset to allow us to use it for the next image, or libjxl 419cabdff1aSopenharmony_ci * will become very confused if the header information is not identical. 420cabdff1aSopenharmony_ci */ 421cabdff1aSopenharmony_ci JxlDecoderReset(ctx->decoder); 422cabdff1aSopenharmony_ci libjxl_init_jxl_decoder(avctx); 423cabdff1aSopenharmony_ci buf = avpkt->data; 424cabdff1aSopenharmony_ci remaining = avpkt->size; 425cabdff1aSopenharmony_ci continue; 426cabdff1aSopenharmony_ci default: 427cabdff1aSopenharmony_ci av_log(avctx, AV_LOG_ERROR, "Bad libjxl event: %d\n", jret); 428cabdff1aSopenharmony_ci return AVERROR_EXTERNAL; 429cabdff1aSopenharmony_ci } 430cabdff1aSopenharmony_ci } 431cabdff1aSopenharmony_ci} 432cabdff1aSopenharmony_ci 433cabdff1aSopenharmony_cistatic av_cold int libjxl_decode_close(AVCodecContext *avctx) 434cabdff1aSopenharmony_ci{ 435cabdff1aSopenharmony_ci LibJxlDecodeContext *ctx = avctx->priv_data; 436cabdff1aSopenharmony_ci 437cabdff1aSopenharmony_ci if (ctx->runner) 438cabdff1aSopenharmony_ci JxlThreadParallelRunnerDestroy(ctx->runner); 439cabdff1aSopenharmony_ci ctx->runner = NULL; 440cabdff1aSopenharmony_ci if (ctx->decoder) 441cabdff1aSopenharmony_ci JxlDecoderDestroy(ctx->decoder); 442cabdff1aSopenharmony_ci ctx->decoder = NULL; 443cabdff1aSopenharmony_ci av_buffer_unref(&ctx->iccp); 444cabdff1aSopenharmony_ci 445cabdff1aSopenharmony_ci return 0; 446cabdff1aSopenharmony_ci} 447cabdff1aSopenharmony_ci 448cabdff1aSopenharmony_ciconst FFCodec ff_libjxl_decoder = { 449cabdff1aSopenharmony_ci .p.name = "libjxl", 450cabdff1aSopenharmony_ci .p.long_name = NULL_IF_CONFIG_SMALL("libjxl JPEG XL"), 451cabdff1aSopenharmony_ci .p.type = AVMEDIA_TYPE_VIDEO, 452cabdff1aSopenharmony_ci .p.id = AV_CODEC_ID_JPEGXL, 453cabdff1aSopenharmony_ci .priv_data_size = sizeof(LibJxlDecodeContext), 454cabdff1aSopenharmony_ci .init = libjxl_decode_init, 455cabdff1aSopenharmony_ci FF_CODEC_DECODE_CB(libjxl_decode_frame), 456cabdff1aSopenharmony_ci .close = libjxl_decode_close, 457cabdff1aSopenharmony_ci .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_OTHER_THREADS, 458cabdff1aSopenharmony_ci .caps_internal = FF_CODEC_CAP_AUTO_THREADS | FF_CODEC_CAP_INIT_CLEANUP, 459cabdff1aSopenharmony_ci .p.wrapper_name = "libjxl", 460cabdff1aSopenharmony_ci}; 461