From patchwork Fri Apr 1 00:20:02 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Leo Izen X-Patchwork-Id: 35123 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:c05:b0:7a:e998:b410 with SMTP id bw5csp376125pzb; Thu, 31 Mar 2022 17:21:37 -0700 (PDT) X-Google-Smtp-Source: ABdhPJxS7C/TO+/E80PCfSZxnkw/nESVAWrMXTp2vqE6lszqB7c6CDYlH1j/fHecoFpma4lMaMvo X-Received: by 2002:a17:907:9805:b0:6db:4c33:7883 with SMTP id ji5-20020a170907980500b006db4c337883mr7280778ejc.555.1648772497456; Thu, 31 Mar 2022 17:21:37 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1648772497; cv=none; d=google.com; s=arc-20160816; b=f93ygSzcRlXohPyXMQCNzCQgDtYmRBMYVW5HdT3lAzeADcCaZturyKtGKi0cniUNdD PGUo42mAiFfkLutt7+tT8eBEXBg0dzcY70icP9usZXBxuQaQo94/j8i1cLADBDWLp103 qsupSBwQmw9yBiYpjvLyYGOuybwr0RaRGjqJMozOaLobv/Cm+Rl2M/7i5QkbhIt4H49i /4g9CZhzBzw+POY+bCI1OGrR+R0jyXN8IPGII9q6I25gkMkdBs7xPkkkowR4HlCR6qaN BZeJbxmmOk5YhLEauQS00IY9lgX83ZV3nPCk13xmBtj1yvDwHY+GszvDTsdpK1bN8WQm gdJQ== 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=Ls21SRZgbJy034taWaNtsS0xxgdiLP7OsKEo3WkzrUI=; b=vlII4XauUHzuRsBFv9Ptsc1XlPgCEHRfXxWOH/Y4RsE/nDIvkVxeO3EUNV23/vnz6V sVX2572crDZ47SgO8sY6eUchNtCPyJFdLSwQjYYlcVl6ermD4tcN5GsDeXpj1ME3OWeh JozFLdA0HRXJ0KEN/sjjPWrmC497/VKm2VdvwBvPPKBMyqck30GNCTLQ58eVTnGLeGCd fIYffu/k7gGMnVtAaW2I1X0+WldFPSONWBJLEYkAhVcnWAYhManWwkCXm30b/z2+WbHV FFvXpqgI90WTQssCfA1MCRPmCblYp22YlGcDEsAqb8ienfOIbH0b1dtDL6mgb4UQCVRT ce2g== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20210112 header.b=HbrABISl; 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 v21-20020a1709062f1500b006df76385d73si624906eji.531.2022.03.31.17.21.36; Thu, 31 Mar 2022 17:21:37 -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=HbrABISl; 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 2341468B23C; Fri, 1 Apr 2022 03:21:03 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-qv1-f44.google.com (mail-qv1-f44.google.com [209.85.219.44]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id DA28368B281 for ; Fri, 1 Apr 2022 03:20:55 +0300 (EEST) Received: by mail-qv1-f44.google.com with SMTP id ke15so851958qvb.11 for ; Thu, 31 Mar 2022 17:20:55 -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=14HkstBua9d98YIQig8UzIXrqbFqjt6GV0QBHnwVV+g=; b=HbrABISlrdBHi74U/rL6p1udzzNb9hR9fGrQJn/zKnf0wwSVqO+fv+wm6T5cKpC7Oa HJsTm+yik/usA2UPWKo426FAiEdkT1u+16kkzwHGd4sFj+Fdm13eDjwuE6e5zMzlLENL GNpyzSbGLtaeZTZdFV7tLNmd9RT0kev7sBKjLHm1H8Uk6DnhIiVo+u11tPq2qam0oi+F UBQLEC8Udn3xjFgIEbB0LP/tF97zZVDZqvU7XCkkOoItBEHHxh5RM9h3By487whEhij5 kzmNhku8rY7borVlibTOkjXmgFYS/Btomg9hJTqTxOKVRViZricBdNXpin8wQTKdF0rh BwWA== 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=14HkstBua9d98YIQig8UzIXrqbFqjt6GV0QBHnwVV+g=; b=6+5jOT/4Lgyw6IiIHnmzdfV46zrwyyIhJxYYPlhba+vtwuy3TusSaPt4j2B3zznWeK DjOfsucZvxpY325xNnp8RJeRUkktnR+WHbNc5FDRVHmKz66gQ7cnT491vKLlHyeP69ED lqv5/5bTTgj4D/dqV5ceVqz3c/6to0hTnFMDK5+jLW3HqLtYBPHVzDL6JW2OGMLLfqK9 qD3Q3WeTqG5sBHiJYMvT/nVVhx0yi4UxJ4Ni3u0RhZ8TmEWTlNK/WS8xpRNgxSPndHiN Xk5E0+xnwvFVkYWr5thWttP5UEuOx1MhTyOkJwrLUyGsPLrqnzj+p+5VN5/kHqQm3h12 2eZQ== X-Gm-Message-State: AOAM532wQ/hKaFG7elp0Qtc8sTNuOR5pG/QN1wBn1a00gIKvdEGLt2ei jHcl0d9iUho2I0mYgF1XHVFdhA3a1k8= X-Received: by 2002:a05:6214:d07:b0:441:65d7:3a26 with SMTP id 7-20020a0562140d0700b0044165d73a26mr6342042qvh.29.1648772449349; Thu, 31 Mar 2022 17:20:49 -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 r17-20020a05620a299100b00680b43004bfsm602489qkp.45.2022.03.31.17.20.48 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 31 Mar 2022 17:20:49 -0700 (PDT) From: Leo Izen To: ffmpeg-devel@ffmpeg.org Date: Thu, 31 Mar 2022 20:20:02 -0400 Message-Id: <20220401002006.44582-2-leo.izen@gmail.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20220401002006.44582-1-leo.izen@gmail.com> References: <20220401002006.44582-1-leo.izen@gmail.com> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v11 1/5] avcodec/jpegxl: add Jpeg XL image codec and parser 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: zM+cYx17/oke 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 | 951 +++++++++++++++++++++++++++++++++++++ libavcodec/parsers.c | 1 + libavcodec/version.h | 2 +- 8 files changed, 1009 insertions(+), 1 deletion(-) create mode 100644 libavcodec/jpegxl.h create mode 100644 libavcodec/jpegxl_parser.c diff --git a/MAINTAINERS b/MAINTAINERS index 76e1332ad8..9ab08bad8e 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 @@ -617,6 +618,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..a0f266c4ff --- /dev/null +++ b/libavcodec/jpegxl.h @@ -0,0 +1,43 @@ +/* + * JPEG XL 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 header + */ + +#ifndef AVCODEC_JPEGXL_H +#define AVCODEC_JPEGXL_H + +#include + +/* 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 success, < 0 upon error + */ +int avpriv_jpegxl_verify_codestream_header(void *avctx, const uint8_t *buf, int buflen); + +#endif /* AVCODEC_JPEGXL_H */ diff --git a/libavcodec/jpegxl_parser.c b/libavcodec/jpegxl_parser.c new file mode 100644 index 0000000000..a3e393564c --- /dev/null +++ b/libavcodec/jpegxl_parser.c @@ -0,0 +1,951 @@ +/* + * JPEG XL parser + * 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 parser + */ + +#include +#include + +#include "libavutil/error.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/macros.h" + +#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; + /* + * to be added later when full parsing is implemented + * float opsin_inverse_matrix[16]; + */ + + uint32_t cw_mask; + /* + * to be added later when full parsing is implemented + * float up2_weight[15]; + * float up4_weight[55]; + * float up8_weight[210]; + */ + + /* + * 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_eof; + int level; + int container; + int found_codestream; + int box_error; +} JpegXLParseContext; + +#define jxl_bits(n) jpegxl_get_bits(avctx, jxlr, (n)) +#define jxl_u32(c0, c1, c2, c3, u0, u1, u2, u3) jpegxl_u32(avctx, jxlr, \ + (const uint32_t[]){c0, c1, c2, c3}, (const uint32_t[]){u0, u1, u2, u3}) +#define jxl_enum() jxl_u32(0, 1, 2, 18, 0, 0, 4, 6) +#define jxl_u64() jpegxl_u64(avctx, jxlr) +#define jxl_f16() jpegxl_f16(avctx, jxlr) + +#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; + int remaining; + while (1) { + if (jxlr->box_eof) { + 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_eof = 0; + } else { + size = 0; + } + + remaining = jxlr->gb.size_in_bits - jxlr->gb.index; + + if (size == 0) { + if (remaining < 32) + goto box_eof; + tag = 0; + size = AV_RB32(jxlr->gb.buffer + jxlr->gb.index / 8); + jxlr->gb.index += 32; + remaining -= 32; + } + if (tag == 0) { + if (remaining < 32) + goto box_eof; + tag = AV_RB32(jxlr->gb.buffer + jxlr->gb.index / 8); + jxlr->gb.index += 32; + remaining -= 32; + } + + /* extra 64-bit size field */ + if (size == 1) { + if (remaining < 64) + goto box_eof; + size = AV_RB64(jxlr->gb.buffer + jxlr->gb.index / 8); + jxlr->gb.index += 64; + if (size < 8) + return -1; /* invalid box size */ + size -= 8; + remaining -= 64; + } + + /* invalid ISOBMFF box size */ + if (size > 0 && size < 8) + return -1; + + /* box too big for GetBitContext */ + if (size > INT_MAX / 8 - AV_INPUT_BUFFER_PADDING_SIZE - 8) + return -1; + + /* empty payload, skip to the next box */ + if (size == 8) + continue; + + /* 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_log(avctx, AV_LOG_DEBUG, "skipping jxl container box: %08" PRIx32 "\n", tag); + + /* 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; + jxlr->box_eof = 1; + return -2; +} + +/* + * get from 1-32 bits (or skip more) from a JpegXLParseContext + */ +static uint32_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) { + jxlr->box_error = status; + return 0; + } + ret |= jpegxl_get_bits(avctx, jxlr, bits - remaining) << remaining; + return ret; + } + jxlr->box_size -= bits; + } + if (bits > 32) { + skip_bits_long(&jxlr->gb, bits - 32); + return get_bits_long(&jxlr->gb, 32); + } else { + return get_bits_long(&jxlr->gb, bits); + } +} + +static uint32_t jpegxl_u32(void *avctx, 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(void *avctx, 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(void *avctx, 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 */ + } +} + +/** parse a Jpeg XL Size Header + * @return >= 0 upon valid size, < 0 upon invalid size found + */ +static int jpegxl_parse_size_header(void *avctx, 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); + } + if (jxlr->level < 10) { + if (w > (1 << 18) || h > (1 << 18) || (w >> 4) * (h >> 4) > (1 << 20)) { + jxl_parse_err("width or height or both"); + return -1; + } + } else { + if (w > (1 << 30) || h > (1 << 30)|| (w >> 14) * (h >> 14) > (1 << 12)) { + jxl_parse_err("width or height or both"); + return -1; + } + } + + *width = w, *height = h; + return 0; +} + +static int jpegxl_parse_preview_header(void *avctx, 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); + } + if (w > 4096 || h > 4096) { + jxl_parse_errv("preview header size %" PRIu32 ", %" PRIu32, w, h); + return -1; + } + *width = w, *height = h; + return 0; +} + +static int jpegxl_parse_animation_header(void *avctx, 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(void *avctx, 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(void *avctx, JpegXLParseContext *jxlr, + JpegXLExtraChannelInfo *info, int level) +{ + int all_default = jxl_bits(1); + + if (!all_default) { + info->type = jxl_enum(); + if (info->type > 63) + return -1; /* enum types cannot be 64+ */ + jpegxl_parse_bit_depth(avctx, jxlr, &info->bits_per_sample, &info->exp_bits_per_sample); + 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 */ + if (info->name_len) + 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 = jxl_f16(); + info->green = jxl_f16(); + info->blue = jxl_f16(); + info->solidity = jxl_f16(); + } + + 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, < 0 upon error + */ +static int jpegxl_parse_codestream_header(void *avctx, + JpegXLParseContext *jxlr, + JpegXLHeader *header) +{ + int all_default, extra_fields = 0, ret; + + /* 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; + } + + /* codestream level */ + if (jxlr->level < 10) + header->level = 5; + else + header->level = 10; + + ret = jpegxl_parse_size_header(avctx, jxlr, &header->width, &header->height); + if (ret) { + jxl_parse_err("main size header"); + goto fail; + } + + 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 */ + /* this header is not validated against the level spec */ + if (jxl_bits(1)) + jpegxl_parse_size_header(avctx, jxlr, &header->intrinsic_width, &header->intrinsic_height); + + /* preview header */ + if (jxl_bits(1)) { + ret = jpegxl_parse_preview_header(avctx, jxlr, &header->preview_width, &header->preview_height); + if (ret) { + jxl_parse_err("preview header"); + goto fail; + } + } + + /* animation header */ + if (jxl_bits(1)) + jpegxl_parse_animation_header(avctx, jxlr, + &header->anim_tb_num, &header->anim_tb_denom, + &header->anim_loop_count, &header->anim_have_pts); + } + + if (!all_default) { + jpegxl_parse_bit_depth(avctx, jxlr, &header->bits_per_sample, &header->exp_bits_per_sample); + + 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++) { + ret = jpegxl_parse_extra_channel_info(avctx, jxlr, &header->extra_channel_info[i], header->level); + if (ret) { + 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 instead 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 = jxl_u64(); + if (header->extensions) { + for (int i = 0; i < 64; i++) { + if (header->extensions & (UINT64_C(1) << i)) + header->extension_bits[i] = jxl_u64(); + } + } + + } 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; + } + + if (jxlr->box_error) { + jxl_parse_err("box format failure occurred"); + goto fail; + } + + return 0; + +fail: + return -1; +} + +static int jpegxl_parse_header(void *avctx, JpegXLParseContext *jxlr, JpegXLHeader *header) +{ + int ret; + if (jxlr->container) { + ret = jpegxl_skip_boxes(avctx, jxlr); + if (ret) + return ret; + } + return jpegxl_parse_codestream_header(avctx, jxlr, header); +} + +int avpriv_jpegxl_verify_codestream_header(void *avctx, const uint8_t *buf, int buflen) +{ + JpegXLParseContext jxlri = { 0 }; + JpegXLHeader header = { 0 }; + int ret; + init_get_bits8(&jxlri.gb, buf, buflen); + jxlri.level = 5; + ret = jpegxl_parse_codestream_header(avctx, &jxlri, &header); + return ret; +} + +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 to parse farther */ + 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; + + 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, \ From patchwork Fri Apr 1 00:20:03 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Leo Izen X-Patchwork-Id: 35120 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:c05:b0:7a:e998:b410 with SMTP id bw5csp375952pzb; Thu, 31 Mar 2022 17:21:01 -0700 (PDT) X-Google-Smtp-Source: ABdhPJy69t67bLodS2N2INPMpiYBBE63OVKnWh1Af2Cir64Xx+FO8+aXNyPcX7LrGaGrhl6GxpYG X-Received: by 2002:a17:907:62a2:b0:6e0:e201:b94e with SMTP id nd34-20020a17090762a200b006e0e201b94emr7081198ejc.730.1648772461409; Thu, 31 Mar 2022 17:21:01 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1648772461; cv=none; d=google.com; s=arc-20160816; b=e4E4jAzpAoHWRNh3Ocm7Eji5KRd1xQeGAkxb2kXJ1FY91P+bnYhtx8snK+xZT0j5AP wbCbCONDDboMJbWCphG4jRpveInozCf7aTfr5IIT1NX/2mhg0e6WMbPtdPcz+BkXslnM 7Dmi/BbStd7fewJJfkPCm/7/aTC1r24en6IGSjTkiKD4M9aIKZpxQ0etL9Pfhj7LaOiO 3btsnTbXSfgfgXheEPRNKV2YQzNnZ7urdhk9UgI+oV2J60R2DYt+bPMnD3xSoK7vAdRX zzx1zGgETKjbm0nOKToSxwqn+a/rtXbWEwisVoAA4Fd/T10ncGqtJ8D8cnMo1iS49S8T shuQ== 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=8o/rxLSMsOvbLqpoQn+N6x6qvZeqfl7ZL+MJQb8V88M=; b=vqa6dUj8VcySP80xaFKneukV3wEVFU326MbakCxWd4hgYjExLbraXIaC+lqjN/YoAq OTUqgT7hljR1FztzSyb7c5rLyfJkBiaKKBPmcBenGQ5ieyTSSSL+3zOt6SU79CPreiKg 9yC/SET3deJsP96Z55hepDDcyCiSvutNmiT0zoL0AmGB8Ufn4M5icfSV9noJxFvcogb5 2GJZpzIXhSXpZgTdKYyqCFyGSrMNciWjT5mP2Myo8rq8BRT53NW6UyZQX17GtBFRHxuC KjzOYJmpPTtjKXWURhvyziZhp8tny49LwjBWdnKsa+WCa2MQY4atEEA3eQ5V62eCM4L0 m35g== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20210112 header.b=bx0DMIrm; 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 f26-20020a170906825a00b006df76385f33si624904ejx.979.2022.03.31.17.21.00; Thu, 31 Mar 2022 17:21:01 -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=bx0DMIrm; 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 30FBD68B229; Fri, 1 Apr 2022 03:20:57 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-qt1-f174.google.com (mail-qt1-f174.google.com [209.85.160.174]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 857AA68B1E2 for ; Fri, 1 Apr 2022 03:20:51 +0300 (EEST) Received: by mail-qt1-f174.google.com with SMTP id i4so961114qti.7 for ; Thu, 31 Mar 2022 17:20:51 -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=gxqcOcKHZPmPoc1AZG/aPIa3PQ83L6/tli6GD+XaEXo=; b=bx0DMIrmnauuwRyXLuCCv1AwHrDUUvQsrEpxM1Fi7p4qPhvNhqgxvPuOsVlIk9BeXz ZrW9L5l4wGItyr0lUnzQsjtzpoaylReHu9PXxajvj0Z6+CdyY9kU71qksyM3t1ihaf7h 6D5AbWw3gIJCg/H7MRiblxI8B3hw7AEPF49aWSBD7RpS5NSUrjc/mEU1cyWvrX/j+N0c KxTiX3CkMyqWQeEFLQyQpz8AbVPJmVveqxbKdJqZygD4RYCk6HJwrD9aDGc5pBYNN8oe 7zmpCndlFB++NN0+ayy436K4wcvovK5LUOLDrK5wyWE7JdpuLb4tliFp8adX+fkRojbF QG3Q== 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=gxqcOcKHZPmPoc1AZG/aPIa3PQ83L6/tli6GD+XaEXo=; b=bb/Kjicfrckahku8EpMMlU7sN7FrgJFzad/8rQVYN1ljPvraziVOzwCxmn8FqCXGUg DRDOeOWE4ikXp1XsHeoI2H91/wJU48WkxYLYxvWtLOJKb2vZ60Lry61G5231efdQL5XR tFjiuRHQecgiDj8mg/SvFkT5D3eYIhwXtaupSD8ayGPJzbN5elhjEDZTRW/vuR7bY7F0 icxK04HfQcPJZ52SMTaObvqja/bo1sP12qW3M/Er35LQs9xworb07JqzDfV4mqHEOGr7 USFP7ZKR4oVG3qtIQscUxwrdQ80iR+W8d6EEW696fCPxqg+K92YHnfaJHDHhNTxrN89e qgOw== X-Gm-Message-State: AOAM530gFgiLceWRZMxetA6h2vXmKph5j16DBMcelAEZjeNiK2CSftJG wD2OnltxpiW2aTKhk+S+RaovqtjDeB4= X-Received: by 2002:a05:622a:13c6:b0:2e2:2778:2ea5 with SMTP id p6-20020a05622a13c600b002e227782ea5mr6733416qtk.512.1648772449974; Thu, 31 Mar 2022 17:20:49 -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 r17-20020a05620a299100b00680b43004bfsm602489qkp.45.2022.03.31.17.20.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 31 Mar 2022 17:20:49 -0700 (PDT) From: Leo Izen To: ffmpeg-devel@ffmpeg.org Date: Thu, 31 Mar 2022 20:20:03 -0400 Message-Id: <20220401002006.44582-3-leo.izen@gmail.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20220401002006.44582-1-leo.izen@gmail.com> References: <20220401002006.44582-1-leo.izen@gmail.com> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v11 2/5] avcodec/libjxl: 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: bza8+SG1NBpN 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 | 301 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 434 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 9ab08bad8e..fd79234d23 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 e4d36aa639..969b13eba3 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 + * + * 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 <= 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 + * + * 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 + +/** + * 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..a7ffc775b3 --- /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/buffer.h" +#include "libavutil/common.h" +#include "libavutil/error.h" +#include "libavutil/mem.h" +#include "libavutil/pixdesc.h" +#include "libavutil/pixfmt.h" +#include "libavutil/frame.h" + +#include "avcodec.h" +#include "codec_internal.h" +#include "internal.h" + +#include +#include +#include "libjxl.h" + +typedef struct LibJxlDecodeContext { + void *runner; + JxlDecoder *decoder; + JxlBasicInfo basic_info; + JxlPixelFormat jxl_pixfmt; + JxlDecoderStatus events; + AVBufferRef *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 jret; + int ret; + *got_frame = 0; + + while (1) { + + jret = JxlDecoderSetInput(ctx->decoder, buf, remaining); + + if (jret == JXL_DEC_ERROR) { + /* this should never happen here unless there's a bug in libjxl */ + av_log(avctx, AV_LOG_ERROR, "Unknown libjxl decode error\n"); + return AVERROR_EXTERNAL; + } + + jret = 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(jret) { + 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; + } + ret = ff_set_dimensions(avctx, ctx->basic_info.xsize, ctx->basic_info.ysize); + if (ret < 0) + return ret; + /* + * 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"); + jret = JxlDecoderGetICCProfileSize(ctx->decoder, &ctx->jxl_pixfmt, JXL_COLOR_PROFILE_TARGET_ORIGINAL, &ctx->iccp_len); + if (jret == JXL_DEC_SUCCESS && ctx->iccp_len > 0) { + av_buffer_unref(&ctx->iccp); + av_assert2(!ctx->iccp); + ctx->iccp = av_buffer_alloc(ctx->iccp_len); + if (!ctx->iccp) + return AVERROR(ENOMEM); + jret = JxlDecoderGetColorAsICCProfile(ctx->decoder, &ctx->jxl_pixfmt, JXL_COLOR_PROFILE_TARGET_ORIGINAL, ctx->iccp->data, ctx->iccp_len); + if (jret != 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", jret == JXL_DEC_FRAME ? "FRAME" : "NEED_IMAGE_OUT_BUFFER"); + ret = ff_get_buffer(avctx, frame, 0); + if (ret < 0) + return ret; + 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_from_buf(frame, AV_FRAME_DATA_ICC_PROFILE, ctx->iccp); + if (!sd) + return AVERROR(ENOMEM); + /* ownership is transfered, and it is not ref-ed */ + ctx->iccp = NULL; + } + 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", jret); + 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; + av_buffer_unref(&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", +}; From patchwork Fri Apr 1 00:20:04 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Leo Izen X-Patchwork-Id: 35124 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:c05:b0:7a:e998:b410 with SMTP id bw5csp376200pzb; Thu, 31 Mar 2022 17:21:48 -0700 (PDT) X-Google-Smtp-Source: ABdhPJxPAkMXNE3/ndz6RA3x23j8/VGc/KPtZD/vWHLFQ2wbATXVq00zx4HQ3QAWDnd1obmewTFi X-Received: by 2002:a17:907:2cc2:b0:6df:f4ec:eebd with SMTP id hg2-20020a1709072cc200b006dff4eceebdmr7194872ejc.486.1648772507919; Thu, 31 Mar 2022 17:21:47 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1648772507; cv=none; d=google.com; s=arc-20160816; b=FffH2QFr7SbaB7i/IcL4Q0jrlmyMcQ2/TSnpjPKDWtb3XPDsvsryCFwucVjui05OLR RGFoj8YtAqWMlA5QKy9aWgb3mMPM9WSzvyjZV4c43d5wFmqIZoF60ZVYETqwzoXg3V8q M+OYnzGLF3LSTOk0SLi7VQQ+/4pw7OQoWEdmjDemtEj8IXofl+ehePjNyik8SE/BFonG 7PQFhSCkCo3TNgqE+WpyjRblUvM+E1pLkbi9zxoFKOyN81FXSJOMnNjy6rHNWfGy8wVi Cuk0ysAI4P4sKWD/Qf987a8bW2zP+vPNplU8iJ02e2gLFfP/jrpmulN8qLdd3VMGz8RA R2DQ== 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=lI8GHjL/cvMzc77/meBEkyuQvDmlrCYy3JFrPBqu35I=; b=zgGRNNDoZYSAmfysg6jRn7SE65/p3QZ52m6UFq0tDO89n4ZUCSFeInV/3uUIBN2oDV nngukgDfyqYmYnGA4/ghytExWn8rObqWV4JrfvjK9sIxhRuFARfSPC44dUdHOQtXEXqM 6EjO4dWf3+nHymtSRyQFbi/wnPqlfQ3b0G6QEzYgq4dIylKiYyBovy1Qr6zO5OQAj72+ PoZl3yDABkoY4Sw1h6zstn7ah23KQ5Hs9BJ2t8gdw0yBzzle1nrKFfifkRjGXrB99bIX 8j73924AfxbwFEL/QbDk50mJkZ0EYWslLofC6Tl/bxa/QP6q2wP7FKBKW0V0ioIYe9r5 Hswg== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20210112 header.b=d7qAVa26; 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 h3-20020a056402280300b004194da3ce89si773316ede.414.2022.03.31.17.21.47; Thu, 31 Mar 2022 17:21:47 -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=d7qAVa26; 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 2E20668B2B5; Fri, 1 Apr 2022 03:21:04 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-qv1-f43.google.com (mail-qv1-f43.google.com [209.85.219.43]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id C386968B298 for ; Fri, 1 Apr 2022 03:20:56 +0300 (EEST) Received: by mail-qv1-f43.google.com with SMTP id kk12so844042qvb.13 for ; Thu, 31 Mar 2022 17:20:56 -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=+YbZXl/vjjneTuVn/DQxTbZkImgwjavUmpdbLBDzMz0=; b=d7qAVa26t+RBGJNCax8nFSpBpkai2rqmpXpw/+W/oVe+YEyiH6zFwg805+EufHRUSy y4ovcXA3Z7bzcejGU/XyEoUeZrcZBKDOkF4U+x7eU2mLBSArRt7eVoeR2Jcwd4V4B83B gHk9X1u5OSTB6tR7Aznn6L/hx6631i3LqlvAadsPpOgwvHMrC+Z7UUNZXmx8deUeogDd AIaDTh9StsdszkZ1nlq9nGtJPJlD9YtfEPw48Ns9lW12AUuvxfY0UC/vuDAWapKOEaA4 qh2JxgLSwARQH1yt/X/GIbgWuvQwyDaZRF2gKdKUQsbkU2X2QCKYigxJRDGlSiIV1sn/ fWHw== 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=+YbZXl/vjjneTuVn/DQxTbZkImgwjavUmpdbLBDzMz0=; b=gHIDvx97wP9pifY8t+fHyX+onJkCY+3X797nTOXDpVljSwV9F1BBBQeKdebhmGyPlj tuKwHYyWLwTwqZ9UeIr/zvQRnwJCLWKrvrXnNg+Xy6m95Z/NkQc+6TIaFxDWPYkH6yZL 2eRf4F9Gek9SosI9tVL104aWYR+Eg+Ogda8y3JiiR36pGhoT6T32iQvFhWyrnVbQz1zp YAytQbSmzTmThXUVCCZqzI9KItHPX7kiEVBBG/eHETsOLG5vk2CnY5GzqlFFCbLBUJMu TY7xiw9j3SU7gLeB8NsMMeAN2phLMzlQFtv8H1dk54I/RPD2HqaLt6gIDiNwCkfwLR0S W37w== X-Gm-Message-State: AOAM531pkJawrG9xO40x6QmEv4Sy7GzhIzjdshCU42US+eXNBi8tydBB PAPCL81ytaRNSVeqqsQBhoYUabvvs8c= X-Received: by 2002:a05:6214:4103:b0:440:e4d1:a2a0 with SMTP id kc3-20020a056214410300b00440e4d1a2a0mr38053896qvb.42.1648772450629; Thu, 31 Mar 2022 17:20:50 -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 r17-20020a05620a299100b00680b43004bfsm602489qkp.45.2022.03.31.17.20.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 31 Mar 2022 17:20:50 -0700 (PDT) From: Leo Izen To: ffmpeg-devel@ffmpeg.org Date: Thu, 31 Mar 2022 20:20:04 -0400 Message-Id: <20220401002006.44582-4-leo.izen@gmail.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20220401002006.44582-1-leo.izen@gmail.com> References: <20220401002006.44582-1-leo.izen@gmail.com> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v11 3/5] avcodec/libjxl: add Jpeg XL encoding 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: U/y+u5/2VYXR 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 | 379 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 383 insertions(+), 1 deletion(-) create mode 100644 libavcodec/libjxlenc.c diff --git a/configure b/configure index 969b13eba3..85a1a8b53c 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..deacc0f1f8 --- /dev/null +++ b/libavcodec/libjxlenc.c @@ -0,0 +1,379 @@ +/* + * JPEG XL encoding 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 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 +#include +#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; + size_t new_size = ctx->buffer_size * 2; + temp = av_realloc(ctx->buffer, new_size); + if (!temp) + return AVERROR(ENOMEM); + ctx->buffer = temp; + ctx->buffer_size = new_size; + next_out = ctx->buffer + bytes_written; + available = new_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; + + av_freep(&ctx->buffer); + + 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", +}; From patchwork Fri Apr 1 00:20:05 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Leo Izen X-Patchwork-Id: 35121 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:c05:b0:7a:e998:b410 with SMTP id bw5csp375998pzb; Thu, 31 Mar 2022 17:21:10 -0700 (PDT) X-Google-Smtp-Source: ABdhPJwK8cKCF5nF9lXVS3IHYLNLrppTgd8x7ZfCUZGxamjW4AtGaQffie2IzI/lrh8u5MG9Bjoo X-Received: by 2002:a17:907:2d89:b0:6df:b7d1:a365 with SMTP id gt9-20020a1709072d8900b006dfb7d1a365mr7681401ejc.386.1648772470480; Thu, 31 Mar 2022 17:21:10 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1648772470; cv=none; d=google.com; s=arc-20160816; b=KWTztWYvUprFzbvj/9Ns64RuielQcNl0rKce4ZS7CHSxr0i+uRPzOw8YdCyRr/sl89 9YETz4PgxO+yJR5B5BRp+9zGGNPWMYwdsX/W45pR+KAqbftRlbhEWGx+YRzR4fpHJV/z kaDbnXbuBhevkPtTPu32RksX7ghxXyerfUMcc4iQy9FOrkOb8vNVqDTuz9G6wZ64eZug WLdaD1x5RK++WAMFnKLbGcQL8ZoR15vtxwe8AlM8DJS8c5+YEkiavFU07TW2wZDnlYXQ 0efdk9eWJOKb9dA+ISL2ORouEmuDUXwMoMdJHzPCo9lSPzqsm9vT6molZabsPZbCQsQp ywqw== 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=LFnTG8jnTzvUhxvoEb6sbo7iGGgPyEm08PRDf/ExFxE=; b=HB1pzeTdaV9EhECz039DU+cXpFFf2KOBISneXVz2mGIEzAzywSSKlQbpKP6uqn5S+x clUWZgdNqBJatQ3L4valceUjk2sXvyLURKjL+hPXJqAj2aOGy5EjLNA9IC3rBhwBPKky AJhEGebwwzWlVtCWWYxi5zZ0HTUx7gN+T0UeNER86GCjDx+gsSnhM2n4gYxs837jRC6U 9B5BklXCYdi7L3Hlk3qA/VQcrDhiJD8KfkCp91oLhKDRC54dbbo5KAoN3O4WlgBZ2hUk b9HX0yDc+kkePrfXzQ+Gu7WAfGlbGDBqaAp3laOU/z/0En/XqA9MNVzeGVYWY8LWTJt8 CPJA== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20210112 header.b=Wb+24cSP; 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 d8-20020a170906640800b006df76385c64si620973ejm.260.2022.03.31.17.21.10; Thu, 31 Mar 2022 17:21:10 -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=Wb+24cSP; 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 40AA968B254; Fri, 1 Apr 2022 03:20:59 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-qt1-f172.google.com (mail-qt1-f172.google.com [209.85.160.172]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id A398468ADF2 for ; Fri, 1 Apr 2022 03:20:52 +0300 (EEST) Received: by mail-qt1-f172.google.com with SMTP id s11so980538qtc.3 for ; Thu, 31 Mar 2022 17:20:52 -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=HDGK9WpScVeMtha6VzxaHMU8npX6OpsqVD5LqpzxJKY=; b=Wb+24cSP/s/EsluUVFPPbvmOmoReQ/fiq+RcRuTrqr6aXrMP2sH44wWkw74N1x2jWl llvtrVhI+W0wC2U9DLN9WT1p3i+xre3Po5l72NF6bolsd8CaUTbHw1FOaMGSgrVp2Coi 2CMSe84+4fJul6f6fOWsx/vAceL6x13svPAWET4P0FcB+4rgyjn6n+RR6rcPFrJYlpDG OeIHk9jEgcu+wkJW9mUn8TDBh+7SLg8Q9hSZlGxNZd64/coFsms67DRPC1bh2bqv/0ln FOro6L6YFg4RzpEf92MNCg88emVLg9mUkT4vw6oJO8kY3Bwnv6VBvYtaxSpyNVQEFs6j pnFQ== 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=HDGK9WpScVeMtha6VzxaHMU8npX6OpsqVD5LqpzxJKY=; b=3U5eoPC6rXr2aIo/ZUYxQnxc3hcseaRYB34o1SaXFd8gUDOV61XDdzwXpv+z1X+AgP Vo5FXzIqr/6sJl79SoqdT0mfIXt62/CFK5FYUnMUA3bGCJPCYVb71QhGDciJl/dJMl9d eWEPdE+vYbEdG5L1cGBhtDYv+1xFdJw/irbVxTzl76Sgxvtb8FLz6N1PwLfQrkxjjQxA IW8dbMz3MuqCKeZBHDsdDC6lEchqa+QokG51cqdBK6Gmabhe5OSHgOz6mNDFMKWqekg8 nYxtK3AkZ7KpFqStgPgNURHSPMSSbxBD8q9tTtFl71G+xSBT/pLSvdppYiOHuiB3Q2vu SXDg== X-Gm-Message-State: AOAM530QButztj8aObb6WJekgNjClcHwY7Qe1DIeT1XcLO2jibscLFfh c7lzXMzCEvSmyZAqhWYb2EpoqwA1+eI= X-Received: by 2002:ac8:5f53:0:b0:2e1:d4fd:c965 with SMTP id y19-20020ac85f53000000b002e1d4fdc965mr6636047qta.590.1648772451241; Thu, 31 Mar 2022 17:20: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 r17-20020a05620a299100b00680b43004bfsm602489qkp.45.2022.03.31.17.20.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 31 Mar 2022 17:20:50 -0700 (PDT) From: Leo Izen To: ffmpeg-devel@ffmpeg.org Date: Thu, 31 Mar 2022 20:20:05 -0400 Message-Id: <20220401002006.44582-5-leo.izen@gmail.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20220401002006.44582-1-leo.izen@gmail.com> References: <20220401002006.44582-1-leo.izen@gmail.com> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v11 4/5] avformat/image2: add Jpeg XL as image2 format 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: Z9M/vWDSjdnK 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, \ From patchwork Fri Apr 1 00:20:06 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Leo Izen X-Patchwork-Id: 35122 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:c05:b0:7a:e998:b410 with SMTP id bw5csp376023pzb; Thu, 31 Mar 2022 17:21:19 -0700 (PDT) X-Google-Smtp-Source: ABdhPJy294K0sW0WUXmpKr2nWm48I3hl5FhD8XC0FjPo6pJG73LiZHy/hYGtUrDlM79JyEsfj635 X-Received: by 2002:a05:6402:3452:b0:418:f963:42a3 with SMTP id l18-20020a056402345200b00418f96342a3mr19013342edc.12.1648772478777; Thu, 31 Mar 2022 17:21:18 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1648772478; cv=none; d=google.com; s=arc-20160816; b=TWgeTg3gNBZR5KWJe1dqJzpF5iIb9EIU+1ay01wgGYea3Z14fJy4T2lsnpiq4tttYm Vdm9vG+0YHBLnPm6NzyxDDFP1vFddArF+adY7cSJJ6pL6DdHFjugpzjIo9X0w9Q8VwyB spZ2Cat5+LeXO5a9AMHGiePbQJMpzFaMZ2U+k+4lVGz0sG7e6wqIojtsTxUKmmKsyMcv st7BUkZRh2xXsyjgOw0pWjhpmgAEFpE/J3uGd4/Qw/Zntfnf3NP0tqR45r52mzyoJ5x0 WHM+TyODaiMom/7tsIgaXSKNMlKK7IVZKS2yRrRzJXK5MYBgx05XCjDG9D2VTl+dTtDf 9IcA== 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=5M6xoVWU3LHSrARqwtlyfEFfkiR8SLTent7SylvoGKA=; b=eDKzQJmebxASejSn0aoqwQ3YNeNgw9uQvxeP+HoFQhUGEi3w0Cpf9Q0/danAiQx68h eSoIbzq5B3Rt7z16jp7wUZqxVM9EfXzIkmpRwfS3gQj90tDTSfslB+kHYfYy6UlJB5lt YJtcnt95MolNZUkxofdwVdH3OFsMk9pxSJ9lC7SJ3Cma+MByjQ6km7FUe101yHe9CskC TdCrJPJg9tr+haaPUzHbrrR4WbOh53LB6aMpwVlL1WFzjH0ppNPUy6rc/ol+gHcR4WxQ UwqqYfydMLw0AsNfA1QsmqHWitzMsmMALzJZcMrYp+OWN8GJSMSFE5YTSX+sbtSmbEMD 5A7g== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20210112 header.b=AjpH9wHc; 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 a14-20020a170906368e00b006df76385becsi652604ejc.140.2022.03.31.17.21.18; Thu, 31 Mar 2022 17:21: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=AjpH9wHc; 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 38AFE68B269; Fri, 1 Apr 2022 03:21:00 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-qv1-f49.google.com (mail-qv1-f49.google.com [209.85.219.49]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 4348368B1FB for ; Fri, 1 Apr 2022 03:20:53 +0300 (EEST) Received: by mail-qv1-f49.google.com with SMTP id kc20so883777qvb.3 for ; Thu, 31 Mar 2022 17:20: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:in-reply-to:references :mime-version:content-transfer-encoding; bh=QBkEW2m+j3x8MnVsgyIfblwLAXez1He4Lfx1N2r5GMg=; b=AjpH9wHcF4PuItEYSSFL1vhDhjc+Jy2bJpogaP/3s4Spq/fy2O5l12thI1x4sDzUJX geLlNE+XRtxff3debmYwUvolcb56GvjWPGoDKLaYWziQJ1ToJcRFDbqmuEJNACl1SPVI LoK29h8A+vSlOjuZZzrNWqo6p/6qEab0Tq/Q+2fuK8pJ7EhCAuQ6nmIh+2qSEX1MxTZC ckIuxY6aA36CaoJobgLyjpN5zvzhrBcmPOuKlHtN0Gh0gV+3RZC8GLf7AFJ0gzrdVsxK rcCo4uO7+RMZzjRQgn2E5BAxGLrqLMMeOKQ9CICRegvYnh3j9bNKqvAOOyf46k7484mA 6FzA== 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=QBkEW2m+j3x8MnVsgyIfblwLAXez1He4Lfx1N2r5GMg=; b=vvqtqtQuaZavjgK6EoDqsZZd2wY5BYNZLMRHBjbYQx3mf8QXl3YPE5vNdfEe99WNnm Vhl28mizJ0pyM5WqzMWzjFQ8BiZqb+wcLUIhtjlMgVo5RHGFFwe5eyYkzdGTcQrpMvos dHLybWDH8AWE8v6mb1x/JTOjxyhZ2lB1FszVfgq2pZntPoOAswFb5NrM//CKMM0hFtYK gTVdTgsG4JEcjWzUiI5q3nJxDDc710ue3znbojZ3L+2Go57Ssx2FenKLvNz3LvarrhMy hnV+bLc9bH31mzn7rgg9BhyLblNI+UKDaUrC8+poza3K6nczM15K7DVPKZK3p8DZr/c9 7hXQ== X-Gm-Message-State: AOAM530reMcb3TZ1SLMp3A8jfCK+1EY4EisEwwJ35yD/XxX+HkO569be LaQwxEJy3TR3egTqtTnvVDowuQ90gVM= X-Received: by 2002:a05:6214:f27:b0:441:1380:99a0 with SMTP id iw7-20020a0562140f2700b00441138099a0mr37349608qvb.112.1648772451830; Thu, 31 Mar 2022 17:20: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 r17-20020a05620a299100b00680b43004bfsm602489qkp.45.2022.03.31.17.20.51 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 31 Mar 2022 17:20:51 -0700 (PDT) From: Leo Izen To: ffmpeg-devel@ffmpeg.org Date: Thu, 31 Mar 2022 20:20:06 -0400 Message-Id: <20220401002006.44582-6-leo.izen@gmail.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20220401002006.44582-1-leo.izen@gmail.com> References: <20220401002006.44582-1-leo.izen@gmail.com> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v11 5/5] fate/jpegxl: add Jpeg XL demux and parse FATE test 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: FP549/Sndff5 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