FFmpeg
Loading...
Searching...
No Matches
libastcdec.c
Go to the documentation of this file.
1/*
2 * astc-encoder wrapper decoder for FFmpeg (ASTC image decoder)
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 decoder using ARM's astc-encoder library (astcenc C API).
26 *
27 * Consumes the raw ASTC bitstream; block size and dimensions are recovered
28 * from the 16-byte .astc-style extradata published by the demuxer.
29 */
30
31#include <astcenc/astcenc.h>
32
33#include "avcodec.h"
34#include "codec_internal.h"
35#include "decode.h"
36#include "profiles.h"
38#include "libavutil/mem.h"
39#include "libavutil/opt.h"
40
41/* .astc container magic (little-endian 0x5CA1AB13). */
42#define ASTC_MAGIC 0x5CA1AB13
43#define ASTC_HEADER_SIZE 16
44#define ASTC_BLOCK_BYTES 16
45
46typedef struct LibAstcDecContext {
47 AVClass *class;
48 struct astcenc_config cfg;
49 struct astcenc_context *ctx;
53 int is_hdr;
54 int dec_profile; /* astcenc_profile, set via private option, -1 = auto */
56
58 enum astcenc_profile prf)
59{
61 enum astcenc_error err;
62
63 if (s->ctx) {
64 astcenc_context_free(s->ctx);
65 s->ctx = NULL;
66 }
67
68 err = astcenc_config_init(prf, s->block_x, s->block_y, s->block_z,
69 0, ASTCENC_FLG_DECOMPRESS_ONLY, &s->cfg);
70 if (err != ASTCENC_SUCCESS) {
71 av_log(avctx, AV_LOG_ERROR, "astcenc_config_init failed: %s\n",
72 astcenc_get_error_string(err));
73 return AVERROR_UNKNOWN;
74 }
75
76 err = astcenc_context_alloc(&s->cfg, 1, &s->ctx, NULL);
77 if (err != ASTCENC_SUCCESS) {
78 av_log(avctx, AV_LOG_ERROR, "astcenc_context_alloc failed: %s\n",
79 astcenc_get_error_string(err));
80 return AVERROR(ENOMEM);
81 }
82
83 s->is_hdr = (prf == ASTCENC_PRF_HDR || prf == ASTCENC_PRF_HDR_RGB_LDR_A);
84 /* Leave avctx->profile untouched: it is a property of the stream, set by
85 * the container, while the profile resolved here is an internal decision.
86 * avformat_find_stream_info() copies this context back into the stream
87 * parameters, so publishing the resolved profile would, for example, turn
88 * a linear KTX into a stream the KTX muxer refuses to copy. */
89 avctx->pix_fmt = s->is_hdr ? AV_PIX_FMT_RGBAF16 : AV_PIX_FMT_RGBA;
90 return 0;
91}
92
94{
96 const uint8_t *ed = avctx->extradata;
97
98 if (!ed || avctx->extradata_size < ASTC_HEADER_SIZE) {
99 av_log(avctx, AV_LOG_ERROR,
100 "astc decoder requires %d-byte .astc-style extradata.\n",
102 return AVERROR_INVALIDDATA;
103 }
104
105 /* .astc header layout (all little-endian):
106 * [0-3] magic
107 * [4-6] block dimensions block_x, block_y, block_z (block_z > 1
108 * selects a 3D block footprint, e.g. 4x4x4)
109 * [7-9] image width (dim_x, texels)
110 * [10-12] image height (dim_y, texels)
111 * [13-15] image depth (dim_z, texel slices; 1 for a 2D image)
112 * block_z (the block footprint) is independent of dim_z (the image
113 * slice count), so a 3D block can still describe a 2D image. */
114 if (AV_RL32(ed) != ASTC_MAGIC || AV_RL24(ed + 13) != 1) {
115 av_log(avctx, AV_LOG_ERROR, "Invalid ASTC header.\n");
116 return AVERROR_INVALIDDATA;
117 }
118
119 s->block_x = ed[4];
120 s->block_y = ed[5];
121 s->block_z = ed[6];
122 avctx->width = AV_RL24(ed + 7);
123 avctx->height = AV_RL24(ed + 10);
124 /* Zero footprints would divide by zero in the block accounting below; the
125 * .astc demuxer rejects them, but the decoder must not trust its input. */
126 if (!s->block_x || !s->block_y || !s->block_z ||
127 avctx->width <= 0 || avctx->height <= 0) {
128 av_log(avctx, AV_LOG_ERROR,
129 "Invalid ASTC header: block %dx%dx%d, image %dx%d.\n",
130 s->block_x, s->block_y, s->block_z,
131 avctx->width, avctx->height);
132 return AVERROR_INVALIDDATA;
133 }
134
135 /* The .astc header does not record the intended decoding profile.
136 * Individual blocks identify their endpoint encodings, but that does not
137 * establish the sRGB versus linear interpretation. Use the container
138 * metadata or -dec_profile; without either, a raw .astc input defaults to
139 * ldr-srgb. A stream that records no profile at all keeps the
140 * AV_PROFILE_UNKNOWN default and lands in the fallback branch below. */
141 enum astcenc_profile prf = (enum astcenc_profile)s->dec_profile;
142 if (s->dec_profile < 0) {
143 switch (avctx->profile) {
145 prf = ASTCENC_PRF_LDR_SRGB;
146 break;
148 prf = ASTCENC_PRF_LDR;
149 break;
151 prf = ASTCENC_PRF_HDR_RGB_LDR_A;
152 break;
154 prf = ASTCENC_PRF_HDR;
155 break;
157 /* A linear texture may hold HDR endpoint encodings, so sample it
158 * with HDR precision; LDR endpoint blocks decode correctly through
159 * the same profile instead of coming back as the magenta error
160 * colour. */
161 prf = ASTCENC_PRF_HDR_RGB_LDR_A;
162 break;
163 default:
164 /* No container information (a raw .astc stream): the documented
165 * LDR sRGB fallback. HDR needs an explicit -dec_profile. */
166 prf = ASTCENC_PRF_LDR_SRGB;
167 break;
168 }
169 }
170
171 return astcdec_configure(avctx, prf);
172}
173
175 int *got_frame, AVPacket *avpkt)
176{
177 LibAstcDecContext *s = avctx->priv_data;
178 const int w = avctx->width;
179 const int h = avctx->height;
180 const int elem = s->is_hdr ? 2 : 1;
181 size_t row_bytes, buf_size, block_count, expected;
182
183 if (av_size_mult(w, 4 * elem, &row_bytes) < 0 ||
184 av_size_mult(h, row_bytes, &buf_size) < 0 ||
185 av_size_mult((w - 1) / s->block_x + 1,
186 (h - 1) / s->block_y + 1, &block_count) < 0 ||
187 av_size_mult(block_count, ASTC_BLOCK_BYTES, &expected) < 0)
188 return AVERROR_INVALIDDATA;
189 /* dim_z is always 1 (2D input), so blocks_z is always 1. */
190
191 struct astcenc_swizzle swz = {
192 ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A
193 };
194
195 uint8_t *out_buf = NULL;
196 void *data_ptrs[1];
197 struct astcenc_image img;
198 enum astcenc_error err;
199 int ret;
200
201 /* A single ASTC image is exactly the blocks its geometry needs; accepting
202 * a larger packet would silently ignore the trailing data below. */
203 if ((size_t)avpkt->size != expected) {
204 av_log(avctx, AV_LOG_ERROR,
205 "ASTC packet size %d does not match the %zu bytes required for "
206 "a %dx%d image with %dx%d blocks.\n",
207 avpkt->size, expected, w, h, s->block_x, s->block_y);
208 return AVERROR_INVALIDDATA;
209 }
210
211 frame->width = w;
212 frame->height = h;
213 if ((ret = ff_get_buffer(avctx, frame, 0)) < 0)
214 return ret;
215
216 /* astcenc writes a tightly packed image. If the frame has no line padding
217 * we can decode straight into it; otherwise use a temporary buffer and
218 * copy row by row. */
219 if (frame->linesize[0] == row_bytes) {
220 data_ptrs[0] = frame->data[0];
221 } else {
222 out_buf = av_malloc(buf_size);
223 if (!out_buf)
224 return AVERROR(ENOMEM);
225 data_ptrs[0] = out_buf;
226 }
227
228 img.dim_x = w;
229 img.dim_y = h;
230 img.dim_z = 1;
231 img.data_type = s->is_hdr ? ASTCENC_TYPE_F16 : ASTCENC_TYPE_U8;
232 img.data = data_ptrs;
233
234 err = astcenc_decompress_image(s->ctx, avpkt->data, avpkt->size,
235 &img, &swz, 0);
236 if (err != ASTCENC_SUCCESS) {
237 av_log(avctx, AV_LOG_ERROR, "astcenc_decompress_image failed: %s\n",
238 astcenc_get_error_string(err));
239 ret = AVERROR_UNKNOWN;
240 goto end;
241 }
242
243 if (out_buf) {
244 for (int y = 0; y < h; y++)
245 memcpy(frame->data[0] + (size_t)y * frame->linesize[0],
246 out_buf + (size_t)y * row_bytes, row_bytes);
247 }
248
249 *got_frame = 1;
250 ret = 0;
251
252end:
253 av_freep(&out_buf);
254 return ret;
255}
256
258{
259 LibAstcDecContext *s = avctx->priv_data;
260 if (s->ctx) {
261 astcenc_context_free(s->ctx);
262 s->ctx = NULL;
263 }
264 return 0;
265}
266
267#define VD AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_DECODING_PARAM
268#define OFFSET(x) offsetof(LibAstcDecContext, x)
269
270static const AVOption dec_options[] = {
271 { "dec_profile", "Decoder color profile (must match encoder)", OFFSET(dec_profile),
272 AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 3, VD, .unit = "dec_profile" },
273 { "ldr", "Linear LDR", 0, AV_OPT_TYPE_CONST, { .i64 = ASTCENC_PRF_LDR }, 0, 0, VD, .unit = "dec_profile" },
274 { "ldr-srgb", "sRGB LDR", 0, AV_OPT_TYPE_CONST, { .i64 = ASTCENC_PRF_LDR_SRGB }, 0, 0, VD, .unit = "dec_profile" },
275 { "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" },
276 { "hdr", "HDR", 0, AV_OPT_TYPE_CONST, { .i64 = ASTCENC_PRF_HDR }, 0, 0, VD, .unit = "dec_profile" },
277 { NULL },
278};
279
281 .class_name = "libastcenc decoder",
282 .item_name = av_default_item_name,
283 .option = dec_options,
284 .version = LIBAVUTIL_VERSION_INT,
285};
286
288 .p.name = "libastcenc",
289 CODEC_LONG_NAME("ASTC (Adaptive Scalable Texture Compression) image using astc-encoder"),
290 .p.type = AVMEDIA_TYPE_VIDEO,
291 .p.id = AV_CODEC_ID_ASTC,
292 .p.capabilities = AV_CODEC_CAP_DR1,
295 .p.priv_class = &libastcenc_dec_class,
296 .p.wrapper_name = "libastcenc",
297 .caps_internal = FF_CODEC_CAP_NOT_INIT_THREADSAFE |
299 .priv_data_size = sizeof(LibAstcDecContext),
302 .close = libastcdec_close,
303};
const FFCodec ff_libastcenc_decoder
Definition libastcdec.c:287
#define VD
Definition amfdec.c:787
Libavcodec external API header.
#define s(width, name)
Definition cbs_vp9.c:198
#define CODEC_PIXFMTS(...)
#define FF_CODEC_CAP_NOT_INIT_THREADSAFE
The codec is not known to be init-threadsafe (i.e.
#define FF_CODEC_DECODE_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
int ff_get_buffer(AVCodecContext *avctx, AVFrame *frame, int flags)
Get a buffer for a frame.
Definition decode.c:1777
#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
#define AV_PROFILE_ASTC_LINEAR_ANY
Definition defs.h:87
static AVFrame * frame
int(* init)(AVBSFContext *ctx)
Definition dts2pts.c:608
@ 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
#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 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_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
#define AV_RL24(x)
#define AV_RL32(p)
static av_cold int astcdec_configure(AVCodecContext *avctx, enum astcenc_profile prf)
Definition libastcdec.c:57
#define ASTC_MAGIC
Definition libastcdec.c:42
#define ASTC_BLOCK_BYTES
Definition libastcdec.c:44
static av_cold int libastcdec_init(AVCodecContext *avctx)
Definition libastcdec.c:93
static const AVClass libastcenc_dec_class
Definition libastcdec.c:280
#define ASTC_HEADER_SIZE
Definition libastcdec.c:43
static int libastcdec_decode(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacket *avpkt)
Definition libastcdec.c:174
#define OFFSET(x)
Definition libastcdec.c:268
static av_cold int libastcdec_close(AVCodecContext *avctx)
Definition libastcdec.c:257
static const AVOption dec_options[]
Definition libastcdec.c:270
#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.
#define av_malloc(s)
Definition ops_static.c:52
AVOptions.
#define AV_PIX_FMT_RGBAF16
Definition pixfmt.h:630
@ AV_PIX_FMT_RGBA
packed RGBA 8:8:8:8, 32bpp, RGBARGBA...
Definition pixfmt.h:100
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
int size
Definition packet.h:604
uint8_t * data
Definition packet.h:603
struct astcenc_config cfg
Definition libastcdec.c:48
struct astcenc_context * ctx
Definition libastcdec.c:49
#define av_freep(p)
#define av_log(a,...)
#define img