mirror of https://github.com/FFmpeg/FFmpeg.git
This commit adds support to libavformat for muxing and demuxing Jpeg XL images as image2 streams.release/5.1
parent
5f0b4e9c70
commit
3ac23440ef
9 changed files with 453 additions and 3 deletions
@ -0,0 +1,393 @@ |
||||
/*
|
||||
* 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" |
||||
|
||||
#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, |
||||
}; |
||||
|
||||
#define jxl_bits(n) get_bits_long(gb, (n)) |
||||
#define jxl_bits_skip(n) skip_bits_long(gb, (n)) |
||||
#define jxl_u32(c0, c1, c2, c3, u0, u1, u2, u3) jpegxl_u32(gb, \ |
||||
(const uint32_t[]){c0, c1, c2, c3}, (const uint32_t[]){u0, u1, u2, u3}) |
||||
#define jxl_u64() jpegxl_u64(gb) |
||||
#define jxl_enum() jxl_u32(0, 1, 2, 18, 0, 0, 4, 6) |
||||
|
||||
/* read a U32(c_i + u(u_i)) */ |
||||
static uint32_t jpegxl_u32(GetBitContext *gb, |
||||
const uint32_t constants[4], const uint32_t ubits[4]) |
||||
{ |
||||
uint32_t ret, choice = jxl_bits(2); |
||||
|
||||
ret = constants[choice]; |
||||
if (ubits[choice]) |
||||
ret += jxl_bits(ubits[choice]); |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
/* read a U64() */ |
||||
static uint64_t jpegxl_u64(GetBitContext *gb) |
||||
{ |
||||
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 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; |
||||
|
||||
if (jxl_bits(1)) { |
||||
/* small size header */ |
||||
height = (jxl_bits(5) + 1) << 3; |
||||
width = jpegxl_width_from_ratio(height, jxl_bits(3)); |
||||
if (!width) |
||||
width = (jxl_bits(5) + 1) << 3; |
||||
} else { |
||||
/* large size header */ |
||||
height = 1 + jxl_u32(0, 0, 0, 0, 9, 13, 18, 30); |
||||
width = jpegxl_width_from_ratio(height, jxl_bits(3)); |
||||
if (!width) |
||||
width = 1 + jxl_u32(0, 0, 0, 0, 9, 13, 18, 30); |
||||
} |
||||
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; |
||||
|
||||
if (jxl_bits(1)) { |
||||
/* coded height and width divided by eight */ |
||||
height = jxl_u32(16, 32, 1, 33, 0, 0, 5, 9) << 3; |
||||
width = jpegxl_width_from_ratio(height, jxl_bits(3)); |
||||
if (!width) |
||||
width = jxl_u32(16, 32, 1, 33, 0, 0, 5, 9) << 3; |
||||
} else { |
||||
/* full height and width coded */ |
||||
height = jxl_u32(1, 65, 321, 1345, 6, 8, 10, 12); |
||||
width = jpegxl_width_from_ratio(height, jxl_bits(3)); |
||||
if (!width) |
||||
width = jxl_u32(1, 65, 321, 1345, 6, 8, 10, 12); |
||||
} |
||||
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) |
||||
{ |
||||
if (jxl_bits(1)) { |
||||
/* float samples */ |
||||
jxl_u32(32, 16, 24, 1, 0, 0, 0, 6); /* mantissa */ |
||||
jxl_bits_skip(4); /* exponent */ |
||||
} else { |
||||
/* integer samples */ |
||||
jxl_u32(8, 10, 12, 1, 0, 0, 0, 6); |
||||
} |
||||
} |
||||
|
||||
/**
|
||||
* validate a Jpeg XL Extra Channel Info bundle |
||||
* @return >= 0 upon valid, < 0 upon invalid |
||||
*/ |
||||
static int jpegxl_read_extra_channel_info(GetBitContext *gb) |
||||
{ |
||||
int all_default = jxl_bits(1); |
||||
uint32_t type, name_len = 0; |
||||
|
||||
if (!all_default) { |
||||
type = jxl_enum(); |
||||
if (type > 63) |
||||
return -1; /* enum types cannot be 64+ */ |
||||
if (type == FF_JPEGXL_CT_BLACK) |
||||
return -1; |
||||
jpegxl_skip_bit_depth(gb); |
||||
jxl_u32(0, 3, 4, 1, 0, 0, 0, 3); /* dim-shift */ |
||||
/* max of name_len is 1071 = 48 + 2^10 - 1 */ |
||||
name_len = jxl_u32(0, 0, 16, 48, 0, 4, 5, 10); |
||||
} else { |
||||
type = FF_JPEGXL_CT_ALPHA; |
||||
} |
||||
|
||||
/* skip over the name */ |
||||
jxl_bits_skip(8 * name_len); |
||||
|
||||
if (!all_default && type == FF_JPEGXL_CT_ALPHA) |
||||
jxl_bits_skip(1); |
||||
|
||||
if (type == FF_JPEGXL_CT_SPOT_COLOR) |
||||
jxl_bits_skip(16 * 4); |
||||
|
||||
if (type == FF_JPEGXL_CT_CFA) |
||||
jxl_u32(1, 0, 3, 19, 0, 2, 4, 8); |
||||
|
||||
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; |
||||
|
||||
init_get_bits8(gb, buf, buflen); |
||||
|
||||
if (jxl_bits(16) != FF_JPEGXL_CODESTREAM_SIGNATURE_LE) |
||||
return -1; |
||||
|
||||
if (jpegxl_read_size_header(gb) < 0) |
||||
return -1; |
||||
|
||||
all_default = jxl_bits(1); |
||||
if (!all_default) |
||||
extra_fields = jxl_bits(1); |
||||
|
||||
if (extra_fields) { |
||||
jxl_bits_skip(3); /* orientation */ |
||||
|
||||
/*
|
||||
* intrinstic size |
||||
* any size header here is valid, but as it |
||||
* is variable length we have to read it |
||||
*/ |
||||
if (jxl_bits(1)) |
||||
jpegxl_read_size_header(gb); |
||||
|
||||
/* preview header */ |
||||
if (jxl_bits(1)) { |
||||
if (jpegxl_read_preview_header(gb) < 0) |
||||
return -1; |
||||
} |
||||
|
||||
/* animation header */ |
||||
if (jxl_bits(1)) { |
||||
jxl_u32(100, 1000, 1, 1, 0, 0, 10, 30); |
||||
jxl_u32(1, 1001, 1, 1, 0, 0, 8, 10); |
||||
jxl_u32(0, 0, 0, 0, 0, 3, 16, 32); |
||||
jxl_bits_skip(1); |
||||
} |
||||
} |
||||
|
||||
if (!all_default) { |
||||
jpegxl_skip_bit_depth(gb); |
||||
|
||||
/* modular_16bit_buffers must equal 1 */ |
||||
if (!jxl_bits(1)) |
||||
return -1; |
||||
|
||||
num_extra_channels = jxl_u32(0, 1, 2, 1, 0, 0, 4, 12); |
||||
if (num_extra_channels > 4) |
||||
return -1; |
||||
for (uint32_t i = 0; i < num_extra_channels; i++) { |
||||
if (jpegxl_read_extra_channel_info(gb) < 0) |
||||
return -1; |
||||
} |
||||
|
||||
xyb_encoded = jxl_bits(1); |
||||
|
||||
/* color encoding bundle */ |
||||
if (!jxl_bits(1)) { |
||||
uint32_t color_space; |
||||
have_icc_profile = jxl_bits(1); |
||||
color_space = jxl_enum(); |
||||
if (color_space > 63) |
||||
return -1; |
||||
|
||||
if (!have_icc_profile) { |
||||
if (color_space != FF_JPEGXL_CS_XYB) { |
||||
uint32_t white_point = jxl_enum(); |
||||
if (white_point > 63) |
||||
return -1; |
||||
if (white_point == FF_JPEGXL_WP_CUSTOM) { |
||||
/* ux and uy values */ |
||||
jxl_u32(0, 524288, 1048576, 2097152, 19, 19, 20, 21); |
||||
jxl_u32(0, 524288, 1048576, 2097152, 19, 19, 20, 21); |
||||
} |
||||
if (color_space != FF_JPEGXL_CS_GRAY) { |
||||
/* primaries */ |
||||
uint32_t primaries = jxl_enum(); |
||||
if (primaries > 63) |
||||
return -1; |
||||
if (primaries == FF_JPEGXL_PR_CUSTOM) { |
||||
/* ux/uy values for r,g,b */ |
||||
for (int i = 0; i < 6; i++) |
||||
jxl_u32(0, 524288, 1048576, 2097152, 19, 19, 20, 21); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/* transfer characteristics */ |
||||
if (jxl_bits(1)) { |
||||
/* gamma */ |
||||
jxl_bits_skip(24); |
||||
} else { |
||||
/* transfer function */ |
||||
if (jxl_enum() > 63) |
||||
return -1; |
||||
} |
||||
|
||||
/* rendering intent */ |
||||
if (jxl_enum() > 63) |
||||
return -1; |
||||
} |
||||
} |
||||
|
||||
/* tone mapping bundle */ |
||||
if (extra_fields && !jxl_bits(1)) |
||||
jxl_bits_skip(16 + 16 + 1 + 16); |
||||
|
||||
extensions = jxl_u64(); |
||||
if (extensions) { |
||||
for (int i = 0; i < 64; i++) { |
||||
if (extensions & (UINT64_C(1) << i)) |
||||
jxl_u64(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/* default transform */ |
||||
if (!jxl_bits(1)) { |
||||
/* opsin inverse matrix */ |
||||
if (xyb_encoded && !jxl_bits(1)) |
||||
jxl_bits_skip(16 * 16); |
||||
/* cw_mask and default weights */ |
||||
if (jxl_bits(1)) |
||||
jxl_bits_skip(16 * 15); |
||||
if (jxl_bits(1)) |
||||
jxl_bits_skip(16 * 55); |
||||
if (jxl_bits(1)) |
||||
jxl_bits_skip(16 * 210); |
||||
} |
||||
|
||||
if (!have_icc_profile) { |
||||
int bits_remaining = 7 - (get_bits_count(gb) - 1) % 8; |
||||
if (bits_remaining && jxl_bits(bits_remaining)) |
||||
return -1; |
||||
} |
||||
|
||||
if (get_bits_left(gb) < 0) |
||||
return -1; |
||||
|
||||
return 0; |
||||
} |
@ -0,0 +1,32 @@ |
||||
/*
|
||||
* 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 |
||||
*/ |
||||
|
||||
#ifndef AVFORMAT_JPEGXL_PROBE_H |
||||
#define AVFORMAT_JPEGXL_PROBE_H |
||||
|
||||
#include <stdint.h> |
||||
|
||||
#define FF_JPEGXL_CODESTREAM_SIGNATURE_LE 0x0aff |
||||
#define FF_JPEGXL_CONTAINER_SIGNATURE_LE 0x204c584a0c000000 |
||||
|
||||
int ff_jpegxl_verify_codestream_header(const uint8_t *buf, int buflen); |
||||
|
||||
#endif /* AVFORMAT_JPEGXL_PROBE_H */ |
Loading…
Reference in new issue