FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/astcdec.c
Date: 2026-09-15 09:36:26
Exec Total Coverage
Lines: 4 63 6.3%
Functions: 1 3 33.3%
Branches: 2 36 5.6%

Line Branch Exec Source
1 /*
2 * ASTC demuxer (.astc container)
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 * ASTC (Adaptive Scalable Texture Compression) demuxer.
25 *
26 * Reads the 16-byte .astc header, exposes it as extradata to the decoder,
27 * and emits the raw ASTC bitstream as a single packet.
28 */
29
30 #include "config_components.h"
31
32 #include <inttypes.h>
33 #include <limits.h>
34
35 #include "avformat.h"
36 #include "avio.h"
37 #include "avio_internal.h"
38 #include "demux.h"
39 #include "internal.h"
40 #include "libavutil/intreadwrite.h"
41
42 #define ASTC_HEADER_SIZE 16
43 #define ASTC_BLOCK_BYTES 16
44 static const uint8_t astc_magic[4] = { 0x13, 0xAB, 0xA1, 0x5C };
45
46 typedef struct ASTCDemuxerContext {
47 uint64_t image_size;
48 int truncated;
49 } ASTCDemuxerContext;
50
51 8043 static int astc_probe(const AVProbeData *p)
52 {
53
1/2
✓ Branch 0 taken 8043 times.
✗ Branch 1 not taken.
8043 if (p->buf_size >= ASTC_HEADER_SIZE &&
54
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8043 times.
8043 !memcmp(p->buf, astc_magic, sizeof(astc_magic)))
55 return AVPROBE_SCORE_MAX;
56 8043 return 0;
57 }
58
59 static int astc_read_header(AVFormatContext *s)
60 {
61 AVIOContext *pb = s->pb;
62 ASTCDemuxerContext *astc = s->priv_data;
63 AVStream *st;
64 uint8_t hdr[ASTC_HEADER_SIZE];
65 unsigned int bx, by, bz, w, h, dz;
66 uint64_t expected;
67
68 if (ffio_read_size(pb, hdr, ASTC_HEADER_SIZE) < 0)
69 return AVERROR_INVALIDDATA;
70
71 if (memcmp(hdr, astc_magic, sizeof(astc_magic))) {
72 av_log(s, AV_LOG_ERROR, "Not an ASTC file (bad magic).\n");
73 return AVERROR_INVALIDDATA;
74 }
75
76 bx = hdr[4];
77 by = hdr[5];
78 bz = hdr[6];
79 w = AV_RL24(hdr + 7);
80 h = AV_RL24(hdr + 10);
81 dz = AV_RL24(hdr + 13);
82
83 /* Validate the geometry before trusting it to size the packet: the
84 * decoder only handles 2D images, and a zero block size would divide
85 * by zero when deriving the block count. */
86 if (!bx || !by || !bz || !w || !h || dz != 1) {
87 av_log(s, AV_LOG_ERROR,
88 "Invalid ASTC header: block %ux%ux%u, image %ux%ux%u.\n",
89 bx, by, bz, w, h, dz);
90 return AVERROR_INVALIDDATA;
91 }
92
93 /* Multiply the block counts in 64 bits: the dimensions fit 24 bits, but
94 * their product overflows 32 bits for large images, and a wrapped result
95 * would under-size the packet, or wrap to zero and look like EOF. */
96 expected = (((uint64_t)w + bx - 1) / bx) *
97 (((uint64_t)h + by - 1) / by) *
98 (((uint64_t)dz + bz - 1) / bz) * ASTC_BLOCK_BYTES;
99 if (expected > INT_MAX) {
100 av_log(s, AV_LOG_ERROR, "ASTC image is too large (%"PRIu64" bytes).\n",
101 expected);
102 return AVERROR_INVALIDDATA;
103 }
104 astc->image_size = expected;
105
106 st = avformat_new_stream(s, NULL);
107 if (!st)
108 return AVERROR(ENOMEM);
109
110 st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
111 st->codecpar->codec_id = AV_CODEC_ID_ASTC;
112 st->codecpar->width = w;
113 st->codecpar->height = h;
114 st->codecpar->format = AV_PIX_FMT_RGBA;
115
116 if (ff_alloc_extradata(st->codecpar, ASTC_HEADER_SIZE) < 0)
117 return AVERROR(ENOMEM);
118 memcpy(st->codecpar->extradata, hdr, ASTC_HEADER_SIZE);
119
120 /* Single image container. */
121 st->duration = 1;
122 avpriv_set_pts_info(st, 64, 1, 1);
123
124 return 0;
125 }
126
127 static int astc_read_packet(AVFormatContext *s, AVPacket *pkt)
128 {
129 ASTCDemuxerContext *astc = s->priv_data;
130 int ret;
131
132 /* Keep reporting a detected truncation instead of falling through to EOF:
133 * a caller that stops at the first error must not see the broken image as
134 * a clean end of stream. */
135 if (astc->truncated)
136 return AVERROR_INVALIDDATA;
137
138 if (!astc->image_size)
139 return AVERROR_EOF;
140
141 /* Read exactly one image worth of blocks; a short read means the input
142 * is truncated, and any trailing data is not part of this image. A payload
143 * that is missing entirely reads as EOF, which has to be reported as the
144 * truncation it is rather than as the end of a valid stream. */
145 ret = av_get_packet(s->pb, pkt, astc->image_size);
146 if (ret == AVERROR_EOF)
147 ret = 0;
148 if (ret < 0)
149 return ret;
150 if (ret != astc->image_size) {
151 av_log(s, AV_LOG_ERROR,
152 "Truncated ASTC image: got %d of %"PRIu64" bytes.\n",
153 ret, astc->image_size);
154 av_packet_unref(pkt);
155 astc->truncated = 1;
156 return AVERROR_INVALIDDATA;
157 }
158
159 astc->image_size = 0;
160 pkt->stream_index = 0;
161 pkt->flags |= AV_PKT_FLAG_KEY;
162 return 0;
163 }
164
165 const FFInputFormat ff_astc_demuxer = {
166 .p.name = "astc",
167 .p.long_name = NULL_IF_CONFIG_SMALL("ASTC (Adaptive Scalable Texture Compression)"),
168 .p.mime_type = "image/astc",
169 .p.extensions = "astc",
170 .p.flags = AVFMT_NOTIMESTAMPS,
171 .priv_data_size = sizeof(ASTCDemuxerContext),
172 .read_probe = astc_probe,
173 .read_header = astc_read_header,
174 .read_packet = astc_read_packet,
175 };
176