php-internal-docs 8.4.8
Unofficial docs for php/php-src
Loading...
Searching...
No Matches
avifinfo.c
Go to the documentation of this file.
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).
19typedef 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.
26
27static AvifInfoStatus AvifInfoInternalConvertStatus(AvifInfoInternalStatus s) {
28 return (s == kFound) ? kAvifInfoOk
32}
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#define AVIFINFO_MAX_VALUE UINT8_MAX
40#define AVIFINFO_UNDEFINED 0
41// Maximum number of stored associations. Past that, they are skipped.
42#define AVIFINFO_MAX_TILES 16
43#define AVIFINFO_MAX_PROPS 32
44#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.
48static uint32_t AvifInfoInternalReadBigEndian(const uint8_t* input,
49 uint32_t num_bytes) {
50 uint32_t value = 0;
51 for (uint32_t i = 0; i < num_bytes; ++i) {
52 value = (value << 8) | input[i];
53 }
54 return value;
55}
56
57//------------------------------------------------------------------------------
58// Convenience macros.
59
60#if defined(AVIFINFO_LOG_ERROR) // Toggle to log encountered issues.
61static void AvifInfoInternalLogError(const char* file, int line,
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 do { \
78 return (check_status); \
79 } while (0)
80#endif
81
82#define AVIFINFO_CHECK(check_condition, check_status) \
83 do { \
84 if (!(check_condition)) AVIFINFO_RETURN(check_status); \
85 } while (0)
86#define AVIFINFO_CHECK_STATUS_IS(check_status, expected_status) \
87 do { \
88 const AvifInfoInternalStatus status_returned = (check_status); \
89 AVIFINFO_CHECK(status_returned == (expected_status), status_returned); \
90 } while (0)
91#define AVIFINFO_CHECK_FOUND(check_status) \
92 AVIFINFO_CHECK_STATUS_IS((check_status), kFound)
93#define AVIFINFO_CHECK_NOT_FOUND(check_status) \
94 AVIFINFO_CHECK_STATUS_IS((check_status), kNotFound)
95
96//------------------------------------------------------------------------------
97// Streamed input struct and helper functions.
98
99typedef 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.
105
106// Reads 'num_bytes' from the 'stream'. They are available at '*data'.
107// 'num_bytes' must be greater than zero.
108static AvifInfoInternalStatus AvifInfoInternalRead(
109 AvifInfoInternalStream* stream, uint32_t num_bytes, const uint8_t** data) {
110 *data = stream->read(stream->stream, num_bytes);
112 return kFound;
113}
114
115// Skips 'num_bytes' from the 'stream'. 'num_bytes' can be zero.
116static AvifInfoInternalStatus AvifInfoInternalSkip(
117 AvifInfoInternalStream* stream, uint32_t num_bytes) {
118 // Avoid a call to the user-defined function for nothing.
119 if (num_bytes > 0) {
120 if (stream->skip == NULL) {
121 const uint8_t* unused;
122 while (num_bytes > AVIFINFO_MAX_NUM_READ_BYTES) {
124 AvifInfoInternalRead(stream, AVIFINFO_MAX_NUM_READ_BYTES, &unused));
125 num_bytes -= AVIFINFO_MAX_NUM_READ_BYTES;
126 }
127 return AvifInfoInternalRead(stream, num_bytes, &unused);
128 }
129 stream->skip(stream->stream, num_bytes);
130 }
131 return kFound;
132}
133
134//------------------------------------------------------------------------------
135// Features are parsed into temporary property associations.
136
137typedef struct {
140} AvifInfoInternalTile; // Tile item id <-> parent item id associations.
141
142typedef struct {
144 uint8_t item_id;
145} AvifInfoInternalProp; // Property index <-> item id associations.
146
147typedef struct {
149 uint32_t width, height;
150} AvifInfoInternalDimProp; // Property <-> features associations.
151
152typedef struct {
155} AvifInfoInternalChanProp; // Property <-> features associations.
156
157typedef struct {
158 uint8_t has_primary_item; // True if "pitm" was parsed.
159 uint8_t has_alpha; // True if an alpha "auxC" was parsed.
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;
166 uint8_t num_props;
173
174// Generates the features of a given 'target_item_id' from internal features.
175static AvifInfoInternalStatus AvifInfoInternalGetItemFeatures(
176 AvifInfoInternalFeatures* f, uint32_t target_item_id, uint32_t tile_depth) {
177 for (uint32_t prop_item = 0; prop_item < f->num_props; ++prop_item) {
178 if (f->props[prop_item].item_id != target_item_id) continue;
179 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 if (target_item_id == f->primary_item_id &&
185 for (uint32_t i = 0; i < f->num_dim_props; ++i) {
186 if (f->dim_props[i].property_index != property_index) continue;
191 return kFound;
192 }
193 break;
194 }
195 }
196 // Retrieve the bit depth and number of channels of the target item if not
197 // already done.
200 for (uint32_t i = 0; i < f->num_chan_props; ++i) {
201 if (f->chan_props[i].property_index != property_index) continue;
206 return kFound;
207 }
208 break;
209 }
210 }
211 }
212
213 // Check for the bit_depth and num_channels in a tile if not yet found.
214 for (uint32_t tile = 0; tile < f->num_tiles && tile_depth < 3; ++tile) {
215 if (f->tiles[tile].parent_item_id != target_item_id) continue;
216 AVIFINFO_CHECK_NOT_FOUND(AvifInfoInternalGetItemFeatures(
217 f, f->tiles[tile].tile_item_id, tile_depth + 1));
218 }
220}
221
222// Generates the 'f->primary_item_features' from the AvifInfoInternalFeatures.
223// Returns kNotFound if there is not enough information.
224static AvifInfoInternalStatus AvifInfoInternalGetPrimaryItemFeatures(
226 // Nothing to do without the primary item ID.
228 // Early exit.
231 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.
235 return kFound;
236}
237
238//------------------------------------------------------------------------------
239// Box header parsing and various size checks.
240
241typedef 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.
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').
252static AvifInfoInternalStatus AvifInfoInternalParseBox(
253 AvifInfoInternalStream* stream, uint32_t num_remaining_bytes,
254 uint32_t* num_parsed_boxes, AvifInfoInternalBox* box) {
255 const uint8_t* data;
256 // See ISO/IEC 14496-12:2012(E) 4.2
257 uint32_t box_header_size = 8; // box 32b size + 32b type (at least)
258 AVIFINFO_CHECK(box_header_size <= num_remaining_bytes, kInvalid);
259 AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 8, &data));
260 box->size = AvifInfoInternalReadBigEndian(data, sizeof(uint32_t));
261 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 if (box->size == 1) {
265 box_header_size += 8;
266 AVIFINFO_CHECK(box_header_size <= num_remaining_bytes, kInvalid);
267 AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 8, &data));
268 // Stop the parsing if any box has a size greater than 4GB.
269 AVIFINFO_CHECK(AvifInfoInternalReadBigEndian(data, sizeof(uint32_t)) == 0,
270 kAborted);
271 // Read the 32 least-significant bits.
272 box->size = AvifInfoInternalReadBigEndian(data + 4, sizeof(uint32_t));
273 } else if (box->size == 0) {
274 box->size = num_remaining_bytes;
275 }
276 AVIFINFO_CHECK(box->size >= box_header_size, kInvalid);
277 AVIFINFO_CHECK(box->size <= num_remaining_bytes, kInvalid);
278
279 const int has_fullbox_header =
280 !memcmp(box->type, "meta", 4) || !memcmp(box->type, "pitm", 4) ||
281 !memcmp(box->type, "ipma", 4) || !memcmp(box->type, "ispe", 4) ||
282 !memcmp(box->type, "pixi", 4) || !memcmp(box->type, "iref", 4) ||
283 !memcmp(box->type, "auxC", 4);
284 if (has_fullbox_header) box_header_size += 4;
285 AVIFINFO_CHECK(box->size >= box_header_size, kInvalid);
286 box->content_size = box->size - box_header_size;
287 // Avoid timeouts. The maximum number of parsed boxes is arbitrary.
288 ++*num_parsed_boxes;
289 AVIFINFO_CHECK(*num_parsed_boxes < 4096, kAborted);
290
291 box->version = 0;
292 box->flags = 0;
293 if (has_fullbox_header) {
294 AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &data));
295 box->version = AvifInfoInternalReadBigEndian(data, 1);
296 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 uint32_t is_parsable = 1;
301 if (!memcmp(box->type, "meta", 4)) is_parsable = (box->version <= 0);
302 if (!memcmp(box->type, "pitm", 4)) is_parsable = (box->version <= 1);
303 if (!memcmp(box->type, "ipma", 4)) is_parsable = (box->version <= 1);
304 if (!memcmp(box->type, "ispe", 4)) is_parsable = (box->version <= 0);
305 if (!memcmp(box->type, "pixi", 4)) is_parsable = (box->version <= 0);
306 if (!memcmp(box->type, "iref", 4)) is_parsable = (box->version <= 1);
307 if (!memcmp(box->type, "auxC", 4)) is_parsable = (box->version <= 0);
308 // Instead of considering this file as invalid, skip unparsable boxes.
309 if (!is_parsable) memcpy(box->type, "\0skp", 4); // \0 so not a valid type
310 }
311 return kFound;
312}
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.
319static AvifInfoInternalStatus ParseIpco(AvifInfoInternalStream* stream,
320 uint32_t num_remaining_bytes,
321 uint32_t* num_parsed_boxes,
323 uint32_t box_index = 1; // 1-based index. Used for iterating over properties.
324 do {
326 AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, num_remaining_bytes,
327 num_parsed_boxes, &box));
328
329 if (!memcmp(box.type, "ispe", 4)) {
330 // See ISO/IEC 23008-12:2017(E) 6.5.3.2
331 const uint8_t* data;
332 AVIFINFO_CHECK(box.content_size >= 8, kInvalid);
333 AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 8, &data));
334 const uint32_t width = AvifInfoInternalReadBigEndian(data + 0, 4);
335 const uint32_t height = AvifInfoInternalReadBigEndian(data + 4, 4);
336 AVIFINFO_CHECK(width != 0 && height != 0, kInvalid);
337 if (features->num_dim_props < AVIFINFO_MAX_FEATURES &&
338 box_index <= AVIFINFO_MAX_VALUE) {
339 features->dim_props[features->num_dim_props].property_index = box_index;
340 features->dim_props[features->num_dim_props].width = width;
341 features->dim_props[features->num_dim_props].height = height;
342 ++features->num_dim_props;
343 } else {
344 features->data_was_skipped = 1;
345 }
346 AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size - 8));
347 } else if (!memcmp(box.type, "pixi", 4)) {
348 // See ISO/IEC 23008-12:2017(E) 6.5.6.2
349 const uint8_t* data;
350 AVIFINFO_CHECK(box.content_size >= 1, kInvalid);
351 AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 1, &data));
352 const uint32_t num_channels = AvifInfoInternalReadBigEndian(data + 0, 1);
353 AVIFINFO_CHECK(num_channels >= 1, kInvalid);
354 AVIFINFO_CHECK(box.content_size >= 1 + num_channels, kInvalid);
355 AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 1, &data));
356 const uint32_t bit_depth = AvifInfoInternalReadBigEndian(data, 1);
357 AVIFINFO_CHECK(bit_depth >= 1, kInvalid);
358 for (uint32_t i = 1; i < num_channels; ++i) {
359 AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 1, &data));
360 // Bit depth should be the same for all channels.
361 AVIFINFO_CHECK(AvifInfoInternalReadBigEndian(data, 1) == bit_depth,
362 kInvalid);
363 AVIFINFO_CHECK(i <= 32, kAborted); // Be reasonable.
364 }
365 if (features->num_chan_props < AVIFINFO_MAX_FEATURES &&
366 box_index <= AVIFINFO_MAX_VALUE && bit_depth <= AVIFINFO_MAX_VALUE &&
367 num_channels <= AVIFINFO_MAX_VALUE) {
368 features->chan_props[features->num_chan_props].property_index =
369 box_index;
370 features->chan_props[features->num_chan_props].bit_depth = bit_depth;
371 features->chan_props[features->num_chan_props].num_channels =
372 num_channels;
373 ++features->num_chan_props;
374 } else {
375 features->data_was_skipped = 1;
376 }
378 AvifInfoInternalSkip(stream, box.content_size - (1 + num_channels)));
379 } 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 const uint8_t* data;
384 AVIFINFO_CHECK(box.content_size >= 3, kInvalid);
385 AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 3, &data));
386 const int high_bitdepth = (data[2] & 0x40) != 0;
387 const int twelve_bit = (data[2] & 0x20) != 0;
388 const int monochrome = (data[2] & 0x10) != 0;
389 if (twelve_bit) {
390 AVIFINFO_CHECK(high_bitdepth, kInvalid);
391 }
392 if (features->num_chan_props < AVIFINFO_MAX_FEATURES &&
393 box_index <= AVIFINFO_MAX_VALUE) {
394 features->chan_props[features->num_chan_props].property_index =
395 box_index;
396 features->chan_props[features->num_chan_props].bit_depth =
397 high_bitdepth ? twelve_bit ? 12 : 10 : 8;
398 features->chan_props[features->num_chan_props].num_channels =
399 monochrome ? 1 : 3;
400 ++features->num_chan_props;
401 } else {
402 features->data_was_skipped = 1;
403 }
404 AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size - 3));
405 } 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 const char* kAlphaStr = "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha";
409 const uint32_t kAlphaStrLength = 44; // Includes terminating character.
410 if (box.content_size >= kAlphaStrLength) {
411 const uint8_t* data;
413 AvifInfoInternalRead(stream, kAlphaStrLength, &data));
414 const char* const aux_type = (const char*)data;
415 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 features->has_alpha = 1;
419 }
421 AvifInfoInternalSkip(stream, box.content_size - kAlphaStrLength));
422 } else {
423 AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
424 }
425 } else {
426 AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
427 }
428 ++box_index;
429 num_remaining_bytes -= box.size;
430 } while (num_remaining_bytes > 0);
432}
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.
436static AvifInfoInternalStatus ParseIprp(AvifInfoInternalStream* stream,
437 uint32_t num_remaining_bytes,
438 uint32_t* num_parsed_boxes,
440 do {
442 AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, num_remaining_bytes,
443 num_parsed_boxes, &box));
444
445 if (!memcmp(box.type, "ipco", 4)) {
447 ParseIpco(stream, box.content_size, num_parsed_boxes, features));
448 } else if (!memcmp(box.type, "ipma", 4)) {
449 // See ISO/IEC 23008-12:2017(E) 9.3.2
450 uint32_t num_read_bytes = 4;
451 const uint8_t* data;
452 AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
453 AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &data));
454 const uint32_t entry_count = AvifInfoInternalReadBigEndian(data, 4);
455 const uint32_t id_num_bytes = (box.version < 1) ? 2 : 4;
456 const uint32_t index_num_bytes = (box.flags & 1) ? 2 : 1;
457 const uint32_t essential_bit_mask = (box.flags & 1) ? 0x8000 : 0x80;
458
459 for (uint32_t entry = 0; entry < entry_count; ++entry) {
460 if (entry >= AVIFINFO_MAX_PROPS ||
461 features->num_props >= AVIFINFO_MAX_PROPS) {
462 features->data_was_skipped = 1;
463 break;
464 }
465 num_read_bytes += id_num_bytes + 1;
466 AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
468 AvifInfoInternalRead(stream, id_num_bytes + 1, &data));
469 const uint32_t item_id =
470 AvifInfoInternalReadBigEndian(data, id_num_bytes);
471 const uint32_t association_count =
472 AvifInfoInternalReadBigEndian(data + id_num_bytes, 1);
473
474 uint32_t property;
475 for (property = 0; property < association_count; ++property) {
477 features->num_props >= AVIFINFO_MAX_PROPS) {
478 features->data_was_skipped = 1;
479 break;
480 }
481 num_read_bytes += index_num_bytes;
482 AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
484 AvifInfoInternalRead(stream, index_num_bytes, &data));
485 const uint32_t value =
486 AvifInfoInternalReadBigEndian(data, index_num_bytes);
487 // const int essential = (value & essential_bit_mask); // Unused.
488 const uint32_t property_index = (value & ~essential_bit_mask);
489 if (property_index <= AVIFINFO_MAX_VALUE &&
490 item_id <= AVIFINFO_MAX_VALUE) {
491 features->props[features->num_props].property_index =
492 property_index;
493 features->props[features->num_props].item_id = item_id;
494 ++features->num_props;
495 } else {
496 features->data_was_skipped = 1;
497 }
498 }
499 if (property < association_count) break; // Do not read garbage.
500 }
501
502 // If all features are available now, do not look further.
504 AvifInfoInternalGetPrimaryItemFeatures(features));
505
507 AvifInfoInternalSkip(stream, box.content_size - num_read_bytes));
508 } else {
509 AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
510 }
511 num_remaining_bytes -= box.size;
512 } while (num_remaining_bytes != 0);
514}
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.
522static AvifInfoInternalStatus ParseIref(AvifInfoInternalStream* stream,
523 uint32_t num_remaining_bytes,
524 uint32_t* num_parsed_boxes,
526 do {
528 AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, num_remaining_bytes,
529 num_parsed_boxes, &box));
530
531 if (!memcmp(box.type, "dimg", 4)) {
532 // See ISO/IEC 14496-12:2015(E) 8.11.12.2
533 const uint32_t num_bytes_per_id = (box.version == 0) ? 2 : 4;
534 uint32_t num_read_bytes = num_bytes_per_id + 2;
535 const uint8_t* data;
536 AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
538 AvifInfoInternalRead(stream, num_bytes_per_id + 2, &data));
539 const uint32_t from_item_id =
540 AvifInfoInternalReadBigEndian(data, num_bytes_per_id);
541 const uint32_t reference_count =
542 AvifInfoInternalReadBigEndian(data + num_bytes_per_id, 2);
543
544 for (uint32_t i = 0; i < reference_count; ++i) {
545 if (i >= AVIFINFO_MAX_TILES) {
546 features->data_was_skipped = 1;
547 break;
548 }
549 num_read_bytes += num_bytes_per_id;
550 AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
552 AvifInfoInternalRead(stream, num_bytes_per_id, &data));
553 const uint32_t to_item_id =
554 AvifInfoInternalReadBigEndian(data, num_bytes_per_id);
555 if (from_item_id <= AVIFINFO_MAX_VALUE &&
556 to_item_id <= AVIFINFO_MAX_VALUE &&
557 features->num_tiles < AVIFINFO_MAX_TILES) {
558 features->tiles[features->num_tiles].tile_item_id = to_item_id;
559 features->tiles[features->num_tiles].parent_item_id = from_item_id;
560 ++features->num_tiles;
561 } else {
562 features->data_was_skipped = 1;
563 }
564 }
565
566 // If all features are available now, do not look further.
568 AvifInfoInternalGetPrimaryItemFeatures(features));
569 } else {
570 AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
571 }
572 num_remaining_bytes -= box.size;
573 } while (num_remaining_bytes > 0);
575}
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'.
581static AvifInfoInternalStatus ParseMeta(AvifInfoInternalStream* stream,
582 uint32_t num_remaining_bytes,
583 uint32_t* num_parsed_boxes,
585 do {
587 AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, num_remaining_bytes,
588 num_parsed_boxes, &box));
589
590 if (!memcmp(box.type, "pitm", 4)) {
591 // See ISO/IEC 14496-12:2015(E) 8.11.4.2
592 const uint32_t num_bytes_per_id = (box.version == 0) ? 2 : 4;
593 const uint8_t* data;
594 AVIFINFO_CHECK(num_bytes_per_id <= num_remaining_bytes, kInvalid);
596 AvifInfoInternalRead(stream, num_bytes_per_id, &data));
597 const uint32_t primary_item_id =
598 AvifInfoInternalReadBigEndian(data, num_bytes_per_id);
599 AVIFINFO_CHECK(primary_item_id <= AVIFINFO_MAX_VALUE, kAborted);
600 features->has_primary_item = 1;
601 features->primary_item_id = primary_item_id;
603 AvifInfoInternalSkip(stream, box.content_size - num_bytes_per_id));
604 } else if (!memcmp(box.type, "iprp", 4)) {
606 ParseIprp(stream, box.content_size, num_parsed_boxes, features));
607 } else if (!memcmp(box.type, "iref", 4)) {
609 ParseIref(stream, box.content_size, num_parsed_boxes, features));
610 } else {
611 AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
612 }
613 num_remaining_bytes -= box.size;
614 } 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 AVIFINFO_RETURN(features->data_was_skipped ? kAborted : kInvalid);
617}
618
619//------------------------------------------------------------------------------
620
621// Parses a file 'stream'. The file type is checked through the "ftyp" box.
622static AvifInfoInternalStatus ParseFtyp(AvifInfoInternalStream* stream) {
624 uint32_t num_parsed_boxes = 0;
625 AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, AVIFINFO_MAX_SIZE,
626 &num_parsed_boxes, &box));
627 AVIFINFO_CHECK(!memcmp(box.type, "ftyp", 4), kInvalid);
628 // Iterate over brands. See ISO/IEC 14496-12:2012(E) 4.3.1
629 AVIFINFO_CHECK(box.content_size >= 8, kInvalid); // major_brand,minor_version
630 for (uint32_t i = 0; i + 4 <= box.content_size; i += 4) {
631 const uint8_t* data;
632 AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &data));
633 if (i == 4) continue; // Skip minor_version.
634 if (!memcmp(data, "avif", 4) || !memcmp(data, "avis", 4)) {
636 AvifInfoInternalSkip(stream, box.content_size - (i + 4)));
637 return kFound;
638 }
639 AVIFINFO_CHECK(i <= 32 * 4, kAborted); // Be reasonable.
640 }
641 AVIFINFO_RETURN(kInvalid); // No AVIF brand no good.
642}
643
644// Parses a file 'stream'. 'features' are extracted from the "meta" box.
645static AvifInfoInternalStatus ParseFile(AvifInfoInternalStream* stream,
646 uint32_t* num_parsed_boxes,
648 while (1) {
650 AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, AVIFINFO_MAX_SIZE,
651 num_parsed_boxes, &box));
652 if (!memcmp(box.type, "meta", 4)) {
653 return ParseMeta(stream, box.content_size, num_parsed_boxes, features);
654 } else {
655 AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
656 }
657 }
658 AVIFINFO_RETURN(kInvalid); // No "meta" no good.
659}
660
661//------------------------------------------------------------------------------
662// Helpers for converting the fixed-size input public API to the streamed one.
663
664typedef struct {
665 const uint8_t* data;
666 size_t data_size;
668
669static const uint8_t* AvifInfoInternalForwardRead(void* stream,
670 size_t num_bytes) {
672 if (num_bytes > forward->data_size) return NULL;
673 const uint8_t* data = forward->data;
674 forward->data += num_bytes;
675 forward->data_size -= num_bytes;
676 return data;
677}
678
679static void AvifInfoInternalForwardSkip(void* stream, size_t num_bytes) {
681 if (num_bytes > forward->data_size) num_bytes = forward->data_size;
682 forward->data += num_bytes;
683 forward->data_size -= num_bytes;
684}
685
686//------------------------------------------------------------------------------
687// Fixed-size input public API
688
689AvifInfoStatus AvifInfoIdentify(const uint8_t* data, size_t data_size) {
691 stream.data = data;
692 stream.data_size = data_size;
693 // Forward null 'data' as a null 'stream' to handle it the same way.
695 (void*)&stream, (data == NULL) ? NULL : AvifInfoInternalForwardRead,
696 AvifInfoInternalForwardSkip);
697}
698
699AvifInfoStatus AvifInfoGetFeatures(const uint8_t* data, size_t data_size,
702 stream.data = data;
703 stream.data_size = data_size;
705 (void*)&stream, (data == NULL) ? NULL : AvifInfoInternalForwardRead,
706 AvifInfoInternalForwardSkip, features);
707}
708
709//------------------------------------------------------------------------------
710// Streamed input API
711
713 skip_stream_t skip) {
714 if (read == NULL) return kAvifInfoNotEnoughData;
715
716 AvifInfoInternalStream internal_stream;
717 internal_stream.stream = stream;
718 internal_stream.read = read;
719 internal_stream.skip = skip; // Fallbacks to 'read' if null.
720 return AvifInfoInternalConvertStatus(ParseFtyp(&internal_stream));
721}
722
724 skip_stream_t skip,
726 if (features != NULL) memset(features, 0, sizeof(*features));
727 if (read == NULL) return kAvifInfoNotEnoughData;
728
729 AvifInfoInternalStream internal_stream;
730 internal_stream.stream = stream;
731 internal_stream.read = read;
732 internal_stream.skip = skip; // Fallbacks to 'read' if null.
733 uint32_t num_parsed_boxes = 0;
734 AvifInfoInternalFeatures internal_features;
735 memset(&internal_features, AVIFINFO_UNDEFINED, sizeof(internal_features));
736
737 // Go through all relevant boxes sequentially.
739 ParseFile(&internal_stream, &num_parsed_boxes, &internal_features);
740 if (status == kFound && features != NULL) {
741 memcpy(features, &internal_features.primary_item_features,
742 sizeof(*features));
743 }
744 return AvifInfoInternalConvertStatus(status);
745}
#define AVIFINFO_CHECK_NOT_FOUND(check_status)
Definition avifinfo.c:93
#define AVIFINFO_MAX_SIZE
Definition avifinfo.c:36
#define AVIFINFO_CHECK_FOUND(check_status)
Definition avifinfo.c:91
#define AVIFINFO_CHECK(check_condition, check_status)
Definition avifinfo.c:82
AvifInfoInternalStatus
Definition avifinfo.c:19
@ kNotFound
Definition avifinfo.c:21
@ kTruncated
Definition avifinfo.c:22
@ kFound
Definition avifinfo.c:20
@ kAborted
Definition avifinfo.c:23
@ kInvalid
Definition avifinfo.c:24
AvifInfoStatus AvifInfoIdentify(const uint8_t *data, size_t data_size)
Definition avifinfo.c:689
AvifInfoStatus AvifInfoIdentifyStream(void *stream, read_stream_t read, skip_stream_t skip)
Definition avifinfo.c:712
#define AVIFINFO_UNDEFINED
Definition avifinfo.c:40
AvifInfoStatus AvifInfoGetFeaturesStream(void *stream, read_stream_t read, skip_stream_t skip, AvifInfoFeatures *features)
Definition avifinfo.c:723
#define AVIFINFO_MAX_TILES
Definition avifinfo.c:42
#define AVIFINFO_RETURN(check_status)
Definition avifinfo.c:76
#define AVIFINFO_MAX_FEATURES
Definition avifinfo.c:44
#define AVIFINFO_MAX_PROPS
Definition avifinfo.c:43
#define AVIFINFO_MAX_VALUE
Definition avifinfo.c:39
AvifInfoStatus AvifInfoGetFeatures(const uint8_t *data, size_t data_size, AvifInfoFeatures *features)
Definition avifinfo.c:699
AvifInfoStatus
Definition avifinfo.h:22
@ kAvifInfoInvalidFile
Definition avifinfo.h:33
@ kAvifInfoOk
Definition avifinfo.h:23
@ kAvifInfoTooComplex
Definition avifinfo.h:30
@ kAvifInfoNotEnoughData
Definition avifinfo.h:27
#define AVIFINFO_MAX_NUM_READ_BYTES
Definition avifinfo.h:74
const uint8_t *(* read_stream_t)(void *stream, size_t num_bytes)
Definition avifinfo.h:69
void(* skip_stream_t)(void *stream, size_t num_bytes)
Definition avifinfo.h:71
fprintf($stream, string $format, mixed ... $values)
file(string $filename, int $flags=0, $context=null)
char s[4]
Definition cdf.c:77
DNS_STATUS status
Definition dns_win32.c:49
memcpy(ptr1, ptr2, size)
memset(ptr, 0, type->size)
#define NULL
Definition gdcache.h:45
int line
Definition php_ffi.h:54
int features
Definition php_soap.h:172
zend_constant * data
uint32_t num_channels
Definition avifinfo.h:40
uint32_t width
Definition avifinfo.h:38
uint32_t height
Definition avifinfo.h:38
uint32_t bit_depth
Definition avifinfo.h:39
uint8_t type[4]
Definition avifinfo.c:243
uint32_t content_size
Definition avifinfo.c:246
AvifInfoInternalDimProp dim_props[AVIFINFO_MAX_FEATURES]
Definition avifinfo.c:169
AvifInfoInternalChanProp chan_props[AVIFINFO_MAX_FEATURES]
Definition avifinfo.c:171
AvifInfoInternalProp props[AVIFINFO_MAX_PROPS]
Definition avifinfo.c:167
AvifInfoFeatures primary_item_features
Definition avifinfo.c:161
AvifInfoInternalTile tiles[AVIFINFO_MAX_TILES]
Definition avifinfo.c:165
const uint8_t * data
Definition avifinfo.c:665
read_stream_t read
Definition avifinfo.c:101
skip_stream_t skip
Definition avifinfo.c:102
strcmp(string $string1, string $string2)
value
property