diff mbox series

[FFmpeg-devel,v14,4/4] avformat/image2: add Jpeg XL as image2 format

Message ID 20220412055333.62424-5-leo.izen@gmail.com
State New
Headers show
Series Jpeg XL Patch Set | expand

Checks

Context Check Description
yinshiyou/make_loongarch64 success Make finished
yinshiyou/make_fate_loongarch64 success Make fate finished
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished
andriy/make_armv7_RPi4 success Make finished
andriy/make_fate_armv7_RPi4 success Make fate finished

Commit Message

Leo Izen April 12, 2022, 5:53 a.m. UTC
This commit adds support to libavformat for muxing
and demuxing Jpeg XL images as image2 streams.
---
 MAINTAINERS                |   1 +
 libavformat/Makefile       |   1 +
 libavformat/allformats.c   |   1 +
 libavformat/img2.c         |   1 +
 libavformat/img2dec.c      |  18 ++
 libavformat/img2enc.c      |   6 +-
 libavformat/jpegxl_probe.c | 393 +++++++++++++++++++++++++++++++++++++
 libavformat/jpegxl_probe.h |  32 +++
 libavformat/mov.c          |   1 +
 9 files changed, 451 insertions(+), 3 deletions(-)
 create mode 100644 libavformat/jpegxl_probe.c
 create mode 100644 libavformat/jpegxl_probe.h

Comments

Andreas Rheinhardt April 15, 2022, 11:34 a.m. UTC | #1
Leo Izen:
> This commit adds support to libavformat for muxing
> and demuxing Jpeg XL images as image2 streams.
> ---
>  MAINTAINERS                |   1 +
>  libavformat/Makefile       |   1 +
>  libavformat/allformats.c   |   1 +
>  libavformat/img2.c         |   1 +
>  libavformat/img2dec.c      |  18 ++
>  libavformat/img2enc.c      |   6 +-
>  libavformat/jpegxl_probe.c | 393 +++++++++++++++++++++++++++++++++++++
>  libavformat/jpegxl_probe.h |  32 +++
>  libavformat/mov.c          |   1 +
>  9 files changed, 451 insertions(+), 3 deletions(-)
>  create mode 100644 libavformat/jpegxl_probe.c
>  create mode 100644 libavformat/jpegxl_probe.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index faea84ebf1..46723972dc 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -439,6 +439,7 @@ Muxers/Demuxers:
>    ipmovie.c                             Mike Melanson
>    ircam*                                Paul B Mahol
>    iss.c                                 Stefan Gehrer
> +  jpegxl_probe.*                        Leo Izen
>    jvdec.c                               Peter Ross
>    kvag.c                                Zane van Iperen
>    libmodplug.c                          Clément Bœsch
> diff --git a/libavformat/Makefile b/libavformat/Makefile
> index d7182d6bd8..beecdf5a66 100644
> --- a/libavformat/Makefile
> +++ b/libavformat/Makefile
> @@ -272,6 +272,7 @@ OBJS-$(CONFIG_IMAGE_GIF_PIPE_DEMUXER)     += img2dec.o img2.o
>  OBJS-$(CONFIG_IMAGE_J2K_PIPE_DEMUXER)     += img2dec.o img2.o
>  OBJS-$(CONFIG_IMAGE_JPEG_PIPE_DEMUXER)    += img2dec.o img2.o
>  OBJS-$(CONFIG_IMAGE_JPEGLS_PIPE_DEMUXER)  += img2dec.o img2.o
> +OBJS-$(CONFIG_IMAGE_JPEGXL_PIPE_DEMUXER)  += img2dec.o img2.o jpegxl_probe.o
>  OBJS-$(CONFIG_IMAGE_PAM_PIPE_DEMUXER)     += img2dec.o img2.o
>  OBJS-$(CONFIG_IMAGE_PBM_PIPE_DEMUXER)     += img2dec.o img2.o
>  OBJS-$(CONFIG_IMAGE_PCX_PIPE_DEMUXER)     += img2dec.o img2.o
> diff --git a/libavformat/allformats.c b/libavformat/allformats.c
> index 7c1d0ac38f..63876c468f 100644
> --- a/libavformat/allformats.c
> +++ b/libavformat/allformats.c
> @@ -510,6 +510,7 @@ extern const AVInputFormat  ff_image_gif_pipe_demuxer;
>  extern const AVInputFormat  ff_image_j2k_pipe_demuxer;
>  extern const AVInputFormat  ff_image_jpeg_pipe_demuxer;
>  extern const AVInputFormat  ff_image_jpegls_pipe_demuxer;
> +extern const AVInputFormat  ff_image_jpegxl_pipe_demuxer;
>  extern const AVInputFormat  ff_image_pam_pipe_demuxer;
>  extern const AVInputFormat  ff_image_pbm_pipe_demuxer;
>  extern const AVInputFormat  ff_image_pcx_pipe_demuxer;
> diff --git a/libavformat/img2.c b/libavformat/img2.c
> index fe2ca7bfff..566ef873ca 100644
> --- a/libavformat/img2.c
> +++ b/libavformat/img2.c
> @@ -88,6 +88,7 @@ const IdStrMap ff_img_tags[] = {
>      { AV_CODEC_ID_GEM,        "ximg"     },
>      { AV_CODEC_ID_GEM,        "timg"     },
>      { AV_CODEC_ID_VBN,        "vbn"      },
> +    { AV_CODEC_ID_JPEGXL,     "jxl"      },
>      { AV_CODEC_ID_NONE,       NULL       }
>  };
>  
> diff --git a/libavformat/img2dec.c b/libavformat/img2dec.c
> index 551b9d508e..627bb67212 100644
> --- a/libavformat/img2dec.c
> +++ b/libavformat/img2dec.c
> @@ -36,6 +36,7 @@
>  #include "avio_internal.h"
>  #include "internal.h"
>  #include "img2.h"
> +#include "jpegxl_probe.h"
>  #include "libavcodec/mjpeg.h"
>  #include "libavcodec/vbn.h"
>  #include "libavcodec/xwd.h"
> @@ -837,6 +838,22 @@ static int jpegls_probe(const AVProbeData *p)
>      return 0;
>  }
>  
> +static int jpegxl_probe(const AVProbeData *p)
> +{
> +    const uint8_t *b = p->buf;
> +
> +    /* ISOBMFF-based container */
> +    /* 0x4a584c20 == "JXL " */
> +    if (AV_RL64(b) == FF_JPEGXL_CONTAINER_SIGNATURE_LE)
> +        return AVPROBE_SCORE_EXTENSION + 1;
> +    /* Raw codestreams all start with 0xff0a */
> +    if (AV_RL16(b) != FF_JPEGXL_CODESTREAM_SIGNATURE_LE)
> +        return 0;
> +    if (ff_jpegxl_verify_codestream_header(p->buf, p->buf_size) >= 0)

This will give a linking failure if the image_jpegxl_pipe_demuxer is
disabled.

> +        return AVPROBE_SCORE_MAX - 2;
> +    return 0;
> +}
> +
>  static int pcx_probe(const AVProbeData *p)
>  {
>      const uint8_t *b = p->buf;
> @@ -1176,6 +1193,7 @@ IMAGEAUTO_DEMUXER(gif,       GIF)
>  IMAGEAUTO_DEMUXER_EXT(j2k,   JPEG2000, J2K)
>  IMAGEAUTO_DEMUXER_EXT(jpeg,  MJPEG, JPEG)
>  IMAGEAUTO_DEMUXER(jpegls,    JPEGLS)
> +IMAGEAUTO_DEMUXER(jpegxl,    JPEGXL)
>  IMAGEAUTO_DEMUXER(pam,       PAM)
>  IMAGEAUTO_DEMUXER(pbm,       PBM)
>  IMAGEAUTO_DEMUXER(pcx,       PCX)
> diff --git a/libavformat/img2enc.c b/libavformat/img2enc.c
> index ae351963d9..5ed97bb833 100644
> --- a/libavformat/img2enc.c
> +++ b/libavformat/img2enc.c
> @@ -263,9 +263,9 @@ static const AVClass img2mux_class = {
>  const AVOutputFormat ff_image2_muxer = {
>      .name           = "image2",
>      .long_name      = NULL_IF_CONFIG_SMALL("image2 sequence"),
> -    .extensions     = "bmp,dpx,exr,jls,jpeg,jpg,ljpg,pam,pbm,pcx,pfm,pgm,pgmyuv,png,"
> -                      "ppm,sgi,tga,tif,tiff,jp2,j2c,j2k,xwd,sun,ras,rs,im1,im8,im24,"
> -                      "sunras,vbn,xbm,xface,pix,y",
> +    .extensions     = "bmp,dpx,exr,jls,jpeg,jpg,jxl,ljpg,pam,pbm,pcx,pfm,pgm,pgmyuv,"
> +                      "png,ppm,sgi,tga,tif,tiff,jp2,j2c,j2k,xwd,sun,ras,rs,im1,im8,"
> +                      "im24,sunras,vbn,xbm,xface,pix,y",
>      .priv_data_size = sizeof(VideoMuxData),
>      .video_codec    = AV_CODEC_ID_MJPEG,
>      .write_header   = write_header,
> diff --git a/libavformat/jpegxl_probe.c b/libavformat/jpegxl_probe.c
> new file mode 100644
> index 0000000000..d3d3822fee
> --- /dev/null
> +++ b/libavformat/jpegxl_probe.c
> @@ -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;
> +    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 Preview Header
> + * @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 - (gb->index - 1) % 8;

Try to avoid accessing GetBitContext members directly; here: Use
get_bits_count().

> +        if (bits_remaining && jxl_bits(bits_remaining))
> +            return -1;
> +    }
> +
> +    if (gb->index > gb->size_in_bits)

get_bits_left(gb) < 0

> +        return -1;
> +
> +    return 0;
> +}

For the record: I'm not really ok with duplicating this code in lavf and
lavc.

> diff --git a/libavformat/jpegxl_probe.h b/libavformat/jpegxl_probe.h
> new file mode 100644
> index 0000000000..2960e81e11
> --- /dev/null
> +++ b/libavformat/jpegxl_probe.h
> @@ -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 */
> diff --git a/libavformat/mov.c b/libavformat/mov.c
> index 6c847de164..c4b8873b0a 100644
> --- a/libavformat/mov.c
> +++ b/libavformat/mov.c
> @@ -7697,6 +7697,7 @@ static int mov_probe(const AVProbeData *p)
>              if (tag == MKTAG('f','t','y','p') &&
>                         (   AV_RL32(p->buf + offset + 8) == MKTAG('j','p','2',' ')
>                          || AV_RL32(p->buf + offset + 8) == MKTAG('j','p','x',' ')
> +                        || AV_RL32(p->buf + offset + 8) == MKTAG('j','x','l',' ')
>                      )) {
>                  score = FFMAX(score, 5);
>              } else {
Leo Izen April 15, 2022, 7:38 p.m. UTC | #2
On 4/15/22 07:34, Andreas Rheinhardt wrote:
> Leo Izen:
>> +static int jpegxl_probe(const AVProbeData *p)
>> +{
>> +    const uint8_t *b = p->buf;
>> +
>> +    /* ISOBMFF-based container */
>> +    /* 0x4a584c20 == "JXL " */
>> +    if (AV_RL64(b) == FF_JPEGXL_CONTAINER_SIGNATURE_LE)
>> +        return AVPROBE_SCORE_EXTENSION + 1;
>> +    /* Raw codestreams all start with 0xff0a */
>> +    if (AV_RL16(b) != FF_JPEGXL_CODESTREAM_SIGNATURE_LE)
>> +        return 0;
>> +    if (ff_jpegxl_verify_codestream_header(p->buf, p->buf_size) >= 0)
> This will give a linking failure if the image_jpegxl_pipe_demuxer is
> disabled.
>
I thought of that, and I tested it, and it doesn't. It produces a 
compiler warning that the static function jpegxl_probe is never called, 
which means the linker probably throws away the function entirely, thus 
eliminating a linking error. Though I admit that's a guess.
> For the record: I'm not really ok with duplicating this code in lavf and
> lavc.
>
This code was removed from avcodec when I moved it over to avformat, for 
now, in order to avoid an avpriv in avcodec's ABI. Essentially we have 
to decide to either duplicate code in the future, or move it to avcodec 
and add an avpriv to the ABI, but that is a decision that can be made 
down the road when Lynne finishes her proper parser. I believe this is a 
better solution now than the other way around since we can later change 
our minds once there will actually be duplicated code, but an avpriv 
cannot be removed once added without waiting the necessary ABI change 
period.

- Leo Izen (thebombzen)
Andreas Rheinhardt April 15, 2022, 7:41 p.m. UTC | #3
Leo Izen:
> 
> On 4/15/22 07:34, Andreas Rheinhardt wrote:
>> Leo Izen:
>>> +static int jpegxl_probe(const AVProbeData *p)
>>> +{
>>> +    const uint8_t *b = p->buf;
>>> +
>>> +    /* ISOBMFF-based container */
>>> +    /* 0x4a584c20 == "JXL " */
>>> +    if (AV_RL64(b) == FF_JPEGXL_CONTAINER_SIGNATURE_LE)
>>> +        return AVPROBE_SCORE_EXTENSION + 1;
>>> +    /* Raw codestreams all start with 0xff0a */
>>> +    if (AV_RL16(b) != FF_JPEGXL_CODESTREAM_SIGNATURE_LE)
>>> +        return 0;
>>> +    if (ff_jpegxl_verify_codestream_header(p->buf, p->buf_size) >= 0)
>> This will give a linking failure if the image_jpegxl_pipe_demuxer is
>> disabled.
>>
> I thought of that, and I tested it, and it doesn't. It produces a
> compiler warning that the static function jpegxl_probe is never called,
> which means the linker probably throws away the function entirely, thus
> eliminating a linking error. Though I admit that's a guess.

That only works when optimizations are enabled. Try again with -O0.

- Andreas
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index faea84ebf1..46723972dc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -439,6 +439,7 @@  Muxers/Demuxers:
   ipmovie.c                             Mike Melanson
   ircam*                                Paul B Mahol
   iss.c                                 Stefan Gehrer
+  jpegxl_probe.*                        Leo Izen
   jvdec.c                               Peter Ross
   kvag.c                                Zane van Iperen
   libmodplug.c                          Clément Bœsch
diff --git a/libavformat/Makefile b/libavformat/Makefile
index d7182d6bd8..beecdf5a66 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -272,6 +272,7 @@  OBJS-$(CONFIG_IMAGE_GIF_PIPE_DEMUXER)     += img2dec.o img2.o
 OBJS-$(CONFIG_IMAGE_J2K_PIPE_DEMUXER)     += img2dec.o img2.o
 OBJS-$(CONFIG_IMAGE_JPEG_PIPE_DEMUXER)    += img2dec.o img2.o
 OBJS-$(CONFIG_IMAGE_JPEGLS_PIPE_DEMUXER)  += img2dec.o img2.o
+OBJS-$(CONFIG_IMAGE_JPEGXL_PIPE_DEMUXER)  += img2dec.o img2.o jpegxl_probe.o
 OBJS-$(CONFIG_IMAGE_PAM_PIPE_DEMUXER)     += img2dec.o img2.o
 OBJS-$(CONFIG_IMAGE_PBM_PIPE_DEMUXER)     += img2dec.o img2.o
 OBJS-$(CONFIG_IMAGE_PCX_PIPE_DEMUXER)     += img2dec.o img2.o
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index 7c1d0ac38f..63876c468f 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -510,6 +510,7 @@  extern const AVInputFormat  ff_image_gif_pipe_demuxer;
 extern const AVInputFormat  ff_image_j2k_pipe_demuxer;
 extern const AVInputFormat  ff_image_jpeg_pipe_demuxer;
 extern const AVInputFormat  ff_image_jpegls_pipe_demuxer;
+extern const AVInputFormat  ff_image_jpegxl_pipe_demuxer;
 extern const AVInputFormat  ff_image_pam_pipe_demuxer;
 extern const AVInputFormat  ff_image_pbm_pipe_demuxer;
 extern const AVInputFormat  ff_image_pcx_pipe_demuxer;
diff --git a/libavformat/img2.c b/libavformat/img2.c
index fe2ca7bfff..566ef873ca 100644
--- a/libavformat/img2.c
+++ b/libavformat/img2.c
@@ -88,6 +88,7 @@  const IdStrMap ff_img_tags[] = {
     { AV_CODEC_ID_GEM,        "ximg"     },
     { AV_CODEC_ID_GEM,        "timg"     },
     { AV_CODEC_ID_VBN,        "vbn"      },
+    { AV_CODEC_ID_JPEGXL,     "jxl"      },
     { AV_CODEC_ID_NONE,       NULL       }
 };
 
diff --git a/libavformat/img2dec.c b/libavformat/img2dec.c
index 551b9d508e..627bb67212 100644
--- a/libavformat/img2dec.c
+++ b/libavformat/img2dec.c
@@ -36,6 +36,7 @@ 
 #include "avio_internal.h"
 #include "internal.h"
 #include "img2.h"
+#include "jpegxl_probe.h"
 #include "libavcodec/mjpeg.h"
 #include "libavcodec/vbn.h"
 #include "libavcodec/xwd.h"
@@ -837,6 +838,22 @@  static int jpegls_probe(const AVProbeData *p)
     return 0;
 }
 
+static int jpegxl_probe(const AVProbeData *p)
+{
+    const uint8_t *b = p->buf;
+
+    /* ISOBMFF-based container */
+    /* 0x4a584c20 == "JXL " */
+    if (AV_RL64(b) == FF_JPEGXL_CONTAINER_SIGNATURE_LE)
+        return AVPROBE_SCORE_EXTENSION + 1;
+    /* Raw codestreams all start with 0xff0a */
+    if (AV_RL16(b) != FF_JPEGXL_CODESTREAM_SIGNATURE_LE)
+        return 0;
+    if (ff_jpegxl_verify_codestream_header(p->buf, p->buf_size) >= 0)
+        return AVPROBE_SCORE_MAX - 2;
+    return 0;
+}
+
 static int pcx_probe(const AVProbeData *p)
 {
     const uint8_t *b = p->buf;
@@ -1176,6 +1193,7 @@  IMAGEAUTO_DEMUXER(gif,       GIF)
 IMAGEAUTO_DEMUXER_EXT(j2k,   JPEG2000, J2K)
 IMAGEAUTO_DEMUXER_EXT(jpeg,  MJPEG, JPEG)
 IMAGEAUTO_DEMUXER(jpegls,    JPEGLS)
+IMAGEAUTO_DEMUXER(jpegxl,    JPEGXL)
 IMAGEAUTO_DEMUXER(pam,       PAM)
 IMAGEAUTO_DEMUXER(pbm,       PBM)
 IMAGEAUTO_DEMUXER(pcx,       PCX)
diff --git a/libavformat/img2enc.c b/libavformat/img2enc.c
index ae351963d9..5ed97bb833 100644
--- a/libavformat/img2enc.c
+++ b/libavformat/img2enc.c
@@ -263,9 +263,9 @@  static const AVClass img2mux_class = {
 const AVOutputFormat ff_image2_muxer = {
     .name           = "image2",
     .long_name      = NULL_IF_CONFIG_SMALL("image2 sequence"),
-    .extensions     = "bmp,dpx,exr,jls,jpeg,jpg,ljpg,pam,pbm,pcx,pfm,pgm,pgmyuv,png,"
-                      "ppm,sgi,tga,tif,tiff,jp2,j2c,j2k,xwd,sun,ras,rs,im1,im8,im24,"
-                      "sunras,vbn,xbm,xface,pix,y",
+    .extensions     = "bmp,dpx,exr,jls,jpeg,jpg,jxl,ljpg,pam,pbm,pcx,pfm,pgm,pgmyuv,"
+                      "png,ppm,sgi,tga,tif,tiff,jp2,j2c,j2k,xwd,sun,ras,rs,im1,im8,"
+                      "im24,sunras,vbn,xbm,xface,pix,y",
     .priv_data_size = sizeof(VideoMuxData),
     .video_codec    = AV_CODEC_ID_MJPEG,
     .write_header   = write_header,
diff --git a/libavformat/jpegxl_probe.c b/libavformat/jpegxl_probe.c
new file mode 100644
index 0000000000..d3d3822fee
--- /dev/null
+++ b/libavformat/jpegxl_probe.c
@@ -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;
+    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 Preview Header
+ * @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 - (gb->index - 1) % 8;
+        if (bits_remaining && jxl_bits(bits_remaining))
+            return -1;
+    }
+
+    if (gb->index > gb->size_in_bits)
+        return -1;
+
+    return 0;
+}
diff --git a/libavformat/jpegxl_probe.h b/libavformat/jpegxl_probe.h
new file mode 100644
index 0000000000..2960e81e11
--- /dev/null
+++ b/libavformat/jpegxl_probe.h
@@ -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 */
diff --git a/libavformat/mov.c b/libavformat/mov.c
index 6c847de164..c4b8873b0a 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -7697,6 +7697,7 @@  static int mov_probe(const AVProbeData *p)
             if (tag == MKTAG('f','t','y','p') &&
                        (   AV_RL32(p->buf + offset + 8) == MKTAG('j','p','2',' ')
                         || AV_RL32(p->buf + offset + 8) == MKTAG('j','p','x',' ')
+                        || AV_RL32(p->buf + offset + 8) == MKTAG('j','x','l',' ')
                     )) {
                 score = FFMAX(score, 5);
             } else {