This is an automated email from the git hooks/post-receive script. Git pushed a commit to branch master in repository ffmpeg.
commit 6e9100c64894728cd92aaf8ce2f1f33cfdbc675f Author: Jun Zhao <[email protected]> AuthorDate: Sun Aug 16 19:08:58 2026 +0800 Commit: Jun Zhao <[email protected]> CommitDate: Mon Sep 14 12:44:07 2026 +0000 lavc: add ASTC encoder/decoder via libastcenc Add the ASTC codec ID and descriptor together with encoder and decoder wrappers for Arm's astc-encoder library. Support the four ASTC profiles, 2D and 3D block footprints, LDR RGB/RGBA input, and floating-point HDR input without clamping the decoded result to 8-bit. Keep the ASTC image dimensions and compressed block sizes bounded by the format and packet limits, and pass KTX-provided color profile metadata to the decoder when no explicit profile was selected. libastcenc is distributed under the Apache License 2.0, so the library is only available when FFmpeg is built with --enable-version3; list it with the other libraries that require that option. Signed-off-by: Jun Zhao <[email protected]> --- LICENSE.md | 2 +- configure | 16 ++ doc/APIchanges | 3 + libavcodec/Makefile | 2 + libavcodec/allcodecs.c | 2 + libavcodec/codec_desc.c | 9 ++ libavcodec/codec_id.h | 1 + libavcodec/defs.h | 9 ++ libavcodec/libastcdec.c | 303 +++++++++++++++++++++++++++++++++++ libavcodec/libastcenc.c | 411 ++++++++++++++++++++++++++++++++++++++++++++++++ libavcodec/profiles.c | 9 ++ libavcodec/profiles.h | 1 + libavcodec/version.h | 2 +- 13 files changed, 768 insertions(+), 2 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 371b0913ce..27d37e9e81 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -105,7 +105,7 @@ The following libraries are under LGPL version 3: When combining them with FFmpeg, use the configure option `--enable-version3` to upgrade FFmpeg to the LGPL v3. -The VMAF, mbedTLS, RK MPI, OpenCORE and VisualOn libraries are under the Apache License +The VMAF, mbedTLS, RK MPI, OpenCORE, VisualOn and libastcenc libraries are under the Apache License 2.0. That license is incompatible with the LGPL v2.1 and the GPL v2, but not with version 3 of those licenses. So to combine these libraries with FFmpeg, the license version needs to be upgraded by passing `--enable-version3` to configure. diff --git a/configure b/configure index d2f6b89b22..eefe2bcef7 100755 --- a/configure +++ b/configure @@ -220,6 +220,7 @@ External library support: --enable-libaribcaption enable ARIB text and caption decoding via libaribcaption [no] --enable-libass enable libass subtitles rendering, needed for subtitles and ass filter [no] + --enable-libastcenc enable ASTC image encoding/decoding via astc-encoder [no] --enable-libbluray enable BluRay reading using libbluray [no] --enable-libbs2b enable bs2b DSP library [no] --enable-libcaca enable textual display using libcaca [no] @@ -2038,6 +2039,7 @@ EXTERNAL_LIBRARY_NONFREE_LIST=" EXTERNAL_LIBRARY_VERSION3_LIST=" gmp libaribb24 + libastcenc liblensfun libopencore_amrnb libopencore_amrwb @@ -3795,6 +3797,8 @@ libaom_av1_encoder_deps="libaom" libaom_av1_encoder_select="extract_extradata_bsf dovi_rpuenc" libaribb24_decoder_deps="libaribb24" libaribcaption_decoder_deps="libaribcaption" +libastcenc_encoder_deps="libastcenc" +libastcenc_decoder_deps="libastcenc" libcodec2_decoder_deps="libcodec2" libcodec2_encoder_deps="libcodec2" libdav1d_decoder_deps="libdav1d" @@ -7178,6 +7182,18 @@ enabled libaribb24 && { check_pkg_config libaribb24 "aribb24 > 1.0.3" "ar { enabled gpl && require_pkg_config libaribb24 aribb24 "aribb24/aribb24.h" arib_instance_new; } || die "ERROR: libaribb24 requires version higher than 1.0.3 or --enable-gpl."; } enabled libaribcaption && require_pkg_config libaribcaption "libaribcaption >= 1.1.1" "aribcaption/aribcaption.h" aribcc_context_alloc +enabled libastcenc && { + require libastcenc astcenc/astcenc.h astcenc_config_init -lastcenc -lstdc++ $libm_extralibs + test_ld "cc" -lastcenc -lstdc++ $libm_extralibs <<EOF || die "ERROR: libastcenc 5.4 or newer is required; the encoder/decoder use the four-argument astcenc_context_alloc() interface added in that release." +#include <astcenc/astcenc.h> +int main(void) { + struct astcenc_config cfg; + struct astcenc_context *ctx; + return astcenc_context_alloc(&cfg, 1, &ctx, 0) != ASTCENC_SUCCESS; +} +EOF +} +libastcenc_extralibs="-lastcenc -lstdc++ $libm_extralibs" enabled lv2 && require_pkg_config lv2 lilv-0 "lilv/lilv.h" lilv_world_new enabled libiec61883 && require libiec61883 libiec61883/iec61883.h iec61883_cmp_connect -lraw1394 -lavc1394 -lrom1394 -liec61883 enabled libass && require_pkg_config libass "libass >= 0.11.0" ass/ass.h ass_library_init diff --git a/doc/APIchanges b/doc/APIchanges index dd57454c4f..5d81cd92d7 100644 --- a/doc/APIchanges +++ b/doc/APIchanges @@ -2,6 +2,9 @@ The last version increases of all libraries were on 2026-06-23. API changes, most recent first: +2026-09-12 - xxxxxxxxxx - lavc 63.13.100 - codec_id.h, defs.h + Add AV_CODEC_ID_ASTC and the AV_PROFILE_ASTC_* profile values. + 2026-09-09 - xxxxxxxxxx - lavc 63.12.100 - avcodec.h Add FF_DCT_RVV to enable the RISC-V Vector FDCT implementation. diff --git a/libavcodec/Makefile b/libavcodec/Makefile index 4a8a06a796..a5c71995ea 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -1182,6 +1182,8 @@ OBJS-$(CONFIG_LIBAOM_AV1_DECODER) += libaomdec.o libaom.o OBJS-$(CONFIG_LIBAOM_AV1_ENCODER) += libaomenc.o libaom.o OBJS-$(CONFIG_LIBARIBB24_DECODER) += libaribb24.o ass.o OBJS-$(CONFIG_LIBARIBCAPTION_DECODER) += libaribcaption.o ass.o +OBJS-$(CONFIG_LIBASTCENC_ENCODER) += libastcenc.o +OBJS-$(CONFIG_LIBASTCENC_DECODER) += libastcdec.o OBJS-$(CONFIG_LIBCODEC2_DECODER) += libcodec2.o OBJS-$(CONFIG_LIBCODEC2_ENCODER) += libcodec2.o OBJS-$(CONFIG_LIBDAV1D_DECODER) += libdav1d.o av1_parse.o diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index b8e744ad86..ca20c76fe6 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -774,6 +774,8 @@ extern const FFCodec ff_libaom_av1_encoder; /* preferred over libaribb24 */ extern const FFCodec ff_libaribcaption_decoder; extern const FFCodec ff_libaribb24_decoder; +extern const FFCodec ff_libastcenc_encoder; +extern const FFCodec ff_libastcenc_decoder; extern const FFCodec ff_libcodec2_encoder; extern const FFCodec ff_libcodec2_decoder; extern const FFCodec ff_libdav1d_decoder; diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c index f5f2b17ec0..dc20b57df4 100644 --- a/libavcodec/codec_desc.c +++ b/libavcodec/codec_desc.c @@ -1992,6 +1992,15 @@ static const AVCodecDescriptor codec_descriptors[] = { .props = AV_CODEC_PROP_LOSSY | AV_CODEC_PROP_LOSSLESS, .mime_types= MT("image/webp"), }, + { + .id = AV_CODEC_ID_ASTC, + .type = AVMEDIA_TYPE_VIDEO, + .name = "astc", + .long_name = NULL_IF_CONFIG_SMALL("ASTC (Adaptive Scalable Texture Compression)"), + .props = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY, + .profiles = NULL_IF_CONFIG_SMALL(ff_astc_profiles), + .mime_types= MT("image/astc"), + }, /* various PCM "codecs" */ { diff --git a/libavcodec/codec_id.h b/libavcodec/codec_id.h index 7cafac610e..841aa28d20 100644 --- a/libavcodec/codec_id.h +++ b/libavcodec/codec_id.h @@ -324,6 +324,7 @@ enum AVCodecID { AV_CODEC_ID_PRORES_RAW, AV_CODEC_ID_JPEGXS, AV_CODEC_ID_WEBP_ANIM, + AV_CODEC_ID_ASTC, /* various PCM "codecs" */ AV_CODEC_ID_FIRST_AUDIO = 0x10000, ///< A dummy id pointing at the start of audio codecs diff --git a/libavcodec/defs.h b/libavcodec/defs.h index b13e983b13..f7495497a2 100644 --- a/libavcodec/defs.h +++ b/libavcodec/defs.h @@ -77,6 +77,15 @@ #define AV_PROFILE_MPEG2_AAC_LOW 128 #define AV_PROFILE_MPEG2_AAC_HE 131 +#define AV_PROFILE_ASTC_LDR_SRGB 1 ///< sRGB LDR. +#define AV_PROFILE_ASTC_LDR 2 ///< Linear LDR. +#define AV_PROFILE_ASTC_HDR_RGB_LDR_A 3 ///< HDR RGB with LDR alpha. +#define AV_PROFILE_ASTC_HDR 4 ///< HDR RGB and alpha. +/* Linear interpretation of a stream, without restricting its block endpoint + * encodings to an LDR or HDR profile. This is stream metadata and does not + * select an encoding profile. */ +#define AV_PROFILE_ASTC_LINEAR_ANY 5 + #define AV_PROFILE_DNXHD 0 #define AV_PROFILE_DNXHR_LB 1 #define AV_PROFILE_DNXHR_SQ 2 diff --git a/libavcodec/libastcdec.c b/libavcodec/libastcdec.c new file mode 100644 index 0000000000..4526ad6b0d --- /dev/null +++ b/libavcodec/libastcdec.c @@ -0,0 +1,303 @@ +/* + * astc-encoder wrapper decoder for FFmpeg (ASTC image decoder) + * + * Copyright (C) 2026 Jun Zhao + * + * 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 + * ASTC decoder using ARM's astc-encoder library (astcenc C API). + * + * Consumes the raw ASTC bitstream; block size and dimensions are recovered + * from the 16-byte .astc-style extradata published by the demuxer. + */ + +#include <astcenc/astcenc.h> + +#include "avcodec.h" +#include "codec_internal.h" +#include "decode.h" +#include "profiles.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/mem.h" +#include "libavutil/opt.h" + +/* .astc container magic (little-endian 0x5CA1AB13). */ +#define ASTC_MAGIC 0x5CA1AB13 +#define ASTC_HEADER_SIZE 16 +#define ASTC_BLOCK_BYTES 16 + +typedef struct LibAstcDecContext { + AVClass *class; + struct astcenc_config cfg; + struct astcenc_context *ctx; + int block_x; + int block_y; + int block_z; + int is_hdr; + int dec_profile; /* astcenc_profile, set via private option, -1 = auto */ +} LibAstcDecContext; + +static av_cold int astcdec_configure(AVCodecContext *avctx, + enum astcenc_profile prf) +{ + LibAstcDecContext *s = avctx->priv_data; + enum astcenc_error err; + + if (s->ctx) { + astcenc_context_free(s->ctx); + s->ctx = NULL; + } + + err = astcenc_config_init(prf, s->block_x, s->block_y, s->block_z, + 0, ASTCENC_FLG_DECOMPRESS_ONLY, &s->cfg); + if (err != ASTCENC_SUCCESS) { + av_log(avctx, AV_LOG_ERROR, "astcenc_config_init failed: %s\n", + astcenc_get_error_string(err)); + return AVERROR_UNKNOWN; + } + + err = astcenc_context_alloc(&s->cfg, 1, &s->ctx, NULL); + if (err != ASTCENC_SUCCESS) { + av_log(avctx, AV_LOG_ERROR, "astcenc_context_alloc failed: %s\n", + astcenc_get_error_string(err)); + return AVERROR(ENOMEM); + } + + s->is_hdr = (prf == ASTCENC_PRF_HDR || prf == ASTCENC_PRF_HDR_RGB_LDR_A); + /* Leave avctx->profile untouched: it is a property of the stream, set by + * the container, while the profile resolved here is an internal decision. + * avformat_find_stream_info() copies this context back into the stream + * parameters, so publishing the resolved profile would, for example, turn + * a linear KTX into a stream the KTX muxer refuses to copy. */ + avctx->pix_fmt = s->is_hdr ? AV_PIX_FMT_RGBAF16 : AV_PIX_FMT_RGBA; + return 0; +} + +static av_cold int libastcdec_init(AVCodecContext *avctx) +{ + LibAstcDecContext *s = avctx->priv_data; + const uint8_t *ed = avctx->extradata; + + if (!ed || avctx->extradata_size < ASTC_HEADER_SIZE) { + av_log(avctx, AV_LOG_ERROR, + "astc decoder requires %d-byte .astc-style extradata.\n", + ASTC_HEADER_SIZE); + return AVERROR_INVALIDDATA; + } + + /* .astc header layout (all little-endian): + * [0-3] magic + * [4-6] block dimensions block_x, block_y, block_z (block_z > 1 + * selects a 3D block footprint, e.g. 4x4x4) + * [7-9] image width (dim_x, texels) + * [10-12] image height (dim_y, texels) + * [13-15] image depth (dim_z, texel slices; 1 for a 2D image) + * block_z (the block footprint) is independent of dim_z (the image + * slice count), so a 3D block can still describe a 2D image. */ + if (AV_RL32(ed) != ASTC_MAGIC || AV_RL24(ed + 13) != 1) { + av_log(avctx, AV_LOG_ERROR, "Invalid ASTC header.\n"); + return AVERROR_INVALIDDATA; + } + + s->block_x = ed[4]; + s->block_y = ed[5]; + s->block_z = ed[6]; + avctx->width = AV_RL24(ed + 7); + avctx->height = AV_RL24(ed + 10); + /* Zero footprints would divide by zero in the block accounting below; the + * .astc demuxer rejects them, but the decoder must not trust its input. */ + if (!s->block_x || !s->block_y || !s->block_z || + avctx->width <= 0 || avctx->height <= 0) { + av_log(avctx, AV_LOG_ERROR, + "Invalid ASTC header: block %dx%dx%d, image %dx%d.\n", + s->block_x, s->block_y, s->block_z, + avctx->width, avctx->height); + return AVERROR_INVALIDDATA; + } + + /* The .astc header does not record the intended decoding profile. + * Individual blocks identify their endpoint encodings, but that does not + * establish the sRGB versus linear interpretation. Use the container + * metadata or -dec_profile; without either, a raw .astc input defaults to + * ldr-srgb. A stream that records no profile at all keeps the + * AV_PROFILE_UNKNOWN default and lands in the fallback branch below. */ + enum astcenc_profile prf = (enum astcenc_profile)s->dec_profile; + if (s->dec_profile < 0) { + switch (avctx->profile) { + case AV_PROFILE_ASTC_LDR_SRGB: + prf = ASTCENC_PRF_LDR_SRGB; + break; + case AV_PROFILE_ASTC_LDR: + prf = ASTCENC_PRF_LDR; + break; + case AV_PROFILE_ASTC_HDR_RGB_LDR_A: + prf = ASTCENC_PRF_HDR_RGB_LDR_A; + break; + case AV_PROFILE_ASTC_HDR: + prf = ASTCENC_PRF_HDR; + break; + case AV_PROFILE_ASTC_LINEAR_ANY: + /* A linear texture may hold HDR endpoint encodings, so sample it + * with HDR precision; LDR endpoint blocks decode correctly through + * the same profile instead of coming back as the magenta error + * colour. */ + prf = ASTCENC_PRF_HDR_RGB_LDR_A; + break; + default: + /* No container information (a raw .astc stream): the documented + * LDR sRGB fallback. HDR needs an explicit -dec_profile. */ + prf = ASTCENC_PRF_LDR_SRGB; + break; + } + } + + return astcdec_configure(avctx, prf); +} + +static int libastcdec_decode(AVCodecContext *avctx, AVFrame *frame, + int *got_frame, AVPacket *avpkt) +{ + LibAstcDecContext *s = avctx->priv_data; + const int w = avctx->width; + const int h = avctx->height; + const int elem = s->is_hdr ? 2 : 1; + size_t row_bytes, buf_size, block_count, expected; + + if (av_size_mult(w, 4 * elem, &row_bytes) < 0 || + av_size_mult(h, row_bytes, &buf_size) < 0 || + av_size_mult((w - 1) / s->block_x + 1, + (h - 1) / s->block_y + 1, &block_count) < 0 || + av_size_mult(block_count, ASTC_BLOCK_BYTES, &expected) < 0) + return AVERROR_INVALIDDATA; + /* dim_z is always 1 (2D input), so blocks_z is always 1. */ + + struct astcenc_swizzle swz = { + ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A + }; + + uint8_t *out_buf = NULL; + void *data_ptrs[1]; + struct astcenc_image img; + enum astcenc_error err; + int ret; + + /* A single ASTC image is exactly the blocks its geometry needs; accepting + * a larger packet would silently ignore the trailing data below. */ + if ((size_t)avpkt->size != expected) { + av_log(avctx, AV_LOG_ERROR, + "ASTC packet size %d does not match the %zu bytes required for " + "a %dx%d image with %dx%d blocks.\n", + avpkt->size, expected, w, h, s->block_x, s->block_y); + return AVERROR_INVALIDDATA; + } + + frame->width = w; + frame->height = h; + if ((ret = ff_get_buffer(avctx, frame, 0)) < 0) + return ret; + + /* astcenc writes a tightly packed image. If the frame has no line padding + * we can decode straight into it; otherwise use a temporary buffer and + * copy row by row. */ + if (frame->linesize[0] == row_bytes) { + data_ptrs[0] = frame->data[0]; + } else { + out_buf = av_malloc(buf_size); + if (!out_buf) + return AVERROR(ENOMEM); + data_ptrs[0] = out_buf; + } + + img.dim_x = w; + img.dim_y = h; + img.dim_z = 1; + img.data_type = s->is_hdr ? ASTCENC_TYPE_F16 : ASTCENC_TYPE_U8; + img.data = data_ptrs; + + err = astcenc_decompress_image(s->ctx, avpkt->data, avpkt->size, + &img, &swz, 0); + if (err != ASTCENC_SUCCESS) { + av_log(avctx, AV_LOG_ERROR, "astcenc_decompress_image failed: %s\n", + astcenc_get_error_string(err)); + ret = AVERROR_UNKNOWN; + goto end; + } + + if (out_buf) { + for (int y = 0; y < h; y++) + memcpy(frame->data[0] + (size_t)y * frame->linesize[0], + out_buf + (size_t)y * row_bytes, row_bytes); + } + + *got_frame = 1; + ret = 0; + +end: + av_freep(&out_buf); + return ret; +} + +static av_cold int libastcdec_close(AVCodecContext *avctx) +{ + LibAstcDecContext *s = avctx->priv_data; + if (s->ctx) { + astcenc_context_free(s->ctx); + s->ctx = NULL; + } + return 0; +} + +#define VD AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_DECODING_PARAM +#define OFFSET(x) offsetof(LibAstcDecContext, x) + +static const AVOption dec_options[] = { + { "dec_profile", "Decoder color profile (must match encoder)", OFFSET(dec_profile), + AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 3, VD, .unit = "dec_profile" }, + { "ldr", "Linear LDR", 0, AV_OPT_TYPE_CONST, { .i64 = ASTCENC_PRF_LDR }, 0, 0, VD, .unit = "dec_profile" }, + { "ldr-srgb", "sRGB LDR", 0, AV_OPT_TYPE_CONST, { .i64 = ASTCENC_PRF_LDR_SRGB }, 0, 0, VD, .unit = "dec_profile" }, + { "hdr-ldr-a", "HDR RGB, LDR alpha", 0, AV_OPT_TYPE_CONST, { .i64 = ASTCENC_PRF_HDR_RGB_LDR_A }, 0, 0, VD, .unit = "dec_profile" }, + { "hdr", "HDR", 0, AV_OPT_TYPE_CONST, { .i64 = ASTCENC_PRF_HDR }, 0, 0, VD, .unit = "dec_profile" }, + { NULL }, +}; + +static const AVClass libastcenc_dec_class = { + .class_name = "libastcenc decoder", + .item_name = av_default_item_name, + .option = dec_options, + .version = LIBAVUTIL_VERSION_INT, +}; + +const FFCodec ff_libastcenc_decoder = { + .p.name = "libastcenc", + CODEC_LONG_NAME("ASTC (Adaptive Scalable Texture Compression) image using astc-encoder"), + .p.type = AVMEDIA_TYPE_VIDEO, + .p.id = AV_CODEC_ID_ASTC, + .p.capabilities = AV_CODEC_CAP_DR1, + CODEC_PIXFMTS(AV_PIX_FMT_RGBA, AV_PIX_FMT_RGBAF16), + .p.profiles = NULL_IF_CONFIG_SMALL(ff_astc_profiles), + .p.priv_class = &libastcenc_dec_class, + .p.wrapper_name = "libastcenc", + .caps_internal = FF_CODEC_CAP_NOT_INIT_THREADSAFE | + FF_CODEC_CAP_INIT_CLEANUP, + .priv_data_size = sizeof(LibAstcDecContext), + .init = libastcdec_init, + FF_CODEC_DECODE_CB(libastcdec_decode), + .close = libastcdec_close, +}; diff --git a/libavcodec/libastcenc.c b/libavcodec/libastcenc.c new file mode 100644 index 0000000000..68f6c467a1 --- /dev/null +++ b/libavcodec/libastcenc.c @@ -0,0 +1,411 @@ +/* + * astc-encoder wrapper for FFmpeg (ASTC image encoder) + * + * Copyright (C) 2026 Jun Zhao + * + * 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 + * ASTC encoder using ARM's astc-encoder library (astcenc C API). + * + * Output is the raw ASTC bitstream (16 bytes per block). The container + * (.astc / .ktx) is added by the corresponding FFmpeg muxer. + */ + +#include <astcenc/astcenc.h> + +#include <limits.h> + +#include "avcodec.h" +#include "codec_internal.h" +#include "encode.h" +#include "profiles.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/pixdesc.h" +#include "libavutil/mem.h" +#include "libavutil/opt.h" + +/* .astc container magic (little-endian 0x5CA1AB13). */ +#define ASTC_MAGIC 0x5CA1AB13 +#define ASTC_HEADER_SIZE 16 +#define ASTC_BLOCK_BYTES 16 + +typedef struct LibAstcEncContext { + AVClass *class; + + /* astc-encoder state */ + struct astcenc_config cfg; + struct astcenc_context *ctx; + + /* options */ + char *block_size_str; + int block_x; + int block_y; + int block_z; + int profile; /* astcenc_profile */ + float quality; /* 0..100 */ + int perceptual; + int alpha_weight; +} LibAstcEncContext; + +static av_cold int libastcenc_init(AVCodecContext *avctx) +{ + LibAstcEncContext *s = avctx->priv_data; + enum astcenc_profile prf; + unsigned int flags = 0; + enum astcenc_error err; + + /* An explicitly set private profile option overrides the public + * AVCodecContext.profile field. AV_PROFILE_ASTC_LINEAR_ANY describes how a + * container should interpret a stream and never selects an encoding + * profile, so it is rejected here. */ + if (s->profile >= 0) { + prf = (enum astcenc_profile)s->profile; + } else { + switch (avctx->profile) { + case AV_PROFILE_UNKNOWN: + case AV_PROFILE_ASTC_LDR_SRGB: + prf = ASTCENC_PRF_LDR_SRGB; + break; + case AV_PROFILE_ASTC_LDR: + prf = ASTCENC_PRF_LDR; + break; + case AV_PROFILE_ASTC_HDR_RGB_LDR_A: + prf = ASTCENC_PRF_HDR_RGB_LDR_A; + break; + case AV_PROFILE_ASTC_HDR: + prf = ASTCENC_PRF_HDR; + break; + default: + av_log(avctx, AV_LOG_ERROR, + "Unsupported ASTC encoding profile %d.\n", avctx->profile); + return AVERROR(EINVAL); + } + } + + /* Resolve profile vs input pixel format. Float/half input must be encoded + * with an HDR profile (auto-promoted here); 8-bit input cannot drive an HDR + * profile (it would be degenerate -> near-black). This runs at init (not in + * encode) because pix_fmt is known at open time; the resolved profile is + * propagated via avctx->profile (-> codecpar->profile) so muxers such as + * the KTX writer can reject HDR without needing to inspect extradata. */ + int is_float = (avctx->pix_fmt == AV_PIX_FMT_RGBAF16 || + avctx->pix_fmt == AV_PIX_FMT_RGBAF32 || + avctx->pix_fmt == AV_PIX_FMT_GBRAPF32); + if (is_float) { + if (prf == ASTCENC_PRF_LDR || prf == ASTCENC_PRF_LDR_SRGB) { + prf = ASTCENC_PRF_HDR_RGB_LDR_A; + av_log(avctx, AV_LOG_INFO, + "Float/half input: auto-promoted profile to HDR_RGB_LDR_A.\n"); + } + } else if (prf == ASTCENC_PRF_HDR || prf == ASTCENC_PRF_HDR_RGB_LDR_A) { + av_log(avctx, AV_LOG_ERROR, + "HDR profile selected but input is 8-bit; use float/half " + "input (e.g. -pix_fmt rgbaf16) for HDR.\n"); + return AVERROR(EINVAL); + } + s->profile = (int)prf; + /* Publish the profile the container records, as one of the public ASTC + * profile values rather than as the astcenc enum, whose ordering is not + * part of the FFmpeg API. */ + switch (prf) { + case ASTCENC_PRF_LDR_SRGB: + avctx->profile = AV_PROFILE_ASTC_LDR_SRGB; + break; + case ASTCENC_PRF_LDR: + avctx->profile = AV_PROFILE_ASTC_LDR; + break; + case ASTCENC_PRF_HDR_RGB_LDR_A: + avctx->profile = AV_PROFILE_ASTC_HDR_RGB_LDR_A; + break; + case ASTCENC_PRF_HDR: + avctx->profile = AV_PROFILE_ASTC_HDR; + break; + default: + avctx->profile = AV_PROFILE_UNKNOWN; + break; + } + + if (avctx->width <= 0 || avctx->height <= 0 || + avctx->width > 0xFFFFFF || avctx->height > 0xFFFFFF) { + av_log(avctx, AV_LOG_ERROR, "Invalid image dimensions %dx%d.\n", + avctx->width, avctx->height); + return AVERROR(EINVAL); + } + + /* Accept exactly "WxH" or "WxHxD". Scanning stops at the third literal 'x' + * when it is absent, so trailing garbage such as "8x8junk" has to be + * caught by checking that the whole string was consumed. */ + int consumed = 0; + + s->block_z = 1; + if (sscanf(s->block_size_str, "%dx%dx%d%n", &s->block_x, &s->block_y, + &s->block_z, &consumed) != 3) { + consumed = 0; + s->block_z = 1; + sscanf(s->block_size_str, "%dx%d%n", &s->block_x, &s->block_y, + &consumed); + } + if (!consumed || s->block_size_str[consumed]) { + av_log(avctx, AV_LOG_ERROR, "Invalid block_size '%s' " + "(expected e.g. 8x8 or 6x6x6).\n", s->block_size_str); + return AVERROR(EINVAL); + } + + if (s->perceptual) + flags |= ASTCENC_FLG_USE_PERCEPTUAL; + if (s->alpha_weight) + flags |= ASTCENC_FLG_USE_ALPHA_WEIGHT; + + err = astcenc_config_init(prf, s->block_x, s->block_y, s->block_z, + s->quality, flags, &s->cfg); + if (err != ASTCENC_SUCCESS) { + av_log(avctx, AV_LOG_ERROR, "astcenc_config_init failed: %s\n", + astcenc_get_error_string(err)); + return AVERROR_UNKNOWN; + } + + /* astcenc normalizes the requested footprint, e.g. a zero block depth is + * clamped to one, so publish the validated footprint instead of the parsed + * one. Both the .astc header below and the block accounting in encode() + * must agree with what the library actually compresses with. */ + s->block_x = s->cfg.block_x; + s->block_y = s->cfg.block_y; + s->block_z = s->cfg.block_z; + + err = astcenc_context_alloc(&s->cfg, 1, &s->ctx, NULL); + if (err != ASTCENC_SUCCESS) { + av_log(avctx, AV_LOG_ERROR, "astcenc_context_alloc failed: %s\n", + astcenc_get_error_string(err)); + return AVERROR(ENOMEM); + } + + /* Publish a 16-byte .astc-style header as extradata so the .astc/.ktx + * muxers can write the container header and the decoder can recover the + * block size and dimensions. The buffer is padded so consumers that read + * it as a bitstream do not over-read. */ + avctx->extradata = av_mallocz(ASTC_HEADER_SIZE + AV_INPUT_BUFFER_PADDING_SIZE); + if (!avctx->extradata) + return AVERROR(ENOMEM); + avctx->extradata_size = ASTC_HEADER_SIZE; + { + uint8_t *h = avctx->extradata; + /* .astc header layout (all little-endian): + * [0-3] magic + * [4-6] block dimensions block_x, block_y, block_z (block_z > 1 + * selects a 3D block footprint, e.g. 4x4x4) + * [7-9] image width (dim_x, texels) + * [10-12] image height (dim_y, texels) + * [13-15] image depth (dim_z, texel slices; 1 for a 2D image) + * block_z (the block footprint) is independent of dim_z (the image + * slice count), so a 3D block can still describe a 2D image. */ + AV_WL32(h, ASTC_MAGIC); + h[4] = (uint8_t)s->block_x; + h[5] = (uint8_t)s->block_y; + h[6] = (uint8_t)s->block_z; + AV_WL24(h + 7, avctx->width); + AV_WL24(h + 10, avctx->height); + AV_WL24(h + 13, 1); /* image depth is 1: 2D image (3D blocks stay 2D) */ + } + + return 0; +} + +static int libastcenc_encode(AVCodecContext *avctx, AVPacket *pkt, + const AVFrame *frame, int *got_packet) +{ + LibAstcEncContext *s = avctx->priv_data; + const int w = frame->width; + const int h = frame->height; + const int bx = s->block_x; + const int by = s->block_y; + size_t blocks_x, blocks_y, block_count, out_size; + + if (w != avctx->width || h != avctx->height) { + av_log(avctx, AV_LOG_ERROR, + "Frame size %dx%d does not match configured %dx%d.\n", + w, h, avctx->width, avctx->height); + return AVERROR_INVALIDDATA; + } + + blocks_x = (w - 1) / bx + 1; + blocks_y = (h - 1) / by + 1; + if (av_size_mult(blocks_x, blocks_y, &block_count) < 0 || + av_size_mult(block_count, ASTC_BLOCK_BYTES, &out_size) < 0 || + out_size > INT_MAX) + return AVERROR(EINVAL); + + struct astcenc_swizzle swz = { + ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A + }; + + uint8_t *buf = NULL; + uint8_t *packed; /* buffer handed to astcenc, buf or the frame itself */ + void *data_ptrs[1]; + struct astcenc_image img; + enum astcenc_error err; + int ret; + + /* Map input pixel format -> astcenc data type and component layout. + * astcenc always consumes a tightly packed, RGBA-ordered buffer. */ + int elem; /* bytes per component */ + int planar; /* GBRAPF32 stores G,B,R,A on separate planes */ + int const_alpha; /* RGB24: synthesize opaque alpha */ + enum astcenc_type type; + + switch (frame->format) { + case AV_PIX_FMT_RGBA: type = ASTCENC_TYPE_U8; elem = 1; planar = 0; const_alpha = 0; break; + case AV_PIX_FMT_RGB24: type = ASTCENC_TYPE_U8; elem = 1; planar = 0; const_alpha = 1; break; + case AV_PIX_FMT_RGBAF16: type = ASTCENC_TYPE_F16; elem = 2; planar = 0; const_alpha = 0; break; + case AV_PIX_FMT_RGBAF32: type = ASTCENC_TYPE_F32; elem = 4; planar = 0; const_alpha = 0; break; + case AV_PIX_FMT_GBRAPF32: type = ASTCENC_TYPE_F32; elem = 4; planar = 1; const_alpha = 0; break; + default: + av_log(avctx, AV_LOG_ERROR, "Unsupported input pixel format %s.\n", + av_get_pix_fmt_name(frame->format)); + return AVERROR(EINVAL); + } + + size_t row_bytes, buf_size; + if (av_size_mult(w, 4 * elem, &row_bytes) < 0 || + av_size_mult(h, row_bytes, &buf_size) < 0) + return AVERROR(EINVAL); + + /* Packed input whose lines already have the size astcenc expects needs no + * repacking, so it can be consumed in place. */ + if (!planar && !const_alpha && frame->linesize[0] == (int)row_bytes) { + packed = frame->data[0]; + } else { + buf = av_malloc(buf_size); + if (!buf) + return AVERROR(ENOMEM); + packed = buf; + + if (planar) { + /* GBRAPF32 -> packed RGBA float. */ + for (int y = 0; y < h; y++) { + const float *g = (const float *)(frame->data[0] + (size_t)y * frame->linesize[0]); + const float *b = (const float *)(frame->data[1] + (size_t)y * frame->linesize[1]); + const float *r = (const float *)(frame->data[2] + (size_t)y * frame->linesize[2]); + const float *a = (const float *)(frame->data[3] + (size_t)y * frame->linesize[3]); + float *dst = (float *)(buf + (size_t)y * row_bytes); + for (int x = 0; x < w; x++) { + dst[4*x+0] = r[x]; dst[4*x+1] = g[x]; + dst[4*x+2] = b[x]; dst[4*x+3] = a[x]; + } + } + } else if (const_alpha) { + for (int y = 0; y < h; y++) { + const uint8_t *src = frame->data[0] + (size_t)y * frame->linesize[0]; + uint8_t *dst = buf + (size_t)y * row_bytes; + for (int x = 0; x < w; x++) { + dst[4*x+0] = src[3*x+0]; dst[4*x+1] = src[3*x+1]; + dst[4*x+2] = src[3*x+2]; dst[4*x+3] = 255; + } + } + } else { + /* packed RGBA / RGBA16 / RGBA32: copy row-by-row to drop line padding. */ + for (int y = 0; y < h; y++) + memcpy(buf + (size_t)y * row_bytes, + frame->data[0] + (size_t)y * frame->linesize[0], row_bytes); + } + } + + img.dim_x = w; + img.dim_y = h; + img.dim_z = 1; + img.data_type = type; + data_ptrs[0] = packed; + img.data = data_ptrs; + + ret = ff_get_encode_buffer(avctx, pkt, out_size, 0); + if (ret < 0) + goto end; + + err = astcenc_compress_image(s->ctx, &img, &swz, pkt->data, pkt->size, 0); + if (err != ASTCENC_SUCCESS) { + av_log(avctx, AV_LOG_ERROR, "astcenc_compress_image failed: %s\n", + astcenc_get_error_string(err)); + ret = AVERROR_UNKNOWN; + goto end; + } + + *got_packet = 1; + ret = 0; + +end: + av_freep(&buf); + return ret; +} + +static av_cold int libastcenc_close(AVCodecContext *avctx) +{ + LibAstcEncContext *s = avctx->priv_data; + if (s->ctx) { + astcenc_context_free(s->ctx); + s->ctx = NULL; + } + return 0; +} + +#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM +#define OFFSET(x) offsetof(LibAstcEncContext, x) + +static const AVOption options[] = { + { "block_size", "ASTC block size WxH[xD] (e.g. 4x4, 8x8, 6x6x6)", OFFSET(block_size_str), + AV_OPT_TYPE_STRING, { .str = "8x8" }, 0, 0, VE }, + { "quality", "Compression quality 0..100 (0=fastest, 100=exhaustive)", + OFFSET(quality), AV_OPT_TYPE_FLOAT, { .dbl = 60.0 }, 0.0, 100.0, VE }, + { "profile", "Color profile (overrides AVCodecContext.profile)", + OFFSET(profile), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 3, VE, .unit = "profile" }, + { "ldr", "Linear LDR", 0, AV_OPT_TYPE_CONST, { .i64 = ASTCENC_PRF_LDR }, 0, 0, VE, .unit = "profile" }, + { "ldr-srgb", "sRGB LDR", 0, AV_OPT_TYPE_CONST, { .i64 = ASTCENC_PRF_LDR_SRGB }, 0, 0, VE, .unit = "profile" }, + { "hdr-ldr-a", "HDR RGB, LDR alpha", 0, AV_OPT_TYPE_CONST, { .i64 = ASTCENC_PRF_HDR_RGB_LDR_A }, 0, 0, VE, .unit = "profile" }, + { "hdr", "HDR", 0, AV_OPT_TYPE_CONST, { .i64 = ASTCENC_PRF_HDR }, 0, 0, VE, .unit = "profile" }, + { "perceptual", "Use perceptual (PSNR-weighted) error metric", OFFSET(perceptual), + AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, VE }, + { "alpha_weight", "Enable alpha weighting", OFFSET(alpha_weight), + AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, VE }, + { NULL }, +}; + +static const AVClass libastcenc_class = { + .class_name = "libastcenc", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +const FFCodec ff_libastcenc_encoder = { + .p.name = "libastcenc", + CODEC_LONG_NAME("ASTC (Adaptive Scalable Texture Compression) image using astc-encoder"), + .p.type = AVMEDIA_TYPE_VIDEO, + .p.id = AV_CODEC_ID_ASTC, + .p.capabilities = AV_CODEC_CAP_DR1, + CODEC_PIXFMTS(AV_PIX_FMT_RGBA, AV_PIX_FMT_RGB24, + AV_PIX_FMT_RGBAF16, AV_PIX_FMT_RGBAF32, AV_PIX_FMT_GBRAPF32), + .p.profiles = NULL_IF_CONFIG_SMALL(ff_astc_profiles), + .p.priv_class = &libastcenc_class, + .p.wrapper_name = "libastcenc", + .caps_internal = FF_CODEC_CAP_NOT_INIT_THREADSAFE | + FF_CODEC_CAP_INIT_CLEANUP, + .priv_data_size = sizeof(LibAstcEncContext), + .init = libastcenc_init, + FF_CODEC_ENCODE_CB(libastcenc_encode), + .close = libastcenc_close, +}; diff --git a/libavcodec/profiles.c b/libavcodec/profiles.c index 1b67870c43..0e572e758f 100644 --- a/libavcodec/profiles.c +++ b/libavcodec/profiles.c @@ -220,4 +220,13 @@ const AVProfile ff_apv_profiles[] = { { AV_PROFILE_UNKNOWN }, }; +const AVProfile ff_astc_profiles[] = { + { AV_PROFILE_ASTC_LDR_SRGB, "sRGB LDR" }, + { AV_PROFILE_ASTC_LDR, "Linear LDR" }, + { AV_PROFILE_ASTC_HDR_RGB_LDR_A, "HDR RGB, LDR alpha" }, + { AV_PROFILE_ASTC_HDR, "HDR" }, + { AV_PROFILE_ASTC_LINEAR_ANY, "Linear (any)" }, + { AV_PROFILE_UNKNOWN }, +}; + #endif /* !CONFIG_SMALL */ diff --git a/libavcodec/profiles.h b/libavcodec/profiles.h index 6f4011ff0c..60dd426a9c 100644 --- a/libavcodec/profiles.h +++ b/libavcodec/profiles.h @@ -79,5 +79,6 @@ extern const AVProfile ff_mjpeg_profiles[]; extern const AVProfile ff_arib_caption_profiles[]; extern const AVProfile ff_evc_profiles[]; extern const AVProfile ff_apv_profiles[]; +extern const AVProfile ff_astc_profiles[]; #endif /* AVCODEC_PROFILES_H */ diff --git a/libavcodec/version.h b/libavcodec/version.h index da2264a097..755c90bbc1 100644 --- a/libavcodec/version.h +++ b/libavcodec/version.h @@ -29,7 +29,7 @@ #include "version_major.h" -#define LIBAVCODEC_VERSION_MINOR 12 +#define LIBAVCODEC_VERSION_MINOR 13 #define LIBAVCODEC_VERSION_MICRO 100 #define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \ -- To stop receiving notification emails like this one, please contact [email protected]. _______________________________________________ ffmpeg-cvslog mailing list -- [email protected] To unsubscribe send an email to [email protected]
