* [FFmpeg-devel] [PATCH v9 2/5] avcodec/libjxl: add Jpeg XL decoding via libjxl
2022-03-23 11:03 [FFmpeg-devel] [PATCH v9 1/5] avcodec/jpegxl: add Jpeg XL image codec and parser Leo Izen
@ 2022-03-23 11:03 ` Leo Izen
2022-03-23 14:58 ` Andreas Rheinhardt
2022-03-23 11:03 ` [FFmpeg-devel] [PATCH v9 3/5] avcodec/libjxl: add Jpeg XL encoding " Leo Izen
` (3 subsequent siblings)
4 siblings, 1 reply; 16+ messages in thread
From: Leo Izen @ 2022-03-23 11:03 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Leo Izen
This commit adds decoding support to libavcodec
for Jpeg XL images via the external library libjxl.
---
MAINTAINERS | 1 +
configure | 5 +
doc/general_contents.texi | 7 +
libavcodec/Makefile | 1 +
libavcodec/allcodecs.c | 1 +
libavcodec/libjxl.c | 70 +++++++++
libavcodec/libjxl.h | 48 ++++++
libavcodec/libjxldec.c | 302 ++++++++++++++++++++++++++++++++++++++
8 files changed, 435 insertions(+)
create mode 100644 libavcodec/libjxl.c
create mode 100644 libavcodec/libjxl.h
create mode 100644 libavcodec/libjxldec.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 2e0de9e224..875d25ca89 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -195,6 +195,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
diff --git a/configure b/configure
index a7953ffc16..4e28114d9c 100755
--- a/configure
+++ b/configure
@@ -240,6 +240,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]
@@ -1833,6 +1834,7 @@ EXTERNAL_LIBRARY_LIST="
libiec61883
libilbc
libjack
+ libjxl
libklvanc
libkvazaar
libmodplug
@@ -3329,6 +3331,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"
@@ -6541,6 +6544,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 fcd9da1b34..a893347fbe 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 3723601b3d..c00b0d3246 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -1060,6 +1060,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 22d56760ec..a9cd69dfce 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -749,6 +749,7 @@ extern const FFCodec ff_libgsm_ms_encoder;
extern const FFCodec ff_libgsm_ms_decoder;
extern const FFCodec ff_libilbc_encoder;
extern const FFCodec ff_libilbc_decoder;
+extern const FFCodec ff_libjxl_decoder;
extern const FFCodec ff_libmp3lame_encoder;
extern const FFCodec ff_libopencore_amrnb_encoder;
extern const FFCodec ff_libopencore_amrnb_decoder;
diff --git a/libavcodec/libjxl.c b/libavcodec/libjxl.c
new file mode 100644
index 0000000000..204d91d8a8
--- /dev/null
+++ b/libavcodec/libjxl.c
@@ -0,0 +1,70 @@
+/*
+ * 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 <jxl/memory_manager.h>
+#include "libjxl.h"
+
+size_t ff_libjxl_get_threadcount(int threads)
+{
+ if (threads <= 0)
+ return av_cpu_count();
+ if (threads == 1)
+ return 0;
+ return 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
+ */
+static void *libjxl_av_malloc(void *opaque, size_t size)
+{
+ return av_malloc(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.
+ */
+static void libjxl_av_free(void *opaque, void *address)
+{
+ av_free(address);
+}
+
+void ff_libjxl_init_memory_manager(JxlMemoryManager *manager)
+{
+ manager->opaque = NULL;
+ manager->alloc = &libjxl_av_malloc;
+ manager->free = &libjxl_av_free;
+}
diff --git a/libavcodec/libjxl.h b/libavcodec/libjxl.h
new file mode 100644
index 0000000000..5387c438fd
--- /dev/null
+++ b/libavcodec/libjxl.h
@@ -0,0 +1,48 @@
+/*
+ * 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
+
+#include <jxl/memory_manager.h>
+
+/**
+ * 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);
+
+/**
+ * Initialize and populate a JxlMemoryManager
+ * with av_malloc() and av_free() so libjxl will use these
+ * functions.
+ * @param manager a pointer to a JxlMemoryManager struct
+ */
+void ff_libjxl_init_memory_manager(JxlMemoryManager *manager);
+
+#endif /* AVCODEC_LIBJXL_H */
diff --git a/libavcodec/libjxldec.c b/libavcodec/libjxldec.c
new file mode 100644
index 0000000000..ad22971fbb
--- /dev/null
+++ b/libavcodec/libjxldec.c
@@ -0,0 +1,302 @@
+/*
+ * 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/error.h"
+#include "libavutil/mem.h"
+#include "libavutil/opt.h"
+#include "libavutil/pixdesc.h"
+#include "libavutil/pixfmt.h"
+#include "libavutil/frame.h"
+#include "libavutil/version.h"
+
+#include "avcodec.h"
+#include "codec_internal.h"
+#include "internal.h"
+
+#include <jxl/decode.h>
+#include <jxl/thread_parallel_runner.h>
+#include "libjxl.h"
+
+typedef struct LibJxlDecodeContext {
+ void *runner;
+ JxlDecoder *decoder;
+ JxlBasicInfo basic_info;
+ JxlPixelFormat jxl_pixfmt;
+ JxlDecoderStatus events;
+ uint8_t *iccp;
+ size_t iccp_len;
+} LibJxlDecodeContext;
+
+static int libjxl_init_jxl_decoder(AVCodecContext *avctx)
+{
+ LibJxlDecodeContext *ctx = avctx->priv_data;
+
+ ctx->events = JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE | JXL_DEC_FRAME | JXL_DEC_COLOR_ENCODING;
+ if (JxlDecoderSubscribeEvents(ctx->decoder, ctx->events) != JXL_DEC_SUCCESS) {
+ av_log(avctx, AV_LOG_ERROR, "Error subscribing to JXL events\n");
+ return AVERROR_EXTERNAL;
+ }
+
+ if (JxlDecoderSetParallelRunner(ctx->decoder, JxlThreadParallelRunner, ctx->runner) != JXL_DEC_SUCCESS) {
+ av_log(avctx, AV_LOG_ERROR, "Failed to set JxlThreadParallelRunner\n");
+ return AVERROR_EXTERNAL;
+ }
+
+ memset(&ctx->basic_info, 0, sizeof(JxlBasicInfo));
+ memset(&ctx->jxl_pixfmt, 0, sizeof(JxlPixelFormat));
+ return 0;
+}
+
+static av_cold int libjxl_decode_init(AVCodecContext *avctx)
+{
+ LibJxlDecodeContext *ctx = avctx->priv_data;
+ JxlMemoryManager manager;
+
+ ff_libjxl_init_memory_manager(&manager);
+ ctx->decoder = JxlDecoderCreate(&manager);
+ if (!ctx->decoder) {
+ av_log(avctx, AV_LOG_ERROR, "Failed to create JxlDecoder\n");
+ return AVERROR_EXTERNAL;
+ }
+
+ ctx->runner = JxlThreadParallelRunnerCreate(&manager, ff_libjxl_get_threadcount(avctx->thread_count));
+ if (!ctx->runner) {
+ av_log(avctx, AV_LOG_ERROR, "Failed to create JxlThreadParallelRunner\n");
+ return AVERROR_EXTERNAL;
+ }
+
+ return libjxl_init_jxl_decoder(avctx);
+}
+
+static enum AVPixelFormat libjxl_get_pix_fmt(AVCodecContext *avctx, JxlBasicInfo *basic_info, JxlPixelFormat *format)
+{
+ format->endianness = JXL_NATIVE_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_GRAYF32;
+ }
+ format->data_type = JXL_TYPE_UINT16;
+ return basic_info->alpha_bits ? AV_PIX_FMT_YA16 : AV_PIX_FMT_GRAY16;
+ }
+ /* 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_RGBA64 : 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;
+ int ff_status;
+ *got_frame = 0;
+
+ while (1) {
+ /*
+ * 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
+ */
+ remaining = JxlDecoderReleaseInput(ctx->decoder);
+ buf = avpkt->data + avpkt->size - remaining;
+
+ switch(status) {
+ case JXL_DEC_ERROR:
+ av_log(avctx, AV_LOG_ERROR, "Unknown libjxl decode error\n");
+ return AVERROR_EXTERNAL;
+ case JXL_DEC_NEED_MORE_INPUT:
+ if (remaining == 0) {
+ av_log(avctx, AV_LOG_WARNING, "Unexpected end of JXL codestream\n");
+ return AVERROR(EAGAIN);
+ }
+ av_log(avctx, AV_LOG_DEBUG, "NEED_MORE_INPUT event emitted\n");
+ continue;
+ case JXL_DEC_BASIC_INFO:
+ av_log(avctx, AV_LOG_DEBUG, "BASIC_INFO event emitted\n");
+ 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;
+ /*
+ * We rewind the decoder and ask for everything again
+ * This futureproofs the decoder since it will make
+ * adding a parser or a dedicated demuxer much easier
+ */
+ buf = avpkt->data;
+ remaining = avpkt->size;
+ JxlDecoderRewind(ctx->decoder);
+ ctx->events &= ~JXL_DEC_BASIC_INFO;
+ if (JxlDecoderSubscribeEvents(ctx->decoder, ctx->events) != JXL_DEC_SUCCESS) {
+ av_log(avctx, AV_LOG_ERROR, "Error subscribing to JXL events after rewind\n");
+ return AVERROR_EXTERNAL;
+ }
+ continue;
+ case JXL_DEC_COLOR_ENCODING:
+ av_log(avctx, AV_LOG_DEBUG, "COLOR_ENCODING event emitted\n");
+ status = JxlDecoderGetICCProfileSize(ctx->decoder, &ctx->jxl_pixfmt, JXL_COLOR_PROFILE_TARGET_ORIGINAL, &ctx->iccp_len);
+ if (status == JXL_DEC_SUCCESS && ctx->iccp_len > 0) {
+ if (ctx->iccp)
+ av_freep(&ctx->iccp);
+ ctx->iccp = av_malloc(ctx->iccp_len);
+ if (!ctx->iccp)
+ return AVERROR(ENOMEM);
+ status = JxlDecoderGetColorAsICCProfile(ctx->decoder, &ctx->jxl_pixfmt, JXL_COLOR_PROFILE_TARGET_ORIGINAL, ctx->iccp, ctx->iccp_len);
+ if (status != JXL_DEC_SUCCESS)
+ av_freep(&ctx->iccp);
+ }
+ continue;
+ case JXL_DEC_FRAME:
+ case JXL_DEC_NEED_IMAGE_OUT_BUFFER:
+ /*
+ * We don't do this at basic info time
+ * because it will happen again when we
+ * rewind anyway
+ */
+ av_log(avctx, AV_LOG_DEBUG, "%s event emitted\n", status == JXL_DEC_FRAME ? "FRAME" : "NEED_IMAGE_OUT_BUFFER");
+ 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;
+ }
+ continue;
+ case JXL_DEC_FULL_IMAGE:
+ /* full image is one frame, even if animated */
+ av_log(avctx, AV_LOG_DEBUG, "FULL_IMAGE event emitted\n");
+ *got_frame = 1;
+ frame->pict_type = AV_PICTURE_TYPE_I;
+ frame->key_frame = 1;
+ if (ctx->iccp) {
+ AVFrameSideData *sd = av_frame_new_side_data(frame, AV_FRAME_DATA_ICC_PROFILE, ctx->iccp_len);
+ if (!sd)
+ return AVERROR(ENOMEM);
+ memcpy(sd->data, ctx->iccp, ctx->iccp_len);
+ av_dict_set(&sd->metadata, "name", "", 0);
+ }
+ return avpkt->size - remaining;
+ case JXL_DEC_SUCCESS:
+ av_log(avctx, AV_LOG_DEBUG, "SUCCESS event emitted\n");
+ /*
+ * The file has finished decoding
+ * reset the decoder to let us
+ * reuse it again for the next image
+ */
+ JxlDecoderReset(ctx->decoder);
+ libjxl_init_jxl_decoder(avctx);
+ buf = avpkt->data;
+ remaining = avpkt->size;
+ continue;
+ default:
+ av_log(avctx, AV_LOG_ERROR, "Bad libjxl event: %d\n", status);
+ return AVERROR_EXTERNAL;
+ }
+ }
+}
+
+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;
+ if (ctx->iccp)
+ av_freep(&ctx->iccp);
+ return 0;
+}
+
+const FFCodec ff_libjxl_decoder = {
+ .p.name = "libjxl",
+ .p.long_name = NULL_IF_CONFIG_SMALL("libjxl JPEG XL"),
+ .p.type = AVMEDIA_TYPE_VIDEO,
+ .p.id = AV_CODEC_ID_JPEGXL,
+ .priv_data_size = sizeof(LibJxlDecodeContext),
+ .init = libjxl_decode_init,
+ .decode = libjxl_decode_frame,
+ .close = libjxl_decode_close,
+ .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_OTHER_THREADS,
+ .caps_internal = FF_CODEC_CAP_AUTO_THREADS | FF_CODEC_CAP_INIT_CLEANUP,
+ .p.wrapper_name = "libjxl",
+};
--
2.35.1
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [FFmpeg-devel] [PATCH v9 2/5] avcodec/libjxl: add Jpeg XL decoding via libjxl
2022-03-23 11:03 ` [FFmpeg-devel] [PATCH v9 2/5] avcodec/libjxl: add Jpeg XL decoding via libjxl Leo Izen
@ 2022-03-23 14:58 ` Andreas Rheinhardt
0 siblings, 0 replies; 16+ messages in thread
From: Andreas Rheinhardt @ 2022-03-23 14:58 UTC (permalink / raw)
To: ffmpeg-devel
Leo Izen:
> This commit adds decoding support to libavcodec
> for Jpeg XL images via the external library libjxl.
> ---
> MAINTAINERS | 1 +
> configure | 5 +
> doc/general_contents.texi | 7 +
> libavcodec/Makefile | 1 +
> libavcodec/allcodecs.c | 1 +
> libavcodec/libjxl.c | 70 +++++++++
> libavcodec/libjxl.h | 48 ++++++
> libavcodec/libjxldec.c | 302 ++++++++++++++++++++++++++++++++++++++
> 8 files changed, 435 insertions(+)
> create mode 100644 libavcodec/libjxl.c
> create mode 100644 libavcodec/libjxl.h
> create mode 100644 libavcodec/libjxldec.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 2e0de9e224..875d25ca89 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -195,6 +195,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
> diff --git a/configure b/configure
> index a7953ffc16..4e28114d9c 100755
> --- a/configure
> +++ b/configure
> @@ -240,6 +240,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]
> @@ -1833,6 +1834,7 @@ EXTERNAL_LIBRARY_LIST="
> libiec61883
> libilbc
> libjack
> + libjxl
> libklvanc
> libkvazaar
> libmodplug
> @@ -3329,6 +3331,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"
> @@ -6541,6 +6544,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 fcd9da1b34..a893347fbe 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 3723601b3d..c00b0d3246 100644
> --- a/libavcodec/Makefile
> +++ b/libavcodec/Makefile
> @@ -1060,6 +1060,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 22d56760ec..a9cd69dfce 100644
> --- a/libavcodec/allcodecs.c
> +++ b/libavcodec/allcodecs.c
> @@ -749,6 +749,7 @@ extern const FFCodec ff_libgsm_ms_encoder;
> extern const FFCodec ff_libgsm_ms_decoder;
> extern const FFCodec ff_libilbc_encoder;
> extern const FFCodec ff_libilbc_decoder;
> +extern const FFCodec ff_libjxl_decoder;
> extern const FFCodec ff_libmp3lame_encoder;
> extern const FFCodec ff_libopencore_amrnb_encoder;
> extern const FFCodec ff_libopencore_amrnb_decoder;
> diff --git a/libavcodec/libjxl.c b/libavcodec/libjxl.c
> new file mode 100644
> index 0000000000..204d91d8a8
> --- /dev/null
> +++ b/libavcodec/libjxl.c
> @@ -0,0 +1,70 @@
> +/*
> + * 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 <jxl/memory_manager.h>
> +#include "libjxl.h"
> +
> +size_t ff_libjxl_get_threadcount(int threads)
> +{
> + if (threads <= 0)
> + return av_cpu_count();
> + if (threads == 1)
> + return 0;
> + return 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
> + */
> +static void *libjxl_av_malloc(void *opaque, size_t size)
> +{
> + return av_malloc(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.
> + */
> +static void libjxl_av_free(void *opaque, void *address)
> +{
> + av_free(address);
> +}
> +
> +void ff_libjxl_init_memory_manager(JxlMemoryManager *manager)
> +{
> + manager->opaque = NULL;
> + manager->alloc = &libjxl_av_malloc;
> + manager->free = &libjxl_av_free;
> +}
> diff --git a/libavcodec/libjxl.h b/libavcodec/libjxl.h
> new file mode 100644
> index 0000000000..5387c438fd
> --- /dev/null
> +++ b/libavcodec/libjxl.h
> @@ -0,0 +1,48 @@
> +/*
> + * 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
> +
> +#include <jxl/memory_manager.h>
> +
> +/**
> + * 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);
> +
> +/**
> + * Initialize and populate a JxlMemoryManager
> + * with av_malloc() and av_free() so libjxl will use these
> + * functions.
> + * @param manager a pointer to a JxlMemoryManager struct
> + */
> +void ff_libjxl_init_memory_manager(JxlMemoryManager *manager);
> +
> +#endif /* AVCODEC_LIBJXL_H */
> diff --git a/libavcodec/libjxldec.c b/libavcodec/libjxldec.c
> new file mode 100644
> index 0000000000..ad22971fbb
> --- /dev/null
> +++ b/libavcodec/libjxldec.c
> @@ -0,0 +1,302 @@
> +/*
> + * 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/error.h"
> +#include "libavutil/mem.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/pixdesc.h"
> +#include "libavutil/pixfmt.h"
> +#include "libavutil/frame.h"
> +#include "libavutil/version.h"
What do you need version for? And opt.h?
> +
> +#include "avcodec.h"
> +#include "codec_internal.h"
> +#include "internal.h"
> +
> +#include <jxl/decode.h>
> +#include <jxl/thread_parallel_runner.h>
> +#include "libjxl.h"
> +
> +typedef struct LibJxlDecodeContext {
> + void *runner;
> + JxlDecoder *decoder;
> + JxlBasicInfo basic_info;
> + JxlPixelFormat jxl_pixfmt;
> + JxlDecoderStatus events;
> + uint8_t *iccp;
> + size_t iccp_len;
> +} LibJxlDecodeContext;
> +
> +static int libjxl_init_jxl_decoder(AVCodecContext *avctx)
> +{
> + LibJxlDecodeContext *ctx = avctx->priv_data;
> +
> + ctx->events = JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE | JXL_DEC_FRAME | JXL_DEC_COLOR_ENCODING;
> + if (JxlDecoderSubscribeEvents(ctx->decoder, ctx->events) != JXL_DEC_SUCCESS) {
> + av_log(avctx, AV_LOG_ERROR, "Error subscribing to JXL events\n");
> + return AVERROR_EXTERNAL;
> + }
> +
> + if (JxlDecoderSetParallelRunner(ctx->decoder, JxlThreadParallelRunner, ctx->runner) != JXL_DEC_SUCCESS) {
> + av_log(avctx, AV_LOG_ERROR, "Failed to set JxlThreadParallelRunner\n");
> + return AVERROR_EXTERNAL;
> + }
> +
> + memset(&ctx->basic_info, 0, sizeof(JxlBasicInfo));
> + memset(&ctx->jxl_pixfmt, 0, sizeof(JxlPixelFormat));
> + return 0;
> +}
> +
> +static av_cold int libjxl_decode_init(AVCodecContext *avctx)
> +{
> + LibJxlDecodeContext *ctx = avctx->priv_data;
> + JxlMemoryManager manager;
> +
> + ff_libjxl_init_memory_manager(&manager);
> + ctx->decoder = JxlDecoderCreate(&manager);
> + if (!ctx->decoder) {
> + av_log(avctx, AV_LOG_ERROR, "Failed to create JxlDecoder\n");
> + return AVERROR_EXTERNAL;
> + }
> +
> + ctx->runner = JxlThreadParallelRunnerCreate(&manager, ff_libjxl_get_threadcount(avctx->thread_count));
> + if (!ctx->runner) {
> + av_log(avctx, AV_LOG_ERROR, "Failed to create JxlThreadParallelRunner\n");
> + return AVERROR_EXTERNAL;
> + }
> +
> + return libjxl_init_jxl_decoder(avctx);
> +}
> +
> +static enum AVPixelFormat libjxl_get_pix_fmt(AVCodecContext *avctx, JxlBasicInfo *basic_info, JxlPixelFormat *format)
> +{
> + format->endianness = JXL_NATIVE_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_GRAYF32;
> + }
> + format->data_type = JXL_TYPE_UINT16;
> + return basic_info->alpha_bits ? AV_PIX_FMT_YA16 : AV_PIX_FMT_GRAY16;
> + }
> + /* 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_RGBA64 : 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;
> + int ff_status;
The typical name for this variable would be ret; JxlDecoderStatus could
be could jret.
> + *got_frame = 0;
> +
> + while (1) {
> + /*
> + * 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
Is it guaranteed that this will never happen, e.g. when ff_get_buffer()
or anything else fails?
> + */
> + 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
> + */
> + remaining = JxlDecoderReleaseInput(ctx->decoder);
> + buf = avpkt->data + avpkt->size - remaining;
> +
> + switch(status) {
> + case JXL_DEC_ERROR:
> + av_log(avctx, AV_LOG_ERROR, "Unknown libjxl decode error\n");
> + return AVERROR_EXTERNAL;
> + case JXL_DEC_NEED_MORE_INPUT:
> + if (remaining == 0) {
> + av_log(avctx, AV_LOG_WARNING, "Unexpected end of JXL codestream\n");
> + return AVERROR(EAGAIN);
> + }
> + av_log(avctx, AV_LOG_DEBUG, "NEED_MORE_INPUT event emitted\n");
> + continue;
> + case JXL_DEC_BASIC_INFO:
> + av_log(avctx, AV_LOG_DEBUG, "BASIC_INFO event emitted\n");
> + 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;
> + /*
> + * We rewind the decoder and ask for everything again
> + * This futureproofs the decoder since it will make
> + * adding a parser or a dedicated demuxer much easier
> + */
> + buf = avpkt->data;
> + remaining = avpkt->size;
> + JxlDecoderRewind(ctx->decoder);
> + ctx->events &= ~JXL_DEC_BASIC_INFO;
> + if (JxlDecoderSubscribeEvents(ctx->decoder, ctx->events) != JXL_DEC_SUCCESS) {
> + av_log(avctx, AV_LOG_ERROR, "Error subscribing to JXL events after rewind\n");
> + return AVERROR_EXTERNAL;
> + }
> + continue;
> + case JXL_DEC_COLOR_ENCODING:
> + av_log(avctx, AV_LOG_DEBUG, "COLOR_ENCODING event emitted\n");
> + status = JxlDecoderGetICCProfileSize(ctx->decoder, &ctx->jxl_pixfmt, JXL_COLOR_PROFILE_TARGET_ORIGINAL, &ctx->iccp_len);
> + if (status == JXL_DEC_SUCCESS && ctx->iccp_len > 0) {
> + if (ctx->iccp)
> + av_freep(&ctx->iccp);
> + ctx->iccp = av_malloc(ctx->iccp_len);
Use av_buffer_alloc() here, as this will allow to avoid the memcpy when
attaching the profile to the frame.
> + if (!ctx->iccp)
> + return AVERROR(ENOMEM);
> + status = JxlDecoderGetColorAsICCProfile(ctx->decoder, &ctx->jxl_pixfmt, JXL_COLOR_PROFILE_TARGET_ORIGINAL, ctx->iccp, ctx->iccp_len);
> + if (status != JXL_DEC_SUCCESS)
> + av_freep(&ctx->iccp);
> + }
> + continue;
> + case JXL_DEC_FRAME:
> + case JXL_DEC_NEED_IMAGE_OUT_BUFFER:
> + /*
> + * We don't do this at basic info time
> + * because it will happen again when we
> + * rewind anyway
> + */
> + av_log(avctx, AV_LOG_DEBUG, "%s event emitted\n", status == JXL_DEC_FRAME ? "FRAME" : "NEED_IMAGE_OUT_BUFFER");
> + 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;
> + }
> + continue;
> + case JXL_DEC_FULL_IMAGE:
> + /* full image is one frame, even if animated */
> + av_log(avctx, AV_LOG_DEBUG, "FULL_IMAGE event emitted\n");
> + *got_frame = 1;
> + frame->pict_type = AV_PICTURE_TYPE_I;
> + frame->key_frame = 1;
> + if (ctx->iccp) {
> + AVFrameSideData *sd = av_frame_new_side_data(frame, AV_FRAME_DATA_ICC_PROFILE, ctx->iccp_len);
> + if (!sd)
> + return AVERROR(ENOMEM);
> + memcpy(sd->data, ctx->iccp, ctx->iccp_len);
> + av_dict_set(&sd->metadata, "name", "", 0);
The name is optional; so don't set this.
> + }
> + return avpkt->size - remaining;
> + case JXL_DEC_SUCCESS:
> + av_log(avctx, AV_LOG_DEBUG, "SUCCESS event emitted\n");
> + /*
> + * The file has finished decoding
> + * reset the decoder to let us
> + * reuse it again for the next image
> + */
> + JxlDecoderReset(ctx->decoder);
> + libjxl_init_jxl_decoder(avctx);
> + buf = avpkt->data;
> + remaining = avpkt->size;
> + continue;
> + default:
> + av_log(avctx, AV_LOG_ERROR, "Bad libjxl event: %d\n", status);
> + return AVERROR_EXTERNAL;
> + }
> + }
> +}
> +
> +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;
> + if (ctx->iccp)
Unnecessary check
> + av_freep(&ctx->iccp);
> + return 0;
> +}
> +
> +const FFCodec ff_libjxl_decoder = {
> + .p.name = "libjxl",
> + .p.long_name = NULL_IF_CONFIG_SMALL("libjxl JPEG XL"),
> + .p.type = AVMEDIA_TYPE_VIDEO,
> + .p.id = AV_CODEC_ID_JPEGXL,
> + .priv_data_size = sizeof(LibJxlDecodeContext),
> + .init = libjxl_decode_init,
> + .decode = libjxl_decode_frame,
> + .close = libjxl_decode_close,
> + .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_OTHER_THREADS,
> + .caps_internal = FF_CODEC_CAP_AUTO_THREADS | FF_CODEC_CAP_INIT_CLEANUP,
> + .p.wrapper_name = "libjxl",
> +};
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 16+ messages in thread
* [FFmpeg-devel] [PATCH v9 3/5] avcodec/libjxl: add Jpeg XL encoding via libjxl
2022-03-23 11:03 [FFmpeg-devel] [PATCH v9 1/5] avcodec/jpegxl: add Jpeg XL image codec and parser Leo Izen
2022-03-23 11:03 ` [FFmpeg-devel] [PATCH v9 2/5] avcodec/libjxl: add Jpeg XL decoding via libjxl Leo Izen
@ 2022-03-23 11:03 ` Leo Izen
2022-03-23 15:08 ` Andreas Rheinhardt
2022-03-23 11:03 ` [FFmpeg-devel] [PATCH v9 4/5] avformat/image2: add Jpeg XL as image2 format Leo Izen
` (2 subsequent siblings)
4 siblings, 1 reply; 16+ messages in thread
From: Leo Izen @ 2022-03-23 11:03 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Leo Izen
This commit adds encoding support to libavcodec
for Jpeg XL images via the external library libjxl.
---
configure | 3 +-
libavcodec/Makefile | 1 +
libavcodec/allcodecs.c | 1 +
libavcodec/libjxlenc.c | 385 +++++++++++++++++++++++++++++++++++++++++
4 files changed, 389 insertions(+), 1 deletion(-)
create mode 100644 libavcodec/libjxlenc.c
diff --git a/configure b/configure
index 4e28114d9c..59095a5866 100755
--- a/configure
+++ b/configure
@@ -240,7 +240,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-libjxl enable JPEG XL de/encoding 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]
@@ -3332,6 +3332,7 @@ libgsm_ms_encoder_deps="libgsm"
libilbc_decoder_deps="libilbc"
libilbc_encoder_deps="libilbc"
libjxl_decoder_deps="libjxl libjxl_threads"
+libjxl_encoder_deps="libjxl libjxl_threads"
libkvazaar_encoder_deps="libkvazaar"
libmodplug_demuxer_deps="libmodplug"
libmp3lame_encoder_deps="libmp3lame"
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index c00b0d3246..b208cc0097 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -1061,6 +1061,7 @@ 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_LIBJXL_ENCODER) += libjxlenc.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 a9cd69dfce..db92fb7af5 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -750,6 +750,7 @@ extern const FFCodec ff_libgsm_ms_decoder;
extern const FFCodec ff_libilbc_encoder;
extern const FFCodec ff_libilbc_decoder;
extern const FFCodec ff_libjxl_decoder;
+extern const FFCodec ff_libjxl_encoder;
extern const FFCodec ff_libmp3lame_encoder;
extern const FFCodec ff_libopencore_amrnb_encoder;
extern const FFCodec ff_libopencore_amrnb_decoder;
diff --git a/libavcodec/libjxlenc.c b/libavcodec/libjxlenc.c
new file mode 100644
index 0000000000..802a543383
--- /dev/null
+++ b/libavcodec/libjxlenc.c
@@ -0,0 +1,385 @@
+/*
+ * JPEG XL encoding 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 encoder using libjxl
+ */
+
+#include "libavutil/avutil.h"
+#include "libavutil/error.h"
+#include "libavutil/frame.h"
+#include "libavutil/libm.h"
+#include "libavutil/opt.h"
+#include "libavutil/pixdesc.h"
+#include "libavutil/pixfmt.h"
+#include "libavutil/version.h"
+
+#include "avcodec.h"
+#include "codec_internal.h"
+#include "internal.h"
+
+#include <jxl/encode.h>
+#include <jxl/thread_parallel_runner.h>
+#include "libjxl.h"
+
+typedef struct LibJxlEncodeContext {
+ AVClass *class;
+ void *runner;
+ JxlEncoder *encoder;
+ JxlEncoderFrameSettings *options;
+ int effort;
+ float distance;
+ int modular;
+ uint8_t *buffer;
+ size_t buffer_size;
+} LibJxlEncodeContext;
+
+/**
+ * Map a quality setting for -qscale roughly from libjpeg
+ * quality numbers to libjxl's butteraugli distance for
+ * photographic content.
+ *
+ * Setting distance explicitly is preferred, but this will
+ * allow qscale to be used as a fallback.
+ *
+ * This function is continuous and injective on [0, 100] which
+ * makes it monotonic.
+ *
+ * @param quality 0.0 to 100.0 quality setting, libjpeg quality
+ * @return Butteraugli distance between 0.0 and 15.0
+ */
+static float quality_to_distance(float quality)
+{
+ if (quality >= 100.0)
+ return 0.0;
+ else if (quality >= 90.0)
+ return (100.0 - quality) * 0.10;
+ else if (quality >= 30.0)
+ return 0.1 + (100.0 - quality) * 0.09;
+ else if (quality > 0.0)
+ return 15.0 + (59.0 * quality - 4350.0) * quality / 9000.0;
+ else
+ return 15.0;
+}
+
+/**
+ * Initalize the decoder on a per-frame basis. All of these need to be set
+ * once each time the decoder is reset, which it must be each frame to make
+ * the image2 muxer work.
+ *
+ * @return 0 upon success, negative on failure.
+ */
+static int libjxl_init_jxl_encoder(AVCodecContext *avctx)
+{
+ LibJxlEncodeContext *ctx = avctx->priv_data;
+
+ /* reset the encoder every frame for image2 muxer */
+ JxlEncoderReset(ctx->encoder);
+
+ ctx->options = JxlEncoderFrameSettingsCreate(ctx->encoder, NULL);
+ if (!ctx->options) {
+ av_log(avctx, AV_LOG_ERROR, "Failed to create JxlEncoderOptions\n");
+ return AVERROR_EXTERNAL;
+ }
+
+ /* This needs to be set each time the decoder is reset */
+ if (JxlEncoderSetParallelRunner(ctx->encoder, JxlThreadParallelRunner, ctx->runner)
+ != JXL_ENC_SUCCESS) {
+ av_log(avctx, AV_LOG_ERROR, "Failed to set JxlThreadParallelRunner\n");
+ return AVERROR_EXTERNAL;
+ }
+
+ /* these shouldn't fail, libjxl bug notwithstanding */
+ if (JxlEncoderFrameSettingsSetOption(ctx->options, JXL_ENC_FRAME_SETTING_EFFORT, ctx->effort)
+ != JXL_ENC_SUCCESS) {
+ av_log(avctx, AV_LOG_ERROR, "Failed to set effort to: %d\n", ctx->effort);
+ return AVERROR_EXTERNAL;
+ }
+
+ /* check for negative zero, our default */
+ if (1.0f / ctx->distance == 1.0f / -0.0f) {
+ /* use ffmpeg.c -q option if passed */
+ if (avctx->flags & AV_CODEC_FLAG_QSCALE)
+ ctx->distance = quality_to_distance((float)avctx->global_quality / FF_QP2LAMBDA);
+ else
+ /* default 1.0 matches cjxl */
+ ctx->distance = 1.0;
+ }
+
+ /*
+ * 0.01 is the minimum distance accepted for lossy
+ * interpreting any positive value less than this as minimum
+ */
+ if (ctx->distance > 0.0 && ctx->distance < 0.01)
+ ctx->distance = 0.01;
+ if (JxlEncoderOptionsSetDistance(ctx->options, ctx->distance) != JXL_ENC_SUCCESS) {
+ av_log(avctx, AV_LOG_ERROR, "Failed to set distance: %f\n", ctx->distance);
+ return AVERROR_EXTERNAL;
+ }
+
+ /*
+ * In theory the library should automatically enable modular if necessary,
+ * but it appears it won't at the moment due to a bug. This will still
+ * work even if that is patched.
+ */
+ if (JxlEncoderFrameSettingsSetOption(ctx->options, JXL_ENC_FRAME_SETTING_MODULAR,
+ ctx->modular || ctx->distance <= 0.0 ? 1 : -1) != JXL_ENC_SUCCESS) {
+ av_log(avctx, AV_LOG_ERROR, "Failed to set modular\n");
+ return AVERROR_EXTERNAL;
+ }
+
+ return 0;
+}
+
+/**
+ * Global encoder initialization. This only needs to be run once,
+ * not every frame.
+ */
+static av_cold int libjxl_encode_init(AVCodecContext *avctx)
+{
+ LibJxlEncodeContext *ctx = avctx->priv_data;
+ JxlMemoryManager manager;
+
+ ff_libjxl_init_memory_manager(&manager);
+ ctx->encoder = JxlEncoderCreate(&manager);
+ if (!ctx->encoder) {
+ av_log(avctx, AV_LOG_ERROR, "Failed to create JxlEncoder\n");
+ return AVERROR_EXTERNAL;
+ }
+
+ ctx->runner = JxlThreadParallelRunnerCreate(&manager, ff_libjxl_get_threadcount(avctx->thread_count));
+ if (!ctx->runner) {
+ av_log(avctx, AV_LOG_ERROR, "Failed to create JxlThreadParallelRunner\n");
+ return AVERROR_EXTERNAL;
+ }
+
+ ctx->buffer_size = 4096;
+ ctx->buffer = av_malloc(ctx->buffer_size);
+
+ if (!ctx->buffer) {
+ av_log(avctx, AV_LOG_ERROR, "Could not allocate encoding buffer\n");
+ return AVERROR(ENOMEM);
+ }
+
+ return 0;
+}
+
+/**
+ * Encode an entire frame. Currently animation, is not supported by
+ * this encoder, so this will always reinitialize a new still image
+ * and encode a one-frame image (for image2 and image2pipe).
+ */
+static int libjxl_encode_frame(AVCodecContext *avctx, AVPacket *pkt, const AVFrame *frame, int *got_packet)
+{
+ LibJxlEncodeContext *ctx = avctx->priv_data;
+ AVFrameSideData *sd;
+ const AVPixFmtDescriptor *pix_desc = av_pix_fmt_desc_get(frame->format);
+ JxlBasicInfo info;
+ JxlColorEncoding jxl_color;
+ JxlPixelFormat jxl_fmt;
+ JxlEncoderStatus status;
+ int ff_status;
+ size_t available = ctx->buffer_size;
+ size_t bytes_written = 0;
+ uint8_t *next_out = ctx->buffer;
+
+ ff_status = libjxl_init_jxl_encoder(avctx);
+ if (ff_status) {
+ av_log(avctx, AV_LOG_ERROR, "Error frame-initializing JxlEncoder\n");
+ return ff_status;
+ }
+
+ /* populate the basic info settings */
+ JxlEncoderInitBasicInfo(&info);
+ jxl_fmt.num_channels = pix_desc->nb_components;
+ info.xsize = frame->width;
+ info.ysize = frame->height;
+ info.num_extra_channels = (jxl_fmt.num_channels + 1) % 2;
+ info.num_color_channels = jxl_fmt.num_channels - info.num_extra_channels;
+ info.bits_per_sample = av_get_bits_per_pixel(pix_desc) / jxl_fmt.num_channels;
+ info.alpha_bits = (info.num_extra_channels > 0) * info.bits_per_sample;
+ if (pix_desc->flags & AV_PIX_FMT_FLAG_FLOAT) {
+ info.exponent_bits_per_sample = info.bits_per_sample > 16 ? 8 : 5;
+ info.alpha_exponent_bits = info.alpha_bits ? info.exponent_bits_per_sample : 0;
+ jxl_fmt.data_type = info.bits_per_sample > 16 ? JXL_TYPE_FLOAT : JXL_TYPE_FLOAT16;
+ JxlColorEncodingSetToLinearSRGB(&jxl_color, info.num_color_channels == 1);
+ } else {
+ info.exponent_bits_per_sample = 0;
+ info.alpha_exponent_bits = 0;
+ jxl_fmt.data_type = info.bits_per_sample <= 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16;
+ JxlColorEncodingSetToSRGB(&jxl_color, info.num_color_channels == 1);
+ }
+
+ if (info.bits_per_sample > 16
+ || info.xsize > (1 << 18) || info.ysize > (1 << 18)
+ || (info.xsize << 4) * (info.ysize << 4) > (1 << 20)) {
+ /*
+ * must upgrade codestream to level 10, from level 5
+ * the encoder will not do this automatically
+ */
+ if (JxlEncoderSetCodestreamLevel(ctx->encoder, 10) != JXL_ENC_SUCCESS) {
+ av_log(avctx, AV_LOG_ERROR, "Could not upgrade JXL Codestream level.\n");
+ return AVERROR_EXTERNAL;
+ }
+ }
+
+ /* bitexact lossless requires there to be no XYB transform */
+ info.uses_original_profile = ctx->distance <= 0.0;
+
+ sd = av_frame_get_side_data(frame, AV_FRAME_DATA_ICC_PROFILE);
+ if (sd && sd->size && JxlEncoderSetICCProfile(ctx->encoder, sd->data, sd->size) != JXL_ENC_SUCCESS) {
+ av_log(avctx, AV_LOG_WARNING, "Could not set ICC Profile\n");
+ } else if (info.uses_original_profile) {
+ /*
+ * the color encoding is not used if uses_original_profile is false
+ * this just works around a bug in libjxl 0.7.0 and lower
+ */
+ if (JxlEncoderSetColorEncoding(ctx->encoder, &jxl_color) != JXL_ENC_SUCCESS) {
+ av_log(avctx, AV_LOG_ERROR, "Failed to set JxlColorEncoding\n");
+ return AVERROR_EXTERNAL;
+ }
+ }
+
+ if (JxlEncoderSetBasicInfo(ctx->encoder, &info) != JXL_ENC_SUCCESS) {
+ av_log(avctx, AV_LOG_ERROR, "Failed to set JxlBasicInfo\n");
+ return AVERROR_EXTERNAL;
+ }
+
+ jxl_fmt.endianness = JXL_NATIVE_ENDIAN;
+ jxl_fmt.align = frame->linesize[0];
+
+ if (JxlEncoderAddImageFrame(ctx->options, &jxl_fmt, frame->data[0], jxl_fmt.align * info.ysize) != JXL_ENC_SUCCESS) {
+ av_log(avctx, AV_LOG_ERROR, "Failed to add Image Frame: %d\n", status);
+ return AVERROR_EXTERNAL;
+ }
+
+ /*
+ * Run this after the last frame in the image has been passed.
+ * TODO support animation
+ */
+ JxlEncoderCloseInput(ctx->encoder);
+
+ while (1) {
+ status = JxlEncoderProcessOutput(ctx->encoder, &next_out, &available);
+ if (status == JXL_ENC_ERROR) {
+ av_log(avctx, AV_LOG_ERROR, "Unspecified libjxl error occurred\n");
+ return AVERROR_EXTERNAL;
+ }
+ bytes_written = ctx->buffer_size - available;
+ /* all data passed has been encoded */
+ if (status == JXL_ENC_SUCCESS)
+ break;
+ if (status == JXL_ENC_NEED_MORE_OUTPUT) {
+ /*
+ * at the moment, libjxl has no way to
+ * tell us how much space it actually needs
+ * so we need to malloc loop
+ */
+ uint8_t *temp;
+ ctx->buffer_size = bytes_written * 2;
+ temp = av_realloc(ctx->buffer, ctx->buffer_size);
+ if (!temp) {
+ av_freep(&ctx->buffer);
+ av_log(avctx, AV_LOG_ERROR, "Error reallocating encoder buffer\n");
+ return AVERROR(ENOMEM);
+ }
+ ctx->buffer = temp;
+ next_out = ctx->buffer + bytes_written;
+ available = ctx->buffer_size - bytes_written;
+ continue;
+ }
+ }
+ /*
+ * This buffer will be copied when the generic
+ * code makes this packet refcounted,
+ * so we can use the buffer again.
+ */
+ pkt->data = ctx->buffer;
+ pkt->size = bytes_written;
+ *got_packet = 1;
+ return 0;
+}
+
+static av_cold int libjxl_encode_close(AVCodecContext *avctx)
+{
+ LibJxlEncodeContext *ctx = avctx->priv_data;
+
+ if (ctx->runner)
+ JxlThreadParallelRunnerDestroy(ctx->runner);
+ ctx->runner = NULL;
+
+ /*
+ * destroying the decoder also frees
+ * ctx->options so we don't need to
+ */
+ if (ctx->encoder)
+ JxlEncoderDestroy(ctx->encoder);
+ ctx->encoder = NULL;
+
+ if (ctx->buffer)
+ av_freep(&ctx->buffer);
+ ctx->buffer = NULL;
+ ctx->buffer_size = 0;
+
+ return 0;
+}
+
+#define OFFSET(x) offsetof(LibJxlEncodeContext, x)
+#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
+
+static const AVOption libjxl_encode_options[] = {
+ { "effort", "Encoding effort", OFFSET(effort), AV_OPT_TYPE_INT, { .i64 = 7 }, 1, 9, VE },
+ { "distance", "Maximum Butteraugli distance (quality setting, "
+ "lower = better, zero = lossless, default 1.0)", OFFSET(distance), AV_OPT_TYPE_FLOAT, { .dbl = -0.0 }, 0.0, 15.0, VE },
+ { "modular", "Force modular mode", OFFSET(modular), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, VE },
+ { NULL },
+};
+
+static const AVClass libjxl_encode_class = {
+ .class_name = "libjxl",
+ .item_name = av_default_item_name,
+ .option = libjxl_encode_options,
+ .version = LIBAVUTIL_VERSION_INT,
+};
+
+const FFCodec ff_libjxl_encoder = {
+ .p.name = "libjxl",
+ .p.long_name = NULL_IF_CONFIG_SMALL("libjxl JPEG XL"),
+ .p.type = AVMEDIA_TYPE_VIDEO,
+ .p.id = AV_CODEC_ID_JPEGXL,
+ .priv_data_size = sizeof(LibJxlEncodeContext),
+ .init = libjxl_encode_init,
+ .encode2 = libjxl_encode_frame,
+ .close = libjxl_encode_close,
+ .p.capabilities = AV_CODEC_CAP_OTHER_THREADS,
+ .caps_internal = FF_CODEC_CAP_AUTO_THREADS | FF_CODEC_CAP_INIT_CLEANUP,
+ .p.pix_fmts = (const enum AVPixelFormat[]) {
+ AV_PIX_FMT_RGB24, AV_PIX_FMT_RGBA,
+ AV_PIX_FMT_RGB48, AV_PIX_FMT_RGBA64,
+ AV_PIX_FMT_GRAY8, AV_PIX_FMT_YA8,
+ AV_PIX_FMT_GRAY16, AV_PIX_FMT_YA16,
+ AV_PIX_FMT_GRAYF32,
+ AV_PIX_FMT_NONE
+ },
+ .p.priv_class = &libjxl_encode_class,
+ .p.wrapper_name = "libjxl",
+};
--
2.35.1
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [FFmpeg-devel] [PATCH v9 3/5] avcodec/libjxl: add Jpeg XL encoding via libjxl
2022-03-23 11:03 ` [FFmpeg-devel] [PATCH v9 3/5] avcodec/libjxl: add Jpeg XL encoding " Leo Izen
@ 2022-03-23 15:08 ` Andreas Rheinhardt
2022-03-28 21:16 ` Leo Izen
0 siblings, 1 reply; 16+ messages in thread
From: Andreas Rheinhardt @ 2022-03-23 15:08 UTC (permalink / raw)
To: ffmpeg-devel
Leo Izen:
> This commit adds encoding support to libavcodec
> for Jpeg XL images via the external library libjxl.
> ---
> configure | 3 +-
> libavcodec/Makefile | 1 +
> libavcodec/allcodecs.c | 1 +
> libavcodec/libjxlenc.c | 385 +++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 389 insertions(+), 1 deletion(-)
> create mode 100644 libavcodec/libjxlenc.c
>
> diff --git a/configure b/configure
> index 4e28114d9c..59095a5866 100755
> --- a/configure
> +++ b/configure
> @@ -240,7 +240,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-libjxl enable JPEG XL de/encoding 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]
> @@ -3332,6 +3332,7 @@ libgsm_ms_encoder_deps="libgsm"
> libilbc_decoder_deps="libilbc"
> libilbc_encoder_deps="libilbc"
> libjxl_decoder_deps="libjxl libjxl_threads"
> +libjxl_encoder_deps="libjxl libjxl_threads"
> libkvazaar_encoder_deps="libkvazaar"
> libmodplug_demuxer_deps="libmodplug"
> libmp3lame_encoder_deps="libmp3lame"
> diff --git a/libavcodec/Makefile b/libavcodec/Makefile
> index c00b0d3246..b208cc0097 100644
> --- a/libavcodec/Makefile
> +++ b/libavcodec/Makefile
> @@ -1061,6 +1061,7 @@ 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_LIBJXL_ENCODER) += libjxlenc.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 a9cd69dfce..db92fb7af5 100644
> --- a/libavcodec/allcodecs.c
> +++ b/libavcodec/allcodecs.c
> @@ -750,6 +750,7 @@ extern const FFCodec ff_libgsm_ms_decoder;
> extern const FFCodec ff_libilbc_encoder;
> extern const FFCodec ff_libilbc_decoder;
> extern const FFCodec ff_libjxl_decoder;
> +extern const FFCodec ff_libjxl_encoder;
> extern const FFCodec ff_libmp3lame_encoder;
> extern const FFCodec ff_libopencore_amrnb_encoder;
> extern const FFCodec ff_libopencore_amrnb_decoder;
> diff --git a/libavcodec/libjxlenc.c b/libavcodec/libjxlenc.c
> new file mode 100644
> index 0000000000..802a543383
> --- /dev/null
> +++ b/libavcodec/libjxlenc.c
> @@ -0,0 +1,385 @@
> +/*
> + * JPEG XL encoding 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 encoder using libjxl
> + */
> +
> +#include "libavutil/avutil.h"
> +#include "libavutil/error.h"
> +#include "libavutil/frame.h"
> +#include "libavutil/libm.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/pixdesc.h"
> +#include "libavutil/pixfmt.h"
> +#include "libavutil/version.h"
> +
> +#include "avcodec.h"
> +#include "codec_internal.h"
> +#include "internal.h"
I think this header is unnecessary.
> +
> +#include <jxl/encode.h>
> +#include <jxl/thread_parallel_runner.h>
> +#include "libjxl.h"
> +
> +typedef struct LibJxlEncodeContext {
> + AVClass *class;
> + void *runner;
> + JxlEncoder *encoder;
> + JxlEncoderFrameSettings *options;
> + int effort;
> + float distance;
> + int modular;
> + uint8_t *buffer;
> + size_t buffer_size;
> +} LibJxlEncodeContext;
> +
> +/**
> + * Map a quality setting for -qscale roughly from libjpeg
> + * quality numbers to libjxl's butteraugli distance for
> + * photographic content.
> + *
> + * Setting distance explicitly is preferred, but this will
> + * allow qscale to be used as a fallback.
> + *
> + * This function is continuous and injective on [0, 100] which
> + * makes it monotonic.
> + *
> + * @param quality 0.0 to 100.0 quality setting, libjpeg quality
> + * @return Butteraugli distance between 0.0 and 15.0
> + */
> +static float quality_to_distance(float quality)
> +{
> + if (quality >= 100.0)
> + return 0.0;
> + else if (quality >= 90.0)
> + return (100.0 - quality) * 0.10;
> + else if (quality >= 30.0)
> + return 0.1 + (100.0 - quality) * 0.09;
> + else if (quality > 0.0)
> + return 15.0 + (59.0 * quality - 4350.0) * quality / 9000.0;
> + else
> + return 15.0;
> +}
> +
> +/**
> + * Initalize the decoder on a per-frame basis. All of these need to be set
> + * once each time the decoder is reset, which it must be each frame to make
> + * the image2 muxer work.
> + *
> + * @return 0 upon success, negative on failure.
> + */
> +static int libjxl_init_jxl_encoder(AVCodecContext *avctx)
> +{
> + LibJxlEncodeContext *ctx = avctx->priv_data;
> +
> + /* reset the encoder every frame for image2 muxer */
> + JxlEncoderReset(ctx->encoder);
> +
> + ctx->options = JxlEncoderFrameSettingsCreate(ctx->encoder, NULL);
> + if (!ctx->options) {
> + av_log(avctx, AV_LOG_ERROR, "Failed to create JxlEncoderOptions\n");
> + return AVERROR_EXTERNAL;
> + }
> +
> + /* This needs to be set each time the decoder is reset */
> + if (JxlEncoderSetParallelRunner(ctx->encoder, JxlThreadParallelRunner, ctx->runner)
> + != JXL_ENC_SUCCESS) {
> + av_log(avctx, AV_LOG_ERROR, "Failed to set JxlThreadParallelRunner\n");
> + return AVERROR_EXTERNAL;
> + }
> +
> + /* these shouldn't fail, libjxl bug notwithstanding */
> + if (JxlEncoderFrameSettingsSetOption(ctx->options, JXL_ENC_FRAME_SETTING_EFFORT, ctx->effort)
> + != JXL_ENC_SUCCESS) {
> + av_log(avctx, AV_LOG_ERROR, "Failed to set effort to: %d\n", ctx->effort);
> + return AVERROR_EXTERNAL;
> + }
> +
> + /* check for negative zero, our default */
> + if (1.0f / ctx->distance == 1.0f / -0.0f) {
> + /* use ffmpeg.c -q option if passed */
> + if (avctx->flags & AV_CODEC_FLAG_QSCALE)
> + ctx->distance = quality_to_distance((float)avctx->global_quality / FF_QP2LAMBDA);
> + else
> + /* default 1.0 matches cjxl */
> + ctx->distance = 1.0;
> + }
> +
> + /*
> + * 0.01 is the minimum distance accepted for lossy
> + * interpreting any positive value less than this as minimum
> + */
> + if (ctx->distance > 0.0 && ctx->distance < 0.01)
> + ctx->distance = 0.01;
> + if (JxlEncoderOptionsSetDistance(ctx->options, ctx->distance) != JXL_ENC_SUCCESS) {
> + av_log(avctx, AV_LOG_ERROR, "Failed to set distance: %f\n", ctx->distance);
> + return AVERROR_EXTERNAL;
> + }
> +
> + /*
> + * In theory the library should automatically enable modular if necessary,
> + * but it appears it won't at the moment due to a bug. This will still
> + * work even if that is patched.
> + */
> + if (JxlEncoderFrameSettingsSetOption(ctx->options, JXL_ENC_FRAME_SETTING_MODULAR,
> + ctx->modular || ctx->distance <= 0.0 ? 1 : -1) != JXL_ENC_SUCCESS) {
> + av_log(avctx, AV_LOG_ERROR, "Failed to set modular\n");
> + return AVERROR_EXTERNAL;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * Global encoder initialization. This only needs to be run once,
> + * not every frame.
> + */
> +static av_cold int libjxl_encode_init(AVCodecContext *avctx)
> +{
> + LibJxlEncodeContext *ctx = avctx->priv_data;
> + JxlMemoryManager manager;
> +
> + ff_libjxl_init_memory_manager(&manager);
> + ctx->encoder = JxlEncoderCreate(&manager);
> + if (!ctx->encoder) {
> + av_log(avctx, AV_LOG_ERROR, "Failed to create JxlEncoder\n");
> + return AVERROR_EXTERNAL;
> + }
> +
> + ctx->runner = JxlThreadParallelRunnerCreate(&manager, ff_libjxl_get_threadcount(avctx->thread_count));
> + if (!ctx->runner) {
> + av_log(avctx, AV_LOG_ERROR, "Failed to create JxlThreadParallelRunner\n");
> + return AVERROR_EXTERNAL;
> + }
> +
> + ctx->buffer_size = 4096;
> + ctx->buffer = av_malloc(ctx->buffer_size);
> +
> + if (!ctx->buffer) {
> + av_log(avctx, AV_LOG_ERROR, "Could not allocate encoding buffer\n");
> + return AVERROR(ENOMEM);
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * Encode an entire frame. Currently animation, is not supported by
> + * this encoder, so this will always reinitialize a new still image
> + * and encode a one-frame image (for image2 and image2pipe).
> + */
> +static int libjxl_encode_frame(AVCodecContext *avctx, AVPacket *pkt, const AVFrame *frame, int *got_packet)
> +{
> + LibJxlEncodeContext *ctx = avctx->priv_data;
> + AVFrameSideData *sd;
> + const AVPixFmtDescriptor *pix_desc = av_pix_fmt_desc_get(frame->format);
> + JxlBasicInfo info;
> + JxlColorEncoding jxl_color;
> + JxlPixelFormat jxl_fmt;
> + JxlEncoderStatus status;
> + int ff_status;
> + size_t available = ctx->buffer_size;
> + size_t bytes_written = 0;
> + uint8_t *next_out = ctx->buffer;
> +
> + ff_status = libjxl_init_jxl_encoder(avctx);
> + if (ff_status) {
> + av_log(avctx, AV_LOG_ERROR, "Error frame-initializing JxlEncoder\n");
> + return ff_status;
> + }
> +
> + /* populate the basic info settings */
> + JxlEncoderInitBasicInfo(&info);
> + jxl_fmt.num_channels = pix_desc->nb_components;
> + info.xsize = frame->width;
> + info.ysize = frame->height;
> + info.num_extra_channels = (jxl_fmt.num_channels + 1) % 2;
> + info.num_color_channels = jxl_fmt.num_channels - info.num_extra_channels;
> + info.bits_per_sample = av_get_bits_per_pixel(pix_desc) / jxl_fmt.num_channels;
> + info.alpha_bits = (info.num_extra_channels > 0) * info.bits_per_sample;
> + if (pix_desc->flags & AV_PIX_FMT_FLAG_FLOAT) {
> + info.exponent_bits_per_sample = info.bits_per_sample > 16 ? 8 : 5;
> + info.alpha_exponent_bits = info.alpha_bits ? info.exponent_bits_per_sample : 0;
> + jxl_fmt.data_type = info.bits_per_sample > 16 ? JXL_TYPE_FLOAT : JXL_TYPE_FLOAT16;
> + JxlColorEncodingSetToLinearSRGB(&jxl_color, info.num_color_channels == 1);
> + } else {
> + info.exponent_bits_per_sample = 0;
> + info.alpha_exponent_bits = 0;
> + jxl_fmt.data_type = info.bits_per_sample <= 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16;
> + JxlColorEncodingSetToSRGB(&jxl_color, info.num_color_channels == 1);
> + }
> +
> + if (info.bits_per_sample > 16
> + || info.xsize > (1 << 18) || info.ysize > (1 << 18)
> + || (info.xsize << 4) * (info.ysize << 4) > (1 << 20)) {
> + /*
> + * must upgrade codestream to level 10, from level 5
> + * the encoder will not do this automatically
> + */
> + if (JxlEncoderSetCodestreamLevel(ctx->encoder, 10) != JXL_ENC_SUCCESS) {
> + av_log(avctx, AV_LOG_ERROR, "Could not upgrade JXL Codestream level.\n");
> + return AVERROR_EXTERNAL;
> + }
> + }
> +
> + /* bitexact lossless requires there to be no XYB transform */
> + info.uses_original_profile = ctx->distance <= 0.0;
> +
> + sd = av_frame_get_side_data(frame, AV_FRAME_DATA_ICC_PROFILE);
> + if (sd && sd->size && JxlEncoderSetICCProfile(ctx->encoder, sd->data, sd->size) != JXL_ENC_SUCCESS) {
> + av_log(avctx, AV_LOG_WARNING, "Could not set ICC Profile\n");
> + } else if (info.uses_original_profile) {
> + /*
> + * the color encoding is not used if uses_original_profile is false
> + * this just works around a bug in libjxl 0.7.0 and lower
> + */
> + if (JxlEncoderSetColorEncoding(ctx->encoder, &jxl_color) != JXL_ENC_SUCCESS) {
> + av_log(avctx, AV_LOG_ERROR, "Failed to set JxlColorEncoding\n");
> + return AVERROR_EXTERNAL;
> + }
> + }
> +
> + if (JxlEncoderSetBasicInfo(ctx->encoder, &info) != JXL_ENC_SUCCESS) {
> + av_log(avctx, AV_LOG_ERROR, "Failed to set JxlBasicInfo\n");
> + return AVERROR_EXTERNAL;
> + }
> +
> + jxl_fmt.endianness = JXL_NATIVE_ENDIAN;
> + jxl_fmt.align = frame->linesize[0];
> +
> + if (JxlEncoderAddImageFrame(ctx->options, &jxl_fmt, frame->data[0], jxl_fmt.align * info.ysize) != JXL_ENC_SUCCESS) {
> + av_log(avctx, AV_LOG_ERROR, "Failed to add Image Frame: %d\n", status);
> + return AVERROR_EXTERNAL;
> + }
> +
> + /*
> + * Run this after the last frame in the image has been passed.
> + * TODO support animation
> + */
> + JxlEncoderCloseInput(ctx->encoder);
> +
> + while (1) {
> + status = JxlEncoderProcessOutput(ctx->encoder, &next_out, &available);
> + if (status == JXL_ENC_ERROR) {
> + av_log(avctx, AV_LOG_ERROR, "Unspecified libjxl error occurred\n");
> + return AVERROR_EXTERNAL;
> + }
> + bytes_written = ctx->buffer_size - available;
> + /* all data passed has been encoded */
> + if (status == JXL_ENC_SUCCESS)
> + break;
> + if (status == JXL_ENC_NEED_MORE_OUTPUT) {
> + /*
> + * at the moment, libjxl has no way to
> + * tell us how much space it actually needs
> + * so we need to malloc loop
> + */
> + uint8_t *temp;
> + ctx->buffer_size = bytes_written * 2;
Is it possible that available is still > 0 if JXL_ENC_NEED_MORE_OUTPUT
is returned? In this case the above might even shrink buffer_size.
> + temp = av_realloc(ctx->buffer, ctx->buffer_size);
> + if (!temp) {
> + av_freep(&ctx->buffer);
If you free this, you will be in a scenario where ctx->buffer is NULL,
yet ctx->buffer_size is > 0. This is inconsistent and might lead to
crashs in JxlEncoderProcessOutput. So don't free this and only set
buffer_size after the reallocation succeeded.
> + av_log(avctx, AV_LOG_ERROR, "Error reallocating encoder buffer\n");
return AVERROR(ENOMEM) is enough.
> + return AVERROR(ENOMEM);
> + }
> + ctx->buffer = temp;
> + next_out = ctx->buffer + bytes_written;
> + available = ctx->buffer_size - bytes_written;
> + continue;
> + }
> + }
> + /*
> + * This buffer will be copied when the generic
> + * code makes this packet refcounted,
> + * so we can use the buffer again.
> + */
> + pkt->data = ctx->buffer;
> + pkt->size = bytes_written;
> + *got_packet = 1;
> + return 0;
> +}
> +
> +static av_cold int libjxl_encode_close(AVCodecContext *avctx)
> +{
> + LibJxlEncodeContext *ctx = avctx->priv_data;
> +
> + if (ctx->runner)
> + JxlThreadParallelRunnerDestroy(ctx->runner);
> + ctx->runner = NULL;
> +
> + /*
> + * destroying the decoder also frees
> + * ctx->options so we don't need to
> + */
> + if (ctx->encoder)
> + JxlEncoderDestroy(ctx->encoder);
> + ctx->encoder = NULL;
> +
> + if (ctx->buffer)
Unnecessary check.
> + av_freep(&ctx->buffer);
> + ctx->buffer = NULL;
> + ctx->buffer_size = 0;
Unnecessary: av_freep() resets ctx->buffer and resetting buffer_size is
unnecessary.
> +
> + return 0;
> +}
> +
> +#define OFFSET(x) offsetof(LibJxlEncodeContext, x)
> +#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
> +
> +static const AVOption libjxl_encode_options[] = {
> + { "effort", "Encoding effort", OFFSET(effort), AV_OPT_TYPE_INT, { .i64 = 7 }, 1, 9, VE },
> + { "distance", "Maximum Butteraugli distance (quality setting, "
> + "lower = better, zero = lossless, default 1.0)", OFFSET(distance), AV_OPT_TYPE_FLOAT, { .dbl = -0.0 }, 0.0, 15.0, VE },
> + { "modular", "Force modular mode", OFFSET(modular), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, VE },
> + { NULL },
> +};
> +
> +static const AVClass libjxl_encode_class = {
> + .class_name = "libjxl",
> + .item_name = av_default_item_name,
> + .option = libjxl_encode_options,
> + .version = LIBAVUTIL_VERSION_INT,
> +};
> +
> +const FFCodec ff_libjxl_encoder = {
> + .p.name = "libjxl",
> + .p.long_name = NULL_IF_CONFIG_SMALL("libjxl JPEG XL"),
> + .p.type = AVMEDIA_TYPE_VIDEO,
> + .p.id = AV_CODEC_ID_JPEGXL,
> + .priv_data_size = sizeof(LibJxlEncodeContext),
> + .init = libjxl_encode_init,
> + .encode2 = libjxl_encode_frame,
> + .close = libjxl_encode_close,
> + .p.capabilities = AV_CODEC_CAP_OTHER_THREADS,
> + .caps_internal = FF_CODEC_CAP_AUTO_THREADS | FF_CODEC_CAP_INIT_CLEANUP,
> + .p.pix_fmts = (const enum AVPixelFormat[]) {
> + AV_PIX_FMT_RGB24, AV_PIX_FMT_RGBA,
> + AV_PIX_FMT_RGB48, AV_PIX_FMT_RGBA64,
> + AV_PIX_FMT_GRAY8, AV_PIX_FMT_YA8,
> + AV_PIX_FMT_GRAY16, AV_PIX_FMT_YA16,
> + AV_PIX_FMT_GRAYF32,
> + AV_PIX_FMT_NONE
> + },
> + .p.priv_class = &libjxl_encode_class,
> + .p.wrapper_name = "libjxl",
> +};
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [FFmpeg-devel] [PATCH v9 3/5] avcodec/libjxl: add Jpeg XL encoding via libjxl
2022-03-23 15:08 ` Andreas Rheinhardt
@ 2022-03-28 21:16 ` Leo Izen
2022-03-30 11:12 ` Andreas Rheinhardt
0 siblings, 1 reply; 16+ messages in thread
From: Leo Izen @ 2022-03-28 21:16 UTC (permalink / raw)
To: ffmpeg-devel
On 3/23/22 11:08, Andreas Rheinhardt wrote:
>> + temp = av_realloc(ctx->buffer, ctx->buffer_size);
>> + if (!temp) {
>> + av_freep(&ctx->buffer);
> If you free this, you will be in a scenario where ctx->buffer is NULL,
> yet ctx->buffer_size is > 0. This is inconsistent and might lead to
> crashs in JxlEncoderProcessOutput. So don't free this and only set
> buffer_size after the reallocation succeeded.
Does it matter what ctx->buffer_size is if ctx->buffer is NULL?
On 3/23/22 11:08, Andreas Rheinhardt wrote:
> Unnecessary: av_freep() resets ctx->buffer and resetting buffer_size is
> unnecessary.
Here, you claim that resetting buffer_size is unnecessary but above you
insist it is necessary. I'm confused here.
Leo Izen (thebombzen)
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [FFmpeg-devel] [PATCH v9 3/5] avcodec/libjxl: add Jpeg XL encoding via libjxl
2022-03-28 21:16 ` Leo Izen
@ 2022-03-30 11:12 ` Andreas Rheinhardt
2022-03-30 13:37 ` Leo Izen
0 siblings, 1 reply; 16+ messages in thread
From: Andreas Rheinhardt @ 2022-03-30 11:12 UTC (permalink / raw)
To: ffmpeg-devel
Leo Izen:
> On 3/23/22 11:08, Andreas Rheinhardt wrote:
>>> + temp = av_realloc(ctx->buffer, ctx->buffer_size);
>>> + if (!temp) {
>>> + av_freep(&ctx->buffer);
>> If you free this, you will be in a scenario where ctx->buffer is NULL,
>> yet ctx->buffer_size is > 0. This is inconsistent and might lead to
>> crashs in JxlEncoderProcessOutput. So don't free this and only set
>> buffer_size after the reallocation succeeded.
> Does it matter what ctx->buffer_size is if ctx->buffer is NULL?
> On 3/23/22 11:08, Andreas Rheinhardt wrote:
>> Unnecessary: av_freep() resets ctx->buffer and resetting buffer_size is
>> unnecessary.
>
> Here, you claim that resetting buffer_size is unnecessary but above you
> insist it is necessary. I'm confused here.
>
buffer_size is not used after libjxl_encode_close() at all, as this is
the codec's close callback; libjxl_encode_frame() meanwhile may be
called again after freeing and if it reaches JxlEncoderProcessOutput(),
the latter would be called with next_out == NULL and available != 0.
JxlEncoderProcessOutput() might check for this (and error out) or it may
just crash.
- Andreas
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [FFmpeg-devel] [PATCH v9 3/5] avcodec/libjxl: add Jpeg XL encoding via libjxl
2022-03-30 11:12 ` Andreas Rheinhardt
@ 2022-03-30 13:37 ` Leo Izen
2022-03-30 13:43 ` Andreas Rheinhardt
0 siblings, 1 reply; 16+ messages in thread
From: Leo Izen @ 2022-03-30 13:37 UTC (permalink / raw)
To: ffmpeg-devel
On 3/30/22 07:12, Andreas Rheinhardt wrote:
> buffer_size is not used after libjxl_encode_close() at all, as this is
> the codec's close callback; libjxl_encode_frame() meanwhile may be
> called again after freeing and if it reaches JxlEncoderProcessOutput(),
> the latter would be called with next_out == NULL and available != 0.
> JxlEncoderProcessOutput() might check for this (and error out) or it may
> just crash.
>
Can encode2 really be called again if it returns with AVERROR(ENOMEM)? I
was under the impression that this preventing further calls.
Leo Izen (thebombzen)
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [FFmpeg-devel] [PATCH v9 3/5] avcodec/libjxl: add Jpeg XL encoding via libjxl
2022-03-30 13:37 ` Leo Izen
@ 2022-03-30 13:43 ` Andreas Rheinhardt
0 siblings, 0 replies; 16+ messages in thread
From: Andreas Rheinhardt @ 2022-03-30 13:43 UTC (permalink / raw)
To: ffmpeg-devel
Leo Izen:
>
> On 3/30/22 07:12, Andreas Rheinhardt wrote:
>> buffer_size is not used after libjxl_encode_close() at all, as this is
>> the codec's close callback; libjxl_encode_frame() meanwhile may be
>> called again after freeing and if it reaches JxlEncoderProcessOutput(),
>> the latter would be called with next_out == NULL and available != 0.
>> JxlEncoderProcessOutput() might check for this (and error out) or it may
>> just crash.
>>
> Can encode2 really be called again if it returns with AVERROR(ENOMEM)? I
> was under the impression that this preventing further calls.
>
Why should this be so? (E.g. a user might close other processes in order
to free up memory upon receiving AVERROR(ENOMEM) and try again.)
- Andreas
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 16+ messages in thread
* [FFmpeg-devel] [PATCH v9 4/5] avformat/image2: add Jpeg XL as image2 format
2022-03-23 11:03 [FFmpeg-devel] [PATCH v9 1/5] avcodec/jpegxl: add Jpeg XL image codec and parser Leo Izen
2022-03-23 11:03 ` [FFmpeg-devel] [PATCH v9 2/5] avcodec/libjxl: add Jpeg XL decoding via libjxl Leo Izen
2022-03-23 11:03 ` [FFmpeg-devel] [PATCH v9 3/5] avcodec/libjxl: add Jpeg XL encoding " Leo Izen
@ 2022-03-23 11:03 ` Leo Izen
2022-03-24 18:38 ` Michael Niedermayer
2022-03-23 11:03 ` [FFmpeg-devel] [PATCH v9 5/5] fate/jpegxl: add Jpeg XL demux and parse FATE test Leo Izen
2022-03-23 14:45 ` [FFmpeg-devel] [PATCH v9 1/5] avcodec/jpegxl: add Jpeg XL image codec and parser Andreas Rheinhardt
4 siblings, 1 reply; 16+ messages in thread
From: Leo Izen @ 2022-03-23 11:03 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Leo Izen
This commit adds support to libavformat for muxing
and demuxing Jpeg XL images as image2 streams.
---
libavformat/allformats.c | 1 +
libavformat/img2.c | 1 +
libavformat/img2dec.c | 21 +++++++++++++++++++++
libavformat/img2enc.c | 6 +++---
libavformat/mov.c | 1 +
libavformat/version.h | 4 ++--
6 files changed, 29 insertions(+), 5 deletions(-)
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index 587ad59b3c..941f3643f8 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 4153102c92..13b1b997b8 100644
--- a/libavformat/img2.c
+++ b/libavformat/img2.c
@@ -87,6 +87,7 @@ const IdStrMap ff_img_tags[] = {
{ AV_CODEC_ID_GEM, "img" },
{ AV_CODEC_ID_GEM, "ximg" },
{ AV_CODEC_ID_GEM, "timg" },
+ { AV_CODEC_ID_JPEGXL, "jxl" },
{ AV_CODEC_ID_NONE, NULL }
};
diff --git a/libavformat/img2dec.c b/libavformat/img2dec.c
index b9c06c5b54..32cadacb9d 100644
--- a/libavformat/img2dec.c
+++ b/libavformat/img2dec.c
@@ -32,6 +32,7 @@
#include "libavutil/parseutils.h"
#include "libavutil/intreadwrite.h"
#include "libavcodec/gif.h"
+#include "libavcodec/jpegxl.h"
#include "avformat.h"
#include "avio_internal.h"
#include "internal.h"
@@ -836,6 +837,25 @@ 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;
+#if CONFIG_JPEGXL_PARSER
+ /* Raw codestreams all start with 0xff0a */
+ if (AV_RL16(b) != FF_JPEGXL_CODESTREAM_SIGNATURE_LE)
+ return 0;
+ if (avpriv_jpegxl_verify_codestream_header(NULL, p->buf, p->buf_size) == 0)
+ return AVPROBE_SCORE_MAX - 2;
+#endif
+ return 0;
+}
+
+
static int pcx_probe(const AVProbeData *p)
{
const uint8_t *b = p->buf;
@@ -1165,6 +1185,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 9b3b8741c8..e6ec6a50aa 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,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 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 {
diff --git a/libavformat/version.h b/libavformat/version.h
index f4a26c2870..683184d5da 100644
--- a/libavformat/version.h
+++ b/libavformat/version.h
@@ -31,8 +31,8 @@
#include "version_major.h"
-#define LIBAVFORMAT_VERSION_MINOR 20
-#define LIBAVFORMAT_VERSION_MICRO 101
+#define LIBAVFORMAT_VERSION_MINOR 21
+#define LIBAVFORMAT_VERSION_MICRO 100
#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
LIBAVFORMAT_VERSION_MINOR, \
--
2.35.1
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [FFmpeg-devel] [PATCH v9 4/5] avformat/image2: add Jpeg XL as image2 format
2022-03-23 11:03 ` [FFmpeg-devel] [PATCH v9 4/5] avformat/image2: add Jpeg XL as image2 format Leo Izen
@ 2022-03-24 18:38 ` Michael Niedermayer
2022-03-28 20:51 ` Leo Izen
0 siblings, 1 reply; 16+ messages in thread
From: Michael Niedermayer @ 2022-03-24 18:38 UTC (permalink / raw)
To: FFmpeg development discussions and patches
[-- Attachment #1.1: Type: text/plain, Size: 853 bytes --]
On Wed, Mar 23, 2022 at 07:03:24AM -0400, Leo Izen wrote:
> This commit adds support to libavformat for muxing
> and demuxing Jpeg XL images as image2 streams.
> ---
> libavformat/allformats.c | 1 +
> libavformat/img2.c | 1 +
> libavformat/img2dec.c | 21 +++++++++++++++++++++
> libavformat/img2enc.c | 6 +++---
> libavformat/mov.c | 1 +
> libavformat/version.h | 4 ++--
> 6 files changed, 29 insertions(+), 5 deletions(-)
make -j32 tools/probetest && tools/probetest 256 4096
testing size=1
testing size=2
testing size=4
Assertion n>=0 && n<=32 failed at libavcodec/get_bits.h:549
Aborted (core dumped)
[...]
--
Michael GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB
Asymptotically faster algorithms should always be preferred if you have
asymptotical amounts of data
[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]
[-- Attachment #2: Type: text/plain, Size: 251 bytes --]
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [FFmpeg-devel] [PATCH v9 4/5] avformat/image2: add Jpeg XL as image2 format
2022-03-24 18:38 ` Michael Niedermayer
@ 2022-03-28 20:51 ` Leo Izen
2022-03-30 11:16 ` Andreas Rheinhardt
0 siblings, 1 reply; 16+ messages in thread
From: Leo Izen @ 2022-03-28 20:51 UTC (permalink / raw)
To: ffmpeg-devel
On 3/24/22 14:38, Michael Niedermayer wrote:
> make -j32 tools/probetest && tools/probetest 256 4096
>
> testing size=1
> testing size=2
> testing size=4
> Assertion n>=0 && n<=32 failed at libavcodec/get_bits.h:549
> Aborted (core dumped)
Not sure where this failure is coming from as I cannot reproduce it.
Leo Izen (thebombzen)
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [FFmpeg-devel] [PATCH v9 4/5] avformat/image2: add Jpeg XL as image2 format
2022-03-28 20:51 ` Leo Izen
@ 2022-03-30 11:16 ` Andreas Rheinhardt
0 siblings, 0 replies; 16+ messages in thread
From: Andreas Rheinhardt @ 2022-03-30 11:16 UTC (permalink / raw)
To: ffmpeg-devel
Leo Izen:
> On 3/24/22 14:38, Michael Niedermayer wrote:
>> make -j32 tools/probetest && tools/probetest 256 4096
>>
>> testing size=1
>> testing size=2
>> testing size=4
>> Assertion n>=0 && n<=32 failed at libavcodec/get_bits.h:549
>> Aborted (core dumped)
> Not sure where this failure is coming from as I cannot reproduce it.
>
It's an av_assert2() assert, i.e. only enabled with --assert-level=2
when running configure. And it is expected that this happens, given that
the get_bits-functions are not supposed to be used for skipping.
Actually, setting assert-level=2 should be done when developing FFmpeg;
it is the equivalent of enabling asserts (for non-release builds).
- Andreas
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 16+ messages in thread
* [FFmpeg-devel] [PATCH v9 5/5] fate/jpegxl: add Jpeg XL demux and parse FATE test
2022-03-23 11:03 [FFmpeg-devel] [PATCH v9 1/5] avcodec/jpegxl: add Jpeg XL image codec and parser Leo Izen
` (2 preceding siblings ...)
2022-03-23 11:03 ` [FFmpeg-devel] [PATCH v9 4/5] avformat/image2: add Jpeg XL as image2 format Leo Izen
@ 2022-03-23 11:03 ` Leo Izen
2022-03-23 14:45 ` [FFmpeg-devel] [PATCH v9 1/5] avcodec/jpegxl: add Jpeg XL image codec and parser Andreas Rheinhardt
4 siblings, 0 replies; 16+ messages in thread
From: Leo Izen @ 2022-03-23 11:03 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Leo Izen
Add a fate test for the Jpeg XL parser in libavcodec and
its image2 wrapper inside libavformat.
---
tests/fate/image.mak | 10 ++++++++++
tests/ref/fate/jxl-parse-codestream | 6 ++++++
tests/ref/fate/jxl-parse-container | 6 ++++++
3 files changed, 22 insertions(+)
create mode 100644 tests/ref/fate/jxl-parse-codestream
create mode 100644 tests/ref/fate/jxl-parse-container
diff --git a/tests/fate/image.mak b/tests/fate/image.mak
index 573d398915..15b6145c58 100644
--- a/tests/fate/image.mak
+++ b/tests/fate/image.mak
@@ -357,6 +357,16 @@ FATE_JPEGLS-$(call DEMDEC, IMAGE2, JPEGLS) += $(FATE_JPEGLS)
FATE_IMAGE += $(FATE_JPEGLS-yes)
fate-jpegls: $(FATE_JPEGLS-yes)
+FATE_JPEGXL += fate-jxl-parse-codestream
+fate-jxl-parse-codestream: CMD = framecrc -i $(TARGET_SAMPLES)/jxl/belgium.jxl -c:v copy
+
+FATE_JPEGXL += fate-jxl-parse-container
+fate-jxl-parse-container: CMD = framecrc -i $(TARGET_SAMPLES)/jxl/lenna-256.jxl -c:v copy
+
+FATE_JPEGXL-$(call DEMDEC, IMAGE2) += $(FATE_JPEGXL)
+FATE_IMAGE += $(FATE_JPEGXL-yes)
+fate-jxl: $(FATE_JPEGXL-yes)
+
FATE_IMAGE-$(call DEMDEC, IMAGE2, QDRAW) += fate-pict
fate-pict: CMD = framecrc -i $(TARGET_SAMPLES)/quickdraw/TRU256.PCT -pix_fmt rgb24
diff --git a/tests/ref/fate/jxl-parse-codestream b/tests/ref/fate/jxl-parse-codestream
new file mode 100644
index 0000000000..b2fe5035ac
--- /dev/null
+++ b/tests/ref/fate/jxl-parse-codestream
@@ -0,0 +1,6 @@
+#tb 0: 1/25
+#media_type 0: video
+#codec_id 0: jpegxl
+#dimensions 0: 768x512
+#sar 0: 0/1
+0, 0, 0, 1, 32, 0xa2930a20
diff --git a/tests/ref/fate/jxl-parse-container b/tests/ref/fate/jxl-parse-container
new file mode 100644
index 0000000000..99233d612a
--- /dev/null
+++ b/tests/ref/fate/jxl-parse-container
@@ -0,0 +1,6 @@
+#tb 0: 1/25
+#media_type 0: video
+#codec_id 0: jpegxl
+#dimensions 0: 256x256
+#sar 0: 0/1
+0, 0, 0, 1, 8088, 0xbbfea9bd
--
2.35.1
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [FFmpeg-devel] [PATCH v9 1/5] avcodec/jpegxl: add Jpeg XL image codec and parser
2022-03-23 11:03 [FFmpeg-devel] [PATCH v9 1/5] avcodec/jpegxl: add Jpeg XL image codec and parser Leo Izen
` (3 preceding siblings ...)
2022-03-23 11:03 ` [FFmpeg-devel] [PATCH v9 5/5] fate/jpegxl: add Jpeg XL demux and parse FATE test Leo Izen
@ 2022-03-23 14:45 ` Andreas Rheinhardt
2022-03-28 21:59 ` Leo Izen
4 siblings, 1 reply; 16+ messages in thread
From: Andreas Rheinhardt @ 2022-03-23 14:45 UTC (permalink / raw)
To: ffmpeg-devel
Leo Izen:
> This commit adds support to libavcodec to read and parse
> encoded Jpeg XL images. Jpeg XL is intended to be an
> extended-life replacement to legacy mjpeg.
> ---
> MAINTAINERS | 2 +
> libavcodec/Makefile | 1 +
> libavcodec/codec_desc.c | 9 +
> libavcodec/codec_id.h | 1 +
> libavcodec/jpegxl.h | 43 ++
> libavcodec/jpegxl_parser.c | 941 +++++++++++++++++++++++++++++++++++++
> libavcodec/parsers.c | 1 +
> libavcodec/version.h | 2 +-
> 8 files changed, 999 insertions(+), 1 deletion(-)
> create mode 100644 libavcodec/jpegxl.h
> create mode 100644 libavcodec/jpegxl_parser.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 931cf4bd2c..2e0de9e224 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -188,6 +188,7 @@ Codecs:
> interplayvideo.c Mike Melanson
> jni*, ffjni* Matthieu Bouron
> jpeg2000* Nicolas Bertrand
> + jpegxl.h, jpegxl_parser.c Leo Izen
> jvdec.c Peter Ross
> lcl*.c Roberto Togni, Reimar Doeffinger
> libcelt_dec.c Nicolas George
> @@ -616,6 +617,7 @@ Haihao Xiang (haihao) 1F0C 31E8 B4FE F7A4 4DC1 DC99 E0F5 76D4 76FC 437F
> Jaikrishnan Menon 61A1 F09F 01C9 2D45 78E1 C862 25DC 8831 AF70 D368
> James Almer 7751 2E8C FD94 A169 57E6 9A7A 1463 01AD 7376 59E0
> Jean Delvare 7CA6 9F44 60F1 BDC4 1FD2 C858 A552 6B9B B3CD 4E6A
> +Leo Izen (thebombzen) B6FD 3CFC 7ACF 83FC 9137 6945 5A71 C331 FD2F A19A
> Loren Merritt ABD9 08F4 C920 3F65 D8BE 35D7 1540 DAA7 060F 56DE
> Lynne FE50 139C 6805 72CA FD52 1F8D A2FE A5F0 3F03 4464
> Michael Niedermayer 9FF2 128B 147E F673 0BAD F133 611E C787 040B 0FAB
> diff --git a/libavcodec/Makefile b/libavcodec/Makefile
> index fb8b0e824b..3723601b3d 100644
> --- a/libavcodec/Makefile
> +++ b/libavcodec/Makefile
> @@ -44,6 +44,7 @@ OBJS = ac3_parser.o \
> dv_profile.o \
> encode.o \
> imgconvert.o \
> + jpegxl_parser.o \
> jni.o \
> mathtables.o \
> mediacodec.o \
> diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
> index 81f3b3c640..1b82870aaa 100644
> --- a/libavcodec/codec_desc.c
> +++ b/libavcodec/codec_desc.c
> @@ -1863,6 +1863,15 @@ static const AVCodecDescriptor codec_descriptors[] = {
> .long_name = NULL_IF_CONFIG_SMALL("GEM Raster image"),
> .props = AV_CODEC_PROP_LOSSY,
> },
> + {
> + .id = AV_CODEC_ID_JPEGXL,
> + .type = AVMEDIA_TYPE_VIDEO,
> + .name = "jpegxl",
> + .long_name = NULL_IF_CONFIG_SMALL("JPEG XL"),
> + .props = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY |
> + AV_CODEC_PROP_LOSSLESS,
> + .mime_types= MT("image/jxl"),
> + },
>
> /* various PCM "codecs" */
> {
> diff --git a/libavcodec/codec_id.h b/libavcodec/codec_id.h
> index 3ffb9bd22e..dbc4f3a208 100644
> --- a/libavcodec/codec_id.h
> +++ b/libavcodec/codec_id.h
> @@ -308,6 +308,7 @@ enum AVCodecID {
> AV_CODEC_ID_SIMBIOSIS_IMX,
> AV_CODEC_ID_SGA_VIDEO,
> AV_CODEC_ID_GEM,
> + AV_CODEC_ID_JPEGXL,
>
> /* various PCM "codecs" */
> AV_CODEC_ID_FIRST_AUDIO = 0x10000, ///< A dummy id pointing at the start of audio codecs
> diff --git a/libavcodec/jpegxl.h b/libavcodec/jpegxl.h
> new file mode 100644
> index 0000000000..4f93c99687
> --- /dev/null
> +++ b/libavcodec/jpegxl.h
> @@ -0,0 +1,43 @@
> +/*
> + * JPEG XL 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 header
> + */
> +
> +#ifndef AVCODEC_JPEGXL_H
> +#define AVCODEC_JPEGXL_H
> +
> +#include <stdint.h>
> +
> +/* these are also used in avformat/img2dec.c */
> +#define FF_JPEGXL_CODESTREAM_SIGNATURE_LE 0x0aff
> +#define FF_JPEGXL_CODESTREAM_SIGNATURE_BE 0xff0a
> +#define FF_JPEGXL_CONTAINER_SIGNATURE_LE 0x204c584a0c000000
> +#define FF_JPEGXL_CONTAINER_SIGNATURE_BE 0x0000000c4a584c20
> +
> +/**
> + * @return 0 upon valid, nonzero upon some parse error
Better: return a value >= 0 if valid, < 0 upon error to future-proof this.
> + */
> +int avpriv_jpegxl_verify_codestream_header(void *avctx, uint8_t *buf, int buflen);
Should be const uint8_t *buf (your probe function added later will be
const-incorrect otherwise).
> +
> +#endif /* AVCODEC_JPEGXL_H */
> diff --git a/libavcodec/jpegxl_parser.c b/libavcodec/jpegxl_parser.c
> new file mode 100644
> index 0000000000..13d1b176cd
> --- /dev/null
> +++ b/libavcodec/jpegxl_parser.c
> @@ -0,0 +1,941 @@
> +/*
> + * JPEG XL parser
> + * 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 parser
> + */
> +
> +#include <inttypes.h>
> +#include <stdlib.h>
> +
> +#include "libavutil/error.h"
> +#include "libavutil/intreadwrite.h"
> +#include "libavutil/macros.h"
> +#include "libavutil/mem.h"
This header is unnecessary now.
> +
> +#define BITSTREAM_READER_LE
> +
> +#include "codec_id.h"
> +#include "config.h"
> +#include "get_bits.h"
> +#include "jpegxl.h"
> +#include "parser.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,
> +};
> +
> +enum JpegXLTransferFunction {
> + FF_JPEGXL_TF_709 = 1,
> + FF_JPEGXL_TF_UNKNOWN,
> + FF_JPEGXL_TF_LINEAR = 8,
> + FF_JPEGXL_TF_SRGB = 13,
> + FF_JPEGXL_TF_PQ = 16,
> + FF_JPEGXL_TF_DCI,
> + FF_JPEGXL_TF_HLG
> +};
> +
> +enum JpegXLRenderingIntent {
> + FF_JPEGXL_RI_PERCEPTUAL = 0,
> + FF_JPEGXL_RI_RELATIVE,
> + FF_JPEGXL_RI_SATURATION,
> + FF_JPEGXL_RI_ABSOLUTE
> +};
> +
> +typedef struct JpegXLExtraChannelInfo {
> + enum JpegXLExtraChannelType type;
> + uint32_t bits_per_sample;
> + uint32_t exp_bits_per_sample;
> + uint32_t dim_shift;
> + size_t name_len;
> + int alpha_associated;
> + float red;
> + float green;
> + float blue;
> + float solidity;
> + uint32_t cfa_channel;
> +} JpegXLExtraChannelInfo;
> +
> +typedef struct JpegXLHeader {
> + uint32_t width;
> + uint32_t height;
> + int orientation;
> + /* zero if not present */
> + uint32_t intrinsic_width;
> + uint32_t intrinsic_height;
> + uint32_t preview_width;
> + uint32_t preview_height;
> + /* BEGIN animation header */
> + uint32_t anim_tb_num;
> + uint32_t anim_tb_denom;
> + uint32_t anim_loop_count;
> + int anim_have_pts;
> + /* END animation header */
> +
> + uint32_t bits_per_sample;
> + uint32_t exp_bits_per_sample;
> +
> + int modular_16bit_buffers;
> +
> + uint32_t num_extra_channels;
> +
> + /*
> + * an array of extra channel info
> + * with length num_extra_channels
> + * this is not NULL-terminated
> + */
> + JpegXLExtraChannelInfo extra_channel_info[256];
> +
> + int xyb_encoded;
> +
> + /* BEGIN color encoding bundle */
> + int have_icc_profile;
> + enum JpegXLColorSpace color_space;
> + enum JpegXLWhitePoint white_point;
> + uint32_t white_ux;
> + uint32_t white_uy;
> + enum JpegXLPrimaries primaries;
> + uint32_t red_ux;
> + uint32_t red_uy;
> + uint32_t green_ux;
> + uint32_t green_uy;
> + uint32_t blue_ux;
> + uint32_t blue_uy;
> + /*
> + * if this is less than 1 << 24,
> + * then interpret it as a gamma value
> + * If this is greater than or equal to 1 << 24,
> + * then subtract 1 << 24 and interpret it as a
> + * an enum JpegXLTransferFunction
> + */
> + int have_gamma;
> + uint32_t transfer_function;
> + enum JpegXLRenderingIntent rendering_intent;
> + /* END color encoding bundle */
> +
> + /* BEGIN tone mapping bundle */
> + float intensity_target;
> + float min_nits;
> + int relative_to_max_display;
> + float linear_below;
> + /* END tone mapping bundle */
> +
> + /* each extension bit determines which extension matters */
> + uint64_t extensions;
> + uint64_t extension_bits[64];
> +
> + int default_transform;
> +
> + int have_opsin_inv;
> + float opsin_inverse_matrix[16];
> +
> + uint32_t cw_mask;
> + float up2_weight[15];
> + float up4_weight[55];
> + float up8_weight[210];
These are not used atm.
> +
> + /*
> + * this is not provided by the header,
> + * but rather, by the container
> + * raw Jpeg XL Codestreams are level 5
> + * the container can choose to up it to 10
> + */
> + int level;
> +
> +} JpegXLHeader;
> +
> +typedef struct JpegXLParseContext {
> + ParseContext pc;
> + GetBitContext gb;
> + int box_size;
> + uint32_t box_tag;
> + int box_index;
> + int level;
> + int container;
> + int found_codestream;
> +} JpegXLParseContext;
> +
> +#define jxl_bits(n) jpegxl_get_bits(NULL, jxlr, (n))
> +#define jxl_u32(c0, c1, c2, c3, u0, u1, u2, u3) jpegxl_u32(jxlr, \
> + (uint32_t[]){c0, c1, c2, c3}, (uint32_t[]){u0, u1, u2, u3})
const uint32_t[]
> +#define jxl_enum() jxl_u32(0, 1, 2, 18, 0, 0, 4, 6)
> +
> +#define jxl_parse_errv(type, ...) av_log(avctx, AV_LOG_DEBUG, \
> + "At position: %d, invalid " type "\n", jxlr->gb.index, __VA_ARGS__)
> +
> +#define jxl_parse_err(msg) jxl_parse_errv("%s", (msg))
> +
> +static int jpegxl_skip_boxes(void *avctx, JpegXLParseContext *jxlr)
> +{
> + uint64_t size = 0;
> + uint32_t tag = 0;
> + char tag_str[5];
> + int remaining;
> + while (1) {
> + if (jxlr->box_index < 0) {
> + if (jxlr->box_size > 1) {
> + size = jxlr->box_size / 8 + 8;
> + tag = jxlr->box_tag;
> + } else if (jxlr->box_size == 1) {
> + size = 1;
> + tag = jxlr->box_tag;
> + } else {
> + size = 0;
> + }
> + jxlr->box_index = 0;
> + } else {
> + size = 0;
> + }
> +
> + remaining = jxlr->gb.size_in_bits - jxlr->gb.index;
> + remaining -= remaining % 8;
If I am not mistaken, then the getbits reader should always be
byte-aligned at this point. So the second statement is redundant.
If you are not aligned, then I don't get this code: You are aligning
your remaining size as if you wanted to align the GetBitContext, but you
never align the GetBitContext.
> + if (remaining < 64)
> + goto box_eof;
> +
> + if (size == 0) {
> + tag = 0;
> + for (int k = 0; k < 4; k++)
> + size = (size << 8) | get_bits(&jxlr->gb, 8);
If I am right about the alignment, then you could just use AV_RB32 here
and below; and AV_RB64() for reading the 64-bit size, too.
> + remaining -= 32;
> + }
> + if (tag == 0) {
> + for (int k = 0; k < 4; k++)
> + tag = (tag << 8) | get_bits(&jxlr->gb, 8);
> + remaining -= 32;
> + }
> +
> + /* extra 64-bit size field */
> + if (size == 1) {
> + if (remaining < 64)
> + goto box_eof;
> + size = 0;
> + for (int k = 0; k < 8; k++)
> + size = (size << 8) | get_bits(&jxlr->gb, 8);
> + size -= 8;
Potential for wraparound here.
> + remaining -= 64;
> + }
> +
> + if (size > 0 && size < 9)
> + return 1; /* invalid ISOBMFF box size */
Really? A box with no payload is legal IIRC. The check should be "size < 8".
> +
> + /* box too big for GetBitContext */
> + /* size - 40 => (size - 8) - 32 */
> + if (size > 40 && size - 40 > INT_MAX / 8)
Where does the 32 come from? If you want to use
AV_INPUT_BUFFER_PADDING_SIZE, use it. (But probably get_bits.h should
define a maximum of the buffer sizes it can handle.)
Anyway, the above is equivalent to "size > INT_MAX / 8 - 40".
> + return 1;
> +
> + /* turn size into something the parser can use */
> + if (size > 0)
> + size = (size - 8) * 8;
> +
> + /* partial jxl codestream box */
> + if (tag == MKBETAG('j','x','l','p')) {
> + if (remaining < 32)
> + goto box_eof;
> + /* 32-bit box index, we ignore it */
> + skip_bits_long(&jxlr->gb, 32);
> + remaining -= 32;
> + break;
> + }
> + /* full jxl codestream box */
> + if (tag == MKBETAG('j','x','l','c'))
> + break;
> + /* jxl level box */
> + if (tag == MKBETAG('j','x','l','l')) {
> + if (size != 8)
> + return 1; /* illegal jxll box */
> + if (remaining < 8)
> + goto box_eof;
> + jxlr->level = get_bits(&jxlr->gb, 8);
> + remaining -= 8;
> + continue;
> + }
> + /* any other box is skipped at this point */
> + AV_WB32(tag_str, tag);
> + av_log(avctx, AV_LOG_VERBOSE, "skipping jxl container box: %s\n", tag_str);
1. tag_str is potentially not-zero terminated.
2. If tag_str contains a \0, it might get truncated; it would be better
to just report it as hex with %X or so.
3. And actually I don't think that this should be reported at all.
> +
> + /* zero size means -> eof, nothing more to skip */
> + if (size == 0)
> + break;
> +
> + if (size - 1 > remaining) {
> + skip_bits_long(&jxlr->gb, remaining);
> + size -= remaining;
> + goto box_eof;
> + }
> +
> + skip_bits_long(&jxlr->gb, size);
> + }
> +
> + jxlr->box_size = size;
> + jxlr->box_tag = tag;
> + return 0;
> +
> +box_eof:
> + jxlr->box_size = size;
> + jxlr->box_tag = tag;
> + return 2;
> +}
> +
> +/*
> + * get from 1-64 bits from a JpegXLParseContext
> + */
> +static uint64_t jpegxl_get_bits(void *avctx, JpegXLParseContext *jxlr, int bits)
> +{
> + if (jxlr->box_size) {
> + if (bits > jxlr->box_size) {
> + int remaining = jxlr->box_size;
> + uint64_t ret = jpegxl_get_bits(avctx, jxlr, remaining);
> + /* go to the next box */
> + int status = jpegxl_skip_boxes(avctx, jxlr);
> + if (status)
> + return 0;
> + ret |= jpegxl_get_bits(avctx, jxlr, bits - remaining) << remaining;
What guarantees that there is not a sequence of boxes with a payload of
1 byte, so that a single read can span more than two boxes?
And does the file format really allow to split the payload into
different boxes at arbitrary positions?
> + return ret;
> + }
> + jxlr->box_size -= bits;
> + }
> + return get_bits64(&jxlr->gb, bits);
As far as I can see, only skips exceed 32 bits.
> +}
> +
> +static uint32_t jpegxl_u32(JpegXLParseContext *jxlr,
> + 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;
> +}
> +
> +static uint64_t jpegxl_u64(JpegXLParseContext *jxlr)
> +{
> + uint64_t shift = 12, ret;
> + switch (jxl_bits(2)) {
> + case 0:
> + ret = 0;
> + break;
> + case 1:
> + ret = 1 + jxl_bits(4);
> + break;
> + case 2:
> + ret = 17 + jxl_bits(8);
> + break;
> + case 3:
> + ret = jxl_bits(12);
> + while (jxl_bits(1)) {
> + if (shift < 60) {
> + ret |= jxl_bits(8) << shift;
> + shift += 8;
> + } else {
> + ret |= jxl_bits(4) << shift;
> + break;
> + }
> + }
> + break;
> + }
> + return ret;
> +}
> +
> +static float jpegxl_f16(JpegXLParseContext *jxlr)
> +{
> + uint32_t mantissa = jxl_bits(10) << 13;
> + uint32_t biased_exponent = jxl_bits(5);
> + if (biased_exponent == 31)
> + mantissa |= 0xFF << 23;
> + else
> + mantissa |= ((biased_exponent - 15 + 127) & 0xFF) << 23;
> + return av_int2float(mantissa);
> +}
> +
> +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:
> + return 0; /* manual width */
> + }
> +}
> +
> +
> +static int jpegxl_parse_size_header(JpegXLParseContext *jxlr,
> + uint32_t *width, uint32_t *height)
> +{
> + uint32_t w, h;
> + if (jxl_bits(1)) {
> + /* small size header */
> + h = (jxl_bits(5) + 1) << 3;
> + w = jpegxl_width_from_ratio(h, jxl_bits(3));
> + if (!w)
> + w = (jxl_bits(5) + 1) << 3;
> + } else {
> + /* large size header */
> + h = 1 + jxl_u32(0, 0, 0, 0, 9, 13, 18, 30);
> + w = jpegxl_width_from_ratio(h, jxl_bits(3));
> + if (!w)
> + w = 1 + jxl_u32(0, 0, 0, 0, 9, 13, 18, 30);
> + }
> + *width = w, *height = h;
> + return 0;
Why does this pretend to be able to fail when it just can't?
> +}
> +
> +static int jpegxl_parse_preview_header(JpegXLParseContext *jxlr,
> + uint32_t *width, uint32_t *height)
> +{
> + uint32_t w, h;
> + if (jxl_bits(1)) {
> + /* coded height and width divided by eight */
> + h = jxl_u32(16, 32, 1, 33, 0, 0, 5, 9) << 3;
> + w = jpegxl_width_from_ratio(h, jxl_bits(3));
> + if (!w)
> + w = jxl_u32(16, 32, 1, 33, 0, 0, 5, 9) << 3;
> + } else {
> + /* full height and width coded */
> + h = jxl_u32(1, 65, 321, 1345, 6, 8, 10, 12);
> + w = jpegxl_width_from_ratio(h, jxl_bits(3));
> + if (!w)
> + w = jxl_u32(1, 65, 321, 1345, 6, 8, 10, 12);
> + }
> + *width = w, *height = h;
> + return 0;
> +}
> +
> +static int jpegxl_parse_animation_header(JpegXLParseContext *jxlr,
> + uint32_t *num, uint32_t *denom, uint32_t *count, int *have_pts)
> +{
> + *num = jxl_u32(100, 1000, 1, 1, 0, 0, 10, 30);
> + *denom = jxl_u32(1, 1001, 1, 1, 0, 0, 8, 10);
> + *count = jxl_u32(0, 0, 0, 0, 0, 3, 16, 32);
> + *have_pts = jxl_bits(1);
> + return 0;
> +}
> +
> +static int jpegxl_parse_bit_depth(JpegXLParseContext *jxlr,
> + uint32_t *depth, uint32_t *exp_depth)
> +{
> + if (jxl_bits(1)) {
> + /* float samples */
> + *depth = jxl_u32(32, 16, 24, 1, 0, 0, 0, 6);
> + *exp_depth = jxl_bits(4) + 1;
> + } else {
> + /* integer samples */
> + *depth = jxl_u32(8, 10, 12, 1, 0, 0, 0, 6);
> + *exp_depth = 0;
> + }
> + return 0;
> +}
> +
> +static int jpegxl_parse_extra_channel_info(JpegXLParseContext *jxlr,
> + JpegXLExtraChannelInfo *info, int level)
> +{
> + int status = 0;
> + int all_default = jxl_bits(1);
> +
> + if (!all_default) {
> + info->type = jxl_enum();
> + if (info->type > 63)
> + return 1; /* enum types cannot be 64+ */
> + status = jpegxl_parse_bit_depth(jxlr, &info->bits_per_sample, &info->exp_bits_per_sample);
> + if (status)
> + return status;
> + info->dim_shift = jxl_u32(0, 3, 4, 1, 0, 0, 0, 3);
> + /* max of name_len is 1071 = 48 + 2^10 - 1 */
> + info->name_len = jxl_u32(0, 0, 16, 48, 0, 4, 5, 10);
> + } else {
> + info->type = FF_JPEGXL_CT_ALPHA;
> + info->bits_per_sample = 8;
> + info->exp_bits_per_sample = 0;
> + }
> +
> + /* skip over the name as it is not used */
> + jxl_bits(8 * info->name_len);
> +
> + info->alpha_associated = !all_default && info->type == FF_JPEGXL_CT_ALPHA && jxl_bits(1);
> +
> + if (info->type == FF_JPEGXL_CT_SPOT_COLOR) {
> + info->red = jpegxl_f16(jxlr);
> + info->green = jpegxl_f16(jxlr);
> + info->blue = jpegxl_f16(jxlr);
> + info->solidity = jpegxl_f16(jxlr);
> + }
> +
> + if (info->type == FF_JPEGXL_CT_CFA)
> + info->cfa_channel = jxl_u32(1, 0, 3, 19, 0, 2, 4, 8);
> + else
> + info->cfa_channel = 1;
> +
> + if (info->type == FF_JPEGXL_CT_BLACK && level < 10)
> + return 1;
> +
> + return 0;
> +}
> +
> +/**
> + * Parse a JpegXL Codestream Header and read it into the argument Header
> + * @return 0 upon success, 1 upon error
> + */
> +static int jpegxl_parse_codestream_header(void *avctx,
> + JpegXLParseContext *jxlr,
> + JpegXLHeader *header)
> +{
> + int all_default, extra_fields = 0, status;
> +
> + /* signature check */
> + if (jxl_bits(16) != FF_JPEGXL_CODESTREAM_SIGNATURE_LE) {
> + av_log(avctx, AV_LOG_ERROR, "Failed JPEG XL Signature Check\n");
> + goto fail;
> + }
> +
> + status = jpegxl_parse_size_header(jxlr, &header->width, &header->height);
> + if (status) {
> + jxl_parse_err("size header");
> + goto fail;
> + }
> +
> + if (jxlr->level < 10) {
> + /* level 5 codestream */
> + if (header->width > (1 << 18) || header->height > (1 << 18)
> + || (header->width >> 4) * (header->height >> 4) > (1 << 20)) {
> + jxl_parse_err("width or height or both");
> + goto fail;
> + }
> + header->level = 5;
> + } else {
> + /* level 10 codestream */
> + if (header->width > (1 << 30) || header->height > (1 << 30)
> + || (header->width >> 14) * (header->height >> 14) > (1 << 12)) {
> + jxl_parse_err("width or height or both");
> + goto fail;
> + }
> + header->level = 10;
> + }
> +
> + all_default = jxl_bits(1);
> +
> + if (!all_default)
> + extra_fields = jxl_bits(1);
> +
> + if (extra_fields) {
> + header->orientation = jxl_bits(3);
> + if (header->orientation > 3)
> + FFSWAP(uint32_t, header->width, header->height);
> +
> + /* intrinstic size */
> + if (jxl_bits(1)) {
> + status = jpegxl_parse_size_header(jxlr, &header->intrinsic_width, &header->intrinsic_height);
> + if (status) {
> + jxl_parse_err("intrinstic size header");
> + goto fail;
> + }
> + }
> +
> + /* preview header */
> + if (jxl_bits(1)) {
> + status = jpegxl_parse_preview_header(jxlr,
> + &header->preview_width, &header->preview_height);
> + if (status) {
> + jxl_parse_err("preview header");
> + goto fail;
> + }
> + if (header->preview_width > 4096 || header->preview_height > 4096) {
> + jxl_parse_errv("preview header size %" PRIu32 ", %" PRIu32,
> + header->preview_width, header->preview_height);
> + goto fail;
> + }
> + }
> +
> + /* animation header */
> + if (jxl_bits(1)) {
> + status = jpegxl_parse_animation_header(jxlr,
> + &header->anim_tb_num, &header->anim_tb_denom,
> + &header->anim_loop_count, &header->anim_have_pts);
> + if (status) {
> + jxl_parse_err("animation header");
> + goto fail;
> + }
> + }
> +
> + }
> +
> + if (!all_default) {
> + status = jpegxl_parse_bit_depth(jxlr,
> + &header->bits_per_sample, &header->exp_bits_per_sample);
> + if (status) {
> + jxl_parse_err("bit depth header");
> + goto fail;
> + }
> +
> + header->modular_16bit_buffers = jxl_bits(1);
> +
> + if (!header->modular_16bit_buffers && header->level < 10) {
> + jxl_parse_err("modular 16bit buffers");
> + goto fail;
> + }
> +
> + header->num_extra_channels = jxl_u32(0, 1, 2, 1, 0, 0, 4, 12);
> + if (header->num_extra_channels > 256 ||
> + header->level < 10 && header->num_extra_channels > 4) {
> + jxl_parse_err("too many extra channels");
> + goto fail;
> + }
> + for (uint32_t i = 0; i < header->num_extra_channels; i++) {
> + status = jpegxl_parse_extra_channel_info(jxlr, &header->extra_channel_info[i], header->level);
> + if (status) {
> + jxl_parse_errv("extra channel number %" PRIu32, i);
> + goto fail;
> + }
> + }
> +
> + header->xyb_encoded = jxl_bits(1);
> +
> + if (jxl_bits(1)) {
> + /* all_default for color encoding */
> + header->have_icc_profile = 0;
> + header->color_space = FF_JPEGXL_CS_RGB;
> + header->white_point = FF_JPEGXL_WP_D65;
> + header->primaries = FF_JPEGXL_PR_SRGB;
> + header->transfer_function = (1 << 24) + FF_JPEGXL_TF_SRGB;
> + header->rendering_intent = FF_JPEGXL_RI_RELATIVE;
> + } else {
> + header->have_icc_profile = jxl_bits(1);
> + header->color_space = jxl_enum();
> + if (header->color_space > 63) {
> + jxl_parse_errv("color space enum %" PRIu32, header->white_point);
> + goto fail;
> + }
> + if (header->color_space != FF_JPEGXL_CS_XYB
> + && !header->have_icc_profile) {
> + header->white_point = jxl_enum();
> + if (header->white_point > 63) {
> + jxl_parse_errv("white point enum %" PRIu32, header->white_point);
> + goto fail;
> + }
> + } else {
> + header->white_point = FF_JPEGXL_WP_D65;
> + }
> + if (header->white_point == FF_JPEGXL_WP_CUSTOM) {
> + header->white_ux = jxl_u32(0, 524288, 1048576, 2097152, 19, 19, 20, 21);
> + header->white_uy = jxl_u32(0, 524288, 1048576, 2097152, 19, 19, 20, 21);
> + }
> + if (header->color_space != FF_JPEGXL_CS_XYB
> + && header->color_space != FF_JPEGXL_CS_GRAY
> + && !header->have_icc_profile) {
> + header->primaries = jxl_enum();
> + if (header->primaries > 63) {
> + jxl_parse_errv("primaries enum %" PRIu32, header->primaries);
> + goto fail;
> + }
> + } else {
> + header->primaries = FF_JPEGXL_PR_SRGB;
> + }
> + if (header->primaries == FF_JPEGXL_PR_CUSTOM) {
> + header->red_ux = jxl_u32(0, 524288, 1048576, 2097152, 19, 19, 20, 21);
> + header->red_uy = jxl_u32(0, 524288, 1048576, 2097152, 19, 19, 20, 21);
> + header->green_ux = jxl_u32(0, 524288, 1048576, 2097152, 19, 19, 20, 21);
> + header->green_uy = jxl_u32(0, 524288, 1048576, 2097152, 19, 19, 20, 21);
> + header->blue_ux = jxl_u32(0, 524288, 1048576, 2097152, 19, 19, 20, 21);
> + header->blue_uy = jxl_u32(0, 524288, 1048576, 2097152, 19, 19, 20, 21);
> + }
> + if (!header->have_icc_profile) {
> + if (jxl_bits(1)) {
> + /* this is gamma */
> + header->transfer_function = jxl_bits(24);
> + } else {
> + header->transfer_function = jxl_enum();
> + if (header->transfer_function > 63) {
> + jxl_parse_errv("transfer function enum %" PRIu32, header->transfer_function);
> + goto fail;
> + }
> + /*
> + * higher than the highest possible gamma value
> + * marks it as an enum isntead of gamma
> + */
> + header->transfer_function += 1 << 24;
> + }
> + header->rendering_intent = jxl_enum();
> + if (header->rendering_intent > 63) {
> + jxl_parse_errv("rendering intent enum %" PRIu32, header->rendering_intent);
> + goto fail;
> + }
> + } else {
> + header->transfer_function = (1 << 24) + FF_JPEGXL_TF_SRGB;
> + header->rendering_intent = FF_JPEGXL_RI_RELATIVE;
> + }
> + }
> +
> + /* intensity_target should be set to 255 without it,
> + * but it's unused
> + * lazy && works with this macro */
> + if (extra_fields && !jxl_bits(1)) {
> + /*
> + * these are 16-bit floats
> + * since these fields are not used at the moment,
> + * we skip 16 bits for each instead of calling
> + * jpegxl_f16(jxlr) and assigning
> + */
> + /* intensity target */
> + jxl_bits(16);
> + /* min nits */
> + jxl_bits(16);
> + /* relative to max display */
> + jxl_bits(1);
> + /* linear below */
> + jxl_bits(16);
> + }
> +
> + header->extensions = jpegxl_u64(jxlr);
> + if (header->extensions) {
> + for (int i = 0; i < 64; i++) {
> + if (header->extensions & (UINT64_C(1) << i))
> + header->extension_bits[i] = jpegxl_u64(jxlr);
> + }
> + }
> +
> + } else {
> + header->modular_16bit_buffers = 1;
> + header->xyb_encoded = 1;
> + }
> +
> + header->default_transform = jxl_bits(1);
> +
> + /* opsin_inverse_matrix skipped over because it is not used atm */
> + if (!header->default_transform && header->xyb_encoded && !jxl_bits(1)) {
> + header->have_opsin_inv = 1;
> + jxl_bits(16 * 16);
> + }
> +
> + if (!header->default_transform)
> + header->cw_mask = jxl_bits(3);
> +
> + /*
> + * up2_weight skipped over because it is not used atm
> + */
> + if (header->cw_mask & 1)
> + jxl_bits(16 * 15);
> +
> + /*
> + * up4_weight skipped over because it is not used atm
> + */
> + if (header->cw_mask & 2)
> + jxl_bits(16 * 55);
> +
> + /*
> + * up8_weight skipped over because it is not used atm
> + */
> + if (header->cw_mask & 4)
> + jxl_bits(16 * 210);
> +
> + /* zero pad to byte */
> + if (!header->have_icc_profile) {
> + int byte_remaining = 7 - (jxlr->gb.index - 1) % 8;
> + if (byte_remaining && jxl_bits(byte_remaining)) {
> + jxl_parse_err("zero padding to byte");
> + goto fail;
> + }
> + }
> +
> + /* bits consumed > buflen */
> + if (jxlr->gb.index > jxlr->gb.size_in_bits) {
> + jxl_parse_err("unexpected end of file");
> + goto fail;
> + }
> +
> + return 0;
> +
> +fail:
> + return 1;
> +}
> +
> +static int jpegxl_parse_header(void *avctx, JpegXLParseContext *jxlr, JpegXLHeader *header)
> +{
> + int status;
> + if (jxlr->container) {
> + status = jpegxl_skip_boxes(avctx, jxlr);
> + if (status)
> + return status;
> + }
> + return jpegxl_parse_codestream_header(avctx, jxlr, header);
> +}
> +
> +int avpriv_jpegxl_verify_codestream_header(void *avctx, uint8_t *buf, int buflen)
> +{
> + JpegXLParseContext jxlri = { 0 };
> + JpegXLHeader header = { 0 };
> + int status;
> + init_get_bits8(&jxlri.gb, buf, buflen);
> + jxlri.level = 5;
> + status = jpegxl_parse_codestream_header(avctx, &jxlri, &header);
> + return status;
> +}
> +
> +static enum AVPixelFormat jpegxl_header_get_pixfmt(JpegXLHeader *header)
> +{
> + int alpha = 0;
> + for (int i = 0; i < header->num_extra_channels; i++) {
> + if (header->extra_channel_info[i].type == FF_JPEGXL_CT_ALPHA) {
> + alpha = 1;
> + break;
> + }
> + }
> + if (header->color_space == FF_JPEGXL_CS_GRAY) {
> + if (header->bits_per_sample <= 8)
> + return alpha ? AV_PIX_FMT_YA8 : AV_PIX_FMT_GRAY8;
> + if (header->bits_per_sample > 16 || header->exp_bits_per_sample)
> + return alpha ? AV_PIX_FMT_NONE : AV_PIX_FMT_GRAYF32;
> + return alpha ? AV_PIX_FMT_YA16 : AV_PIX_FMT_GRAY16;
> + } else if (header->color_space == FF_JPEGXL_CS_RGB
> + || header->color_space == FF_JPEGXL_CS_XYB) {
> + if (header->bits_per_sample <= 8)
> + return alpha ? AV_PIX_FMT_RGBA : AV_PIX_FMT_RGB24;
> + if (header->bits_per_sample > 16 || header->exp_bits_per_sample)
> + return alpha ? AV_PIX_FMT_GBRAPF32 : AV_PIX_FMT_GBRPF32;
> + return alpha ? AV_PIX_FMT_RGBA64 : AV_PIX_FMT_RGB48;
> + }
> + return AV_PIX_FMT_NONE;
> +}
> +
> +static av_cold int jpegxl_parse_init(AVCodecParserContext *s1)
> +{
> + s1->pict_type = AV_PICTURE_TYPE_NONE;
> + return 0;
> +}
> +
> +static int jpegxl_parse(AVCodecParserContext *s1,
> + AVCodecContext *avctx,
> + const uint8_t **poutbuf, int *poutbuf_size,
> + const uint8_t *buf, int buf_size)
> +{
> + JpegXLParseContext *jxlr = s1->priv_data;
> + JpegXLHeader header = { 0 };
> + int next = END_NOT_FOUND, status = 0;
> + int i = 0;
> +
> + *poutbuf_size = 0;
> + *poutbuf = NULL;
> +
> + if (buf_size == 0 || s1->flags & PARSER_FLAG_COMPLETE_FRAMES) {
> + /* eof is a frame boundary */
> + next = buf_size;
> + } else if (!jxlr->pc.frame_start_found) {
> + /* look for stream signature */
> + uint64_t state64 = jxlr->pc.state64;
> + for (; i < buf_size; i++) {
> + state64 = (state64 << 8) | buf[i];
> + if ((state64 & 0xFFFF) == FF_JPEGXL_CODESTREAM_SIGNATURE_BE) {
> + i -= 1;
> + jxlr->pc.frame_start_found = 1;
> + jxlr->container = 0;
> + jxlr->level = 5;
> + break;
> + }
> + if (state64 == FF_JPEGXL_CONTAINER_SIGNATURE_BE) {
> + i -= 7;
> + jxlr->pc.frame_start_found = 1;
> + jxlr->container = 1;
> + jxlr->level = 5;
> + break;
> + }
> + }
> + jxlr->pc.state64 = state64;
> + }
> +
> + if (jxlr->pc.frame_start_found && !jxlr->found_codestream) {
> + init_get_bits8(&jxlr->gb, buf + i, buf_size - i);
> + status = jpegxl_parse_header(NULL, jxlr, &header);
> + if (status == 0) {
> + /* parsed successfully */
> + s1->pict_type = AV_PICTURE_TYPE_I;
> + s1->key_frame = 1;
> + s1->width = avctx->width = header.width;
> + s1->height = avctx->height = header.height;
> + s1->format = avctx->pix_fmt = jpegxl_header_get_pixfmt(&header);
> + }
> + if (status == 2)
> + /* need higher probesize */
> + jxlr->found_codestream = 0;
> + else
> + jxlr->found_codestream = 1;
> + }
> +
> + if (ff_combine_frame(&jxlr->pc, next, &buf, &buf_size) < 0) {
> + *poutbuf = NULL;
> + *poutbuf_size = 0;
> + return buf_size;
> + }
> +
> + jxlr->pc.frame_start_found = 0;
> +
> + *poutbuf = buf + i;
> + *poutbuf_size = buf_size - i;
Seems like the parser is discarding some data here (if i != 0).
> +
> + return next;
> +}
> +
> +const AVCodecParser ff_jpegxl_parser = {
> + .codec_ids = { AV_CODEC_ID_JPEGXL },
> + .priv_data_size = sizeof(JpegXLParseContext),
> + .parser_init = jpegxl_parse_init,
> + .parser_parse = jpegxl_parse,
> + .parser_close = ff_parse_close,
> +};
> diff --git a/libavcodec/parsers.c b/libavcodec/parsers.c
> index 6b40c18d80..18a40eceea 100644
> --- a/libavcodec/parsers.c
> +++ b/libavcodec/parsers.c
> @@ -52,6 +52,7 @@ extern const AVCodecParser ff_h264_parser;
> extern const AVCodecParser ff_hevc_parser;
> extern const AVCodecParser ff_ipu_parser;
> extern const AVCodecParser ff_jpeg2000_parser;
> +extern const AVCodecParser ff_jpegxl_parser;
> extern const AVCodecParser ff_mjpeg_parser;
> extern const AVCodecParser ff_mlp_parser;
> extern const AVCodecParser ff_mpeg4video_parser;
> diff --git a/libavcodec/version.h b/libavcodec/version.h
> index a744e7469f..26ee41eb1f 100644
> --- a/libavcodec/version.h
> +++ b/libavcodec/version.h
> @@ -29,7 +29,7 @@
>
> #include "version_major.h"
>
> -#define LIBAVCODEC_VERSION_MINOR 25
> +#define LIBAVCODEC_VERSION_MINOR 26
> #define LIBAVCODEC_VERSION_MICRO 100
>
> #define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [FFmpeg-devel] [PATCH v9 1/5] avcodec/jpegxl: add Jpeg XL image codec and parser
2022-03-23 14:45 ` [FFmpeg-devel] [PATCH v9 1/5] avcodec/jpegxl: add Jpeg XL image codec and parser Andreas Rheinhardt
@ 2022-03-28 21:59 ` Leo Izen
0 siblings, 0 replies; 16+ messages in thread
From: Leo Izen @ 2022-03-28 21:59 UTC (permalink / raw)
To: ffmpeg-devel
On 3/23/22 10:45, Andreas Rheinhardt wrote:
> Leo Izen:
> + /* any other box is skipped at this point */
>> + AV_WB32(tag_str, tag);
>> + av_log(avctx, AV_LOG_VERBOSE, "skipping jxl container box: %s\n", tag_str);
> 1. tag_str is potentially not-zero terminated.
> 2. If tag_str contains a \0, it might get truncated; it would be better
> to just report it as hex with %X or so.
> 3. And actually I don't think that this should be reported at all.
If I change the report level to AV_LOG_DEBUG and report it as hex, does
this work?
>> +static uint64_t jpegxl_get_bits(void *avctx, JpegXLParseContext *jxlr, int bits)
>> +{
>> + if (jxlr->box_size) {
>> + if (bits > jxlr->box_size) {
>> + int remaining = jxlr->box_size;
>> + uint64_t ret = jpegxl_get_bits(avctx, jxlr, remaining);
>> + /* go to the next box */
>> + int status = jpegxl_skip_boxes(avctx, jxlr);
>> + if (status)
>> + return 0;
>> + ret |= jpegxl_get_bits(avctx, jxlr, bits - remaining) << remaining;
> What guarantees that there is not a sequence of boxes with a payload of
> 1 byte, so that a single read can span more than two boxes?
>
> And does the file format really allow to split the payload into
> different boxes at arbitrary positions?
>
Nothing guarantees it. If it does, the second call to jpegxl_get_bits
will recurse. Since you can only request 64 bits at once and all jxlp
boxes are at least one byte of payload, this has worst-case-scenario of
8 calls for a 64 bits request. And unfortunately, it does allow the
payload to be split at arbitrary positions.
>> + *width = w, *height = h;
>> + return 0;
>Why does this pretend to be able to fail when it just can't?
I was going to move the size validity check to these, but I forgot. I
will do that next revision of the patch.
>> + *poutbuf = buf + i;
>> + *poutbuf_size = buf_size - i;
> Seems like the parser is discarding some data here (if i != 0).
That's the idea. It discards data that precedes the start of the frame.
Is it not supposed to do this?
Leo Izen (thebombzen)
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 16+ messages in thread