From patchwork Tue Nov 2 11:51:51 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Leo Izen X-Patchwork-Id: 31276 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a5e:a610:0:0:0:0:0 with SMTP id q16csp3891964ioi; Tue, 2 Nov 2021 04:53:05 -0700 (PDT) X-Google-Smtp-Source: ABdhPJxfX2sVLrRKvtjtufblrJopJC8qncPsbY/uZHis3ig//MAzJ21taRIDiMc/Ak+A/ofpNN8J X-Received: by 2002:a17:906:b00c:: with SMTP id v12mr44017873ejy.523.1635853985507; Tue, 02 Nov 2021 04:53:05 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1635853985; cv=none; d=google.com; s=arc-20160816; b=MrjS/uJkfpP0mUDPsYoALyWjykjjsOQqnhGZ460uRkkqXPEpnABaXjpHKfD7vGwuJt Wx3oh+oEay0lW1cE08r7jTdDS92yWXaNz9YE6b07LXh/imfBgmePVO0BJAtOT9HN/njt 9zvyq6pE9NcS4sBW2jZCOiB8J7R+ni6S96zKUHTLKS0ESpNKGHfPAnSqIag/YEZjwi5X xGbskYBLN+qblVsUTbjDueXxHO3sjlHTkLZ0FMB8NLszCbcYZdGTAiG/4S9QFNOJBqFu Z1IzZRMLJeH2BEsJs8L3lmDEe2dGUGZfEjPhQ9LShtIaVN+CxzTAxl2yGQeVAjXGLXV7 iwBw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:cc:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:mime-version:message-id:date:to:from :dkim-signature:delivered-to; bh=lzK+qM7rthYk4uQQf4JlayCjL+BFOGbvXNFIAdoRoLY=; b=K4FgVhRl9HdUIgtZ97fDhhIswmcSmA3szTsLu4YgH64hqQKE+zDtgxxXgEUdFE79QT F8CIDI+YG23y1RNhFljNLy4UWhDR3DcmVUjecyKTNKQa68hw8TWYyeriwfvZpYCRANJ3 QEavgFGWz+C+NJwrFA08SfQ2zZxRRUH/qsmD6yIMXnocrQ2RbtCDIUh3LrjGfXz7rzJB Ps2O8C6letNiCZKmm7llETBsNooIpB8ZlHs7vNB/t0Flh6FDpzlb5TmQqcbCq1P9VZU+ lZo/DzDvwdAvzVi7JEViiN8Dt2fqgzceVGPBpMVVnTmuWyJmzPi8nYricEJ97Snncbu7 gMGg== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20210112 header.b=nnBW1V1f; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id c18si3592198ejj.1.2021.11.02.04.53.04; Tue, 02 Nov 2021 04:53:05 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20210112 header.b=nnBW1V1f; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id AE1D6689A91; Tue, 2 Nov 2021 13:52:59 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-qk1-f175.google.com (mail-qk1-f175.google.com [209.85.222.175]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 411126804B8 for ; Tue, 2 Nov 2021 13:52:53 +0200 (EET) Received: by mail-qk1-f175.google.com with SMTP id ay20so4008983qkb.7 for ; Tue, 02 Nov 2021 04:52:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=/RQZNYkWxSqDyGkqpJhZL2CuNUBGygC8zQLJ2ys/4A0=; b=nnBW1V1fV3bxzMPRI7dYNLzX3C25BT3jrqxyH5RpK2WTwfUmvRKaTT74Yc2cA8KOHj e2DXjzyH3yHSV6Bwkmv9g96jN5XbpSSFh4M3DWF6iCKXOdYr1M5vbhbcrsMDlgDDtLqo xtl4K+piVOKgdsCqgIvjX662E3egH+6id2Zm/dMB/0w9q+/SJi745NSZglzs8rDou1l1 v7GiN5g9y0zsWH6USRgwMVrxxQuyUP2/dnzUhBqM8Q5An6OYPDbIT4axwravcPNMg5N0 W5f2hfhUQrruvs6FCNudmZf5rQS78u1mgBCSWZ+oV+S/Xd7LmHwFzm5XhMYiCjLl7ZxJ kvxw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=/RQZNYkWxSqDyGkqpJhZL2CuNUBGygC8zQLJ2ys/4A0=; b=jhQ0F1o3gLc2AIFPzG7mO6INyW6AHPoWyaRQ2glbEBgkyIQ/8O1lx+SwSGRMjEoChv RWi3E7JDT0SiV5sIUu8Yqj8ZmZfWTtiBTs/WzlyMYSu313vOADtKKN+ygVhfDq1IV7kd uHK6s9psTxKB5eE2DT29/+2/GEPFgFJT9dM7K/o2NUIetmKEUvS9WYwXnAtDuZb7jrWW gnx9o/LMonI1XYfxRk4bIxAAvvKl8hl5V2WzN20gVzs5Sgivc6ouqyiZhNOSN5X44BD+ oybSiV+mJFpUvkChJ9Z6KUHh74/iVzw7KzWcHc+jk7ar81Ve+SiMhlAlYDdrFQkGqiWT auiw== X-Gm-Message-State: AOAM530dFmWxy4HZghncNwUS55YharnARuX2MWrIuwGogl9rzoNMzG4x NWCp9nRSXyfpLkRrVomhlt9LShxhYs4nag== X-Received: by 2002:a05:620a:b4d:: with SMTP id x13mr28314668qkg.430.1635853971689; Tue, 02 Nov 2021 04:52:51 -0700 (PDT) Received: from gauss.local (c-68-41-54-207.hsd1.mi.comcast.net. [68.41.54.207]) by smtp.gmail.com with ESMTPSA id c16sm1107446qte.95.2021.11.02.04.52.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 02 Nov 2021 04:52:51 -0700 (PDT) From: Leo Izen To: ffmpeg-devel@ffmpeg.org Date: Tue, 2 Nov 2021 07:51:51 -0400 Message-Id: <20211102115152.132482-1-leo.izen@gmail.com> X-Mailer: git-send-email 2.33.1 MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v4 1/2] avcodec/libjxc: add JPEG XL decoding via libjxl X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Leo Izen Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: bB3rH2uypRQP Add support for JPEG XL decoding in libavcodec via the external library libjxl. --- MAINTAINERS | 2 + configure | 5 + doc/general_contents.texi | 7 + libavcodec/Makefile | 1 + libavcodec/allcodecs.c | 1 + libavcodec/codec_desc.c | 9 ++ libavcodec/codec_id.h | 1 + libavcodec/libjxl.c | 72 +++++++++ libavcodec/libjxl.h | 44 ++++++ libavcodec/libjxldec.c | 301 ++++++++++++++++++++++++++++++++++++++ libavcodec/version.h | 2 +- 11 files changed, 444 insertions(+), 1 deletion(-) 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 dcac46003e..2e34ebbad7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -193,6 +193,7 @@ Codecs: libcodec2.c Tomas Härdin libdirac* David Conrad libdavs2.c Huiwen Ren + libjxl*.c, libjxl.h Leo Izen libgsm.c Michel Bardiaux libkvazaar.c Arttu Ylä-Outinen libopenh264enc.c Martin Storsjo, Linjie Fu @@ -614,6 +615,7 @@ Gwenole Beauchesne 2E63 B3A6 3E44 37E2 017D 2704 53C7 6266 B153 99C4 Jaikrishnan Menon 61A1 F09F 01C9 2D45 78E1 C862 25DC 8831 AF70 D368 James Almer 7751 2E8C FD94 A169 57E6 9A7A 1463 01AD 7376 59E0 Jean Delvare 7CA6 9F44 60F1 BDC4 1FD2 C858 A552 6B9B B3CD 4E6A +Leo Izen (thebombzen) B6FD 3CFC 7ACF 83FC 9137 6945 5A71 C331 FD2F A19A Loren Merritt ABD9 08F4 C920 3F65 D8BE 35D7 1540 DAA7 060F 56DE Lynne FE50 139C 6805 72CA FD52 1F8D A2FE A5F0 3F03 4464 Michael Niedermayer 9FF2 128B 147E F673 0BAD F133 611E C787 040B 0FAB diff --git a/configure b/configure index 92610c7edc..b5fadebbb1 100755 --- a/configure +++ b/configure @@ -241,6 +241,7 @@ External library support: --enable-libiec61883 enable iec61883 via libiec61883 [no] --enable-libilbc enable iLBC de/encoding via libilbc [no] --enable-libjack enable JACK audio sound server [no] + --enable-libjxl enable JPEG XL decoding via libjxl [no] --enable-libklvanc enable Kernel Labs VANC processing [no] --enable-libkvazaar enable HEVC encoding via libkvazaar [no] --enable-liblensfun enable lensfun lens correction [no] @@ -1816,6 +1817,7 @@ EXTERNAL_LIBRARY_LIST=" libiec61883 libilbc libjack + libjxl libklvanc libkvazaar libmodplug @@ -3281,6 +3283,7 @@ libgsm_ms_decoder_deps="libgsm" libgsm_ms_encoder_deps="libgsm" libilbc_decoder_deps="libilbc" libilbc_encoder_deps="libilbc" +libjxl_decoder_deps="libjxl libjxl_threads" libkvazaar_encoder_deps="libkvazaar" libmodplug_demuxer_deps="libmodplug" libmp3lame_encoder_deps="libmp3lame" @@ -6429,6 +6432,8 @@ enabled libgsm && { for gsm_hdr in "gsm.h" "gsm/gsm.h"; do check_lib libgsm "${gsm_hdr}" gsm_create -lgsm && break; done || die "ERROR: libgsm not found"; } enabled libilbc && require libilbc ilbc.h WebRtcIlbcfix_InitDecode -lilbc $pthreads_extralibs +enabled libjxl && require_pkg_config libjxl "libjxl >= 0.7.0" jxl/decode.h JxlDecoderVersion && + require_pkg_config libjxl_threads "libjxl_threads >= 0.7.0" jxl/thread_parallel_runner.h JxlThreadParallelRunner enabled libklvanc && require libklvanc libklvanc/vanc.h klvanc_context_create -lklvanc enabled libkvazaar && require_pkg_config libkvazaar "kvazaar >= 0.8.1" kvazaar.h kvz_api_get enabled liblensfun && require_pkg_config liblensfun lensfun lensfun.h lf_db_new diff --git a/doc/general_contents.texi b/doc/general_contents.texi index df1692c8df..2778e20091 100644 --- a/doc/general_contents.texi +++ b/doc/general_contents.texi @@ -171,6 +171,13 @@ Go to @url{https://github.com/TimothyGu/libilbc} and follow the instructions for installing the library. Then pass @code{--enable-libilbc} to configure to enable it. +@section libjxl + +JPEG XL is an image format intended to fully replace legacy JPEG for an extended +period of life. See @url{https://jpegxl.info/} for more information, and see +@url{https://github.com/libjxl/libjxl} for the library source. You can pass +@code{--enable-libjxl} to configure in order enable the libjxl wrapper. + @section libvpx FFmpeg can make use of the libvpx library for VP8/VP9 decoding and encoding. diff --git a/libavcodec/Makefile b/libavcodec/Makefile index 14fbd2ecbc..3514279836 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -1035,6 +1035,7 @@ OBJS-$(CONFIG_LIBGSM_MS_DECODER) += libgsmdec.o OBJS-$(CONFIG_LIBGSM_MS_ENCODER) += libgsmenc.o OBJS-$(CONFIG_LIBILBC_DECODER) += libilbc.o OBJS-$(CONFIG_LIBILBC_ENCODER) += libilbc.o +OBJS-$(CONFIG_LIBJXL_DECODER) += libjxldec.o libjxl.o OBJS-$(CONFIG_LIBKVAZAAR_ENCODER) += libkvazaar.o OBJS-$(CONFIG_LIBMP3LAME_ENCODER) += libmp3lame.o OBJS-$(CONFIG_LIBOPENCORE_AMRNB_DECODER) += libopencore-amr.o diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index 9ede09be17..a6ebd7593b 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -743,6 +743,7 @@ extern const AVCodec ff_libgsm_ms_encoder; extern const AVCodec ff_libgsm_ms_decoder; extern const AVCodec ff_libilbc_encoder; extern const AVCodec ff_libilbc_decoder; +extern const AVCodec ff_libjxl_decoder; extern const AVCodec ff_libmp3lame_encoder; extern const AVCodec ff_libopencore_amrnb_encoder; extern const AVCodec ff_libopencore_amrnb_decoder; diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c index 0974ee03de..c21cd12ab8 100644 --- a/libavcodec/codec_desc.c +++ b/libavcodec/codec_desc.c @@ -113,6 +113,15 @@ static const AVCodecDescriptor codec_descriptors[] = { .props = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY | AV_CODEC_PROP_LOSSLESS, }, + { + .id = AV_CODEC_ID_JPEGXL, + .type = AVMEDIA_TYPE_VIDEO, + .name = "jpegxl", + .long_name = NULL_IF_CONFIG_SMALL("JPEG XL"), + .props = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY | + AV_CODEC_PROP_LOSSLESS, + .mime_types= MT("image/jxl"), + }, { .id = AV_CODEC_ID_MPEG4, .type = AVMEDIA_TYPE_VIDEO, diff --git a/libavcodec/codec_id.h b/libavcodec/codec_id.h index ab265ec584..853bbd4240 100644 --- a/libavcodec/codec_id.h +++ b/libavcodec/codec_id.h @@ -59,6 +59,7 @@ enum AVCodecID { AV_CODEC_ID_LJPEG, AV_CODEC_ID_SP5X, AV_CODEC_ID_JPEGLS, + AV_CODEC_ID_JPEGXL, AV_CODEC_ID_MPEG4, AV_CODEC_ID_RAWVIDEO, AV_CODEC_ID_MSMPEG4V1, diff --git a/libavcodec/libjxl.c b/libavcodec/libjxl.c new file mode 100644 index 0000000000..6e7f69c66a --- /dev/null +++ b/libavcodec/libjxl.c @@ -0,0 +1,72 @@ +/* + * JPEG XL de/encoding via libjxl, common support implementation + * Copyright (c) 2021 Leo Izen + * + * 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 +#include "libjxl.h" + +size_t ff_libjxl_get_threadcount(int threads) +{ + if (threads > FF_LIBJXL_THREADS_MAX) + return FF_LIBJXL_THREADS_MAX; + if (threads <= 0) + return av_cpu_count(); + if (threads == 1) + return 0; + return threads; +} + +/** + * 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 *ff_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 ff_libjxl_av_free(void *opaque, void *address) +{ + av_free(address); +} + +void ff_libjxl_init_memory_manager(JxlMemoryManager *manager) +{ + manager->opaque = NULL; + manager->alloc = &ff_libjxl_av_malloc; + manager->free = &ff_libjxl_av_free; +} diff --git a/libavcodec/libjxl.h b/libavcodec/libjxl.h new file mode 100644 index 0000000000..84db500bb3 --- /dev/null +++ b/libavcodec/libjxl.h @@ -0,0 +1,44 @@ +/* + * JPEG XL de/encoding via libjxl, common support header + * Copyright (c) 2021 Leo Izen + * + * 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 + +#define FF_LIBJXL_THREADS_MAX ((1 << 14) - 1) + +/** + * Transform threadcount in ffmpeg to one used by libjxl. + * + * @param threads ffmpeg's threads AVOption + * @return thread count for libjxl's parallel runner + */ +size_t ff_libjxl_get_threadcount(int threads); + +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..9bb536ba91 --- /dev/null +++ b/libavcodec/libjxldec.c @@ -0,0 +1,301 @@ +/* + * JPEG XL decoding support via libjxl + * Copyright (c) 2021 Leo Izen + * + * 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 "internal.h" + +#include +#include +#include "libjxl.h" + +typedef struct LibJxlDecodeContext { + AVClass *class; + void *runner; + int ff_threads; + JxlDecoder *decoder; + JxlBasicInfo basic_info; + JxlPixelFormat jxl_pixfmt; + JxlDecoderStatus events; +} 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; + if (JxlDecoderSubscribeEvents(ctx->decoder, ctx->events) != JXL_DEC_SUCCESS) { + av_log(avctx, AV_LOG_ERROR, "Error subscribing to JXL events"); + return AVERROR_EXTERNAL; + } + + if (JxlDecoderSetParallelRunner(ctx->decoder, JxlThreadParallelRunner, ctx->runner) != JXL_DEC_SUCCESS) { + av_log(avctx, AV_LOG_ERROR, "Failed to set JxlThreadParallelRunner"); + 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"); + return AVERROR_EXTERNAL; + } + + ctx->runner = JxlThreadParallelRunnerCreate(&manager, ff_libjxl_get_threadcount(ctx->ff_threads)); + if (!ctx->runner) { + av_log(avctx, AV_LOG_ERROR, "Failed to create JxlThreadParallelRunner"); + 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_LITTLE_ENDIAN; + format->num_channels = basic_info->num_color_channels + (basic_info->alpha_bits > 0); + /* av_malloc handles alignment already */ + format->align = 1; + /* Gray */ + if (basic_info->num_color_channels == 1) { + if (basic_info->bits_per_sample <= 8) { + format->data_type = JXL_TYPE_UINT8; + return basic_info->alpha_bits ? AV_PIX_FMT_YA8 : AV_PIX_FMT_GRAY8; + } + if (basic_info->exponent_bits_per_sample || basic_info->bits_per_sample > 16) { + if (basic_info->alpha_bits) + return AV_PIX_FMT_NONE; + format->data_type = JXL_TYPE_FLOAT; + return AV_PIX_FMT_GRAYF32LE; + } + format->data_type = JXL_TYPE_UINT16; + return basic_info->alpha_bits ? AV_PIX_FMT_YA16LE : AV_PIX_FMT_GRAY16LE; + } + /* rgb only */ + /* libjxl only supports packed RGB and gray output at the moment */ + if (basic_info->num_color_channels == 3) { + if (basic_info->bits_per_sample <= 8) { + format->data_type = JXL_TYPE_UINT8; + return basic_info->alpha_bits ? AV_PIX_FMT_RGBA : AV_PIX_FMT_RGB24; + } + if (basic_info->bits_per_sample > 16) + av_log(avctx, AV_LOG_WARNING, "Downsampling larger integer to 16-bit via libjxl\n"); + if (basic_info->exponent_bits_per_sample) + av_log(avctx, AV_LOG_WARNING, "Downsampling float to 16-bit integer via libjxl\n"); + format->data_type = JXL_TYPE_UINT16; + return basic_info->alpha_bits ? AV_PIX_FMT_RGBA64LE : AV_PIX_FMT_RGB48; + } + return AV_PIX_FMT_NONE; +} + +static void libjxl_row_fill(void *avframe, size_t x, size_t y, size_t num_pixels, const void *pixels) +{ + AVFrame *frame = avframe; + int bytes = av_get_padded_bits_per_pixel(av_pix_fmt_desc_get(frame->format)) / 8; + size_t offset = y * frame->linesize[0] + x * bytes; + memcpy(frame->data[0] + offset, pixels, num_pixels * bytes); +} + +static int libjxl_decode_frame(AVCodecContext *avctx, void *avframe, int *got_frame, AVPacket *avpkt) +{ + LibJxlDecodeContext *ctx = avctx->priv_data; + uint8_t *buf = avpkt->data; + size_t remaining = avpkt->size; + AVFrame *frame = avframe; + JxlDecoderStatus status; + int ff_status; + *got_frame = 0; + + for (;;) { + /* + * 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_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; + 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; + return 0; +} + +static const AVCodecDefault libjxl_decode_defaults[] = { + { "threads", "0" }, + { NULL }, +}; + +#define OFFSET(x) offsetof(LibJxlDecodeContext, x) +#define VD AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_DECODING_PARAM + +static const AVOption libjxl_decode_options[] = { + { "threads", "Number of worker threads", OFFSET(ff_threads), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, FF_LIBJXL_THREADS_MAX, VD, "threads" }, + { "auto", "automatic", 0, AV_OPT_TYPE_CONST, { .i64 = 0 }, 0, FF_LIBJXL_THREADS_MAX, VD, "threads" }, + { NULL }, +}; + +static const AVClass libjxl_decode_class = { + .class_name = "libjxl", + .item_name = av_default_item_name, + .option = libjxl_decode_options, + .version = LIBAVUTIL_VERSION_INT, +}; + +const AVCodec ff_libjxl_decoder = { + .name = "libjxl", + .long_name = NULL_IF_CONFIG_SMALL("libjxl JPEG XL decoder"), + .type = AVMEDIA_TYPE_VIDEO, + .id = AV_CODEC_ID_JPEGXL, + .priv_data_size = sizeof(LibJxlDecodeContext), + .init = libjxl_decode_init, + .decode = libjxl_decode_frame, + .close = libjxl_decode_close, + .capabilities = AV_CODEC_CAP_OTHER_THREADS, + .caps_internal = FF_CODEC_CAP_AUTO_THREADS | FF_CODEC_CAP_INIT_CLEANUP, + .defaults = libjxl_decode_defaults, + .priv_class = &libjxl_decode_class, + .wrapper_name = "libjxl", +}; diff --git a/libavcodec/version.h b/libavcodec/version.h index 74b8baa5f3..76af066d32 100644 --- a/libavcodec/version.h +++ b/libavcodec/version.h @@ -28,7 +28,7 @@ #include "libavutil/version.h" #define LIBAVCODEC_VERSION_MAJOR 59 -#define LIBAVCODEC_VERSION_MINOR 12 +#define LIBAVCODEC_VERSION_MINOR 13 #define LIBAVCODEC_VERSION_MICRO 100 #define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \ From patchwork Tue Nov 2 11:51:52 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Leo Izen X-Patchwork-Id: 31275 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a5e:a610:0:0:0:0:0 with SMTP id q16csp3892138ioi; Tue, 2 Nov 2021 04:53:18 -0700 (PDT) X-Google-Smtp-Source: ABdhPJwraOmact4V1i8uxf7r4jQtqWd0BmGDjFDfdtBij1sLNvWPdOqwCzBe+ba+eTrhBfMnVgn/ X-Received: by 2002:a05:6402:268a:: with SMTP id w10mr50342994edd.351.1635853998530; Tue, 02 Nov 2021 04:53:18 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1635853998; cv=none; d=google.com; s=arc-20160816; b=NpPNNvt3ld7rE5Wypw0VThGPrDizv5+qfYDo5ohNmFR9sNDnHeHcmK6Y11osHWGOtS V6jHuZK4G2JseffLus44CwcU+zOtURq+/+J3IJY3TzbV97TUJGZntphkSWFpVPzllD6m G8dQ/R7sA37PUGOWxtBRk8JdFvrn3LhEKqnTyYOLY2ZlnOzFMs5YESqKjp00GNxkslo7 8SbeiBoFF/OZ8MhQXemGY71400LzGGs5dfRHcruoD9YOcmICYdQ2Tw29zIme9aqWZuek rBnkE4gWrf+6OD2qRZTI7b9Rp6kaRY5aak9KCq5MbRptCXK1ksTVRgrC7QAb+gzJU0an ndyw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:cc:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:mime-version:references:in-reply-to :message-id:date:to:from:dkim-signature:delivered-to; bh=h1Rk3YMJd1ut4Oo0TU5xwx0xnd9jf+DTbho0LyBUoQg=; b=XpW/YFK/a1MijWcwX5S8FdB9WhBaUnMeTAu+bAGlQAFAH26IICHU6URT5cUf3szTgB cAdEGIOCE120bRmSFVJlfo/cQYQlzxgXgoVPlqtDs5VjAkonP/gxqPEXAEYyNu97wcsY 5Ph0N9OJwyePW2rXgE9hbwy9daRaoJe123FinJVoANBSL6FyHsjghsAMYCDrleBbRlqz BSprFn81Fbj0BRJqgR7IZK+mwHBphCDrv42hP0vARNthQmcfGBqTyDnDZ7Hk7r2SO63a gCLgFm/hNXvuJhdynvXT0Y591MyhXQwNg+73C+y67+5X0Wr3ybuqEm1MraamPmU5wI8l udvw== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20210112 header.b=XDWWWpLl; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id ho36si14692940ejc.607.2021.11.02.04.53.18; Tue, 02 Nov 2021 04:53:18 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20210112 header.b=XDWWWpLl; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id CEFC5689FB2; Tue, 2 Nov 2021 13:53:08 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-qt1-f182.google.com (mail-qt1-f182.google.com [209.85.160.182]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id DF285689FCE for ; Tue, 2 Nov 2021 13:53:02 +0200 (EET) Received: by mail-qt1-f182.google.com with SMTP id h14so17755670qtb.3 for ; Tue, 02 Nov 2021 04:53:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=5CNekyV+ryJSaVurmUiSloU+XDWs7qolD3g365QxWgk=; b=XDWWWpLljGi3i8Pm5ikdhvwmqZGbPdZlZ8l4finX9wai6FE0YqgUDgr/Po8c10qLYQ OfxvbVEtt1nT1CnWhLMs8l3HCRwhLtRs8nnxmIv1Og53bhjMJC7S5Mtq2OIkUtMfJIeV gZaUkIYjU1vzEU+Ahoml2gQZGq2rutGBIbDf7heO4c1WfiCbruotJ46AHOSwePrLQOGU ps/atTGPT+EUddy5SnnQ2Tmw+JcCFy21aJnNx0ke3Uw1HBFOh1nIiB4jT6EKFMPNpBN1 xs+rsispw6TQfeJTmK30LuiKv8vDcb+8uvXYmFW/gNlt4naTPy4hJBNRPItirjF3qPpf V0ug== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=5CNekyV+ryJSaVurmUiSloU+XDWs7qolD3g365QxWgk=; b=0I2wpEbfmiBMPOazJVnlHXuyHnuY250RS6jUfPkiQ5GBrKUzcPG886RuDamxLf2e6h sptCzLSIfT+2MQ7NBonJBvaivlZMbeJO0ejkt0jbC4Wkbw3g5M+6Rrq/+eqSnhjvtrrz nTFh9giOfFwGaAi1dpJYLEDl+EQrzDqUG96KFVkPhFUFtkJ1lcKHhMZnc6Nalw6C8H2L XbMxhnJXSRyiv9G4oS9NkKqbk14s2sSFn9Q01dDrxCacRjNTqjOHKg84tan1ydiSsL7l cDkIZl6SWCPzVJpPihy/Ph56iHProfcBy5jhyrdT1f3hThQQfj4fpQsrxImNuA47CawG NEqQ== X-Gm-Message-State: AOAM530bynu7G8XGYzhMR0A+AFf2B3l4kQfMO9/eJVqlqeiUcYoquZRb B7sw4HfesIDU5Ki+uKswZII9D8JvgV2kPA== X-Received: by 2002:ac8:5f8d:: with SMTP id j13mr37969436qta.108.1635853981603; Tue, 02 Nov 2021 04:53:01 -0700 (PDT) Received: from gauss.local (c-68-41-54-207.hsd1.mi.comcast.net. [68.41.54.207]) by smtp.gmail.com with ESMTPSA id c16sm1107446qte.95.2021.11.02.04.53.01 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 02 Nov 2021 04:53:01 -0700 (PDT) From: Leo Izen To: ffmpeg-devel@ffmpeg.org Date: Tue, 2 Nov 2021 07:51:52 -0400 Message-Id: <20211102115152.132482-2-leo.izen@gmail.com> X-Mailer: git-send-email 2.33.1 In-Reply-To: <20211102115152.132482-1-leo.izen@gmail.com> References: <20211102115152.132482-1-leo.izen@gmail.com> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v4 2/2] avformat/image2: add JPEG XL image2 demuxer and muxer X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Leo Izen Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: j8Ue4+R208JS Add JPEG XL image demuxer and muxer to image2 to allow JPEG XL files to be read/written by libavformat. --- libavformat/allformats.c | 1 + libavformat/img2.c | 1 + libavformat/img2dec.c | 336 +++++++++++++++++++++++++++++++++++++++ libavformat/img2enc.c | 6 +- libavformat/mov.c | 1 + 5 files changed, 342 insertions(+), 3 deletions(-) diff --git a/libavformat/allformats.c b/libavformat/allformats.c index cbfadcb639..fedce9493c 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -505,6 +505,7 @@ extern const AVInputFormat ff_image_gif_pipe_demuxer; extern const AVInputFormat ff_image_j2k_pipe_demuxer; extern const AVInputFormat ff_image_jpeg_pipe_demuxer; extern const AVInputFormat ff_image_jpegls_pipe_demuxer; +extern const AVInputFormat ff_image_jpegxl_pipe_demuxer; extern const AVInputFormat ff_image_pam_pipe_demuxer; extern const AVInputFormat ff_image_pbm_pipe_demuxer; extern const AVInputFormat ff_image_pcx_pipe_demuxer; diff --git a/libavformat/img2.c b/libavformat/img2.c index 4153102c92..d8751d66bf 100644 --- a/libavformat/img2.c +++ b/libavformat/img2.c @@ -31,6 +31,7 @@ const IdStrMap ff_img_tags[] = { { AV_CODEC_ID_MJPEG, "mpo" }, { AV_CODEC_ID_LJPEG, "ljpg" }, { AV_CODEC_ID_JPEGLS, "jls" }, + { AV_CODEC_ID_JPEGXL, "jxl" }, { AV_CODEC_ID_PNG, "png" }, { AV_CODEC_ID_PNG, "pns" }, { AV_CODEC_ID_PNG, "mng" }, diff --git a/libavformat/img2dec.c b/libavformat/img2dec.c index b535831e1c..5f48e0dde6 100644 --- a/libavformat/img2dec.c +++ b/libavformat/img2dec.c @@ -830,6 +830,341 @@ static int jpegls_probe(const AVProbeData *p) return 0; } +#define jxl_bits(bc) (tbits += (bc), ((p->buf_size + AVPROBE_PADDING_SIZE) * 8 < tbits) ? 0 : (AV_RL64(p->buf + (tbits-(bc))/8) >> ((tbits-(bc)) % 8)) & ~(~(uint64_t)0 << (bc))) + +static inline uint32_t ff_jxl_ratio(uint32_t h, uint32_t ratio){ + switch (ratio){ + case 1: + return h; + case 2: + return (uint32_t)(((uint64_t)h * 12) / 10); + case 3: + return (uint32_t)(((uint64_t)h * 4) / 3); + case 4: + return (uint32_t)(((uint64_t)h * 3) / 2); + case 5: + return (uint32_t)(((uint64_t)h * 16) / 9); + case 6: + return (uint32_t)(((uint64_t)h * 5) / 4); + case 7: + return (uint32_t)(((uint64_t)h * 2) / 1); + default: + /* width coded separately */ + return 0; + } +} + +static inline uint32_t ff_jxl_u32(const AVProbeData *p, size_t *tbitsp, uint32_t *c, uint32_t *u) +{ + size_t tbits; + uint32_t bits, ret; + tbits = *tbitsp; + bits = jxl_bits(2); + ret = c[bits]; + if (u[bits]) + ret += jxl_bits(u[bits]); + *tbitsp = tbits; + return ret; +} + +static inline uint64_t ff_jxl_u64(const AVProbeData *p, size_t *tbitsp) +{ + size_t tbits; + uint64_t bits, ret, shift = 12; + tbits = *tbitsp; + bits = jxl_bits(2); + switch (bits) { + 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; + } + *tbitsp = tbits; + return ret; +} + +static inline uint32_t ff_jxl_bitdepth(const AVProbeData *p, size_t *tbitsp){ + size_t tbits; + uint32_t ret; + tbits = *tbitsp; + if (jxl_bits(1)) { + /* float */ + ret = ff_jxl_u32(p, &tbits, (uint32_t[]){32, 16, 24, 1}, (uint32_t[]){0, 0, 0, 6}); + tbits += 4; + } else { + /* integer */ + ret = ff_jxl_u32(p, &tbits, (uint32_t[]){8, 10, 12, 1}, (uint32_t[]){0, 0, 0, 6}); + } + *tbitsp = tbits; + return ret; +} + +static size_t ff_jxl_size_header(const AVProbeData *p, size_t start_bits, uint32_t *w, uint32_t *h) +{ + size_t tbits; + uint32_t wr = 0, hr; + tbits = start_bits; + if (jxl_bits(1)) { + /* small size header */ + hr = (jxl_bits(5) + 1) << 3; + /* ratio */ + wr = ff_jxl_ratio(hr, jxl_bits(3)); + if (!wr){ + wr = (jxl_bits(5) + 1) << 3; + } + } else { + /* full size header */ + hr = 1 + ff_jxl_u32(p, &tbits, (uint32_t[]){0, 0, 0, 0}, (uint32_t[]){9, 13, 18, 30}); + /* ratio */ + wr = ff_jxl_ratio(hr, jxl_bits(3)); + if (!wr){ + wr = 1 + ff_jxl_u32(p, &tbits, (uint32_t[]){0, 0, 0, 0}, (uint32_t[]){9, 13, 18, 30}); + } + } + if (hr > (1 << 18) || wr > (1 << 18)) + /* + * raw JXL codestream files capped at level 5 + * a violation is a false positive + * level 5 mandates that w, h <= 2^18 + */ + return 0; + if ((hr >> 4) * (wr >> 4) > (1 << 20)) + /* + * (h >> 4) * (w >> 4) avoids uint32_t overflow + * level 5 also mandates that w * h <= 2^28 + */ + return 0; + *h = hr; + *w = wr; + return tbits - start_bits; +} + +static int jpegxl_probe(const AVProbeData *p) +{ + const uint8_t *b = p->buf; + uint32_t w, h; + int all_default, extra_fields = 0, xyb = 1; + size_t tbits = 16, bits, pbits, bonus; + uint64_t count; + + /* ISOBMFF-based container */ + /* 0x4a584c20 == "JXL " */ + if (AV_RB64(b) == 0x0000000c4a584c20) + return AVPROBE_SCORE_EXTENSION + 1; + /* Codestreams all start with 0xff0a */ + if (AV_RB16(b) != 0xff0a) + return 0; + pbits = ff_jxl_size_header(p, tbits, &w, &h); + if (pbits) + tbits += pbits; + else + /* invalid size header */ + return 0; + + /* all_default */ + all_default = jxl_bits(1); + if (!all_default) + extra_fields = jxl_bits(1); + + if (extra_fields) { + /* orientation */ + tbits += 3; + /* have intrinstic size */ + if (jxl_bits(1)) { + pbits = ff_jxl_size_header(p, tbits, &w, &h); + if (pbits) + tbits += pbits; + else + return 0; + } + /* have preview */ + if (jxl_bits(1)) { + /* div8 */ + bits = jxl_bits(1); + if (bits) + h = ff_jxl_u32(p, &tbits, (uint32_t[]){16, 32, 1, 33}, (uint32_t[]){0, 0, 5, 9}); + else + h = ff_jxl_u32(p, &tbits, (uint32_t[]){1, 65, 321, 1345}, (uint32_t[]){6, 8, 10, 12}); + if (h > 4096) + /* invalid for preview headers */ + return 0; + /* ratio */ + w = ff_jxl_ratio(h, jxl_bits(3)); + if (!w){ + if (bits) + w = ff_jxl_u32(p, &tbits, (uint32_t[]){16, 32, 1, 33}, (uint32_t[]){0, 0, 5, 9}); + else + w = ff_jxl_u32(p, &tbits, (uint32_t[]){1, 65, 321, 1345}, (uint32_t[]){6, 8, 10, 12}); + } + if (w > 4096) + /* invalid for preview headers */ + return 0; + } + /* have animation */ + if (jxl_bits(1)) { + /* timebase */ + ff_jxl_u32(p, &tbits, (uint32_t[]){100, 1000, 1, 1}, (uint32_t[]){0, 0, 10, 30}); + ff_jxl_u32(p, &tbits, (uint32_t[]){1, 1001, 1, 1}, (uint32_t[]){0, 0, 8, 10}); + /* loopcount */ + ff_jxl_u32(p, &tbits, (uint32_t[]){0, 0, 0, 0}, (uint32_t[]){0, 3, 16, 32}); + /* PTS present */ + tbits += 1; + } + } + + if (!all_default) { + /* bit depth */ + ff_jxl_bitdepth(p, &tbits); + /* modular 16-bit */ + tbits += 1; + /* extra channel count */ + count = ff_jxl_u32(p, &tbits, (uint32_t[]){0, 1, 2, 1}, (uint32_t[]){0, 0, 4, 12}); + for (int i = 0; i < count; i++){ + if (!jxl_bits(1)){ + /* type */ + bits = ff_jxl_u32(p, &tbits, (uint32_t[]){0, 1, 2, 18}, (uint32_t[]){0, 0, 4, 6}); + if (bits > 63) + /* enum types cannot be 64+ */ + return 0; + /* bit depth */ + ff_jxl_bitdepth(p, &tbits); + /* dimension shift */ + ff_jxl_u32(p, &tbits, (uint32_t[]){0, 3, 4, 1}, (uint32_t[]){0, 0, 0, 3}); + /* name len */ + pbits = ff_jxl_u32(p, &tbits, (uint32_t[]){0, 0, 16, 48}, (uint32_t[]){0, 4, 5, 10}); + /* name, UTF-8 (not parsing it) */ + tbits += 8 * pbits; + /* alpha channel */ + if (bits == 1) + /* alpha premultiplied */ + tbits += 1; + /* spot color channel */ + if (bits == 2) + /* RGB+S */ + tbits += 16 * 4; + /* color filter array channel */ + if (bits == 5) + ff_jxl_u32(p, &tbits, (uint32_t[]){1, 0, 3, 19}, (uint32_t[]){0, 2, 4, 8}); + } + } + xyb = jxl_bits(1); + /* color encoding */ + if (!jxl_bits(1)) { + /* icc profile */ + bonus = jxl_bits(1); + bits = ff_jxl_u32(p, &tbits, (uint32_t[]){0, 1, 2, 18}, (uint32_t[]){0, 0, 4, 6}); + if (bits > 63) + return 0; + if (!bonus){ + /* bits == 2 -> XYB color space */ + if (bits != 2) + /* white point */ + pbits = ff_jxl_u32(p, &tbits, (uint32_t[]){0, 1, 2, 18}, (uint32_t[]){0, 0, 4, 6}); + else + pbits = 1; + if (pbits > 63) + return 0; + /* custom white point */ + if (pbits == 2) { + for (int i = 0; i < 2; i++) + ff_jxl_u32(p, &tbits, (uint32_t[]){0, 524288, 1048576, 2097152}, (uint32_t[]){19, 19, 20, 21}); + } + /* bits == 1 -> grayscale */ + if (bits != 2 && bits != 1) + /* primaries */ + pbits = ff_jxl_u32(p, &tbits, (uint32_t[]){0, 1, 2, 18}, (uint32_t[]){0, 0, 4, 6}); + if (pbits > 63) + return 0; + /* custom primaries */ + if (pbits == 2){ + for (int i = 0; i < 6; i++) + ff_jxl_u32(p, &tbits, (uint32_t[]){0, 524288, 1048576, 2097152}, (uint32_t[]){19, 19, 20, 21}); + } + /* gamma is present */ + if (jxl_bits(1)){ + /* gamma */ + tbits += 24; + } else { + /* transfer function */ + if (ff_jxl_u32(p, &tbits, (uint32_t[]){0, 1, 2, 18}, (uint32_t[]){0, 0, 4, 6}) > 63) + return 0; + } + /* rendering intent */ + if (ff_jxl_u32(p, &tbits, (uint32_t[]){0, 1, 2, 18}, (uint32_t[]){0, 0, 4, 6}) > 63) + return 0; + } + } + } + + /* tone mapping */ + if (extra_fields){ + /* everything default */ + if (!jxl_bits(1)){ + /* three 16-bit fields and a bool */ + tbits += 16 * 3 + 1; + } + } + + /* extensions */ + if (!all_default){ + count = ff_jxl_u64(p, &tbits); + while (count){ + if (count & 1) + ff_jxl_u64(p, &tbits); + count >>= 1; + } + } + + /* default transform */ + bonus = jxl_bits(1); + if (!bonus && xyb){ + /* opsin inverse matrix */ + bits = jxl_bits(1); + if (!bits){ + /* 16 fields, 16 bits each */ + tbits += 16 * 16; + } + bits = jxl_bits(3); + } else { + bits = 0; + } + if (bits & 1) + tbits += 16 * 15; + if (bits & 2) + tbits += 16 * 55; + if (bits & 4) + tbits += 16 * 210; + bits = tbits % 8; + if (bits) + bits = jxl_bits(8 - bits); + if (bits) + /* header is zero padded to the byte */ + return 0; + + if (p->buf_size < (tbits - 1) / 8 + 2) + /* file too small */ + return 0; + return AVPROBE_SCORE_EXTENSION + 1; +} +#undef jxl_bits + static int pcx_probe(const AVProbeData *p) { const uint8_t *b = p->buf; @@ -1149,6 +1484,7 @@ IMAGEAUTO_DEMUXER(gif, AV_CODEC_ID_GIF) IMAGEAUTO_DEMUXER(j2k, AV_CODEC_ID_JPEG2000) IMAGEAUTO_DEMUXER(jpeg, AV_CODEC_ID_MJPEG) IMAGEAUTO_DEMUXER(jpegls, AV_CODEC_ID_JPEGLS) +IMAGEAUTO_DEMUXER(jpegxl, AV_CODEC_ID_JPEGXL) IMAGEAUTO_DEMUXER(pam, AV_CODEC_ID_PAM) IMAGEAUTO_DEMUXER(pbm, AV_CODEC_ID_PBM) IMAGEAUTO_DEMUXER(pcx, AV_CODEC_ID_PCX) diff --git a/libavformat/img2enc.c b/libavformat/img2enc.c index 62202de9f4..72e64da984 100644 --- a/libavformat/img2enc.c +++ b/libavformat/img2enc.c @@ -259,9 +259,9 @@ static const AVClass img2mux_class = { const AVOutputFormat ff_image2_muxer = { .name = "image2", .long_name = NULL_IF_CONFIG_SMALL("image2 sequence"), - .extensions = "bmp,dpx,exr,jls,jpeg,jpg,ljpg,pam,pbm,pcx,pfm,pgm,pgmyuv,png," - "ppm,sgi,tga,tif,tiff,jp2,j2c,j2k,xwd,sun,ras,rs,im1,im8,im24," - "sunras,xbm,xface,pix,y", + .extensions = "bmp,dpx,exr,jls,jpeg,jpg,jxl,ljpg,pam,pbm,pcx,pfm,pgm,pgmyuv," + "png,ppm,sgi,tga,tif,tiff,jp2,j2c,j2k,xwd,sun,ras,rs,im1,im8," + "im24,sunras,xbm,xface,pix,y", .priv_data_size = sizeof(VideoMuxData), .video_codec = AV_CODEC_ID_MJPEG, .write_header = write_header, diff --git a/libavformat/mov.c b/libavformat/mov.c index 57c67e3aac..3a23f5dad8 100644 --- a/libavformat/mov.c +++ b/libavformat/mov.c @@ -7437,6 +7437,7 @@ static int mov_probe(const AVProbeData *p) if (tag == MKTAG('f','t','y','p') && ( AV_RL32(p->buf + offset + 8) == MKTAG('j','p','2',' ') || AV_RL32(p->buf + offset + 8) == MKTAG('j','p','x',' ') + || AV_RL32(p->buf + offset + 8) == MKTAG('j','x','l',' ') )) { score = FFMAX(score, 5); } else {