FFmpeg
Loading...
Searching...
No Matches
ktxenc.c
Go to the documentation of this file.
1/*
2 * KTX 1.0 muxer for ASTC
3 * Copyright (c) 2026 Jun Zhao
4 *
5 * This file is part of FFmpeg.
6 *
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * FFmpeg is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22/**
23 * @file
24 * KTX 1.0 muxer for ASTC textures.
25 *
26 * Writes the KTX 1.0 header (64 bytes) followed by a uint32 image size and
27 * the raw ASTC bitstream. The block size is recovered from the encoder's
28 * .astc-style extradata; the sRGB vs linear GL enum is chosen by the
29 * "srgb" muxer option.
30 */
31
32#include "avformat.h"
33#include "avio.h"
34#include "mux.h"
35#include "libavcodec/defs.h"
37#include "libavutil/opt.h"
38
39#define ASTC_HEADER_SIZE 16
40static const uint8_t astc_magic[4] = { 0x13, 0xAB, 0xA1, 0x5C };
41
42/* ASTC GL internal format enums (2D), linear base 0x93B0, sRGB = +0x20.
43 * Order matches the astc-encoder ASTC_FORMATS table. */
44static const int astc_gl_linear[14] = {
45 0x93B0, 0x93B1, 0x93B2, 0x93B3, 0x93B4, 0x93B5, 0x93B6, 0x93B7,
46 0x93B8, 0x93B9, 0x93BA, 0x93BB, 0x93BC, 0x93BD
47};
48static const int astc_gl_srgb[14] = {
49 0x93D0, 0x93D1, 0x93D2, 0x93D3, 0x93D4, 0x93D5, 0x93D6, 0x93D7,
50 0x93D8, 0x93D9, 0x93DA, 0x93DB, 0x93DC, 0x93DD
51};
52/* Block sizes paired with the enum tables above. */
53static const int astc_bx[14] = { 4, 5, 5, 6, 6, 8, 8, 8, 10, 10, 10, 10, 12, 12 };
54static const int astc_by[14] = { 4, 4, 5, 5, 6, 5, 6, 8, 5, 6, 8, 10, 10, 12 };
55
56typedef struct KTXMuxerContext {
57 AVClass *class;
58 int srgb; /* -1 = auto (follow the encoder profile) */
61
62static const char ktx_orientation_key[] = "KTXorientation";
63
64/* Derive the stored row order from the stream's display matrix. The demuxer
65 * converts KTXorientation into exactly such a matrix, so a stream copied from
66 * a flipped KTX keeps its orientation instead of being relabelled. Any other
67 * transform is rejected rather than silently dropped: KTXorientation only has
68 * room for axis flips, and applying a rotation or a scale to the pixels would
69 * mean decoding and re-encoding the texture. */
71 int *hflip, int *vflip)
72{
73 const AVPacketSideData *sd;
74 const int32_t *matrix;
75
76 *hflip = *vflip = 0;
80 if (!sd || sd->size < 9 * sizeof(*matrix))
81 return 0;
82
83 matrix = (const int32_t *)sd->data;
84 /* The layout av_display_matrix_flip() produces: the 2x2 part (a, b, c, d)
85 * is diagonal with unit scales, negative when the axis is flipped, the
86 * translation (x, y) is zero, and the perspective terms (u, v, w) are the
87 * identity ones. Note that a, b, c, d, x and y are 16.16 fixed-point while
88 * u, v and w are 2.30. */
89 if (matrix[1] || matrix[2] || matrix[3] || matrix[5] ||
90 matrix[6] || matrix[7] || matrix[8] != 1 << 30 ||
91 FFABSU(matrix[0]) != 65536 || FFABSU(matrix[4]) != 65536) {
92 av_log(s, AV_LOG_ERROR, "Unsupported display matrix: KTXorientation "
93 "only records axis flips, not rotations, scales, translations "
94 "or perspective.\n");
95 return AVERROR(EINVAL);
96 }
97
98 *hflip = matrix[0] < 0;
99 *vflip = matrix[4] < 0;
100 return 0;
101}
102
104{
105 KTXMuxerContext *ctx = s->priv_data;
106 AVStream *st = s->streams[0];
107 const uint8_t *ed = st->codecpar->extradata;
108 uint8_t bx = 0, by = 0;
109 int glfmt = 0, i, srgb, profile_srgb, ret;
110 int hflip, vflip, kv_size, kv_padded;
111 uint8_t hdr[64];
112 char kv[32];
113 char orientation[8];
114
116 av_log(s, AV_LOG_ERROR, ".ktx muxer requires 16-byte extradata "
117 "(block size) from the encoder.\n");
118 return AVERROR(EINVAL);
119 }
120 if (st->codecpar->width <= 0 || st->codecpar->height <= 0 ||
121 st->codecpar->width > 0xFFFFFF || st->codecpar->height > 0xFFFFFF) {
122 av_log(s, AV_LOG_ERROR, "Invalid image dimensions %dx%d.\n",
123 st->codecpar->width, st->codecpar->height);
124 return AVERROR(EINVAL);
125 }
126 if (memcmp(ed, astc_magic, sizeof(astc_magic)) ||
127 AV_RL24(ed + 7) != st->codecpar->width ||
128 AV_RL24(ed + 10) != st->codecpar->height ||
129 AV_RL24(ed + 13) != 1) {
130 av_log(s, AV_LOG_ERROR, ".ktx muxer received invalid ASTC extradata.\n");
131 return AVERROR_INVALIDDATA;
132 }
133 bx = ed[4];
134 by = ed[5];
135
136 /* ASTC bitstreams do not record whether they hold LDR or HDR endpoints,
137 * and the KTX 1.0 GL enums do not express it either: the linear ASTC
138 * formats can also carry HDR endpoint encodings. Writing HDR through this
139 * muxer is simply not implemented, so reject the profiles that name an HDR
140 * endpoint format and point at the .astc container. A stream that only
141 * reports a linear colour space is not known to be HDR and is still
142 * written as a linear texture. */
146 "Writing HDR ASTC to KTX 1.0 is not supported; use the .astc "
147 "container for HDR output.\n");
148 return AVERROR(EINVAL);
149 }
150 /* codecpar->profile publishes the colour space (and, when it is known, the
151 * endpoint format) of the stream. Only the profiles that name a colour
152 * space determine the GL enum here; a stream without profile information
153 * leaves it undetermined. */
154 switch (st->codecpar->profile) {
156 profile_srgb = 1;
157 break;
160 profile_srgb = 0;
161 break;
162 default:
163 profile_srgb = -1;
164 break;
165 }
166
167 /* Auto keeps the color space the encoder was configured with, so that
168 * remuxing a linear KTX does not silently flip it to sRGB; a stream
169 * without profile information falls back to the sRGB ASTC format. */
170 srgb = ctx->srgb >= 0 ? ctx->srgb :
171 profile_srgb >= 0 ? profile_srgb : 1;
172
173 /* An explicit request must not contradict a known encoder profile. */
174 if (ctx->srgb >= 0 && profile_srgb >= 0 && profile_srgb != ctx->srgb) {
176 "KTX srgb option does not match the encoder profile.\n");
177 return AVERROR(EINVAL);
178 }
179
180 /* Writing 3D ASTC blocks is not implemented: only the 2D GL internal
181 * formats are emitted. */
182 if (ed[6] != 1) {
184 "Writing 3D ASTC blocks to KTX is not supported; use the .astc "
185 "container for 3D block output.\n");
186 return AVERROR(EINVAL);
187 }
188
189 for (i = 0; i < 14; i++) {
190 if (astc_bx[i] == bx && astc_by[i] == by) {
191 glfmt = srgb ? astc_gl_srgb[i] : astc_gl_linear[i];
192 break;
193 }
194 }
195 if (!glfmt) {
196 av_log(s, AV_LOG_ERROR, "Unsupported ASTC block size %dx%d for KTX.\n", bx, by);
197 return AVERROR(EINVAL);
198 }
199
200 /* The stored rows are passed through unchanged, so describe them as they
201 * are: the encoder writes them top-down, left-to-right, unless the input
202 * brings its own orientation from a previous KTX. */
203 ret = ktx_orientation_from_side_data(s, st, &hflip, &vflip);
204 if (ret < 0)
205 return ret;
206 memcpy(orientation, "S=r,T=d", sizeof(orientation));
207 orientation[2] = hflip ? 'l' : 'r';
208 orientation[6] = vflip ? 'u' : 'd';
209
210 memcpy(kv, ktx_orientation_key, sizeof(ktx_orientation_key));
211 memcpy(kv + sizeof(ktx_orientation_key), orientation, sizeof(orientation));
212 kv_size = sizeof(ktx_orientation_key) + sizeof(orientation);
213 kv_padded = (kv_size + 3) & ~3;
214
215 memset(hdr, 0, sizeof(hdr));
216 /* KTX 1.0 file identifier: 0xAB 'K' 'T' 'X' ' ' '1' '1' 0xBB 0x0D 0x0A 0x1A 0x0A */
217 hdr[0] = 0xAB; hdr[1] = 'K'; hdr[2] = 'T'; hdr[3] = 'X';
218 hdr[4] = ' '; hdr[5] = '1'; hdr[6] = '1'; hdr[7] = 0xBB;
219 hdr[8] = 0x0D; hdr[9] = 0x0A; hdr[10] = 0x1A; hdr[11] = 0x0A;
220 AV_WL32(hdr + 12, 0x04030201); /* endianness */
221 /* gl_type(16)=0, gl_type_size(20)=1, gl_format(24)=0 */
222 AV_WL32(hdr + 20, 1);
223 AV_WL32(hdr + 28, glfmt); /* gl_internal_format */
224 AV_WL32(hdr + 32, 0x1908); /* gl_base_internal_format = GL_RGBA */
225 AV_WL32(hdr + 36, st->codecpar->width); /* pixel_width */
226 AV_WL32(hdr + 40, st->codecpar->height); /* pixel_height */
227 /* pixel_depth(44)=0, array(48)=0 */
228 AV_WL32(hdr + 52, 1); /* number_of_faces */
229 AV_WL32(hdr + 56, 1); /* number_of_mipmap_levels */
230 AV_WL32(hdr + 60, 4 + kv_padded); /* bytes_of_key_value_data */
231
232 avio_write(s->pb, hdr, sizeof(hdr));
233
234 /* Self-describe the row convention so readers do not have to guess
235 * whether the texture is stored top-down or bottom-up. */
236 avio_wl32(s->pb, kv_size);
237 avio_write(s->pb, kv, kv_size);
238 for (i = kv_size; i % 4; i++)
239 avio_w8(s->pb, 0);
240
241 return 0;
242}
243
245{
246 KTXMuxerContext *ctx = s->priv_data;
247
248 /* The container holds a single image; FF_OFMT_FLAG_MAX_ONE_OF_EACH only
249 * limits the number of streams, so packets must be rejected here. */
250 if (ctx->wrote_image) {
252 ".ktx muxer supports a single image per file.\n");
253 return AVERROR(EINVAL);
254 }
255 ctx->wrote_image = 1;
256
257 avio_wl32(s->pb, pkt->size); /* imageSize */
258 avio_write(s->pb, pkt->data, pkt->size);
259 return 0;
260}
261
262#define OFFSET(x) offsetof(KTXMuxerContext, x)
263#define VE AV_OPT_FLAG_ENCODING_PARAM
264static const AVOption ktx_options[] = {
265 { "srgb", "GL internal format color space", OFFSET(srgb),
266 AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 1, VE, .unit = "srgb" },
267 { "auto", "Follow the encoder profile", 0, AV_OPT_TYPE_CONST, { .i64 = -1 }, 0, 0, VE, .unit = "srgb" },
268 { "linear", "Write the linear ASTC format", 0, AV_OPT_TYPE_CONST, { .i64 = 0 }, 0, 0, VE, .unit = "srgb" },
269 { "srgb", "Write the sRGB ASTC format", 0, AV_OPT_TYPE_CONST, { .i64 = 1 }, 0, 0, VE, .unit = "srgb" },
270 { NULL },
271};
272
273static const AVClass ktx_muxer_class = {
274 .class_name = "ktx_muxer",
275 .item_name = av_default_item_name,
276 .option = ktx_options,
277 .version = LIBAVUTIL_VERSION_INT,
278};
279
281 .p.name = "ktx",
282 .p.long_name = NULL_IF_CONFIG_SMALL("KTX 1.0 (Khronos Texture) for ASTC"),
283 .p.mime_type = "image/ktx",
284 .p.extensions = "ktx",
285 .p.audio_codec = AV_CODEC_ID_NONE,
286 .p.video_codec = AV_CODEC_ID_ASTC,
287 .p.subtitle_codec = AV_CODEC_ID_NONE,
288 .flags_internal = FF_OFMT_FLAG_MAX_ONE_OF_EACH |
290 .priv_data_size = sizeof(KTXMuxerContext),
291 .p.priv_class = &ktx_muxer_class,
292 .write_header = ktx_write_header,
293 .write_packet = ktx_write_packet,
294 .p.flags = AVFMT_NOTIMESTAMPS,
295};
const FFOutputFormat ff_ktx_muxer
Definition ktxenc.c:280
#define VE
Definition amfenc_av1.c:30
static AVFormatContext * ctx
static const uint8_t astc_magic[4]
Definition astcdec.c:44
int32_t
Main libavformat public API header.
#define AVFMT_NOTIMESTAMPS
Format does not need / have any timestamps.
Definition avformat.h:500
Buffered I/O operations.
void avio_wl32(AVIOContext *s, unsigned int val)
Definition aviobuf.c:360
void avio_w8(AVIOContext *s, int b)
Definition aviobuf.c:184
void avio_write(AVIOContext *s, const unsigned char *buf, int size)
Definition aviobuf.c:206
#define i(width, name, range_min, range_max)
Definition cbs_h264.c:63
#define s(width, name)
Definition cbs_vp9.c:198
#define FFABSU(a)
Unsigned Absolute value.
Definition common.h:91
#define NULL
Definition coverity.c:32
Misc types and constants that do not belong anywhere else.
#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 AVPacket * pkt
@ 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_CODEC_ID_NONE
Definition codec_id.h:48
@ AV_CODEC_ID_ASTC
Definition codec_id.h:327
const AVPacketSideData * av_packet_side_data_get(const AVPacketSideData *sd, int nb_sd, enum AVPacketSideDataType type)
Get side information from a side data array.
Definition packet.c:570
@ AV_PKT_DATA_DISPLAYMATRIX
This side data contains a 3x3 transformation matrix describing an affine transformation that needs to...
Definition packet.h:105
#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
#define LIBAVUTIL_VERSION_INT
Definition version.h:85
#define AV_RL24(x)
#define AV_WL32(p, v)
static const int astc_gl_linear[14]
Definition ktxdec.c:52
static const int astc_by[14]
Definition ktxdec.c:57
static const int astc_bx[14]
Definition ktxdec.c:56
static int ktx_orientation_from_side_data(AVFormatContext *s, AVStream *st, int *hflip, int *vflip)
Definition ktxenc.c:70
static const AVClass ktx_muxer_class
Definition ktxenc.c:273
static const AVOption ktx_options[]
Definition ktxenc.c:264
static const char ktx_orientation_key[]
Definition ktxenc.c:62
static const int astc_gl_srgb[14]
Definition ktxenc.c:48
#define OFFSET(x)
Definition ktxenc.c:262
static int ktx_write_header(AVFormatContext *s)
Definition ktxenc.c:103
static int ktx_write_packet(AVFormatContext *s, AVPacket *pkt)
Definition ktxenc.c:244
#define ASTC_HEADER_SIZE
Definition libastcdec.c:43
#define NULL_IF_CONFIG_SMALL(x)
Return NULL if CONFIG_SMALL is true, otherwise the argument without modification.
Definition internal.h:97
#define FF_OFMT_FLAG_MAX_ONE_OF_EACH
If this flag is set, it indicates that for each codec type whose corresponding default codec (i....
Definition mux.h:50
#define FF_OFMT_FLAG_ONLY_DEFAULT_CODECS
If this flag is set, then the only permitted audio/video/subtitle codec ids are AVOutputFormat....
Definition mux.h:59
AVOptions.
Describe the class of an AVClass context structure.
Definition log.h:76
int extradata_size
Size of the extradata content in bytes.
Definition codec_par.h:75
int height
The height of the video frame in pixels.
Definition codec_par.h:150
int nb_coded_side_data
Amount of entries in coded_side_data.
Definition codec_par.h:88
int width
The width of the video frame in pixels.
Definition codec_par.h:143
int profile
Codec-specific bitstream restrictions that the stream conforms to.
Definition codec_par.h:135
uint8_t * extradata
Extra binary data needed for initializing the decoder, codec-dependent.
Definition codec_par.h:71
AVPacketSideData * coded_side_data
Additional data associated with the entire stream.
Definition codec_par.h:83
Format I/O context.
Definition avformat.h:1335
AVOption.
Definition opt.h:428
This structure stores auxiliary information for decoding, presenting, or otherwise processing the cod...
Definition packet.h:424
uint8_t * data
Definition packet.h:425
This structure stores compressed data.
Definition packet.h:580
Stream structure.
Definition avformat.h:768
AVCodecParameters * codecpar
Codec parameters associated with this stream.
Definition avformat.h:791
int wrote_image
Definition ktxenc.c:59
#define av_log(a,...)