FFmpeg
Loading...
Searching...
No Matches
libastcenc.c
Go to the documentation of this file.
1/*
2 * astc-encoder wrapper for FFmpeg (ASTC image encoder)
3 *
4 * Copyright (C) 2026 Jun Zhao
5 *
6 * This file is part of FFmpeg.
7 *
8 * FFmpeg is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * FFmpeg is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with FFmpeg; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 */
22
23/**
24 * @file
25 * ASTC encoder using ARM's astc-encoder library (astcenc C API).
26 *
27 * Output is the raw ASTC bitstream (16 bytes per block). The container
28 * (.astc / .ktx) is added by the corresponding FFmpeg muxer.
29 */
30
31#include <astcenc/astcenc.h>
32
33#include <limits.h>
34
35#include "avcodec.h"
36#include "codec_internal.h"
37#include "encode.h"
38#include "profiles.h"
40#include "libavutil/pixdesc.h"
41#include "libavutil/mem.h"
42#include "libavutil/opt.h"
43
44/* .astc container magic (little-endian 0x5CA1AB13). */
45#define ASTC_MAGIC 0x5CA1AB13
46#define ASTC_HEADER_SIZE 16
47#define ASTC_BLOCK_BYTES 16
48
49typedef struct LibAstcEncContext {
50 AVClass *class;
51
52 /* astc-encoder state */
53 struct astcenc_config cfg;
54 struct astcenc_context *ctx;
55
56 /* options */
61 int profile; /* astcenc_profile */
62 float quality; /* 0..100 */
66
68{
70 enum astcenc_profile prf;
71 unsigned int flags = 0;
72 enum astcenc_error err;
73
74 /* An explicitly set private profile option overrides the public
75 * AVCodecContext.profile field. AV_PROFILE_ASTC_LINEAR_ANY describes how a
76 * container should interpret a stream and never selects an encoding
77 * profile, so it is rejected here. */
78 if (s->profile >= 0) {
79 prf = (enum astcenc_profile)s->profile;
80 } else {
81 switch (avctx->profile) {
84 prf = ASTCENC_PRF_LDR_SRGB;
85 break;
87 prf = ASTCENC_PRF_LDR;
88 break;
90 prf = ASTCENC_PRF_HDR_RGB_LDR_A;
91 break;
93 prf = ASTCENC_PRF_HDR;
94 break;
95 default:
96 av_log(avctx, AV_LOG_ERROR,
97 "Unsupported ASTC encoding profile %d.\n", avctx->profile);
98 return AVERROR(EINVAL);
99 }
100 }
101
102 /* Resolve profile vs input pixel format. Float/half input must be encoded
103 * with an HDR profile (auto-promoted here); 8-bit input cannot drive an HDR
104 * profile (it would be degenerate -> near-black). This runs at init (not in
105 * encode) because pix_fmt is known at open time; the resolved profile is
106 * propagated via avctx->profile (-> codecpar->profile) so muxers such as
107 * the KTX writer can reject HDR without needing to inspect extradata. */
108 int is_float = (avctx->pix_fmt == AV_PIX_FMT_RGBAF16 ||
109 avctx->pix_fmt == AV_PIX_FMT_RGBAF32 ||
110 avctx->pix_fmt == AV_PIX_FMT_GBRAPF32);
111 if (is_float) {
112 if (prf == ASTCENC_PRF_LDR || prf == ASTCENC_PRF_LDR_SRGB) {
113 prf = ASTCENC_PRF_HDR_RGB_LDR_A;
114 av_log(avctx, AV_LOG_INFO,
115 "Float/half input: auto-promoted profile to HDR_RGB_LDR_A.\n");
116 }
117 } else if (prf == ASTCENC_PRF_HDR || prf == ASTCENC_PRF_HDR_RGB_LDR_A) {
118 av_log(avctx, AV_LOG_ERROR,
119 "HDR profile selected but input is 8-bit; use float/half "
120 "input (e.g. -pix_fmt rgbaf16) for HDR.\n");
121 return AVERROR(EINVAL);
122 }
123 s->profile = (int)prf;
124 /* Publish the profile the container records, as one of the public ASTC
125 * profile values rather than as the astcenc enum, whose ordering is not
126 * part of the FFmpeg API. */
127 switch (prf) {
128 case ASTCENC_PRF_LDR_SRGB:
130 break;
131 case ASTCENC_PRF_LDR:
133 break;
134 case ASTCENC_PRF_HDR_RGB_LDR_A:
136 break;
137 case ASTCENC_PRF_HDR:
139 break;
140 default:
142 break;
143 }
144
145 if (avctx->width <= 0 || avctx->height <= 0 ||
146 avctx->width > 0xFFFFFF || avctx->height > 0xFFFFFF) {
147 av_log(avctx, AV_LOG_ERROR, "Invalid image dimensions %dx%d.\n",
148 avctx->width, avctx->height);
149 return AVERROR(EINVAL);
150 }
151
152 /* Accept exactly "WxH" or "WxHxD". Scanning stops at the third literal 'x'
153 * when it is absent, so trailing garbage such as "8x8junk" has to be
154 * caught by checking that the whole string was consumed. */
155 int consumed = 0;
156
157 s->block_z = 1;
158 if (sscanf(s->block_size_str, "%dx%dx%d%n", &s->block_x, &s->block_y,
159 &s->block_z, &consumed) != 3) {
160 consumed = 0;
161 s->block_z = 1;
162 sscanf(s->block_size_str, "%dx%d%n", &s->block_x, &s->block_y,
163 &consumed);
164 }
165 if (!consumed || s->block_size_str[consumed]) {
166 av_log(avctx, AV_LOG_ERROR, "Invalid block_size '%s' "
167 "(expected e.g. 8x8 or 6x6x6).\n", s->block_size_str);
168 return AVERROR(EINVAL);
169 }
170
171 if (s->perceptual)
172 flags |= ASTCENC_FLG_USE_PERCEPTUAL;
173 if (s->alpha_weight)
174 flags |= ASTCENC_FLG_USE_ALPHA_WEIGHT;
175
176 err = astcenc_config_init(prf, s->block_x, s->block_y, s->block_z,
177 s->quality, flags, &s->cfg);
178 if (err != ASTCENC_SUCCESS) {
179 av_log(avctx, AV_LOG_ERROR, "astcenc_config_init failed: %s\n",
180 astcenc_get_error_string(err));
181 return AVERROR_UNKNOWN;
182 }
183
184 /* astcenc normalizes the requested footprint, e.g. a zero block depth is
185 * clamped to one, so publish the validated footprint instead of the parsed
186 * one. Both the .astc header below and the block accounting in encode()
187 * must agree with what the library actually compresses with. */
188 s->block_x = s->cfg.block_x;
189 s->block_y = s->cfg.block_y;
190 s->block_z = s->cfg.block_z;
191
192 err = astcenc_context_alloc(&s->cfg, 1, &s->ctx, NULL);
193 if (err != ASTCENC_SUCCESS) {
194 av_log(avctx, AV_LOG_ERROR, "astcenc_context_alloc failed: %s\n",
195 astcenc_get_error_string(err));
196 return AVERROR(ENOMEM);
197 }
198
199 /* Publish a 16-byte .astc-style header as extradata so the .astc/.ktx
200 * muxers can write the container header and the decoder can recover the
201 * block size and dimensions. The buffer is padded so consumers that read
202 * it as a bitstream do not over-read. */
204 if (!avctx->extradata)
205 return AVERROR(ENOMEM);
207 {
208 uint8_t *h = avctx->extradata;
209 /* .astc header layout (all little-endian):
210 * [0-3] magic
211 * [4-6] block dimensions block_x, block_y, block_z (block_z > 1
212 * selects a 3D block footprint, e.g. 4x4x4)
213 * [7-9] image width (dim_x, texels)
214 * [10-12] image height (dim_y, texels)
215 * [13-15] image depth (dim_z, texel slices; 1 for a 2D image)
216 * block_z (the block footprint) is independent of dim_z (the image
217 * slice count), so a 3D block can still describe a 2D image. */
219 h[4] = (uint8_t)s->block_x;
220 h[5] = (uint8_t)s->block_y;
221 h[6] = (uint8_t)s->block_z;
222 AV_WL24(h + 7, avctx->width);
223 AV_WL24(h + 10, avctx->height);
224 AV_WL24(h + 13, 1); /* image depth is 1: 2D image (3D blocks stay 2D) */
225 }
226
227 return 0;
228}
229
231 const AVFrame *frame, int *got_packet)
232{
233 LibAstcEncContext *s = avctx->priv_data;
234 const int w = frame->width;
235 const int h = frame->height;
236 const int bx = s->block_x;
237 const int by = s->block_y;
238 size_t blocks_x, blocks_y, block_count, out_size;
239
240 if (w != avctx->width || h != avctx->height) {
241 av_log(avctx, AV_LOG_ERROR,
242 "Frame size %dx%d does not match configured %dx%d.\n",
243 w, h, avctx->width, avctx->height);
244 return AVERROR_INVALIDDATA;
245 }
246
247 blocks_x = (w - 1) / bx + 1;
248 blocks_y = (h - 1) / by + 1;
249 if (av_size_mult(blocks_x, blocks_y, &block_count) < 0 ||
250 av_size_mult(block_count, ASTC_BLOCK_BYTES, &out_size) < 0 ||
251 out_size > INT_MAX)
252 return AVERROR(EINVAL);
253
254 struct astcenc_swizzle swz = {
255 ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A
256 };
257
258 uint8_t *buf = NULL;
259 uint8_t *packed; /* buffer handed to astcenc, buf or the frame itself */
260 void *data_ptrs[1];
261 struct astcenc_image img;
262 enum astcenc_error err;
263 int ret;
264
265 /* Map input pixel format -> astcenc data type and component layout.
266 * astcenc always consumes a tightly packed, RGBA-ordered buffer. */
267 int elem; /* bytes per component */
268 int planar; /* GBRAPF32 stores G,B,R,A on separate planes */
269 int const_alpha; /* RGB24: synthesize opaque alpha */
270 enum astcenc_type type;
271
272 switch (frame->format) {
273 case AV_PIX_FMT_RGBA: type = ASTCENC_TYPE_U8; elem = 1; planar = 0; const_alpha = 0; break;
274 case AV_PIX_FMT_RGB24: type = ASTCENC_TYPE_U8; elem = 1; planar = 0; const_alpha = 1; break;
275 case AV_PIX_FMT_RGBAF16: type = ASTCENC_TYPE_F16; elem = 2; planar = 0; const_alpha = 0; break;
276 case AV_PIX_FMT_RGBAF32: type = ASTCENC_TYPE_F32; elem = 4; planar = 0; const_alpha = 0; break;
277 case AV_PIX_FMT_GBRAPF32: type = ASTCENC_TYPE_F32; elem = 4; planar = 1; const_alpha = 0; break;
278 default:
279 av_log(avctx, AV_LOG_ERROR, "Unsupported input pixel format %s.\n",
280 av_get_pix_fmt_name(frame->format));
281 return AVERROR(EINVAL);
282 }
283
284 size_t row_bytes, buf_size;
285 if (av_size_mult(w, 4 * elem, &row_bytes) < 0 ||
286 av_size_mult(h, row_bytes, &buf_size) < 0)
287 return AVERROR(EINVAL);
288
289 /* Packed input whose lines already have the size astcenc expects needs no
290 * repacking, so it can be consumed in place. */
291 if (!planar && !const_alpha && frame->linesize[0] == (int)row_bytes) {
292 packed = frame->data[0];
293 } else {
294 buf = av_malloc(buf_size);
295 if (!buf)
296 return AVERROR(ENOMEM);
297 packed = buf;
298
299 if (planar) {
300 /* GBRAPF32 -> packed RGBA float. */
301 for (int y = 0; y < h; y++) {
302 const float *g = (const float *)(frame->data[0] + (size_t)y * frame->linesize[0]);
303 const float *b = (const float *)(frame->data[1] + (size_t)y * frame->linesize[1]);
304 const float *r = (const float *)(frame->data[2] + (size_t)y * frame->linesize[2]);
305 const float *a = (const float *)(frame->data[3] + (size_t)y * frame->linesize[3]);
306 float *dst = (float *)(buf + (size_t)y * row_bytes);
307 for (int x = 0; x < w; x++) {
308 dst[4*x+0] = r[x]; dst[4*x+1] = g[x];
309 dst[4*x+2] = b[x]; dst[4*x+3] = a[x];
310 }
311 }
312 } else if (const_alpha) {
313 for (int y = 0; y < h; y++) {
314 const uint8_t *src = frame->data[0] + (size_t)y * frame->linesize[0];
315 uint8_t *dst = buf + (size_t)y * row_bytes;
316 for (int x = 0; x < w; x++) {
317 dst[4*x+0] = src[3*x+0]; dst[4*x+1] = src[3*x+1];
318 dst[4*x+2] = src[3*x+2]; dst[4*x+3] = 255;
319 }
320 }
321 } else {
322 /* packed RGBA / RGBA16 / RGBA32: copy row-by-row to drop line padding. */
323 for (int y = 0; y < h; y++)
324 memcpy(buf + (size_t)y * row_bytes,
325 frame->data[0] + (size_t)y * frame->linesize[0], row_bytes);
326 }
327 }
328
329 img.dim_x = w;
330 img.dim_y = h;
331 img.dim_z = 1;
332 img.data_type = type;
333 data_ptrs[0] = packed;
334 img.data = data_ptrs;
335
336 ret = ff_get_encode_buffer(avctx, pkt, out_size, 0);
337 if (ret < 0)
338 goto end;
339
340 err = astcenc_compress_image(s->ctx, &img, &swz, pkt->data, pkt->size, 0);
341 if (err != ASTCENC_SUCCESS) {
342 av_log(avctx, AV_LOG_ERROR, "astcenc_compress_image failed: %s\n",
343 astcenc_get_error_string(err));
344 ret = AVERROR_UNKNOWN;
345 goto end;
346 }
347
348 *got_packet = 1;
349 ret = 0;
350
351end:
352 av_freep(&buf);
353 return ret;
354}
355
357{
358 LibAstcEncContext *s = avctx->priv_data;
359 if (s->ctx) {
360 astcenc_context_free(s->ctx);
361 s->ctx = NULL;
362 }
363 return 0;
364}
365
366#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
367#define OFFSET(x) offsetof(LibAstcEncContext, x)
368
369static const AVOption options[] = {
370 { "block_size", "ASTC block size WxH[xD] (e.g. 4x4, 8x8, 6x6x6)", OFFSET(block_size_str),
371 AV_OPT_TYPE_STRING, { .str = "8x8" }, 0, 0, VE },
372 { "quality", "Compression quality 0..100 (0=fastest, 100=exhaustive)",
373 OFFSET(quality), AV_OPT_TYPE_FLOAT, { .dbl = 60.0 }, 0.0, 100.0, VE },
374 { "profile", "Color profile (overrides AVCodecContext.profile)",
375 OFFSET(profile), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 3, VE, .unit = "profile" },
376 { "ldr", "Linear LDR", 0, AV_OPT_TYPE_CONST, { .i64 = ASTCENC_PRF_LDR }, 0, 0, VE, .unit = "profile" },
377 { "ldr-srgb", "sRGB LDR", 0, AV_OPT_TYPE_CONST, { .i64 = ASTCENC_PRF_LDR_SRGB }, 0, 0, VE, .unit = "profile" },
378 { "hdr-ldr-a", "HDR RGB, LDR alpha", 0, AV_OPT_TYPE_CONST, { .i64 = ASTCENC_PRF_HDR_RGB_LDR_A }, 0, 0, VE, .unit = "profile" },
379 { "hdr", "HDR", 0, AV_OPT_TYPE_CONST, { .i64 = ASTCENC_PRF_HDR }, 0, 0, VE, .unit = "profile" },
380 { "perceptual", "Use perceptual (PSNR-weighted) error metric", OFFSET(perceptual),
381 AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, VE },
382 { "alpha_weight", "Enable alpha weighting", OFFSET(alpha_weight),
383 AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, VE },
384 { NULL },
385};
386
387static const AVClass libastcenc_class = {
388 .class_name = "libastcenc",
389 .item_name = av_default_item_name,
390 .option = options,
391 .version = LIBAVUTIL_VERSION_INT,
392};
393
395 .p.name = "libastcenc",
396 CODEC_LONG_NAME("ASTC (Adaptive Scalable Texture Compression) image using astc-encoder"),
397 .p.type = AVMEDIA_TYPE_VIDEO,
398 .p.id = AV_CODEC_ID_ASTC,
399 .p.capabilities = AV_CODEC_CAP_DR1,
403 .p.priv_class = &libastcenc_class,
404 .p.wrapper_name = "libastcenc",
405 .caps_internal = FF_CODEC_CAP_NOT_INIT_THREADSAFE |
407 .priv_data_size = sizeof(LibAstcEncContext),
410 .close = libastcenc_close,
411};
uint8_t ptrdiff_t const uint8_t ptrdiff_t int intptr_t intptr_t int int16_t * dst
Definition dsp.h:87
const FFCodec ff_libastcenc_encoder
Definition libastcenc.c:394
#define VE
Definition amfenc_av1.c:30
static int out_size
Libavcodec external API header.
#define flags(name, subs,...)
Definition cbs_h264.c:74
#define s(width, name)
Definition cbs_vp9.c:198
static IPT perceptual(const CmsCtx *ctx, IPT ipt)
Definition cms.c:520
#define CODEC_PIXFMTS(...)
#define FF_CODEC_CAP_NOT_INIT_THREADSAFE
The codec is not known to be init-threadsafe (i.e.
#define FF_CODEC_ENCODE_CB(func)
#define CODEC_LONG_NAME(str)
#define FF_CODEC_CAP_INIT_CLEANUP
The codec allows calling the close function for deallocation even if the init function returned a fai...
#define NULL
Definition coverity.c:32
#define AV_PROFILE_UNKNOWN
Definition defs.h:65
#define AV_PROFILE_ASTC_LDR
Linear LDR.
Definition defs.h:81
#define AV_PROFILE_ASTC_HDR
HDR RGB and alpha.
Definition defs.h:83
#define AV_PROFILE_ASTC_HDR_RGB_LDR_A
HDR RGB with LDR alpha.
Definition defs.h:82
#define AV_PROFILE_ASTC_LDR_SRGB
sRGB LDR.
Definition defs.h:80
static AVPacket * pkt
static AVFrame * frame
int(* init)(AVBSFContext *ctx)
Definition dts2pts.c:608
int ff_get_encode_buffer(AVCodecContext *avctx, AVPacket *avpkt, int64_t size, int flags)
Get a buffer for a packet.
Definition encode.c:106
@ AV_OPT_TYPE_CONST
Special option type for declaring named constants.
Definition opt.h:298
@ AV_OPT_TYPE_INT
Underlying C type is int.
Definition opt.h:258
@ AV_OPT_TYPE_FLOAT
Underlying C type is float.
Definition opt.h:270
@ AV_OPT_TYPE_BOOL
Underlying C type is int.
Definition opt.h:326
@ AV_OPT_TYPE_STRING
Underlying C type is a uint8_t* that is either NULL or points to a C string allocated with the av_mal...
Definition opt.h:275
#define AV_CODEC_CAP_DR1
Codec uses get_buffer() or get_encode_buffer() for allocating buffers and supports custom allocators.
Definition codec.h:49
@ AV_CODEC_ID_ASTC
Definition codec_id.h:327
#define AV_INPUT_BUFFER_PADDING_SIZE
Required number of additionally allocated bytes at the end of the input bitstream for decoding.
Definition defs.h:40
#define AVERROR_UNKNOWN
Unknown error, typically from an external library.
Definition error.h:73
#define AVERROR_INVALIDDATA
Invalid data found when processing input.
Definition error.h:61
#define AVERROR(e)
Definition error.h:45
#define AV_LOG_INFO
Standard information.
Definition log.h:221
#define AV_LOG_ERROR
Something went wrong and cannot losslessly be recovered.
Definition log.h:210
const char * av_default_item_name(void *ptr)
Return the context name.
Definition log.c:241
int av_size_mult(size_t a, size_t b, size_t *r)
Multiply two size_t values checking for overflow.
Definition mem.c:565
@ AVMEDIA_TYPE_VIDEO
Definition avutil.h:200
#define LIBAVUTIL_VERSION_INT
Definition version.h:85
int a
cl_device_type type
#define r
Definition input.c:42
#define b
Definition input.c:43
#define AV_WL32(p, v)
#define AV_WL24(p, d)
#define ASTC_MAGIC
Definition libastcdec.c:42
#define ASTC_BLOCK_BYTES
Definition libastcdec.c:44
#define ASTC_HEADER_SIZE
Definition libastcdec.c:43
static av_cold int libastcenc_init(AVCodecContext *avctx)
Definition libastcenc.c:67
static av_cold int libastcenc_close(AVCodecContext *avctx)
Definition libastcenc.c:356
static const AVClass libastcenc_class
Definition libastcenc.c:387
static int libastcenc_encode(AVCodecContext *avctx, AVPacket *pkt, const AVFrame *frame, int *got_packet)
Definition libastcenc.c:230
#define OFFSET(x)
Definition libastcenc.c:367
#define av_cold
Definition attributes.h:117
#define NULL_IF_CONFIG_SMALL(x)
Return NULL if CONFIG_SMALL is true, otherwise the argument without modification.
Definition internal.h:97
uint8_t w
Definition llvidencdsp.c:39
Memory handling functions.
int profile
Definition mxfenc.c:2299
#define av_malloc(s)
Definition ops_static.c:52
AVOptions.
const char * av_get_pix_fmt_name(enum AVPixelFormat pix_fmt)
Return the short name for a pixel format, NULL in case pix_fmt is unknown.
Definition pixdesc.c:3380
#define AV_PIX_FMT_RGBAF32
Definition pixfmt.h:633
#define AV_PIX_FMT_RGBAF16
Definition pixfmt.h:630
@ AV_PIX_FMT_RGB24
packed RGB 8:8:8, 24bpp, RGBRGB...
Definition pixfmt.h:75
@ AV_PIX_FMT_RGBA
packed RGBA 8:8:8:8, 32bpp, RGBARGBA...
Definition pixfmt.h:100
#define AV_PIX_FMT_GBRAPF32
Definition pixfmt.h:585
const AVProfile ff_astc_profiles[]
Definition profiles.c:223
Describe the class of an AVClass context structure.
Definition log.h:76
main external API structure.
Definition avcodec.h:443
enum AVPixelFormat pix_fmt
Pixel format, see AV_PIX_FMT_xxx.
Definition avcodec.h:643
int width
picture width / height.
Definition avcodec.h:604
int profile
profile
Definition avcodec.h:1641
uint8_t * extradata
Out-of-band global headers that may be used by some codecs.
Definition avcodec.h:526
int extradata_size
Definition avcodec.h:527
void * priv_data
Definition avcodec.h:470
This structure describes decoded (raw) audio or video data.
Definition frame.h:472
AVOption.
Definition opt.h:428
This structure stores compressed data.
Definition packet.h:580
struct astcenc_config cfg
Definition libastcenc.c:53
struct astcenc_context * ctx
Definition libastcenc.c:54
char * block_size_str
Definition libastcenc.c:57
#define av_mallocz(s)
#define av_freep(p)
#define av_log(a,...)
#define src
Definition vp8dsp.c:248
#define img
const char * g
Definition vf_curves.c:128
static const uint8_t quality[]
Definition vmixdec.c:58