/src/php-src/ext/standard/libavifinfo/avifinfo.c
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) 2021, Alliance for Open Media. All rights reserved |
2 | | // |
3 | | // This source code is subject to the terms of the BSD 2 Clause License and |
4 | | // the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License |
5 | | // was not distributed with this source code in the LICENSE file, you can |
6 | | // obtain it at www.aomedia.org/license/software. If the Alliance for Open |
7 | | // Media Patent License 1.0 was not distributed with this source code in the |
8 | | // PATENTS file, you can obtain it at www.aomedia.org/license/patent. |
9 | | |
10 | | #include "avifinfo.h" |
11 | | |
12 | | #include <stdint.h> |
13 | | #include <stdio.h> |
14 | | #include <string.h> |
15 | | |
16 | | //------------------------------------------------------------------------------ |
17 | | |
18 | | // Status returned when reading the content of a box (or file). |
19 | | typedef enum { |
20 | | kFound, // Input correctly parsed and information retrieved. |
21 | | kNotFound, // Input correctly parsed but information is missing or elsewhere. |
22 | | kTruncated, // Input correctly parsed until missing bytes to continue. |
23 | | kAborted, // Input correctly parsed until stopped to avoid timeout or crash. |
24 | | kInvalid, // Input incorrectly parsed. |
25 | | } AvifInfoInternalStatus; |
26 | | |
27 | 0 | static AvifInfoStatus AvifInfoInternalConvertStatus(AvifInfoInternalStatus s) { |
28 | 0 | return (s == kFound) ? kAvifInfoOk |
29 | 0 | : (s == kNotFound || s == kTruncated) ? kAvifInfoNotEnoughData |
30 | 0 | : (s == kAborted) ? kAvifInfoTooComplex |
31 | 0 | : kAvifInfoInvalidFile; |
32 | 0 | } |
33 | | |
34 | | // uint32_t is used everywhere in this file. It is unlikely to be insufficient |
35 | | // to parse AVIF headers. |
36 | | #define AVIFINFO_MAX_SIZE UINT32_MAX |
37 | | // AvifInfoInternalFeatures uses uint8_t to store values and the number of |
38 | | // values is clamped to 32 to limit the stack size. |
39 | 0 | #define AVIFINFO_MAX_VALUE UINT8_MAX |
40 | 0 | #define AVIFINFO_UNDEFINED 0 |
41 | | // Maximum number of stored associations. Past that, they are skipped. |
42 | 0 | #define AVIFINFO_MAX_TILES 16 |
43 | 0 | #define AVIFINFO_MAX_PROPS 32 |
44 | 0 | #define AVIFINFO_MAX_FEATURES 8 |
45 | | |
46 | | // Reads an unsigned integer from 'input' with most significant bits first. |
47 | | // 'input' must be at least 'num_bytes'-long. |
48 | | static uint32_t AvifInfoInternalReadBigEndian(const uint8_t* input, |
49 | 0 | uint32_t num_bytes) { |
50 | 0 | uint32_t value = 0; |
51 | 0 | for (uint32_t i = 0; i < num_bytes; ++i) { |
52 | 0 | value = (value << 8) | input[i]; |
53 | 0 | } |
54 | 0 | return value; |
55 | 0 | } |
56 | | |
57 | | //------------------------------------------------------------------------------ |
58 | | // Convenience macros. |
59 | | |
60 | | #if defined(AVIFINFO_LOG_ERROR) // Toggle to log encountered issues. |
61 | | static void AvifInfoInternalLogError(const char* file, int line, |
62 | | AvifInfoInternalStatus status) { |
63 | | const char* kStr[] = {"Found", "NotFound", "Truncated", "Invalid", "Aborted"}; |
64 | | fprintf(stderr, " %s:%d: %s\n", file, line, kStr[status]); |
65 | | // Set a breakpoint here to catch the first detected issue. |
66 | | } |
67 | | #define AVIFINFO_RETURN(check_status) \ |
68 | | do { \ |
69 | | const AvifInfoInternalStatus status_checked = (check_status); \ |
70 | | if (status_checked != kFound && status_checked != kNotFound) { \ |
71 | | AvifInfoInternalLogError(__FILE__, __LINE__, status_checked); \ |
72 | | } \ |
73 | | return status_checked; \ |
74 | | } while (0) |
75 | | #else |
76 | | #define AVIFINFO_RETURN(check_status) \ |
77 | 0 | do { \ |
78 | 0 | return (check_status); \ |
79 | 0 | } while (0) |
80 | | #endif |
81 | | |
82 | | #define AVIFINFO_CHECK(check_condition, check_status) \ |
83 | 0 | do { \ |
84 | 0 | if (!(check_condition)) AVIFINFO_RETURN(check_status); \ |
85 | 0 | } while (0) |
86 | | #define AVIFINFO_CHECK_STATUS_IS(check_status, expected_status) \ |
87 | 0 | do { \ |
88 | 0 | const AvifInfoInternalStatus status_returned = (check_status); \ |
89 | 0 | AVIFINFO_CHECK(status_returned == (expected_status), status_returned); \ |
90 | 0 | } while (0) |
91 | | #define AVIFINFO_CHECK_FOUND(check_status) \ |
92 | 0 | AVIFINFO_CHECK_STATUS_IS((check_status), kFound) |
93 | | #define AVIFINFO_CHECK_NOT_FOUND(check_status) \ |
94 | 0 | AVIFINFO_CHECK_STATUS_IS((check_status), kNotFound) |
95 | | |
96 | | //------------------------------------------------------------------------------ |
97 | | // Streamed input struct and helper functions. |
98 | | |
99 | | typedef struct { |
100 | | void* stream; // User-defined data. |
101 | | read_stream_t read; // Used to fetch more bytes from the 'stream'. |
102 | | skip_stream_t skip; // Used to advance the position in the 'stream'. |
103 | | // Fallback to 'read' if 'skip' is null. |
104 | | } AvifInfoInternalStream; |
105 | | |
106 | | // Reads 'num_bytes' from the 'stream'. They are available at '*data'. |
107 | | // 'num_bytes' must be greater than zero. |
108 | | static AvifInfoInternalStatus AvifInfoInternalRead( |
109 | 0 | AvifInfoInternalStream* stream, uint32_t num_bytes, const uint8_t** data) { |
110 | 0 | *data = stream->read(stream->stream, num_bytes); |
111 | 0 | AVIFINFO_CHECK(*data != NULL, kTruncated); |
112 | 0 | return kFound; |
113 | 0 | } |
114 | | |
115 | | // Skips 'num_bytes' from the 'stream'. 'num_bytes' can be zero. |
116 | | static AvifInfoInternalStatus AvifInfoInternalSkip( |
117 | 0 | AvifInfoInternalStream* stream, uint32_t num_bytes) { |
118 | | // Avoid a call to the user-defined function for nothing. |
119 | 0 | if (num_bytes > 0) { |
120 | 0 | if (stream->skip == NULL) { |
121 | 0 | const uint8_t* unused; |
122 | 0 | while (num_bytes > AVIFINFO_MAX_NUM_READ_BYTES) { |
123 | 0 | AVIFINFO_CHECK_FOUND( |
124 | 0 | AvifInfoInternalRead(stream, AVIFINFO_MAX_NUM_READ_BYTES, &unused)); |
125 | 0 | num_bytes -= AVIFINFO_MAX_NUM_READ_BYTES; |
126 | 0 | } |
127 | 0 | return AvifInfoInternalRead(stream, num_bytes, &unused); |
128 | 0 | } |
129 | 0 | stream->skip(stream->stream, num_bytes); |
130 | 0 | } |
131 | 0 | return kFound; |
132 | 0 | } |
133 | | |
134 | | //------------------------------------------------------------------------------ |
135 | | // Features are parsed into temporary property associations. |
136 | | |
137 | | typedef struct { |
138 | | uint8_t tile_item_id; |
139 | | uint8_t parent_item_id; |
140 | | } AvifInfoInternalTile; // Tile item id <-> parent item id associations. |
141 | | |
142 | | typedef struct { |
143 | | uint8_t property_index; |
144 | | uint8_t item_id; |
145 | | } AvifInfoInternalProp; // Property index <-> item id associations. |
146 | | |
147 | | typedef struct { |
148 | | uint8_t property_index; |
149 | | uint32_t width, height; |
150 | | } AvifInfoInternalDimProp; // Property <-> features associations. |
151 | | |
152 | | typedef struct { |
153 | | uint8_t property_index; |
154 | | uint8_t bit_depth, num_channels; |
155 | | } AvifInfoInternalChanProp; // Property <-> features associations. |
156 | | |
157 | | typedef struct { |
158 | | uint8_t has_primary_item; // True if "pitm" was parsed. |
159 | | uint8_t has_alpha; // True if an alpha "auxC" was parsed. |
160 | | uint8_t primary_item_id; |
161 | | AvifInfoFeatures primary_item_features; // Deduced from the data below. |
162 | | uint8_t data_was_skipped; // True if some loops/indices were skipped. |
163 | | |
164 | | uint8_t num_tiles; |
165 | | AvifInfoInternalTile tiles[AVIFINFO_MAX_TILES]; |
166 | | uint8_t num_props; |
167 | | AvifInfoInternalProp props[AVIFINFO_MAX_PROPS]; |
168 | | uint8_t num_dim_props; |
169 | | AvifInfoInternalDimProp dim_props[AVIFINFO_MAX_FEATURES]; |
170 | | uint8_t num_chan_props; |
171 | | AvifInfoInternalChanProp chan_props[AVIFINFO_MAX_FEATURES]; |
172 | | } AvifInfoInternalFeatures; |
173 | | |
174 | | // Generates the features of a given 'target_item_id' from internal features. |
175 | | static AvifInfoInternalStatus AvifInfoInternalGetItemFeatures( |
176 | 0 | AvifInfoInternalFeatures* f, uint32_t target_item_id, uint32_t tile_depth) { |
177 | 0 | for (uint32_t prop_item = 0; prop_item < f->num_props; ++prop_item) { |
178 | 0 | if (f->props[prop_item].item_id != target_item_id) continue; |
179 | 0 | const uint32_t property_index = f->props[prop_item].property_index; |
180 | | |
181 | | // Retrieve the width and height of the primary item if not already done. |
182 | 0 | if (target_item_id == f->primary_item_id && |
183 | 0 | (f->primary_item_features.width == AVIFINFO_UNDEFINED || |
184 | 0 | f->primary_item_features.height == AVIFINFO_UNDEFINED)) { |
185 | 0 | for (uint32_t i = 0; i < f->num_dim_props; ++i) { |
186 | 0 | if (f->dim_props[i].property_index != property_index) continue; |
187 | 0 | f->primary_item_features.width = f->dim_props[i].width; |
188 | 0 | f->primary_item_features.height = f->dim_props[i].height; |
189 | 0 | if (f->primary_item_features.bit_depth != AVIFINFO_UNDEFINED && |
190 | 0 | f->primary_item_features.num_channels != AVIFINFO_UNDEFINED) { |
191 | 0 | return kFound; |
192 | 0 | } |
193 | 0 | break; |
194 | 0 | } |
195 | 0 | } |
196 | | // Retrieve the bit depth and number of channels of the target item if not |
197 | | // already done. |
198 | 0 | if (f->primary_item_features.bit_depth == AVIFINFO_UNDEFINED || |
199 | 0 | f->primary_item_features.num_channels == AVIFINFO_UNDEFINED) { |
200 | 0 | for (uint32_t i = 0; i < f->num_chan_props; ++i) { |
201 | 0 | if (f->chan_props[i].property_index != property_index) continue; |
202 | 0 | f->primary_item_features.bit_depth = f->chan_props[i].bit_depth; |
203 | 0 | f->primary_item_features.num_channels = f->chan_props[i].num_channels; |
204 | 0 | if (f->primary_item_features.width != AVIFINFO_UNDEFINED && |
205 | 0 | f->primary_item_features.height != AVIFINFO_UNDEFINED) { |
206 | 0 | return kFound; |
207 | 0 | } |
208 | 0 | break; |
209 | 0 | } |
210 | 0 | } |
211 | 0 | } |
212 | | |
213 | | // Check for the bit_depth and num_channels in a tile if not yet found. |
214 | 0 | for (uint32_t tile = 0; tile < f->num_tiles && tile_depth < 3; ++tile) { |
215 | 0 | if (f->tiles[tile].parent_item_id != target_item_id) continue; |
216 | 0 | AVIFINFO_CHECK_NOT_FOUND(AvifInfoInternalGetItemFeatures( |
217 | 0 | f, f->tiles[tile].tile_item_id, tile_depth + 1)); |
218 | 0 | } |
219 | 0 | AVIFINFO_RETURN(kNotFound); |
220 | 0 | } |
221 | | |
222 | | // Generates the 'f->primary_item_features' from the AvifInfoInternalFeatures. |
223 | | // Returns kNotFound if there is not enough information. |
224 | | static AvifInfoInternalStatus AvifInfoInternalGetPrimaryItemFeatures( |
225 | 0 | AvifInfoInternalFeatures* f) { |
226 | | // Nothing to do without the primary item ID. |
227 | 0 | AVIFINFO_CHECK(f->has_primary_item, kNotFound); |
228 | | // Early exit. |
229 | 0 | AVIFINFO_CHECK(f->num_dim_props > 0 && f->num_chan_props, kNotFound); |
230 | 0 | AVIFINFO_CHECK_FOUND( |
231 | 0 | AvifInfoInternalGetItemFeatures(f, f->primary_item_id, /*tile_depth=*/0)); |
232 | | |
233 | | // "auxC" is parsed before the "ipma" properties so it is known now, if any. |
234 | 0 | if (f->has_alpha) ++f->primary_item_features.num_channels; |
235 | 0 | return kFound; |
236 | 0 | } |
237 | | |
238 | | //------------------------------------------------------------------------------ |
239 | | // Box header parsing and various size checks. |
240 | | |
241 | | typedef struct { |
242 | | uint32_t size; // In bytes. |
243 | | uint8_t type[4]; // Four characters. |
244 | | uint32_t version; // 0 or actual version if this is a full box. |
245 | | uint32_t flags; // 0 or actual value if this is a full box. |
246 | | uint32_t content_size; // 'size' minus the header size. |
247 | | } AvifInfoInternalBox; |
248 | | |
249 | | // Reads the header of a 'box' starting at the beginning of a 'stream'. |
250 | | // 'num_remaining_bytes' is the remaining size of the container of the 'box' |
251 | | // (either the file size itself or the content size of the parent of the 'box'). |
252 | | static AvifInfoInternalStatus AvifInfoInternalParseBox( |
253 | | AvifInfoInternalStream* stream, uint32_t num_remaining_bytes, |
254 | 0 | uint32_t* num_parsed_boxes, AvifInfoInternalBox* box) { |
255 | 0 | const uint8_t* data; |
256 | | // See ISO/IEC 14496-12:2012(E) 4.2 |
257 | 0 | uint32_t box_header_size = 8; // box 32b size + 32b type (at least) |
258 | 0 | AVIFINFO_CHECK(box_header_size <= num_remaining_bytes, kInvalid); |
259 | 0 | AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 8, &data)); |
260 | 0 | box->size = AvifInfoInternalReadBigEndian(data, sizeof(uint32_t)); |
261 | 0 | memcpy(box->type, data + 4, 4); |
262 | | // 'box->size==1' means 64-bit size should be read after the box type. |
263 | | // 'box->size==0' means this box extends to all remaining bytes. |
264 | 0 | if (box->size == 1) { |
265 | 0 | box_header_size += 8; |
266 | 0 | AVIFINFO_CHECK(box_header_size <= num_remaining_bytes, kInvalid); |
267 | 0 | AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 8, &data)); |
268 | | // Stop the parsing if any box has a size greater than 4GB. |
269 | 0 | AVIFINFO_CHECK(AvifInfoInternalReadBigEndian(data, sizeof(uint32_t)) == 0, |
270 | 0 | kAborted); |
271 | | // Read the 32 least-significant bits. |
272 | 0 | box->size = AvifInfoInternalReadBigEndian(data + 4, sizeof(uint32_t)); |
273 | 0 | } else if (box->size == 0) { |
274 | 0 | box->size = num_remaining_bytes; |
275 | 0 | } |
276 | 0 | AVIFINFO_CHECK(box->size >= box_header_size, kInvalid); |
277 | 0 | AVIFINFO_CHECK(box->size <= num_remaining_bytes, kInvalid); |
278 | | |
279 | 0 | const int has_fullbox_header = |
280 | 0 | !memcmp(box->type, "meta", 4) || !memcmp(box->type, "pitm", 4) || |
281 | 0 | !memcmp(box->type, "ipma", 4) || !memcmp(box->type, "ispe", 4) || |
282 | 0 | !memcmp(box->type, "pixi", 4) || !memcmp(box->type, "iref", 4) || |
283 | 0 | !memcmp(box->type, "auxC", 4); |
284 | 0 | if (has_fullbox_header) box_header_size += 4; |
285 | 0 | AVIFINFO_CHECK(box->size >= box_header_size, kInvalid); |
286 | 0 | box->content_size = box->size - box_header_size; |
287 | | // Avoid timeouts. The maximum number of parsed boxes is arbitrary. |
288 | 0 | ++*num_parsed_boxes; |
289 | 0 | AVIFINFO_CHECK(*num_parsed_boxes < 4096, kAborted); |
290 | | |
291 | 0 | box->version = 0; |
292 | 0 | box->flags = 0; |
293 | 0 | if (has_fullbox_header) { |
294 | 0 | AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &data)); |
295 | 0 | box->version = AvifInfoInternalReadBigEndian(data, 1); |
296 | 0 | box->flags = AvifInfoInternalReadBigEndian(data + 1, 3); |
297 | | // See AV1 Image File Format (AVIF) 8.1 |
298 | | // at https://aomediacodec.github.io/av1-avif/#avif-boxes (available when |
299 | | // https://github.com/AOMediaCodec/av1-avif/pull/170 is merged). |
300 | 0 | uint32_t is_parsable = 1; |
301 | 0 | if (!memcmp(box->type, "meta", 4)) is_parsable = (box->version <= 0); |
302 | 0 | if (!memcmp(box->type, "pitm", 4)) is_parsable = (box->version <= 1); |
303 | 0 | if (!memcmp(box->type, "ipma", 4)) is_parsable = (box->version <= 1); |
304 | 0 | if (!memcmp(box->type, "ispe", 4)) is_parsable = (box->version <= 0); |
305 | 0 | if (!memcmp(box->type, "pixi", 4)) is_parsable = (box->version <= 0); |
306 | 0 | if (!memcmp(box->type, "iref", 4)) is_parsable = (box->version <= 1); |
307 | 0 | if (!memcmp(box->type, "auxC", 4)) is_parsable = (box->version <= 0); |
308 | | // Instead of considering this file as invalid, skip unparsable boxes. |
309 | 0 | if (!is_parsable) memcpy(box->type, "\0skp", 4); // \0 so not a valid type |
310 | 0 | } |
311 | 0 | return kFound; |
312 | 0 | } |
313 | | |
314 | | //------------------------------------------------------------------------------ |
315 | | |
316 | | // Parses a 'stream' of an "ipco" box into 'features'. |
317 | | // "ispe" is used for width and height, "pixi" and "av1C" are used for bit depth |
318 | | // and number of channels, and "auxC" is used for alpha. |
319 | | static AvifInfoInternalStatus ParseIpco(AvifInfoInternalStream* stream, |
320 | | uint32_t num_remaining_bytes, |
321 | | uint32_t* num_parsed_boxes, |
322 | 0 | AvifInfoInternalFeatures* features) { |
323 | 0 | uint32_t box_index = 1; // 1-based index. Used for iterating over properties. |
324 | 0 | do { |
325 | 0 | AvifInfoInternalBox box; |
326 | 0 | AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, num_remaining_bytes, |
327 | 0 | num_parsed_boxes, &box)); |
328 | | |
329 | 0 | if (!memcmp(box.type, "ispe", 4)) { |
330 | | // See ISO/IEC 23008-12:2017(E) 6.5.3.2 |
331 | 0 | const uint8_t* data; |
332 | 0 | AVIFINFO_CHECK(box.content_size >= 8, kInvalid); |
333 | 0 | AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 8, &data)); |
334 | 0 | const uint32_t width = AvifInfoInternalReadBigEndian(data + 0, 4); |
335 | 0 | const uint32_t height = AvifInfoInternalReadBigEndian(data + 4, 4); |
336 | 0 | AVIFINFO_CHECK(width != 0 && height != 0, kInvalid); |
337 | 0 | if (features->num_dim_props < AVIFINFO_MAX_FEATURES && |
338 | 0 | box_index <= AVIFINFO_MAX_VALUE) { |
339 | 0 | features->dim_props[features->num_dim_props].property_index = box_index; |
340 | 0 | features->dim_props[features->num_dim_props].width = width; |
341 | 0 | features->dim_props[features->num_dim_props].height = height; |
342 | 0 | ++features->num_dim_props; |
343 | 0 | } else { |
344 | 0 | features->data_was_skipped = 1; |
345 | 0 | } |
346 | 0 | AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size - 8)); |
347 | 0 | } else if (!memcmp(box.type, "pixi", 4)) { |
348 | | // See ISO/IEC 23008-12:2017(E) 6.5.6.2 |
349 | 0 | const uint8_t* data; |
350 | 0 | AVIFINFO_CHECK(box.content_size >= 1, kInvalid); |
351 | 0 | AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 1, &data)); |
352 | 0 | const uint32_t num_channels = AvifInfoInternalReadBigEndian(data + 0, 1); |
353 | 0 | AVIFINFO_CHECK(num_channels >= 1, kInvalid); |
354 | 0 | AVIFINFO_CHECK(box.content_size >= 1 + num_channels, kInvalid); |
355 | 0 | AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 1, &data)); |
356 | 0 | const uint32_t bit_depth = AvifInfoInternalReadBigEndian(data, 1); |
357 | 0 | AVIFINFO_CHECK(bit_depth >= 1, kInvalid); |
358 | 0 | for (uint32_t i = 1; i < num_channels; ++i) { |
359 | 0 | AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 1, &data)); |
360 | | // Bit depth should be the same for all channels. |
361 | 0 | AVIFINFO_CHECK(AvifInfoInternalReadBigEndian(data, 1) == bit_depth, |
362 | 0 | kInvalid); |
363 | 0 | AVIFINFO_CHECK(i <= 32, kAborted); // Be reasonable. |
364 | 0 | } |
365 | 0 | if (features->num_chan_props < AVIFINFO_MAX_FEATURES && |
366 | 0 | box_index <= AVIFINFO_MAX_VALUE && bit_depth <= AVIFINFO_MAX_VALUE && |
367 | 0 | num_channels <= AVIFINFO_MAX_VALUE) { |
368 | 0 | features->chan_props[features->num_chan_props].property_index = |
369 | 0 | box_index; |
370 | 0 | features->chan_props[features->num_chan_props].bit_depth = bit_depth; |
371 | 0 | features->chan_props[features->num_chan_props].num_channels = |
372 | 0 | num_channels; |
373 | 0 | ++features->num_chan_props; |
374 | 0 | } else { |
375 | 0 | features->data_was_skipped = 1; |
376 | 0 | } |
377 | 0 | AVIFINFO_CHECK_FOUND( |
378 | 0 | AvifInfoInternalSkip(stream, box.content_size - (1 + num_channels))); |
379 | 0 | } else if (!memcmp(box.type, "av1C", 4)) { |
380 | | // See AV1 Codec ISO Media File Format Binding 2.3.1 |
381 | | // at https://aomediacodec.github.io/av1-isobmff/#av1c |
382 | | // Only parse the necessary third byte. Assume that the others are valid. |
383 | 0 | const uint8_t* data; |
384 | 0 | AVIFINFO_CHECK(box.content_size >= 3, kInvalid); |
385 | 0 | AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 3, &data)); |
386 | 0 | const int high_bitdepth = (data[2] & 0x40) != 0; |
387 | 0 | const int twelve_bit = (data[2] & 0x20) != 0; |
388 | 0 | const int monochrome = (data[2] & 0x10) != 0; |
389 | 0 | if (twelve_bit) { |
390 | 0 | AVIFINFO_CHECK(high_bitdepth, kInvalid); |
391 | 0 | } |
392 | 0 | if (features->num_chan_props < AVIFINFO_MAX_FEATURES && |
393 | 0 | box_index <= AVIFINFO_MAX_VALUE) { |
394 | 0 | features->chan_props[features->num_chan_props].property_index = |
395 | 0 | box_index; |
396 | 0 | features->chan_props[features->num_chan_props].bit_depth = |
397 | 0 | high_bitdepth ? twelve_bit ? 12 : 10 : 8; |
398 | 0 | features->chan_props[features->num_chan_props].num_channels = |
399 | 0 | monochrome ? 1 : 3; |
400 | 0 | ++features->num_chan_props; |
401 | 0 | } else { |
402 | 0 | features->data_was_skipped = 1; |
403 | 0 | } |
404 | 0 | AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size - 3)); |
405 | 0 | } else if (!memcmp(box.type, "auxC", 4)) { |
406 | | // See AV1 Image File Format (AVIF) 4 |
407 | | // at https://aomediacodec.github.io/av1-avif/#auxiliary-images |
408 | 0 | const char* kAlphaStr = "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"; |
409 | 0 | const uint32_t kAlphaStrLength = 44; // Includes terminating character. |
410 | 0 | if (box.content_size >= kAlphaStrLength) { |
411 | 0 | const uint8_t* data; |
412 | 0 | AVIFINFO_CHECK_FOUND( |
413 | 0 | AvifInfoInternalRead(stream, kAlphaStrLength, &data)); |
414 | 0 | const char* const aux_type = (const char*)data; |
415 | 0 | if (strcmp(aux_type, kAlphaStr) == 0) { |
416 | | // Note: It is unlikely but it is possible that this alpha plane does |
417 | | // not belong to the primary item or a tile. Ignore this issue. |
418 | 0 | features->has_alpha = 1; |
419 | 0 | } |
420 | 0 | AVIFINFO_CHECK_FOUND( |
421 | 0 | AvifInfoInternalSkip(stream, box.content_size - kAlphaStrLength)); |
422 | 0 | } else { |
423 | 0 | AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size)); |
424 | 0 | } |
425 | 0 | } else { |
426 | 0 | AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size)); |
427 | 0 | } |
428 | 0 | ++box_index; |
429 | 0 | num_remaining_bytes -= box.size; |
430 | 0 | } while (num_remaining_bytes > 0); |
431 | 0 | AVIFINFO_RETURN(kNotFound); |
432 | 0 | } |
433 | | |
434 | | // Parses a 'stream' of an "iprp" box into 'features'. The "ipco" box contain |
435 | | // the properties which are linked to items by the "ipma" box. |
436 | | static AvifInfoInternalStatus ParseIprp(AvifInfoInternalStream* stream, |
437 | | uint32_t num_remaining_bytes, |
438 | | uint32_t* num_parsed_boxes, |
439 | 0 | AvifInfoInternalFeatures* features) { |
440 | 0 | do { |
441 | 0 | AvifInfoInternalBox box; |
442 | 0 | AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, num_remaining_bytes, |
443 | 0 | num_parsed_boxes, &box)); |
444 | | |
445 | 0 | if (!memcmp(box.type, "ipco", 4)) { |
446 | 0 | AVIFINFO_CHECK_NOT_FOUND( |
447 | 0 | ParseIpco(stream, box.content_size, num_parsed_boxes, features)); |
448 | 0 | } else if (!memcmp(box.type, "ipma", 4)) { |
449 | | // See ISO/IEC 23008-12:2017(E) 9.3.2 |
450 | 0 | uint32_t num_read_bytes = 4; |
451 | 0 | const uint8_t* data; |
452 | 0 | AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid); |
453 | 0 | AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &data)); |
454 | 0 | const uint32_t entry_count = AvifInfoInternalReadBigEndian(data, 4); |
455 | 0 | const uint32_t id_num_bytes = (box.version < 1) ? 2 : 4; |
456 | 0 | const uint32_t index_num_bytes = (box.flags & 1) ? 2 : 1; |
457 | 0 | const uint32_t essential_bit_mask = (box.flags & 1) ? 0x8000 : 0x80; |
458 | |
|
459 | 0 | for (uint32_t entry = 0; entry < entry_count; ++entry) { |
460 | 0 | if (entry >= AVIFINFO_MAX_PROPS || |
461 | 0 | features->num_props >= AVIFINFO_MAX_PROPS) { |
462 | 0 | features->data_was_skipped = 1; |
463 | 0 | break; |
464 | 0 | } |
465 | 0 | num_read_bytes += id_num_bytes + 1; |
466 | 0 | AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid); |
467 | 0 | AVIFINFO_CHECK_FOUND( |
468 | 0 | AvifInfoInternalRead(stream, id_num_bytes + 1, &data)); |
469 | 0 | const uint32_t item_id = |
470 | 0 | AvifInfoInternalReadBigEndian(data, id_num_bytes); |
471 | 0 | const uint32_t association_count = |
472 | 0 | AvifInfoInternalReadBigEndian(data + id_num_bytes, 1); |
473 | |
|
474 | 0 | uint32_t property; |
475 | 0 | for (property = 0; property < association_count; ++property) { |
476 | 0 | if (property >= AVIFINFO_MAX_PROPS || |
477 | 0 | features->num_props >= AVIFINFO_MAX_PROPS) { |
478 | 0 | features->data_was_skipped = 1; |
479 | 0 | break; |
480 | 0 | } |
481 | 0 | num_read_bytes += index_num_bytes; |
482 | 0 | AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid); |
483 | 0 | AVIFINFO_CHECK_FOUND( |
484 | 0 | AvifInfoInternalRead(stream, index_num_bytes, &data)); |
485 | 0 | const uint32_t value = |
486 | 0 | AvifInfoInternalReadBigEndian(data, index_num_bytes); |
487 | | // const int essential = (value & essential_bit_mask); // Unused. |
488 | 0 | const uint32_t property_index = (value & ~essential_bit_mask); |
489 | 0 | if (property_index <= AVIFINFO_MAX_VALUE && |
490 | 0 | item_id <= AVIFINFO_MAX_VALUE) { |
491 | 0 | features->props[features->num_props].property_index = |
492 | 0 | property_index; |
493 | 0 | features->props[features->num_props].item_id = item_id; |
494 | 0 | ++features->num_props; |
495 | 0 | } else { |
496 | 0 | features->data_was_skipped = 1; |
497 | 0 | } |
498 | 0 | } |
499 | 0 | if (property < association_count) break; // Do not read garbage. |
500 | 0 | } |
501 | | |
502 | | // If all features are available now, do not look further. |
503 | 0 | AVIFINFO_CHECK_NOT_FOUND( |
504 | 0 | AvifInfoInternalGetPrimaryItemFeatures(features)); |
505 | | |
506 | 0 | AVIFINFO_CHECK_FOUND( |
507 | 0 | AvifInfoInternalSkip(stream, box.content_size - num_read_bytes)); |
508 | 0 | } else { |
509 | 0 | AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size)); |
510 | 0 | } |
511 | 0 | num_remaining_bytes -= box.size; |
512 | 0 | } while (num_remaining_bytes != 0); |
513 | 0 | AVIFINFO_RETURN(kNotFound); |
514 | 0 | } |
515 | | |
516 | | //------------------------------------------------------------------------------ |
517 | | |
518 | | // Parses a 'stream' of an "iref" box into 'features'. |
519 | | // The "dimg" boxes contain links between tiles and their parent items, which |
520 | | // can be used to infer bit depth and number of channels for the primary item |
521 | | // when the latter does not have these properties. |
522 | | static AvifInfoInternalStatus ParseIref(AvifInfoInternalStream* stream, |
523 | | uint32_t num_remaining_bytes, |
524 | | uint32_t* num_parsed_boxes, |
525 | 0 | AvifInfoInternalFeatures* features) { |
526 | 0 | do { |
527 | 0 | AvifInfoInternalBox box; |
528 | 0 | AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, num_remaining_bytes, |
529 | 0 | num_parsed_boxes, &box)); |
530 | | |
531 | 0 | if (!memcmp(box.type, "dimg", 4)) { |
532 | | // See ISO/IEC 14496-12:2015(E) 8.11.12.2 |
533 | 0 | const uint32_t num_bytes_per_id = (box.version == 0) ? 2 : 4; |
534 | 0 | uint32_t num_read_bytes = num_bytes_per_id + 2; |
535 | 0 | const uint8_t* data; |
536 | 0 | AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid); |
537 | 0 | AVIFINFO_CHECK_FOUND( |
538 | 0 | AvifInfoInternalRead(stream, num_bytes_per_id + 2, &data)); |
539 | 0 | const uint32_t from_item_id = |
540 | 0 | AvifInfoInternalReadBigEndian(data, num_bytes_per_id); |
541 | 0 | const uint32_t reference_count = |
542 | 0 | AvifInfoInternalReadBigEndian(data + num_bytes_per_id, 2); |
543 | |
|
544 | 0 | for (uint32_t i = 0; i < reference_count; ++i) { |
545 | 0 | if (i >= AVIFINFO_MAX_TILES) { |
546 | 0 | features->data_was_skipped = 1; |
547 | 0 | break; |
548 | 0 | } |
549 | 0 | num_read_bytes += num_bytes_per_id; |
550 | 0 | AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid); |
551 | 0 | AVIFINFO_CHECK_FOUND( |
552 | 0 | AvifInfoInternalRead(stream, num_bytes_per_id, &data)); |
553 | 0 | const uint32_t to_item_id = |
554 | 0 | AvifInfoInternalReadBigEndian(data, num_bytes_per_id); |
555 | 0 | if (from_item_id <= AVIFINFO_MAX_VALUE && |
556 | 0 | to_item_id <= AVIFINFO_MAX_VALUE && |
557 | 0 | features->num_tiles < AVIFINFO_MAX_TILES) { |
558 | 0 | features->tiles[features->num_tiles].tile_item_id = to_item_id; |
559 | 0 | features->tiles[features->num_tiles].parent_item_id = from_item_id; |
560 | 0 | ++features->num_tiles; |
561 | 0 | } else { |
562 | 0 | features->data_was_skipped = 1; |
563 | 0 | } |
564 | 0 | } |
565 | | |
566 | | // If all features are available now, do not look further. |
567 | 0 | AVIFINFO_CHECK_NOT_FOUND( |
568 | 0 | AvifInfoInternalGetPrimaryItemFeatures(features)); |
569 | 0 | } else { |
570 | 0 | AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size)); |
571 | 0 | } |
572 | 0 | num_remaining_bytes -= box.size; |
573 | 0 | } while (num_remaining_bytes > 0); |
574 | 0 | AVIFINFO_RETURN(kNotFound); |
575 | 0 | } |
576 | | |
577 | | //------------------------------------------------------------------------------ |
578 | | |
579 | | // Parses a 'stream' of a "meta" box. It looks for the primary item ID in the |
580 | | // "pitm" box and recurses into other boxes to find its 'features'. |
581 | | static AvifInfoInternalStatus ParseMeta(AvifInfoInternalStream* stream, |
582 | | uint32_t num_remaining_bytes, |
583 | | uint32_t* num_parsed_boxes, |
584 | 0 | AvifInfoInternalFeatures* features) { |
585 | 0 | do { |
586 | 0 | AvifInfoInternalBox box; |
587 | 0 | AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, num_remaining_bytes, |
588 | 0 | num_parsed_boxes, &box)); |
589 | | |
590 | 0 | if (!memcmp(box.type, "pitm", 4)) { |
591 | | // See ISO/IEC 14496-12:2015(E) 8.11.4.2 |
592 | 0 | const uint32_t num_bytes_per_id = (box.version == 0) ? 2 : 4; |
593 | 0 | const uint8_t* data; |
594 | 0 | AVIFINFO_CHECK(num_bytes_per_id <= num_remaining_bytes, kInvalid); |
595 | 0 | AVIFINFO_CHECK_FOUND( |
596 | 0 | AvifInfoInternalRead(stream, num_bytes_per_id, &data)); |
597 | 0 | const uint32_t primary_item_id = |
598 | 0 | AvifInfoInternalReadBigEndian(data, num_bytes_per_id); |
599 | 0 | AVIFINFO_CHECK(primary_item_id <= AVIFINFO_MAX_VALUE, kAborted); |
600 | 0 | features->has_primary_item = 1; |
601 | 0 | features->primary_item_id = primary_item_id; |
602 | 0 | AVIFINFO_CHECK_FOUND( |
603 | 0 | AvifInfoInternalSkip(stream, box.content_size - num_bytes_per_id)); |
604 | 0 | } else if (!memcmp(box.type, "iprp", 4)) { |
605 | 0 | AVIFINFO_CHECK_NOT_FOUND( |
606 | 0 | ParseIprp(stream, box.content_size, num_parsed_boxes, features)); |
607 | 0 | } else if (!memcmp(box.type, "iref", 4)) { |
608 | 0 | AVIFINFO_CHECK_NOT_FOUND( |
609 | 0 | ParseIref(stream, box.content_size, num_parsed_boxes, features)); |
610 | 0 | } else { |
611 | 0 | AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size)); |
612 | 0 | } |
613 | 0 | num_remaining_bytes -= box.size; |
614 | 0 | } while (num_remaining_bytes != 0); |
615 | | // According to ISO/IEC 14496-12:2012(E) 8.11.1.1 there is at most one "meta". |
616 | 0 | AVIFINFO_RETURN(features->data_was_skipped ? kAborted : kInvalid); |
617 | 0 | } |
618 | | |
619 | | //------------------------------------------------------------------------------ |
620 | | |
621 | | // Parses a file 'stream'. The file type is checked through the "ftyp" box. |
622 | 0 | static AvifInfoInternalStatus ParseFtyp(AvifInfoInternalStream* stream) { |
623 | 0 | AvifInfoInternalBox box; |
624 | 0 | uint32_t num_parsed_boxes = 0; |
625 | 0 | AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, AVIFINFO_MAX_SIZE, |
626 | 0 | &num_parsed_boxes, &box)); |
627 | 0 | AVIFINFO_CHECK(!memcmp(box.type, "ftyp", 4), kInvalid); |
628 | | // Iterate over brands. See ISO/IEC 14496-12:2012(E) 4.3.1 |
629 | 0 | AVIFINFO_CHECK(box.content_size >= 8, kInvalid); // major_brand,minor_version |
630 | 0 | for (uint32_t i = 0; i + 4 <= box.content_size; i += 4) { |
631 | 0 | const uint8_t* data; |
632 | 0 | AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &data)); |
633 | 0 | if (i == 4) continue; // Skip minor_version. |
634 | 0 | if (!memcmp(data, "avif", 4) || !memcmp(data, "avis", 4)) { |
635 | 0 | AVIFINFO_CHECK_FOUND( |
636 | 0 | AvifInfoInternalSkip(stream, box.content_size - (i + 4))); |
637 | 0 | return kFound; |
638 | 0 | } |
639 | 0 | AVIFINFO_CHECK(i <= 32 * 4, kAborted); // Be reasonable. |
640 | 0 | } |
641 | 0 | AVIFINFO_RETURN(kInvalid); // No AVIF brand no good. |
642 | 0 | } |
643 | | |
644 | | // Parses a file 'stream'. 'features' are extracted from the "meta" box. |
645 | | static AvifInfoInternalStatus ParseFile(AvifInfoInternalStream* stream, |
646 | | uint32_t* num_parsed_boxes, |
647 | 0 | AvifInfoInternalFeatures* features) { |
648 | 0 | while (1) { |
649 | 0 | AvifInfoInternalBox box; |
650 | 0 | AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, AVIFINFO_MAX_SIZE, |
651 | 0 | num_parsed_boxes, &box)); |
652 | 0 | if (!memcmp(box.type, "meta", 4)) { |
653 | 0 | return ParseMeta(stream, box.content_size, num_parsed_boxes, features); |
654 | 0 | } else { |
655 | 0 | AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size)); |
656 | 0 | } |
657 | 0 | } |
658 | 0 | AVIFINFO_RETURN(kInvalid); // No "meta" no good. |
659 | 0 | } |
660 | | |
661 | | //------------------------------------------------------------------------------ |
662 | | // Helpers for converting the fixed-size input public API to the streamed one. |
663 | | |
664 | | typedef struct { |
665 | | const uint8_t* data; |
666 | | size_t data_size; |
667 | | } AvifInfoInternalForward; |
668 | | |
669 | | static const uint8_t* AvifInfoInternalForwardRead(void* stream, |
670 | 0 | size_t num_bytes) { |
671 | 0 | AvifInfoInternalForward* forward = (AvifInfoInternalForward*)stream; |
672 | 0 | if (num_bytes > forward->data_size) return NULL; |
673 | 0 | const uint8_t* data = forward->data; |
674 | 0 | forward->data += num_bytes; |
675 | 0 | forward->data_size -= num_bytes; |
676 | 0 | return data; |
677 | 0 | } |
678 | | |
679 | 0 | static void AvifInfoInternalForwardSkip(void* stream, size_t num_bytes) { |
680 | 0 | AvifInfoInternalForward* forward = (AvifInfoInternalForward*)stream; |
681 | 0 | if (num_bytes > forward->data_size) num_bytes = forward->data_size; |
682 | 0 | forward->data += num_bytes; |
683 | 0 | forward->data_size -= num_bytes; |
684 | 0 | } |
685 | | |
686 | | //------------------------------------------------------------------------------ |
687 | | // Fixed-size input public API |
688 | | |
689 | 0 | AvifInfoStatus AvifInfoIdentify(const uint8_t* data, size_t data_size) { |
690 | 0 | AvifInfoInternalForward stream; |
691 | 0 | stream.data = data; |
692 | 0 | stream.data_size = data_size; |
693 | | // Forward null 'data' as a null 'stream' to handle it the same way. |
694 | 0 | return AvifInfoIdentifyStream( |
695 | 0 | (void*)&stream, (data == NULL) ? NULL : AvifInfoInternalForwardRead, |
696 | 0 | AvifInfoInternalForwardSkip); |
697 | 0 | } |
698 | | |
699 | | AvifInfoStatus AvifInfoGetFeatures(const uint8_t* data, size_t data_size, |
700 | 0 | AvifInfoFeatures* features) { |
701 | 0 | AvifInfoInternalForward stream; |
702 | 0 | stream.data = data; |
703 | 0 | stream.data_size = data_size; |
704 | 0 | return AvifInfoGetFeaturesStream( |
705 | 0 | (void*)&stream, (data == NULL) ? NULL : AvifInfoInternalForwardRead, |
706 | 0 | AvifInfoInternalForwardSkip, features); |
707 | 0 | } |
708 | | |
709 | | //------------------------------------------------------------------------------ |
710 | | // Streamed input API |
711 | | |
712 | | AvifInfoStatus AvifInfoIdentifyStream(void* stream, read_stream_t read, |
713 | 0 | skip_stream_t skip) { |
714 | 0 | if (read == NULL) return kAvifInfoNotEnoughData; |
715 | | |
716 | 0 | AvifInfoInternalStream internal_stream; |
717 | 0 | internal_stream.stream = stream; |
718 | 0 | internal_stream.read = read; |
719 | 0 | internal_stream.skip = skip; // Fallbacks to 'read' if null. |
720 | 0 | return AvifInfoInternalConvertStatus(ParseFtyp(&internal_stream)); |
721 | 0 | } |
722 | | |
723 | | AvifInfoStatus AvifInfoGetFeaturesStream(void* stream, read_stream_t read, |
724 | | skip_stream_t skip, |
725 | 0 | AvifInfoFeatures* features) { |
726 | 0 | if (features != NULL) memset(features, 0, sizeof(*features)); |
727 | 0 | if (read == NULL) return kAvifInfoNotEnoughData; |
728 | | |
729 | 0 | AvifInfoInternalStream internal_stream; |
730 | 0 | internal_stream.stream = stream; |
731 | 0 | internal_stream.read = read; |
732 | 0 | internal_stream.skip = skip; // Fallbacks to 'read' if null. |
733 | 0 | uint32_t num_parsed_boxes = 0; |
734 | 0 | AvifInfoInternalFeatures internal_features; |
735 | 0 | memset(&internal_features, AVIFINFO_UNDEFINED, sizeof(internal_features)); |
736 | | |
737 | | // Go through all relevant boxes sequentially. |
738 | 0 | const AvifInfoInternalStatus status = |
739 | 0 | ParseFile(&internal_stream, &num_parsed_boxes, &internal_features); |
740 | 0 | if (status == kFound && features != NULL) { |
741 | 0 | memcpy(features, &internal_features.primary_item_features, |
742 | 0 | sizeof(*features)); |
743 | 0 | } |
744 | 0 | return AvifInfoInternalConvertStatus(status); |
745 | 0 | } |