Leo Izen: > This commit adds support to libavcodec to read and parse > encoded Jpeg XL images. Jpeg XL is intended to be an > extended-life replacement to legacy mjpeg. > --- > MAINTAINERS | 2 + > libavcodec/Makefile | 1 + > libavcodec/codec_desc.c | 9 + > libavcodec/codec_id.h | 1 + > libavcodec/jpegxl.h | 206 ++++++++++ > libavcodec/jpegxl_parser.c | 809 +++++++++++++++++++++++++++++++++++++ > libavcodec/parsers.c | 1 + > libavcodec/version.h | 2 +- > 8 files changed, 1030 insertions(+), 1 deletion(-) > create mode 100644 libavcodec/jpegxl.h > create mode 100644 libavcodec/jpegxl_parser.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index c065e94498..17c0104672 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -187,6 +187,7 @@ Codecs: > interplayvideo.c Mike Melanson > jni*, ffjni* Matthieu Bouron > jpeg2000* Nicolas Bertrand > + jpegxl.h, jpegxl_parser.c Leo Izen > jvdec.c Peter Ross > lcl*.c Roberto Togni, Reimar Doeffinger > libcelt_dec.c Nicolas George > @@ -615,6 +616,7 @@ Haihao Xiang (haihao) 1F0C 31E8 B4FE F7A4 4DC1 > DC99 E0F5 76D4 76FC 437F > Jaikrishnan Menon 61A1 F09F 01C9 2D45 78E1 C862 25DC 8831 AF70 > D368 > James Almer 7751 2E8C FD94 A169 57E6 9A7A 1463 01AD 7376 > 59E0 > Jean Delvare 7CA6 9F44 60F1 BDC4 1FD2 C858 A552 6B9B B3CD > 4E6A > +Leo Izen (thebombzen) B6FD 3CFC 7ACF 83FC 9137 6945 5A71 C331 FD2F > A19A > Loren Merritt ABD9 08F4 C920 3F65 D8BE 35D7 1540 DAA7 060F > 56DE > Lynne FE50 139C 6805 72CA FD52 1F8D A2FE A5F0 3F03 > 4464 > Michael Niedermayer 9FF2 128B 147E F673 0BAD F133 611E C787 040B > 0FAB > diff --git a/libavcodec/Makefile b/libavcodec/Makefile > index cfc70a3eaf..423022e714 100644 > --- a/libavcodec/Makefile > +++ b/libavcodec/Makefile > @@ -43,6 +43,7 @@ OBJS = ac3_parser.o > \ > dv_profile.o \ > encode.o \ > imgconvert.o \ > + jpegxl_parser.o \ > jni.o \ > mathtables.o \ > mediacodec.o \ > diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c > index 0974ee03de..0f3d0f910b 100644 > --- a/libavcodec/codec_desc.c > +++ b/libavcodec/codec_desc.c > @@ -1862,6 +1862,15 @@ static const AVCodecDescriptor codec_descriptors[] = { > .long_name = NULL_IF_CONFIG_SMALL("GEM Raster image"), > .props = AV_CODEC_PROP_LOSSY, > }, > + { > + .id = AV_CODEC_ID_JPEGXL, > + .type = AVMEDIA_TYPE_VIDEO, > + .name = "jpegxl", > + .long_name = NULL_IF_CONFIG_SMALL("JPEG XL"), > + .props = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY | > + AV_CODEC_PROP_LOSSLESS, > + .mime_types= MT("image/jxl"), > + }, > > /* various PCM "codecs" */ > { > diff --git a/libavcodec/codec_id.h b/libavcodec/codec_id.h > index ab265ec584..551a516446 100644 > --- a/libavcodec/codec_id.h > +++ b/libavcodec/codec_id.h > @@ -308,6 +308,7 @@ enum AVCodecID { > AV_CODEC_ID_SIMBIOSIS_IMX, > AV_CODEC_ID_SGA_VIDEO, > AV_CODEC_ID_GEM, > + AV_CODEC_ID_JPEGXL, > > /* various PCM "codecs" */ > AV_CODEC_ID_FIRST_AUDIO = 0x10000, ///< A dummy id pointing at the > start of audio codecs > diff --git a/libavcodec/jpegxl.h b/libavcodec/jpegxl.h > new file mode 100644 > index 0000000000..cbfb33f74c > --- /dev/null > +++ b/libavcodec/jpegxl.h > @@ -0,0 +1,206 @@ > +/* > + * JPEG XL header > + * Copyright (c) 2021 Leo Izen <leo.i...@gmail.com> > + * > + * This file is part of FFmpeg. > + * > + * FFmpeg is free software; you can redistribute it and/or > + * modify it under the terms of the GNU Lesser General Public > + * License as published by the Free Software Foundation; either > + * version 2.1 of the License, or (at your option) any later version. > + * > + * FFmpeg is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * Lesser General Public License for more details. > + * > + * You should have received a copy of the GNU Lesser General Public > + * License along with FFmpeg; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 > USA > + */ > + > +/** > + * @file > + * JPEG XL header > + */ > + > +#ifndef AVCODEC_JPEGXL_H > +#define AVCODEC_JPEGXL_H > + > +#include <stdint.h> > + > +#define FF_JPEGXL_CODESTREAM_SIGNATURE_LE 0x0aff > +#define FF_JPEGXL_CODESTREAM_SIGNATURE_BE 0xff0a > +#define FF_JPEGXL_CONTAINER_SIGNATURE_LE 0x204c584a0c000000 > +#define FF_JPEGXL_CONTAINER_SIGNATURE_BE 0x0000000c4a584c20 > + > +enum JpegXLExtraChannelType { > + FF_JPEGXL_CT_ALPHA = 0, > + FF_JPEGXL_CT_DEPTH, > + FF_JPEGXL_CT_SPOT_COLOR, > + FF_JPEGXL_CT_SELECTION_MASK, > + FF_JPEGXL_CT_BLACK, > + FF_JPEGXL_CT_CFA, > + FF_JPEGXL_CT_THERMAL, > + FF_JPEGXL_CT_NON_OPTIONAL = 15, > + FF_JPEGXL_CT_OPTIONAL > +}; > + > +enum JpegXLColorSpace { > + FF_JPEGXL_CS_RGB = 0, > + FF_JPEGXL_CS_GRAY, > + FF_JPEGXL_CS_XYB, > + FF_JPEGXL_CS_UNKNOWN > +}; > + > +enum JpegXLWhitePoint { > + FF_JPEGXL_WP_D65 = 1, > + FF_JPEGXL_WP_CUSTOM, > + FF_JPEGXL_WP_E = 10, > + FF_JPEGXL_WP_DCI = 11 > +}; > + > +enum JpegXLPrimaries { > + FF_JPEGXL_PR_SRGB = 1, > + FF_JPEGXL_PR_CUSTOM, > + FF_JPEGXL_PR_2100 = 9, > + FF_JPEGXL_PR_P3 = 11, > +}; > + > +enum JpegXLTransferFunction { > + FF_JPEGXL_TF_709 = 1, > + FF_JPEGXL_TF_UNKNOWN, > + FF_JPEGXL_TF_LINEAR = 8, > + FF_JPEGXL_TF_SRGB = 13, > + FF_JPEGXL_TF_PQ = 16, > + FF_JPEGXL_TF_DCI, > + FF_JPEGXL_TF_HLG > +}; > + > +enum JpegXLRenderingIntent { > + FF_JPEGXL_RI_PERCEPTUAL = 0, > + FF_JPEGXL_RI_RELATIVE, > + FF_JPEGXL_RI_SATURATION, > + FF_JPEGXL_RI_ABSOLUTE > +}; > + > +typedef struct JpegXLExtraChannelInfo { > + enum JpegXLExtraChannelType type; > + uint32_t bits_per_sample; > + uint32_t exp_bits_per_sample; > + uint32_t dim_shift; > + size_t name_len; > + /* utf-8 */ > + char *name; > + int alpha_associated; > + float red; > + float green; > + float blue; > + float solidity; > + uint32_t cfa_channel; > +} JpegXLExtraChannelInfo; > + > +typedef struct JpegXLHeader { > + uint32_t width; > + uint32_t height; > + int orientation; > + /* zero if not present */ > + uint32_t intrinsic_width; > + uint32_t intrinsic_height; > + uint32_t preview_width; > + uint32_t preview_height; > + /* BEGIN animation header */ > + uint32_t anim_tb_num; > + uint32_t anim_tb_denom; > + uint32_t anim_loop_count; > + int anim_have_pts; > + /* END animation header */ > + > + uint32_t bits_per_sample; > + uint32_t exp_bits_per_sample; > + > + int modular_16bit_buffers; > + > + uint32_t num_extra_channels; > + > + /* > + * NULL if no extra channels > + * otherwise an array of extra channel info > + * with length num_extra_channels > + */ > + JpegXLExtraChannelInfo *extra_channel_info; > + > + int xyb_encoded; > + > + /* BEGIN color encoding bundle */ > + int have_icc_profile; > + enum JpegXLColorSpace color_space; > + enum JpegXLWhitePoint white_point; > + uint32_t white_ux; > + uint32_t white_uy; > + enum JpegXLPrimaries primaries; > + uint32_t red_ux; > + uint32_t red_uy; > + uint32_t green_ux; > + uint32_t green_uy; > + uint32_t blue_ux; > + uint32_t blue_uy; > + /* > + * if this is less than 1 << 24, > + * then interpret it as a gamma value > + * If this is greater than or equal to 1 << 24, > + * then subtract 1 << 24 and interpret it as a > + * an enum JpegXLTransferFunction > + */ > + int have_gamma; > + uint32_t transfer_function; > + enum JpegXLRenderingIntent rendering_intent; > + /* END color encoding bundle */ > + > + /* BEGIN tone mapping bundle */ > + float intensity_target; > + float min_nits; > + int relative_to_max_display; > + float linear_below; > + /* END tone mapping bundle */ > + > + uint64_t extensions; > + /* if extensions is nonzero, this will be length 64 */ > + /* otherwise it will be NULL */ > + uint64_t *extension_bits; > + > + int default_transform; > + > + /* if present, an array of length 16 */ > + /* NULL if not present */ > + float *opsin_inverse_matrix; > + > + uint32_t cw_mask; > + > + /* if these are not present > + * use NULL for these pointers > + * otherwise up2_weight is an > + * array of length 15, up4_weight > + * is length 55, and up8_weight is > + * length 210 > + */ > + float *up2_weight; > + float *up4_weight; > + float *up8_weight; > + > + /* > + * this is not provided by the header, > + * but rather, by the container > + * raw Jpeg XL Codestreams are level 5 > + * the container can choose to up it to 10 > + */ > + int level; > + > +} JpegXLHeader;
Why is this structure exported in the header when it is only used by the parser? > + > +/** > + * @return 0 upon valid, nonzero upon some parse error > + */ > +int avpriv_jpegxl_verify_codestream_header(void *avctx, uint8_t *buf, size_t > buflen, int level); > + > +#endif /* AVCODEC_JPEGXL_H */ > diff --git a/libavcodec/jpegxl_parser.c b/libavcodec/jpegxl_parser.c > new file mode 100644 > index 0000000000..3355ca603e > --- /dev/null > +++ b/libavcodec/jpegxl_parser.c > @@ -0,0 +1,809 @@ > +/* > + * JPEG XL parser > + * Copyright (c) 2021 Leo Izen <leo.i...@gmail.com> > + * > + * This file is part of FFmpeg. > + * > + * FFmpeg is free software; you can redistribute it and/or > + * modify it under the terms of the GNU Lesser General Public > + * License as published by the Free Software Foundation; either > + * version 2.1 of the License, or (at your option) any later version. > + * > + * FFmpeg is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * Lesser General Public License for more details. > + * > + * You should have received a copy of the GNU Lesser General Public > + * License along with FFmpeg; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 > USA > + */ > + > +/** > + * @file > + * JPEG XL parser > + */ > + > +#include <stdint.h> Unnecessary, as inttypes.h is guaranteed by the spec to include stdint.h. > +#include <inttypes.h> > +#include <stdlib.h> > + > +#ifndef BITSTREAM_READER_LE > +#define BITSTREAM_READER_LE > +#endif The check here is nonsense. > +#ifdef CACHED_BITSTREAM_READER > +#undef CACHED_BITSTREAM_READER > +#endif > +#define CACHED_BITSTREAM_READER 1 Is there a reason you insist on the cached bitstream reader? > + > +#include "libavutil/error.h" > +#include "libavutil/intreadwrite.h" > +#include "libavutil/mem.h" > + > +#include "codec_id.h" > +#include "config.h" > +#include "get_bits.h" > +#include "jpegxl.h" > +#include "parser.h" > + > +#if CONFIG_JPEGXL_PARSER > + > +typedef struct JpegXLParseContext { > + ParseContext pc; > + GetBitContext gb; > + const uint8_t *buf; > + size_t buflen; > + size_t bits_read; What exactly is the point of duplicating the GetBitContext's internal status? > +} JpegXLParseContext; > + > +#define jxl_bits(n) jpegxl_get_bits(jxlr, (n)) > +#define jxl_enum() jpegxl_u32(jxlr, (uint32_t[]){0, 1, 2, 18}, > (uint32_t[]){0, 0, 4, 6}) > + > +#define jxl_parse_errv(type, value) av_log(avctx, AV_LOG_DEBUG, \ > + "Invalid " type " at position: %zu", \ > + value, jxlr->bits_read) > + > +#define jxl_parse_errvv(type, v1, v2) av_log(avctx, AV_LOG_DEBUG, \ > + "Invalid " type " at position: %zu", \ > + v1, v2, jxlr->bits_read) > + > +#define jxl_parse_err(type) jxl_parse_errv("%s", type) > + > +static void jpegxl_reset_pc(JpegXLParseContext *jxlr) > +{ > + memset(&jxlr->gb, 0, sizeof(GetBitContext)); Unnecessary, as you reset gb below. > + jxlr->bits_read = 0; > + init_get_bits8(&jxlr->gb, jxlr->buf, jxlr->buflen); > +} > + > +static void jpegxl_init_pc(JpegXLParseContext *jxlr, const uint8_t *buf, > size_t buflen) > +{ > + memset(&jxlr->pc, 0, sizeof(ParseContext)); > + memset(&jxlr->gb, 0, sizeof(GetBitContext)); Unnecessary, as you are initializing gb below. > + jxlr->buf = buf; > + jxlr->buflen = buflen; > + jxlr->bits_read = 0; > + init_get_bits8(&jxlr->gb, buf, buflen); > +} > + > +static uint64_t jpegxl_get_bits(JpegXLParseContext *jxlr, size_t bits) > +{ > + if (!bits) > + return 0; > + if (bits > INT_MAX) > + /* what are you doing with all those bits... */ > + return 0; > + jxlr->bits_read += bits; > + /* extra buffer size of 8 bytes */ > + if ((jxlr->bits_read + 1) / 8 + 8 > jxlr->buflen - 1) This check looks weird. E.g. why (jxlr->bits_read + 1) / 8 and not (jxlr->bits_read + 7) / 8? And why the extra buffer size of eight bytes? > + /* overflowing buffer */ > + return 0; > + while (bits > 64) { I do not see anything where you would read more than 64 bits at a time. It would also make no sense (the format would be wasting bits in this case). > + size_t bitcount = (bits - 1) % 64 + 1; > + get_bits64(&jxlr->gb, bitcount); > + bits -= bitcount; > + } > + return get_bits64(&jxlr->gb, bits); > +} > + > +static uint32_t jpegxl_u32(JpegXLParseContext *jxlr, > + uint32_t *constants, uint32_t *ubits) Both arrays can be made const. And actually, both arrays should be declared as const uint32_t [4]. > +{ > + uint32_t ret, choice = jxl_bits(2); > + ret = constants[choice]; > + if (ubits[choice]) > + ret += jxl_bits(ubits[choice]); > + return ret; > +} > + > +static uint64_t jpegxl_u64(JpegXLParseContext *jxlr) > +{ > + uint64_t shift = 12, ret; > + switch (jxl_bits(2)) { > + case 0: > + ret = 0; > + break; > + case 1: > + ret = 1 + jxl_bits(4); > + break; > + case 2: > + ret = 17 + jxl_bits(8); > + break; > + case 3: > + ret = jxl_bits(12); > + while (jxl_bits(1)){ > + if (shift < 60) { > + ret |= jxl_bits(8) << shift; > + shift += 8; > + } else { > + ret |= jxl_bits(4) << shift; > + break; > + } > + } > + break; > + } > + return ret; > +} > + > +static float jpegxl_f16(JpegXLParseContext *jxlr) > +{ > + float ret; > + uint32_t mantissa = jxl_bits(10) << 13; > + uint32_t biased_exponent = jxl_bits(5); > + if (biased_exponent == 31) > + mantissa |= 0xFF << 23; > + else > + mantissa |= ((biased_exponent - 15 + 127) & 0xFF) << 23; > + memcpy(&ret, &mantissa, sizeof(float)); return av_int2float(mantissa); > + return ret; > +} > + > +static uint32_t jpegxl_width_from_ratio(uint32_t height, int ratio) > +{ > + switch (ratio){ > + case 1: > + return height; > + case 2: > + return (height * 12) / 10; > + case 3: > + return (height * 4) / 3; > + case 4: > + return (height * 3) / 2; > + case 5: > + return (height * 16) / 9; > + case 6: > + return (height * 5) / 4; > + case 7: > + return height * 2; > + default: > + /* manual width */ > + return 0; > + } > +} > + > + > +static int jpegxl_parse_size_header(JpegXLParseContext *jxlr, > + uint32_t *width, uint32_t *height) > +{ > + uint32_t w, h; > + if (jxl_bits(1)) { > + /* small size header */ > + h = (jxl_bits(5) + 1) << 3; > + w = jpegxl_width_from_ratio(h, jxl_bits(3)); > + if (!w) > + w = (jxl_bits(5) + 1) << 3; > + } else { > + /* large size header */ > + h = 1 + jpegxl_u32(jxlr, > + (uint32_t[]){0, 0, 0, 0}, (uint32_t[]){9, 13, 18, 30}); h can be up to 2^30 here. This means that the multiplications in jpegxl_width_from_ratio() can overflow and need to be performed in 64bit (although the end result always fits into an uint32_t). (E.g. if h were 2^30, then jpegxl_width_from_ratio() will return 0 in cases 2, 3 and 5.) > + w = jpegxl_width_from_ratio(h, jxl_bits(3)); > + if (!w) > + w = 1 + jpegxl_u32(jxlr, > + (uint32_t[]){0, 0, 0, 0}, (uint32_t[]){9, 13, 18, > 30}); > + } > + *width = w, *height = h; > + return 0; > +} > + > +static int jpegxl_parse_preview_header(JpegXLParseContext *jxlr, > + uint32_t *width, uint32_t *height) > +{ > + uint32_t w, h; > + if (jxl_bits(1)) { > + /* div8 */ > + h = jpegxl_u32(jxlr, > + (uint32_t[]){16, 32, 1, 33}, (uint32_t[]){0, 0, 5, 9}) << 3; > + w = jpegxl_width_from_ratio(h, jxl_bits(3)); > + if (!w) > + w = jpegxl_u32(jxlr, > + (uint32_t[]){16, 32, 1, 33}, (uint32_t[]){0, 0, 5, 9}) > << 3; > + } else { > + /* full */ > + h = jpegxl_u32(jxlr, > + (uint32_t[]){1, 65, 321, 1345}, (uint32_t[]){6, 8, 10, 12}); > + w = jpegxl_width_from_ratio(h, jxl_bits(3)); > + if (!w) > + w = jpegxl_u32(jxlr, > + (uint32_t[]){1, 65, 321, 1345}, (uint32_t[]){6, 8, 10, > 12}); > + } > + *width = w, *height = h; > + return 0; > +} > + > +static int jpegxl_parse_animation_header(JpegXLParseContext *jxlr, > + uint32_t *num, uint32_t *denom, uint32_t *count, int *have_pts) > +{ > + uint32_t n, d, c; > + int p; > + n = jpegxl_u32(jxlr, > + (uint32_t[]){100, 1000, 1, 1}, (uint32_t[]){0, 0, 10, 30}); > + d = jpegxl_u32(jxlr, > + (uint32_t[]){1, 1001, 1, 1}, (uint32_t[]){0, 0, 8, 10}); > + c = jpegxl_u32(jxlr, > + (uint32_t[]){0, 0, 0, 0}, (uint32_t[]){0, 3, 16, 32}); > + p = jxl_bits(1); > + *num = n, *denom = d, *count = c, *have_pts = p; > + return 0; > +} > + > +static int jpegxl_parse_bit_depth(JpegXLParseContext *jxlr, > + uint32_t *depth, uint32_t *exp_depth) > +{ > + uint32_t d, e; > + if (jxl_bits(1)) { > + /* float samples */ > + d = jpegxl_u32(jxlr, > + (uint32_t[]){32, 16, 24, 1}, (uint32_t[]){0, 0, 0, 6}); > + e = jxl_bits(4) + 1; > + } else { > + /* integer samples */ > + d = jpegxl_u32(jxlr, > + (uint32_t[]){8, 10, 12, 1}, (uint32_t[]){0, 0, 0, 6}); > + e = 0; > + } > + *depth = d, *exp_depth = e; > + return 0; > +} > + > +static int jpegxl_parse_extra_channel_info(JpegXLParseContext *jxlr, > + JpegXLExtraChannelInfo *info, int level) > +{ > + int status = 0; > + int all_default = jxl_bits(1); > + > + if (!all_default) { > + info->type = jxl_enum(); > + if (info->type > 63) > + /* enum types cannot be 64+ */ > + return 1; > + status = jpegxl_parse_bit_depth(jxlr, &info->bits_per_sample, > &info->exp_bits_per_sample); > + if (!status) > + return status; > + info->dim_shift = jpegxl_u32(jxlr, (uint32_t[]){0, 3, 4, 1}, > (uint32_t[]){0, 0, 0, 3}); > + info->name_len = jpegxl_u32(jxlr, (uint32_t[]){0, 0, 16, 48}, > (uint32_t[]){0, 4, 5, 10}); > + } else { > + info->type = FF_JPEGXL_CT_ALPHA; > + info->bits_per_sample = 8; > + info->exp_bits_per_sample = 0; > + } > + > + info->name = av_malloc(info->name_len + 1); > + if (!info->name) > + return AVERROR(ENOMEM); > + > + for (uint32_t i = 0; i < info->name_len; i++) > + /* there is no byte-alignment guarantee so no memcpy */ > + info->name[i] = jxl_bits(8); > + > + /* null-terminate it for string operations */ > + /* even though we have name_len */ > + info->name[info->name_len] = '\0'; > + > + info->alpha_associated = > + !all_default && info->type == FF_JPEGXL_CT_ALPHA && jxl_bits(1); > + > + if (info->type == FF_JPEGXL_CT_SPOT_COLOR) { > + info->red = jpegxl_f16(jxlr); > + info->green = jpegxl_f16(jxlr); > + info->blue = jpegxl_f16(jxlr); > + info->solidity = jpegxl_f16(jxlr); > + } > + > + if (info->type == FF_JPEGXL_CT_CFA) > + info->cfa_channel = jpegxl_u32(jxlr, (uint32_t[]){1, 0, 3, 19}, > (uint32_t[]){0, 2, 4, 8}); > + else > + info->cfa_channel = 1; > + > + if (info->type == FF_JPEGXL_CT_BLACK && level < 10) > + return 1; > + > + return 0; > +} > + > +static void jpegxl_free_header(JpegXLHeader *header) > +{ > + if (header) { > + if (header->extra_channel_info) { > + for (uint32_t i = 0; i < header->num_extra_channels; i++) { > + if (header->extra_channel_info + i) > + av_freep(&header->extra_channel_info[i].name); > + } > + av_freep(&header->extra_channel_info); > + } > + if (header->extension_bits) > + av_freep(&header->extension_bits); > + if (header->opsin_inverse_matrix) > + av_freep(&header->opsin_inverse_matrix); > + if (header->up2_weight) > + av_freep(&header->up2_weight); > + if (header->up4_weight) > + av_freep(&header->up4_weight); > + if (header->up8_weight) > + av_freep(&header->up8_weight); > + av_freep(&header); > + } > +} > + > +/** > + * Parse a JpegXL Codestream Header and read it into the argument Header > + * @param level Codestream level provided by the container, 5 if raw > codestream > + * @return 0 upon success, negative upon error, and positive if the buffer > overran > + */ > +static int jpegxl_parse_codestream_header(void *avctx, JpegXLParseContext > *jxlr, JpegXLHeader **headerp, int level) > +{ > + JpegXLHeader *header = NULL; > + int all_default, extra_fields = 0, status; > + > + header = av_mallocz(sizeof(JpegXLHeader)); This allocation is completely unnecessary, just put a JpegXLHeader on the stack in avpriv_jpegxl_verify_codestream_header() and jpegxl_parse(). > + if (!header) { > + av_log(avctx, AV_LOG_ERROR, "Could not allocate JpegXLHeader"); > + status = AVERROR(ENOMEM); > + goto fail; > + } > + > + /* signature check */ > + if (jxl_bits(16) != FF_JPEGXL_CODESTREAM_SIGNATURE_LE) { > + av_log(avctx, AV_LOG_DEBUG, "Failed JPEG XL Signature Check"); > + goto fail; > + } > + > + status = jpegxl_parse_size_header(jxlr, > + &header->width, &header->height); > + if (status) { > + jxl_parse_err("size header"); > + goto fail; > + } > + > + /* level 5 codestream */ > + if (level < 10) { > + if (header->width > (1 << 18) || header->height > (1 << 18) > + || (header->width >> 4) * (header->height >> 4) > (1 << 20)) { > + jxl_parse_err("width or height or both"); > + goto fail; > + } > + header->level = 5; > + } else { > + if (header->width > (1 << 30) || header->height > (1 << 30) > + || (header->width >> 14) * (header->height >> 14) > (1 << 12)) { > + jxl_parse_err("width or height or both"); > + goto fail; > + } > + header->level = 10; > + } > + > + all_default = jxl_bits(1); > + > + if (!all_default) > + extra_fields = jxl_bits(1); > + > + if (extra_fields) { > + header->orientation = jxl_bits(3); > + /* intrinstic size */ > + if (jxl_bits(1)) { > + status = jpegxl_parse_size_header(jxlr, > + &header->intrinsic_width, &header->intrinsic_height); > + if (status) { > + jxl_parse_err("intrinstic size header"); > + goto fail; > + } > + } > + > + /* preview header */ > + if (jxl_bits(1)) { > + status = jpegxl_parse_preview_header(jxlr, > + &header->preview_width, &header->preview_height); > + if (status) { > + jxl_parse_err("preview header"); > + goto fail; > + } > + if (header->preview_width > 4096 || header->preview_height > > 4096) { > + jxl_parse_errvv("preview header size %" PRIu32 ", %" PRIu32, > + header->preview_width, header->preview_height); > + goto fail; > + } > + } > + > + /* animation header */ > + if (jxl_bits(1)) { > + status = jpegxl_parse_animation_header(jxlr, > + &header->anim_tb_num, &header->anim_tb_denom, > + &header->anim_loop_count, &header->anim_have_pts); > + if (status) { > + jxl_parse_err("animation header"); > + goto fail; > + } > + } > + > + } > + > + if (!all_default) { > + status = jpegxl_parse_bit_depth(jxlr, > + &header->bits_per_sample, &header->exp_bits_per_sample); > + if (status) { > + jxl_parse_err("bit depth header"); > + goto fail; > + } > + > + header->modular_16bit_buffers = jxl_bits(1); > + > + if (!header->modular_16bit_buffers && level < 10) { > + jxl_parse_err("modular 16bit buffers"); > + goto fail; > + } > + > + header->num_extra_channels = jpegxl_u32(jxlr, > + (uint32_t[]){0, 1, 2, 1}, (uint32_t[]){0, 0, 4, 12}); > + if (header->num_extra_channels > 256 || > + level < 10 && header->num_extra_channels > 4) { > + jxl_parse_err("too many extra channels"); > + goto fail; > + } > + if (header->num_extra_channels) { > + header->extra_channel_info = > + av_calloc(header->num_extra_channels + 1, > sizeof(JpegXLExtraChannelInfo)); > + for (uint32_t i = 0; i < header->num_extra_channels; i++) { > + status = jpegxl_parse_extra_channel_info(jxlr, > header->extra_channel_info + i, level); > + if (status) { > + jxl_parse_errv("extra channel number %" PRIu32, i); > + goto fail; > + } > + } > + } > + > + header->xyb_encoded = jxl_bits(1); > + > + if (jxl_bits(1)) { > + /* all_default for color encoding */ > + header->have_icc_profile = 0; > + header->color_space = FF_JPEGXL_CS_RGB; > + header->white_point = FF_JPEGXL_WP_D65; > + header->primaries = FF_JPEGXL_PR_SRGB; > + header->transfer_function = (1 << 24) + FF_JPEGXL_TF_SRGB; > + header->rendering_intent = FF_JPEGXL_RI_RELATIVE; > + } else { > + header->have_icc_profile = jxl_bits(1); > + header->color_space = jxl_enum(); > + if (header->color_space > 63) { > + jxl_parse_errv("color space enum %" PRIu32, > header->white_point); > + goto fail; > + } > + if (header->color_space != FF_JPEGXL_CS_XYB > + && !header->have_icc_profile) { > + header->white_point = jxl_enum(); > + if (header->white_point > 63) { > + jxl_parse_errv("white point enum %" PRIu32, > header->white_point); > + goto fail; > + } > + } else { > + header->white_point = FF_JPEGXL_WP_D65; > + } > + if (header->white_point == FF_JPEGXL_WP_CUSTOM) { > + header->white_ux = jpegxl_u32(jxlr, (uint32_t[]){0, 524288, > 1048576, 2097152}, (uint32_t[]){19, 19, 20, 21}); > + header->white_uy = jpegxl_u32(jxlr, (uint32_t[]){0, 524288, > 1048576, 2097152}, (uint32_t[]){19, 19, 20, 21}); > + } > + if (header->color_space != FF_JPEGXL_CS_XYB > + && header->color_space != FF_JPEGXL_CS_GRAY > + && !header->have_icc_profile) { > + header->primaries = jxl_enum(); > + if (header->primaries > 63) { > + jxl_parse_errv("primaries enum %" PRIu32, > header->primaries); > + goto fail; > + } > + } else { > + header->primaries = FF_JPEGXL_PR_SRGB; > + } > + if (header->primaries == FF_JPEGXL_PR_CUSTOM) { > + header->red_ux = jpegxl_u32(jxlr, (uint32_t[]){0, 524288, > 1048576, 2097152}, (uint32_t[]){19, 19, 20, 21}); > + header->red_uy = jpegxl_u32(jxlr, (uint32_t[]){0, 524288, > 1048576, 2097152}, (uint32_t[]){19, 19, 20, 21}); > + header->green_ux = jpegxl_u32(jxlr, (uint32_t[]){0, 524288, > 1048576, 2097152}, (uint32_t[]){19, 19, 20, 21}); > + header->green_uy = jpegxl_u32(jxlr, (uint32_t[]){0, 524288, > 1048576, 2097152}, (uint32_t[]){19, 19, 20, 21}); > + header->blue_ux = jpegxl_u32(jxlr, (uint32_t[]){0, 524288, > 1048576, 2097152}, (uint32_t[]){19, 19, 20, 21}); > + header->blue_uy = jpegxl_u32(jxlr, (uint32_t[]){0, 524288, > 1048576, 2097152}, (uint32_t[]){19, 19, 20, 21}); > + } > + if (!header->have_icc_profile) { > + if (jxl_bits(1)) { > + /* this is gamma */ > + header->transfer_function = jxl_bits(24); > + } else { > + header->transfer_function = jxl_enum(); > + if (header->transfer_function > 63) { > + jxl_parse_errv("transfer function enum %" PRIu32, > header->transfer_function); > + goto fail; > + } > + /* > + * higher than the highest possible gamma value > + * marks it as an enum isntead of gamma > + */ > + header->transfer_function += 1 << 24; > + } > + header->rendering_intent = jxl_enum(); > + if (header->rendering_intent > 63) { > + jxl_parse_errv("rendering intent enum %" PRIu32, > header->rendering_intent); > + goto fail; > + } > + } else { > + header->transfer_function = (1 << 24) + FF_JPEGXL_TF_SRGB; > + header->rendering_intent = FF_JPEGXL_RI_RELATIVE; > + } > + } > + > + /* lazy && works with this macro */ > + if (extra_fields && !jxl_bits(1)) { > + header->intensity_target = jpegxl_f16(jxlr); > + header->min_nits = jpegxl_f16(jxlr); > + header->relative_to_max_display = jxl_bits(1); > + header->linear_below = jpegxl_f16(jxlr); > + } else { > + header->intensity_target = 255; > + } > + > + header->extensions = jpegxl_u64(jxlr); > + if (header->extensions) { > + header->extension_bits = av_calloc(64, sizeof(uint64_t)); This array is write-only. > + if (!header->extension_bits) { > + av_log(avctx, AV_LOG_ERROR, "Could not allocate extension > bit array"); > + status = AVERROR(ENOMEM); > + goto fail; > + } > + for (int i = 0; i < 64; i++) { > + if (header->extensions & (UINT64_C(1) << i)) > + header->extension_bits[i] = jpegxl_u64(jxlr); > + } > + } > + > + } else { > + header->modular_16bit_buffers = 1; > + header->xyb_encoded = 1; > + } > + > + header->default_transform = jxl_bits(1); > + > + /* lazy && works with this macro */ > + if (!header->default_transform && header->xyb_encoded && !jxl_bits(1)) { > + header->opsin_inverse_matrix = av_malloc_array(16, sizeof(float)); > + if (!header->opsin_inverse_matrix) { > + av_log(avctx, AV_LOG_ERROR, "Could not allocate Opsin Inverse > Matrix"); > + status = AVERROR(ENOMEM); > + goto fail; > + } > + for (int i = 0; i < 16; i++) { > + header->opsin_inverse_matrix[i] = jpegxl_f16(jxlr); > + } > + } > + > + if (!header->default_transform) { > + header->cw_mask = jxl_bits(3); > + } > + > + if (header->cw_mask & 1) { > + header->up2_weight = av_malloc_array(15, sizeof(float)); > + if (!header->up2_weight) { > + av_log(avctx, AV_LOG_ERROR, "Could not allocate up2_weight"); > + status = AVERROR(ENOMEM); > + goto fail; > + } > + for (int i = 0; i < 15; i++) { > + header->up2_weight[i] = jpegxl_f16(jxlr); > + } > + } > + if (header->cw_mask & 2) { > + header->up4_weight = av_malloc_array(55, sizeof(float)); > + if (!header->up4_weight) { > + av_log(avctx, AV_LOG_ERROR, "Could not allocate up4_weight"); > + status = AVERROR(ENOMEM); > + goto fail; > + } > + for (int i = 0; i < 55; i++) { > + header->up4_weight[i] = jpegxl_f16(jxlr); > + } > + } > + if (header->cw_mask & 4) { > + header->up8_weight = av_malloc_array(210, sizeof(float)); These arrays are never used at all, so you can just avoid the allocation. In fact I wonder whether it is necessary to parse this stuff here at all (AFAIK no other parser does such deep parsing). > + if (!header->up8_weight) { > + av_log(avctx, AV_LOG_ERROR, "Could not allocate up8_weight"); > + status = AVERROR(ENOMEM); > + goto fail; > + } > + for (int i = 0; i < 210; i++) { > + header->up8_weight[i] = jpegxl_f16(jxlr); > + } > + } > + > + /* zero pad to byte */ > + if (jxl_bits(7 - ((jxlr->bits_read - 1) % 8))) { > + jxl_parse_err("zero padding to byte"); > + goto fail; > + } > + > + /* bytes consumed > buflen */ > + if ((jxlr->bits_read + 1) / 8 - 1 > jxlr->buflen) { > + jxl_parse_err("unexpected end of file"); > + goto fail; > + } > + > + *headerp = header; > + return 0; > + > +fail: > + jpegxl_free_header(header); > + if (status >= 0 && (jxlr->bits_read + 1) / 8 - 1 > jxlr->buflen) > + return FFMIN(jxlr->bits_read, INT_MAX); > + if (status > 0) > + return -status; > + return status || -1; This is equivalent to "return 1;" > +} > + > +static int jpegxl_parse_header(void *avctx, JpegXLParseContext *jxlr, > JpegXLHeader **headerp, int level) > +{ > + uint64_t sig = jxl_bits(64); > + jpegxl_reset_pc(jxlr); > + if (sig == FF_JPEGXL_CONTAINER_SIGNATURE_LE) { > + for (;;) { > + uint64_t size = 0; > + uint32_t tag = 0; > + for (int k = 0; k < 4; k++) > + size = (size << 8) | jxl_bits(8); > + for (int k = 0; k < 4; k++) > + tag = (tag << 8) | jxl_bits(8); > + if (tag == MKBETAG('j','x','l','p')) { > + jxl_bits(32); > + break; > + } > + if (tag == MKBETAG('j','x','l','c')) > + break; > + if (size == 1) { > + size = 0; > + for (int k = 0; k < 8; k++) > + size = (size << 8) | jxl_bits(8); > + if (size > INT_MAX) > + break; > + size -= 8; > + } > + if (jxlr->bits_read / 8 > jxlr->buflen) > + break; This code looks weird: You read the size of an isobmff-style box, but then you actually ignore the size. (Apart from that: You are always byte-aligned here, so you could just as well read the stuff bytewise and skip the corresponding number of bits lateron.) > + } > + } > + return jpegxl_parse_codestream_header(avctx, jxlr, headerp, level); > +} > + > +int avpriv_jpegxl_verify_codestream_header(void *avctx, uint8_t *buf, size_t > buflen, int level) > +{ > + JpegXLParseContext jxlri; > + JpegXLHeader *header = NULL; > + int status; > + jpegxl_init_pc(&jxlri, buf, buflen); > + status = jpegxl_parse_codestream_header(avctx, &jxlri, &header, level); > + if (header) > + jpegxl_free_header(header); > + return status; > +} > + > +static enum AVPixelFormat jpegxl_header_get_pixfmt(JpegXLHeader *header) { > + int alpha = 0; > + for (int i = 0; i < header->num_extra_channels; i++) { > + if ((header->extra_channel_info + i)->type == FF_JPEGXL_CT_ALPHA) { > + alpha = 1; > + break; > + } > + } > + if (header->color_space == FF_JPEGXL_CS_GRAY) { > + if (header->bits_per_sample <= 8) > + return alpha ? AV_PIX_FMT_YA8 : AV_PIX_FMT_GRAY8; > + if (header->bits_per_sample > 16 || header->exp_bits_per_sample) > + return alpha ? AV_PIX_FMT_NONE : AV_PIX_FMT_GRAYF32LE; > + return alpha ? AV_PIX_FMT_YA16LE : AV_PIX_FMT_GRAY16LE; > + } else if (header->color_space == FF_JPEGXL_CS_RGB > + || header->color_space == FF_JPEGXL_CS_XYB) { > + if (header->bits_per_sample <= 8) > + return alpha ? AV_PIX_FMT_RGBA : AV_PIX_FMT_RGB24; > + if (header->bits_per_sample > 16 || header->exp_bits_per_sample) > + return alpha ? AV_PIX_FMT_GBRAPF32LE : AV_PIX_FMT_GBRPF32LE; > + return alpha ? AV_PIX_FMT_RGBA64LE : AV_PIX_FMT_RGB48LE; > + } > + return AV_PIX_FMT_NONE; > +} > + > +static av_cold int jpegxl_parse_init(AVCodecParserContext *s1) > +{ > + s1->pict_type = AV_PICTURE_TYPE_NONE; > + return 0; > +} > + > +static int jpegxl_parse(AVCodecParserContext *s1, > + AVCodecContext *avctx, > + const uint8_t **poutbuf, int *poutbuf_size, > + const uint8_t *buf, int buf_size) > +{ > + JpegXLParseContext *jxlr = s1->priv_data; > + JpegXLHeader *header = NULL; > + int next = END_NOT_FOUND, status = 0; > + size_t i = 0; > + > + *poutbuf_size = 0; > + *poutbuf = NULL; > + > + if (buf_size == 0 || s1->flags & PARSER_FLAG_COMPLETE_FRAMES) { > + /* eof is a frame boundary */ > + next = buf_size; > + } else if (!jxlr->pc.frame_start_found) { > + /* look for stream signature */ > + uint64_t state64 = jxlr->pc.state64; > + for (; i < buf_size; i++) { > + state64 = (state64 << 8) | buf[i]; > + if ((state64 & 0xFFFF) == FF_JPEGXL_CODESTREAM_SIGNATURE_BE > + || state64 == FF_JPEGXL_CONTAINER_SIGNATURE_BE) { > + jxlr->pc.frame_start_found = 1; > + break; > + } > + } > + jxlr->pc.state64 = state64; Did you test this part? Did it work? I am wondering because you are actually supposed to indicate how many bytes of input you have consumed, yet you always indicate END_NOT_FOUND. > + } > + > + if (jxlr->pc.frame_start_found && s1->pict_type == AV_PICTURE_TYPE_NONE) > { > + jpegxl_init_pc(jxlr, buf, buf_size); > + status = jpegxl_parse_header(NULL, jxlr, &header, 5); Why don't you forward the logcontext? > + if (status == 0) { > + /* parsed successfully */ > + s1->pict_type = AV_PICTURE_TYPE_I; > + s1->key_frame = 1; > + s1->width = avctx->width = avctx->coded_width = header->width; > + s1->height = avctx->height = avctx->coded_height = > header->height; > + s1->format = avctx->pix_fmt = jpegxl_header_get_pixfmt(header); > + jxlr->pc.frame_start_found = 1; > + } > + if (header) > + jpegxl_free_header(header); > + header = NULL; > + } > + > + if (ff_combine_frame(&jxlr->pc, next, &buf, &buf_size) < 0) { > + *poutbuf = NULL; > + *poutbuf_size = 0; > + return buf_size; > + } > + > + jxlr->pc.frame_start_found = 0; > + > + *poutbuf = buf; > + *poutbuf_size = buf_size; > + > + return next; > +} > + > +const AVCodecParser ff_jpegxl_parser = { > + .codec_ids = { AV_CODEC_ID_JPEGXL }, > + .priv_data_size = sizeof(JpegXLParseContext), > + .parser_init = jpegxl_parse_init, > + .parser_parse = jpegxl_parse, > + .parser_close = ff_parse_close, > +}; > + > +#else /* CONFIG_JPEGXL_PARSER */ > + > +int avpriv_jpegxl_verify_codestream_header(void *avctx, uint8_t *buf, size_t > buflen, int level) > +{ We don't provide fallback-stubs for avpriv functions; instead we just add configure/Makefile depencies. > + /* parser disabled at compile-time */ > + return AVERROR(ENOSYS); > +} > + > +#endif /* CONFIG_JPEGXL_PARSER */ > diff --git a/libavcodec/parsers.c b/libavcodec/parsers.c > index 6b40c18d80..18a40eceea 100644 > --- a/libavcodec/parsers.c > +++ b/libavcodec/parsers.c > @@ -52,6 +52,7 @@ extern const AVCodecParser ff_h264_parser; > extern const AVCodecParser ff_hevc_parser; > extern const AVCodecParser ff_ipu_parser; > extern const AVCodecParser ff_jpeg2000_parser; > +extern const AVCodecParser ff_jpegxl_parser; > extern const AVCodecParser ff_mjpeg_parser; > extern const AVCodecParser ff_mlp_parser; > extern const AVCodecParser ff_mpeg4video_parser; > diff --git a/libavcodec/version.h b/libavcodec/version.h > index a46fb05f1a..b5867ad041 100644 > --- a/libavcodec/version.h > +++ b/libavcodec/version.h > @@ -28,7 +28,7 @@ > #include "libavutil/version.h" > > #define LIBAVCODEC_VERSION_MAJOR 59 > -#define LIBAVCODEC_VERSION_MINOR 20 > +#define LIBAVCODEC_VERSION_MINOR 21 > #define LIBAVCODEC_VERSION_MICRO 100 > > #define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \ > _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".