Files
jellyfin-ffmpeg/libavformat/jpegxl_probe.c
T

412 lines
12 KiB
C
Raw Normal View History

2022-07-23 10:30:09 +08:00
/*
* Jpeg XL header verification
* Copyright (c) 2022 Leo Izen <leo.izen@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
*/
#include "jpegxl_probe.h"
2023-11-11 20:50:06 +08:00
#define UNCHECKED_BITSTREAM_READER 0
2022-07-23 10:30:09 +08:00
#define BITSTREAM_READER_LE
#include "libavcodec/get_bits.h"
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,
};
/* read a U32(c_i + u(u_i)) */
2023-11-11 20:50:06 +08:00
static av_always_inline uint32_t jxl_u32(GetBitContext *gb,
uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3,
uint32_t u0, uint32_t u1, uint32_t u2, uint32_t u3)
2022-07-23 10:30:09 +08:00
{
2023-11-11 20:50:06 +08:00
const uint32_t constants[4] = {c0, c1, c2, c3};
const uint32_t ubits [4] = {u0, u1, u2, u3};
uint32_t ret, choice = get_bits(gb, 2);
2022-07-23 10:30:09 +08:00
ret = constants[choice];
if (ubits[choice])
2023-11-11 20:50:06 +08:00
ret += get_bits_long(gb, ubits[choice]);
2022-07-23 10:30:09 +08:00
return ret;
}
2023-11-11 20:50:06 +08:00
static av_always_inline uint32_t jxl_enum(GetBitContext *gb)
{
return jxl_u32(gb, 0, 1, 2, 18, 0, 0, 4, 6);
}
2022-07-23 10:30:09 +08:00
/* read a U64() */
static uint64_t jpegxl_u64(GetBitContext *gb)
{
uint64_t shift = 12, ret;
2023-11-11 20:50:06 +08:00
switch (get_bits(gb, 2)) {
2022-07-23 10:30:09 +08:00
case 0:
ret = 0;
break;
case 1:
2023-11-11 20:50:06 +08:00
ret = 1 + get_bits(gb, 4);
2022-07-23 10:30:09 +08:00
break;
case 2:
2023-11-11 20:50:06 +08:00
ret = 17 + get_bits(gb, 8);
2022-07-23 10:30:09 +08:00
break;
case 3:
2023-11-11 20:50:06 +08:00
ret = get_bits(gb, 12);
while (get_bits1(gb)) {
2022-07-23 10:30:09 +08:00
if (shift < 60) {
2023-11-11 20:50:06 +08:00
ret |= (uint64_t)get_bits(gb, 8) << shift;
2022-07-23 10:30:09 +08:00
shift += 8;
} else {
2023-11-11 20:50:06 +08:00
ret |= (uint64_t)get_bits(gb, 4) << shift;
2022-07-23 10:30:09 +08:00
break;
}
}
break;
}
return ret;
}
static uint32_t jpegxl_width_from_ratio(uint32_t height, int ratio)
{
uint64_t height64 = height; /* avoid integer overflow */
switch (ratio) {
case 1:
return height;
case 2:
return (uint32_t)((height64 * 12) / 10);
case 3:
return (uint32_t)((height64 * 4) / 3);
case 4:
return (uint32_t)((height64 * 3) / 2);
case 5:
return (uint32_t)((height64 * 16) / 9);
case 6:
return (uint32_t)((height64 * 5) / 4);
case 7:
return (uint32_t)(height64 * 2);
default:
break;
}
return 0; /* manual width */
}
/**
* validate a Jpeg XL Size Header
* @return >= 0 upon valid size, < 0 upon invalid size found
*/
static int jpegxl_read_size_header(GetBitContext *gb)
{
uint32_t width, height;
2023-11-11 20:50:06 +08:00
if (get_bits1(gb)) {
2022-07-23 10:30:09 +08:00
/* small size header */
2023-11-11 20:50:06 +08:00
height = (get_bits(gb, 5) + 1) << 3;
width = jpegxl_width_from_ratio(height, get_bits(gb, 3));
2022-07-23 10:30:09 +08:00
if (!width)
2023-11-11 20:50:06 +08:00
width = (get_bits(gb, 5) + 1) << 3;
2022-07-23 10:30:09 +08:00
} else {
/* large size header */
2023-11-11 20:50:06 +08:00
height = 1 + jxl_u32(gb, 0, 0, 0, 0, 9, 13, 18, 30);
width = jpegxl_width_from_ratio(height, get_bits(gb, 3));
2022-07-23 10:30:09 +08:00
if (!width)
2023-11-11 20:50:06 +08:00
width = 1 + jxl_u32(gb, 0, 0, 0, 0, 9, 13, 18, 30);
2022-07-23 10:30:09 +08:00
}
if (width > (1 << 18) || height > (1 << 18)
|| (width >> 4) * (height >> 4) > (1 << 20))
return -1;
return 0;
}
/**
* validate a Jpeg XL Preview Header
* @return >= 0 upon valid size, < 0 upon invalid size found
*/
static int jpegxl_read_preview_header(GetBitContext *gb)
{
uint32_t width, height;
2023-11-11 20:50:06 +08:00
if (get_bits1(gb)) {
2022-07-23 10:30:09 +08:00
/* coded height and width divided by eight */
2023-11-11 20:50:06 +08:00
height = jxl_u32(gb, 16, 32, 1, 33, 0, 0, 5, 9) << 3;
width = jpegxl_width_from_ratio(height, get_bits(gb, 3));
2022-07-23 10:30:09 +08:00
if (!width)
2023-11-11 20:50:06 +08:00
width = jxl_u32(gb, 16, 32, 1, 33, 0, 0, 5, 9) << 3;
2022-07-23 10:30:09 +08:00
} else {
/* full height and width coded */
2023-11-11 20:50:06 +08:00
height = jxl_u32(gb, 1, 65, 321, 1345, 6, 8, 10, 12);
width = jpegxl_width_from_ratio(height, get_bits(gb, 3));
2022-07-23 10:30:09 +08:00
if (!width)
2023-11-11 20:50:06 +08:00
width = jxl_u32(gb, 1, 65, 321, 1345, 6, 8, 10, 12);
2022-07-23 10:30:09 +08:00
}
if (width > 4096 || height > 4096)
return -1;
return 0;
}
/**
* skip a Jpeg XL BitDepth Header. These cannot be invalid.
*/
static void jpegxl_skip_bit_depth(GetBitContext *gb)
{
2023-11-11 20:50:06 +08:00
if (get_bits1(gb)) {
2022-07-23 10:30:09 +08:00
/* float samples */
2023-11-11 20:50:06 +08:00
jxl_u32(gb, 32, 16, 24, 1, 0, 0, 0, 6); /* mantissa */
skip_bits_long(gb, 4); /* exponent */
2022-07-23 10:30:09 +08:00
} else {
/* integer samples */
2023-11-11 20:50:06 +08:00
jxl_u32(gb, 8, 10, 12, 1, 0, 0, 0, 6);
2022-07-23 10:30:09 +08:00
}
}
/**
* validate a Jpeg XL Extra Channel Info bundle
* @return >= 0 upon valid, < 0 upon invalid
*/
static int jpegxl_read_extra_channel_info(GetBitContext *gb)
{
2023-11-11 20:50:06 +08:00
int all_default = get_bits1(gb);
2022-07-23 10:30:09 +08:00
uint32_t type, name_len = 0;
if (!all_default) {
2023-11-11 20:50:06 +08:00
type = jxl_enum(gb);
2022-07-23 10:30:09 +08:00
if (type > 63)
return -1; /* enum types cannot be 64+ */
if (type == FF_JPEGXL_CT_BLACK)
return -1;
jpegxl_skip_bit_depth(gb);
2023-11-11 20:50:06 +08:00
jxl_u32(gb, 0, 3, 4, 1, 0, 0, 0, 3); /* dim-shift */
2022-07-23 10:30:09 +08:00
/* max of name_len is 1071 = 48 + 2^10 - 1 */
2023-11-11 20:50:06 +08:00
name_len = jxl_u32(gb, 0, 0, 16, 48, 0, 4, 5, 10);
2022-07-23 10:30:09 +08:00
} else {
type = FF_JPEGXL_CT_ALPHA;
}
/* skip over the name */
2023-11-11 20:50:06 +08:00
skip_bits_long(gb, 8 * name_len);
2022-07-23 10:30:09 +08:00
if (!all_default && type == FF_JPEGXL_CT_ALPHA)
2023-11-11 20:50:06 +08:00
skip_bits1(gb);
2022-07-23 10:30:09 +08:00
if (type == FF_JPEGXL_CT_SPOT_COLOR)
2023-11-11 20:50:06 +08:00
skip_bits_long(gb, 16 * 4);
2022-07-23 10:30:09 +08:00
if (type == FF_JPEGXL_CT_CFA)
2023-11-11 20:50:06 +08:00
jxl_u32(gb, 1, 0, 3, 19, 0, 2, 4, 8);
2022-07-23 10:30:09 +08:00
return 0;
}
/* verify that a codestream header is valid */
int ff_jpegxl_verify_codestream_header(const uint8_t *buf, int buflen)
{
GetBitContext gbi, *gb = &gbi;
int all_default, extra_fields = 0;
int xyb_encoded = 1, have_icc_profile = 0;
uint32_t num_extra_channels;
uint64_t extensions;
int ret;
ret = init_get_bits8(gb, buf, buflen);
if (ret < 0)
return ret;
2023-11-11 20:50:06 +08:00
if (get_bits_long(gb, 16) != FF_JPEGXL_CODESTREAM_SIGNATURE_LE)
2022-07-23 10:30:09 +08:00
return -1;
2023-11-11 20:50:06 +08:00
if ((ret = jpegxl_read_size_header(gb)) < 0)
return ret;
2022-07-23 10:30:09 +08:00
2023-11-11 20:50:06 +08:00
all_default = get_bits1(gb);
2022-07-23 10:30:09 +08:00
if (!all_default)
2023-11-11 20:50:06 +08:00
extra_fields = get_bits1(gb);
2022-07-23 10:30:09 +08:00
if (extra_fields) {
2023-11-11 20:50:06 +08:00
skip_bits_long(gb, 3); /* orientation */
2022-07-23 10:30:09 +08:00
/*
* intrinstic size
* any size header here is valid, but as it
* is variable length we have to read it
*/
2023-11-11 20:50:06 +08:00
if (get_bits1(gb))
2022-07-23 10:30:09 +08:00
jpegxl_read_size_header(gb);
/* preview header */
2023-11-11 20:50:06 +08:00
if (get_bits1(gb)) {
ret = jpegxl_read_preview_header(gb);
if (ret < 0)
return ret;
2022-07-23 10:30:09 +08:00
}
/* animation header */
2023-11-11 20:50:06 +08:00
if (get_bits1(gb)) {
jxl_u32(gb, 100, 1000, 1, 1, 0, 0, 10, 30);
jxl_u32(gb, 1, 1001, 1, 1, 0, 0, 8, 10);
jxl_u32(gb, 0, 0, 0, 0, 0, 3, 16, 32);
skip_bits_long(gb, 1);
2022-07-23 10:30:09 +08:00
}
}
2023-11-11 20:50:06 +08:00
if (get_bits_left(gb) < 1)
return AVERROR_INVALIDDATA;
2022-07-23 10:30:09 +08:00
if (!all_default) {
jpegxl_skip_bit_depth(gb);
/* modular_16bit_buffers must equal 1 */
2023-11-11 20:50:06 +08:00
if (!get_bits1(gb))
2022-07-23 10:30:09 +08:00
return -1;
2023-11-11 20:50:06 +08:00
num_extra_channels = jxl_u32(gb, 0, 1, 2, 1, 0, 0, 4, 12);
2022-07-23 10:30:09 +08:00
if (num_extra_channels > 4)
return -1;
for (uint32_t i = 0; i < num_extra_channels; i++) {
2023-11-11 20:50:06 +08:00
ret = jpegxl_read_extra_channel_info(gb);
if (ret < 0)
return ret;
if (get_bits_left(gb) < 1)
return AVERROR_INVALIDDATA;
2022-07-23 10:30:09 +08:00
}
2023-11-11 20:50:06 +08:00
xyb_encoded = get_bits1(gb);
2022-07-23 10:30:09 +08:00
/* color encoding bundle */
2023-11-11 20:50:06 +08:00
if (!get_bits1(gb)) {
2022-07-23 10:30:09 +08:00
uint32_t color_space;
2023-11-11 20:50:06 +08:00
have_icc_profile = get_bits1(gb);
color_space = jxl_enum(gb);
2022-07-23 10:30:09 +08:00
if (color_space > 63)
return -1;
if (!have_icc_profile) {
if (color_space != FF_JPEGXL_CS_XYB) {
2023-11-11 20:50:06 +08:00
uint32_t white_point = jxl_enum(gb);
2022-07-23 10:30:09 +08:00
if (white_point > 63)
return -1;
if (white_point == FF_JPEGXL_WP_CUSTOM) {
/* ux and uy values */
2023-11-11 20:50:06 +08:00
jxl_u32(gb, 0, 524288, 1048576, 2097152, 19, 19, 20, 21);
jxl_u32(gb, 0, 524288, 1048576, 2097152, 19, 19, 20, 21);
2022-07-23 10:30:09 +08:00
}
if (color_space != FF_JPEGXL_CS_GRAY) {
/* primaries */
2023-11-11 20:50:06 +08:00
uint32_t primaries = jxl_enum(gb);
2022-07-23 10:30:09 +08:00
if (primaries > 63)
return -1;
if (primaries == FF_JPEGXL_PR_CUSTOM) {
/* ux/uy values for r,g,b */
2023-11-11 20:50:06 +08:00
for (int i = 0; i < 6; i++) {
jxl_u32(gb, 0, 524288, 1048576, 2097152, 19, 19, 20, 21);
if (get_bits_left(gb) < 1)
return AVERROR_INVALIDDATA;
}
2022-07-23 10:30:09 +08:00
}
}
}
/* transfer characteristics */
2023-11-11 20:50:06 +08:00
if (get_bits1(gb)) {
2022-07-23 10:30:09 +08:00
/* gamma */
2023-11-11 20:50:06 +08:00
skip_bits_long(gb, 24);
2022-07-23 10:30:09 +08:00
} else {
/* transfer function */
2023-11-11 20:50:06 +08:00
if (jxl_enum(gb) > 63)
2022-07-23 10:30:09 +08:00
return -1;
}
/* rendering intent */
2023-11-11 20:50:06 +08:00
if (jxl_enum(gb) > 63)
2022-07-23 10:30:09 +08:00
return -1;
}
}
/* tone mapping bundle */
2023-11-11 20:50:06 +08:00
if (extra_fields && !get_bits1(gb))
skip_bits_long(gb, 16 + 16 + 1 + 16);
2022-07-23 10:30:09 +08:00
2023-11-11 20:50:06 +08:00
extensions = jpegxl_u64(gb);
if (get_bits_left(gb) < 1)
return AVERROR_INVALIDDATA;
2022-07-23 10:30:09 +08:00
if (extensions) {
for (int i = 0; i < 64; i++) {
if (extensions & (UINT64_C(1) << i))
2023-11-11 20:50:06 +08:00
jpegxl_u64(gb);
if (get_bits_left(gb) < 1)
return AVERROR_INVALIDDATA;
2022-07-23 10:30:09 +08:00
}
}
}
/* default transform */
2023-11-11 20:50:06 +08:00
if (!get_bits1(gb)) {
2022-07-23 10:30:09 +08:00
/* opsin inverse matrix */
2023-11-11 20:50:06 +08:00
if (xyb_encoded && !get_bits1(gb))
skip_bits_long(gb, 16 * 16);
2022-07-23 10:30:09 +08:00
/* cw_mask and default weights */
2023-11-11 20:50:06 +08:00
if (get_bits1(gb))
skip_bits_long(gb, 16 * 15);
if (get_bits1(gb))
skip_bits_long(gb, 16 * 55);
if (get_bits1(gb))
skip_bits_long(gb, 16 * 210);
2022-07-23 10:30:09 +08:00
}
if (!have_icc_profile) {
int bits_remaining = 7 - (get_bits_count(gb) - 1) % 8;
2023-11-11 20:50:06 +08:00
if (bits_remaining && get_bits(gb, bits_remaining))
2022-07-23 10:30:09 +08:00
return -1;
}
if (get_bits_left(gb) < 0)
return -1;
return 0;
}