diff mbox series

[FFmpeg-devel,v2] avcodec/libjxl: add JPEG XL decoding via libjxl

Message ID 20211020173357.635052-1-leo.izen@gmail.com
State New
Headers show
Series [FFmpeg-devel,v2] avcodec/libjxl: add JPEG XL decoding via libjxl | expand

Checks

Context Check Description
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished
andriy/make_ppc success Make finished
andriy/make_fate_ppc success Make fate finished

Commit Message

Leo Izen Oct. 20, 2021, 5:33 p.m. UTC
Use the external libjxl library to add decoding for JPEG XL
images, and add the appropriate image2 muxers and demuxers
to read/write JPEG XL image files.
---
 MAINTAINERS               |   2 +
 configure                 |   5 +
 doc/general_contents.texi |   7 +
 libavcodec/Makefile       |   1 +
 libavcodec/allcodecs.c    |   1 +
 libavcodec/codec_desc.c   |   9 ++
 libavcodec/codec_id.h     |   1 +
 libavcodec/libjxl.c       |  51 ++++++++
 libavcodec/libjxl.h       |  57 ++++++++
 libavcodec/libjxldec.c    | 264 ++++++++++++++++++++++++++++++++++++++
 libavcodec/version.h      |   2 +-
 libavformat/allformats.c  |   1 +
 libavformat/img2.c        |   1 +
 libavformat/img2dec.c     |  11 ++
 libavformat/img2enc.c     |   6 +-
 libavformat/mov.c         |   1 +
 16 files changed, 416 insertions(+), 4 deletions(-)
 create mode 100644 libavcodec/libjxl.c
 create mode 100644 libavcodec/libjxl.h
 create mode 100644 libavcodec/libjxldec.c

Comments

Michael Niedermayer Oct. 20, 2021, 6:27 p.m. UTC | #1
On Wed, Oct 20, 2021 at 01:33:57PM -0400, Leo Izen wrote:
[...]
> --- a/libavformat/img2dec.c
> +++ b/libavformat/img2dec.c
> @@ -830,6 +830,16 @@ static int jpegls_probe(const AVProbeData *p)
>      return 0;
>  }
>  
> +static int jpegxl_probe(const AVProbeData *p)
> +{
> +    const uint8_t *b = p->buf;
> +
> +    if (AV_RB16(b) == 0xff0a ||
> +        AV_RB64(b) == 0x0000000c4a584c20)
> +        return AVPROBE_SCORE_EXTENSION + 1;
> +    return 0;
> +}

only 16bit for detection is a bit weak

thx

[...]
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index dcac46003e..2e34ebbad7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -193,6 +193,7 @@  Codecs:
   libcodec2.c                           Tomas Härdin
   libdirac*                             David Conrad
   libdavs2.c                            Huiwen Ren
+  libjxl*.c, libjxl.h                   Leo Izen
   libgsm.c                              Michel Bardiaux
   libkvazaar.c                          Arttu Ylä-Outinen
   libopenh264enc.c                      Martin Storsjo, Linjie Fu
@@ -614,6 +615,7 @@  Gwenole Beauchesne            2E63 B3A6 3E44 37E2 017D 2704 53C7 6266 B153 99C4
 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/configure b/configure
index 92610c7edc..b5fadebbb1 100755
--- a/configure
+++ b/configure
@@ -241,6 +241,7 @@  External library support:
   --enable-libiec61883     enable iec61883 via libiec61883 [no]
   --enable-libilbc         enable iLBC de/encoding via libilbc [no]
   --enable-libjack         enable JACK audio sound server [no]
+  --enable-libjxl          enable JPEG XL decoding via libjxl [no]
   --enable-libklvanc       enable Kernel Labs VANC processing [no]
   --enable-libkvazaar      enable HEVC encoding via libkvazaar [no]
   --enable-liblensfun      enable lensfun lens correction [no]
@@ -1816,6 +1817,7 @@  EXTERNAL_LIBRARY_LIST="
     libiec61883
     libilbc
     libjack
+    libjxl
     libklvanc
     libkvazaar
     libmodplug
@@ -3281,6 +3283,7 @@  libgsm_ms_decoder_deps="libgsm"
 libgsm_ms_encoder_deps="libgsm"
 libilbc_decoder_deps="libilbc"
 libilbc_encoder_deps="libilbc"
+libjxl_decoder_deps="libjxl libjxl_threads"
 libkvazaar_encoder_deps="libkvazaar"
 libmodplug_demuxer_deps="libmodplug"
 libmp3lame_encoder_deps="libmp3lame"
@@ -6429,6 +6432,8 @@  enabled libgsm            && { for gsm_hdr in "gsm.h" "gsm/gsm.h"; do
                                    check_lib libgsm "${gsm_hdr}" gsm_create -lgsm && break;
                                done || die "ERROR: libgsm not found"; }
 enabled libilbc           && require libilbc ilbc.h WebRtcIlbcfix_InitDecode -lilbc $pthreads_extralibs
+enabled libjxl            && require_pkg_config libjxl "libjxl >= 0.7.0" jxl/decode.h JxlDecoderVersion &&
+                             require_pkg_config libjxl_threads "libjxl_threads >= 0.7.0" jxl/thread_parallel_runner.h JxlThreadParallelRunner
 enabled libklvanc         && require libklvanc libklvanc/vanc.h klvanc_context_create -lklvanc
 enabled libkvazaar        && require_pkg_config libkvazaar "kvazaar >= 0.8.1" kvazaar.h kvz_api_get
 enabled liblensfun        && require_pkg_config liblensfun lensfun lensfun.h lf_db_new
diff --git a/doc/general_contents.texi b/doc/general_contents.texi
index df1692c8df..2778e20091 100644
--- a/doc/general_contents.texi
+++ b/doc/general_contents.texi
@@ -171,6 +171,13 @@  Go to @url{https://github.com/TimothyGu/libilbc} and follow the instructions for
 installing the library. Then pass @code{--enable-libilbc} to configure to
 enable it.
 
+@section libjxl
+
+JPEG XL is an image format intended to fully replace legacy JPEG for an extended
+period of life. See @url{https://jpegxl.info/} for more information, and see
+@url{https://github.com/libjxl/libjxl} for the library source. You can pass
+@code{--enable-libjxl} to configure in order enable the libjxl wrapper.
+
 @section libvpx
 
 FFmpeg can make use of the libvpx library for VP8/VP9 decoding and encoding.
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 14fbd2ecbc..3514279836 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -1035,6 +1035,7 @@  OBJS-$(CONFIG_LIBGSM_MS_DECODER)          += libgsmdec.o
 OBJS-$(CONFIG_LIBGSM_MS_ENCODER)          += libgsmenc.o
 OBJS-$(CONFIG_LIBILBC_DECODER)            += libilbc.o
 OBJS-$(CONFIG_LIBILBC_ENCODER)            += libilbc.o
+OBJS-$(CONFIG_LIBJXL_DECODER)             += libjxldec.o libjxl.o
 OBJS-$(CONFIG_LIBKVAZAAR_ENCODER)         += libkvazaar.o
 OBJS-$(CONFIG_LIBMP3LAME_ENCODER)         += libmp3lame.o
 OBJS-$(CONFIG_LIBOPENCORE_AMRNB_DECODER)  += libopencore-amr.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index 9ede09be17..a6ebd7593b 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -743,6 +743,7 @@  extern const AVCodec ff_libgsm_ms_encoder;
 extern const AVCodec ff_libgsm_ms_decoder;
 extern const AVCodec ff_libilbc_encoder;
 extern const AVCodec ff_libilbc_decoder;
+extern const AVCodec ff_libjxl_decoder;
 extern const AVCodec ff_libmp3lame_encoder;
 extern const AVCodec ff_libopencore_amrnb_encoder;
 extern const AVCodec ff_libopencore_amrnb_decoder;
diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
index 0974ee03de..c21cd12ab8 100644
--- a/libavcodec/codec_desc.c
+++ b/libavcodec/codec_desc.c
@@ -113,6 +113,15 @@  static const AVCodecDescriptor codec_descriptors[] = {
         .props     = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY |
                      AV_CODEC_PROP_LOSSLESS,
     },
+    {
+        .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"),
+    },
     {
         .id        = AV_CODEC_ID_MPEG4,
         .type      = AVMEDIA_TYPE_VIDEO,
diff --git a/libavcodec/codec_id.h b/libavcodec/codec_id.h
index ab265ec584..853bbd4240 100644
--- a/libavcodec/codec_id.h
+++ b/libavcodec/codec_id.h
@@ -59,6 +59,7 @@  enum AVCodecID {
     AV_CODEC_ID_LJPEG,
     AV_CODEC_ID_SP5X,
     AV_CODEC_ID_JPEGLS,
+    AV_CODEC_ID_JPEGXL,
     AV_CODEC_ID_MPEG4,
     AV_CODEC_ID_RAWVIDEO,
     AV_CODEC_ID_MSMPEG4V1,
diff --git a/libavcodec/libjxl.c b/libavcodec/libjxl.c
new file mode 100644
index 0000000000..933551c8a3
--- /dev/null
+++ b/libavcodec/libjxl.c
@@ -0,0 +1,51 @@ 
+/*
+ * JPEG XL de/encoding via libjxl, common support implementation
+ * Copyright (c) 2021 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
+ */
+
+/**
+ * @file
+ * JPEG XL via libjxl common support implementation
+ */
+
+#include "libavutil/cpu.h"
+#include "libavutil/mem.h"
+
+#include "libjxl.h"
+
+size_t ff_libjxl_get_threadcount(int threads)
+{
+    if (threads > FF_LIBJXL_THREADS_MAX)
+        return FF_LIBJXL_THREADS_MAX;
+    if (threads <= 0)
+        return av_cpu_count();
+    if (threads == 1)
+        return 0;
+    return threads;
+}
+
+void *ff_libjxl_av_malloc(void *opaque, size_t size)
+{
+    return av_malloc(size);
+}
+
+void ff_libjxl_av_free(void *opaque, void *address)
+{
+    av_free(address);
+}
diff --git a/libavcodec/libjxl.h b/libavcodec/libjxl.h
new file mode 100644
index 0000000000..bd1c865afa
--- /dev/null
+++ b/libavcodec/libjxl.h
@@ -0,0 +1,57 @@ 
+/*
+ * JPEG XL de/encoding via libjxl, common support header
+ * Copyright (c) 2021 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
+ */
+
+/**
+ * @file
+ * JPEG XL via libjxl common support header
+ */
+
+#ifndef AVCODEC_LIBJXL_H
+#define AVCODEC_LIBJXL_H
+
+#define FF_LIBJXL_THREADS_MAX ((1 << 14) - 1)
+
+/**
+ * Transform threadcount in ffmpeg to one used by libjxl.
+ *
+ * @param  threads ffmpeg's threads AVOption
+ * @return         thread count for libjxl's parallel runner
+ */
+size_t ff_libjxl_get_threadcount(int threads);
+
+/**
+ * Wrapper around av_malloc used as a jpegxl_alloc_func.
+ *
+ * @param  opaque opaque pointer for jpegxl_alloc_func, always ignored
+ * @param  size Size in bytes for the memory block to be allocated
+ * @return Pointer to the allocated block, or `NULL` if it cannot be allocated
+ */
+void *ff_libjxl_av_malloc(void *opaque, size_t size);
+
+/**
+ * Wrapper around av_free used as a jpegxl_free_func.
+ *
+ * @param opaque  opaque pointer for jpegxl_free_func, always ignored
+ * @param address Pointer to the allocated block, to free. `NULL` permitted as a no-op.
+ */
+void ff_libjxl_av_free(void *opaque, void *address);
+
+#endif /* AVCODEC_LIBJXL_H */
diff --git a/libavcodec/libjxldec.c b/libavcodec/libjxldec.c
new file mode 100644
index 0000000000..9e1d048793
--- /dev/null
+++ b/libavcodec/libjxldec.c
@@ -0,0 +1,264 @@ 
+/*
+ * JPEG XL decoding support via libjxl
+ * Copyright (c) 2021 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
+ */
+
+/**
+ * @file
+ * JPEG XL decoder using libjxl
+ */
+
+#include "libavutil/avassert.h"
+#include "libavutil/common.h"
+#include "libavutil/mem.h"
+#include "libavutil/opt.h"
+#include "libavutil/pixdesc.h"
+#include "libavutil/pixfmt.h"
+#include "libavutil/frame.h"
+
+#include "avcodec.h"
+#include "internal.h"
+
+#include <jxl/decode.h>
+#include <jxl/thread_parallel_runner.h>
+#include "libjxl.h"
+
+typedef struct LibJxlDecodeContext {
+    AVClass *class;
+    int threads;
+    JxlDecoder *decoder;
+    void *runner;
+    JxlMemoryManager manager;
+    JxlBasicInfo basic_info;
+    JxlPixelFormat jxl_pixfmt;
+} LibJxlDecodeContext;
+
+static av_cold int libjxl_decode_init(AVCodecContext *avctx)
+{
+    LibJxlDecodeContext *ctx = avctx->priv_data;
+    JxlDecoderStatus status;
+
+    ctx->manager.opaque = NULL;
+    ctx->manager.alloc = &ff_libjxl_av_malloc;
+    ctx->manager.free = &ff_libjxl_av_free;
+
+    ctx->decoder = JxlDecoderCreate(&ctx->manager);
+    if (!ctx->decoder) {
+        av_log(avctx, AV_LOG_ERROR, "Failed to create JxlDecoder");
+        return AVERROR_EXTERNAL;
+    }
+
+    ctx->runner = JxlThreadParallelRunnerCreate(&ctx->manager, ff_libjxl_get_threadcount(ctx->threads));
+    if (!ctx->runner) {
+        av_log(avctx, AV_LOG_ERROR, "Failed to create JxlThreadParallelRunner");
+        return AVERROR_EXTERNAL;
+    }
+
+    status = JxlDecoderSetParallelRunner(ctx->decoder, JxlThreadParallelRunner, ctx->runner);
+    if (status != JXL_DEC_SUCCESS) {
+        av_log(avctx, AV_LOG_ERROR, "Failed to set JxlThreadParallelRunner");
+        return AVERROR_EXTERNAL;
+    }
+
+    status = JxlDecoderSubscribeEvents(ctx->decoder, JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE);
+    if (status != JXL_DEC_SUCCESS) {
+        av_log(avctx, AV_LOG_ERROR, "Error subscribing to JXL events");
+        return AVERROR_EXTERNAL;
+    }
+
+    memset(&ctx->basic_info, 0, sizeof(JxlBasicInfo));
+
+    return 0;
+}
+
+static enum AVPixelFormat libjxl_get_pix_fmt(AVCodecContext *avctx, JxlBasicInfo *basic_info, JxlPixelFormat *format)
+{
+    format->endianness = JXL_LITTLE_ENDIAN;
+    format->num_channels = basic_info->num_color_channels + (basic_info->alpha_bits > 0);
+    /* av_malloc handles alignment already */
+    format->align = 1;
+    /* Gray */
+    if (basic_info->num_color_channels == 1) {
+        if (basic_info->bits_per_sample <= 8) {
+            format->data_type = JXL_TYPE_UINT8;
+            return basic_info->alpha_bits ? AV_PIX_FMT_YA8 : AV_PIX_FMT_GRAY8;
+        }
+        if (basic_info->exponent_bits_per_sample || basic_info->bits_per_sample > 16) {
+            if (basic_info->alpha_bits)
+                return AV_PIX_FMT_NONE;
+            format->data_type = JXL_TYPE_FLOAT;
+            return AV_PIX_FMT_GRAYF32LE;
+        }
+        format->data_type = JXL_TYPE_UINT16;
+        return basic_info->alpha_bits ? AV_PIX_FMT_YA16LE : AV_PIX_FMT_GRAY16LE;
+    }
+    /* rgb only */
+    /* libjxl only supports packed RGB and gray output at the moment */
+    if (basic_info->num_color_channels == 3) {
+        if (basic_info->bits_per_sample <= 8) {
+            format->data_type = JXL_TYPE_UINT8;
+            return basic_info->alpha_bits ? AV_PIX_FMT_RGBA : AV_PIX_FMT_RGB24;
+        }
+        if (basic_info->bits_per_sample > 16)
+            av_log(avctx, AV_LOG_WARNING, "Downsampling larger integer to 16-bit via libjxl\n");
+        if (basic_info->exponent_bits_per_sample)
+            av_log(avctx, AV_LOG_WARNING, "Downsampling float to 16-bit integer via libjxl\n");
+        format->data_type = JXL_TYPE_UINT16;
+        return basic_info->alpha_bits ? AV_PIX_FMT_RGBA64LE : AV_PIX_FMT_RGB48;
+    }
+    return AV_PIX_FMT_NONE;
+}
+
+static void libjxl_row_fill(void *avframe, size_t x, size_t y, size_t num_pixels, const void *pixels)
+{
+    AVFrame *frame = avframe;
+    int bytes = av_get_padded_bits_per_pixel(av_pix_fmt_desc_get(frame->format)) / 8;
+    size_t offset = y * frame->linesize[0] + x * bytes;
+    memcpy(frame->data[0] + offset, pixels, num_pixels * bytes);
+}
+
+static int libjxl_decode_frame(AVCodecContext *avctx, void *avframe, int *got_frame, AVPacket *avpkt)
+{
+    LibJxlDecodeContext *ctx = avctx->priv_data;
+    uint8_t *buf = avpkt->data;
+    size_t remaining = avpkt->size;
+    AVFrame *frame = avframe;
+    JxlDecoderStatus status = JXL_DEC_NEED_MORE_INPUT;
+    int ff_status;
+    size_t bytes_read;
+    *got_frame = 0;
+
+    while (status != JXL_DEC_SUCCESS && status != JXL_DEC_FULL_IMAGE) {
+        /*
+         * it only returns JXL_DEC_ERROR here if the input
+         * was not released since the last time this was called
+         * if this happens, it's a programmer error
+         */
+        status = JxlDecoderSetInput(ctx->decoder, buf, remaining);
+        av_assert0(status != JXL_DEC_ERROR);
+
+        status = JxlDecoderProcessInput(ctx->decoder);
+        /*
+         * JxlDecoderReleaseInput returns the number
+         * of bytes remaining to be read, rather than
+         * the number of bytes that it did read
+         */
+        bytes_read = remaining - JxlDecoderReleaseInput(ctx->decoder);
+
+        buf += bytes_read;
+        remaining -= bytes_read;
+
+        switch(status) {
+            case JXL_DEC_NEED_MORE_INPUT:
+                if (remaining == 0) {
+                    av_log(avctx, AV_LOG_ERROR, "Unexpected end of JXL codestream\n");
+                    return AVERROR(EINVAL);
+                }
+                break;
+            case JXL_DEC_ERROR:
+                av_log(avctx, AV_LOG_ERROR, "Unknown libjxl decode error\n");
+                return AVERROR_EXTERNAL;
+            case JXL_DEC_BASIC_INFO:
+                if (JxlDecoderGetBasicInfo(ctx->decoder, &ctx->basic_info) != JXL_DEC_SUCCESS) {
+                    /*
+                     * this should never happen
+                     * if it does it is likely
+                     * a libjxl decoder bug
+                     */
+                    av_log(avctx, AV_LOG_ERROR, "Bad libjxl basic info event\n");
+                    return AVERROR_EXTERNAL;
+                }
+                avctx->pix_fmt = libjxl_get_pix_fmt(avctx, &ctx->basic_info, &ctx->jxl_pixfmt);
+                if (avctx->pix_fmt == AV_PIX_FMT_NONE) {
+                    av_log(avctx, AV_LOG_ERROR, "Bad libjxl pixel format\n");
+                    return AVERROR_EXTERNAL;
+                }
+                ff_status = ff_set_dimensions(avctx, ctx->basic_info.xsize, ctx->basic_info.ysize);
+                if (ff_status < 0)
+                    return ff_status;
+                ff_status = ff_get_buffer(avctx, frame, 0);
+                if (ff_status < 0)
+                    return ff_status;
+                if (JxlDecoderSetImageOutCallback(ctx->decoder, &ctx->jxl_pixfmt, &libjxl_row_fill, frame) != JXL_DEC_SUCCESS) {
+                    av_log(avctx, AV_LOG_ERROR, "Bad libjxl dec need image out buffer event\n");
+                    return AVERROR_EXTERNAL;
+                }
+                break;
+            case JXL_DEC_SUCCESS:
+                JxlDecoderReset(ctx->decoder);
+                break;
+            default:
+                continue;
+        }
+    }
+
+    *got_frame = 1;
+    frame->pict_type = AV_PICTURE_TYPE_I;
+    frame->key_frame = 1;
+    return avpkt->size;
+}
+
+static av_cold int libjxl_decode_close(AVCodecContext *avctx)
+{
+    LibJxlDecodeContext *ctx = avctx->priv_data;
+    if (ctx->runner)
+        JxlThreadParallelRunnerDestroy(ctx->runner);
+    ctx->runner = NULL;
+    if (ctx->decoder)
+        JxlDecoderDestroy(ctx->decoder);
+    ctx->decoder = NULL;
+    return 0;
+}
+
+static const AVCodecDefault libjxl_decode_defaults[] = {
+    { "threads",             "0" },
+    { NULL },
+};
+
+#define OFFSET(x) offsetof(LibJxlDecodeContext, x)
+#define VD AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_DECODING_PARAM
+
+static const AVOption libjxl_decode_options[] = {
+    { "threads",               "Number of worker threads",             OFFSET(threads),             AV_OPT_TYPE_INT,   { .i64 = 0 }, 0, FF_LIBJXL_THREADS_MAX, VD, "threads" },
+    { "auto",                  "automatic",                            0,                           AV_OPT_TYPE_CONST, { .i64 = 0 }, 0, FF_LIBJXL_THREADS_MAX, VD, "threads" },
+    { NULL },
+};
+
+static const AVClass libjxl_decode_class = {
+    .class_name = "libjxl",
+    .item_name  = av_default_item_name,
+    .option     = libjxl_decode_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+const AVCodec ff_libjxl_decoder = {
+    .name           = "libjxl",
+    .long_name      = NULL_IF_CONFIG_SMALL("libjxl JPEG XL decoder"),
+    .type           = AVMEDIA_TYPE_VIDEO,
+    .id             = AV_CODEC_ID_JPEGXL,
+    .priv_data_size = sizeof(LibJxlDecodeContext),
+    .init           = libjxl_decode_init,
+    .decode         = libjxl_decode_frame,
+    .close          = libjxl_decode_close,
+    .capabilities   = AV_CODEC_CAP_OTHER_THREADS,
+    .caps_internal  = FF_CODEC_CAP_AUTO_THREADS | FF_CODEC_CAP_INIT_CLEANUP,
+    .defaults       = libjxl_decode_defaults,
+    .priv_class     = &libjxl_decode_class,
+    .wrapper_name   = "libjxl",
+};
diff --git a/libavcodec/version.h b/libavcodec/version.h
index 74b8baa5f3..76af066d32 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  12
+#define LIBAVCODEC_VERSION_MINOR  13
 #define LIBAVCODEC_VERSION_MICRO 100
 
 #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index cbfadcb639..fedce9493c 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -505,6 +505,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 4153102c92..d8751d66bf 100644
--- a/libavformat/img2.c
+++ b/libavformat/img2.c
@@ -31,6 +31,7 @@  const IdStrMap ff_img_tags[] = {
     { AV_CODEC_ID_MJPEG,      "mpo"      },
     { AV_CODEC_ID_LJPEG,      "ljpg"     },
     { AV_CODEC_ID_JPEGLS,     "jls"      },
+    { AV_CODEC_ID_JPEGXL,     "jxl"      },
     { AV_CODEC_ID_PNG,        "png"      },
     { AV_CODEC_ID_PNG,        "pns"      },
     { AV_CODEC_ID_PNG,        "mng"      },
diff --git a/libavformat/img2dec.c b/libavformat/img2dec.c
index b535831e1c..752dbf235b 100644
--- a/libavformat/img2dec.c
+++ b/libavformat/img2dec.c
@@ -830,6 +830,16 @@  static int jpegls_probe(const AVProbeData *p)
     return 0;
 }
 
+static int jpegxl_probe(const AVProbeData *p)
+{
+    const uint8_t *b = p->buf;
+
+    if (AV_RB16(b) == 0xff0a ||
+        AV_RB64(b) == 0x0000000c4a584c20)
+        return AVPROBE_SCORE_EXTENSION + 1;
+    return 0;
+}
+
 static int pcx_probe(const AVProbeData *p)
 {
     const uint8_t *b = p->buf;
@@ -1149,6 +1159,7 @@  IMAGEAUTO_DEMUXER(gif,     AV_CODEC_ID_GIF)
 IMAGEAUTO_DEMUXER(j2k,     AV_CODEC_ID_JPEG2000)
 IMAGEAUTO_DEMUXER(jpeg,    AV_CODEC_ID_MJPEG)
 IMAGEAUTO_DEMUXER(jpegls,  AV_CODEC_ID_JPEGLS)
+IMAGEAUTO_DEMUXER(jpegxl,  AV_CODEC_ID_JPEGXL)
 IMAGEAUTO_DEMUXER(pam,     AV_CODEC_ID_PAM)
 IMAGEAUTO_DEMUXER(pbm,     AV_CODEC_ID_PBM)
 IMAGEAUTO_DEMUXER(pcx,     AV_CODEC_ID_PCX)
diff --git a/libavformat/img2enc.c b/libavformat/img2enc.c
index 62202de9f4..72e64da984 100644
--- a/libavformat/img2enc.c
+++ b/libavformat/img2enc.c
@@ -259,9 +259,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,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,xbm,xface,pix,y",
     .priv_data_size = sizeof(VideoMuxData),
     .video_codec    = AV_CODEC_ID_MJPEG,
     .write_header   = write_header,
diff --git a/libavformat/mov.c b/libavformat/mov.c
index 57c67e3aac..3a23f5dad8 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -7437,6 +7437,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 {