| // Copyright (c) 2021, Alliance for Open Media. All rights reserved |
| // |
| // This source code is subject to the terms of the BSD 2 Clause License and |
| // the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License |
| // was not distributed with this source code in the LICENSE file, you can |
| // obtain it at www.aomedia.org/license/software. If the Alliance for Open |
| // Media Patent License 1.0 was not distributed with this source code in the |
| // PATENTS file, you can obtain it at www.aomedia.org/license/patent. |
| |
| #include "avifinfo.h" |
| |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| //------------------------------------------------------------------------------ |
| |
| // Status returned when reading the content of a box (or file). |
| typedef enum { |
| kFound, // Input correctly parsed and information retrieved. |
| kNotFound, // Input correctly parsed but information is missing or elsewhere. |
| kTruncated, // Input correctly parsed until missing bytes to continue. |
| kAborted, // Input correctly parsed until stopped to avoid timeout or crash. |
| kInvalid, // Input incorrectly parsed. |
| } AvifInfoInternalStatus; |
| |
| // uint32_t is used everywhere in this file. It is unlikely to be insufficient |
| // to parse AVIF headers. Clamp any input to 2^32-1 for simplicity. |
| static const uint32_t kAvifInfoInternalMaxSize = UINT32_MAX; |
| |
| // Reads an unsigned integer from 'input' with most significant bits first. |
| // 'input' must be at least 'num_bytes'-long. |
| static uint32_t AvifInfoInternalReadBigEndian(const uint8_t* input, |
| uint32_t num_bytes) { |
| uint32_t value = 0; |
| for (uint32_t i = 0; i < num_bytes; ++i) { |
| value = (value << 8) | input[i]; |
| } |
| return value; |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Convenience macros. |
| |
| #if defined(AVIFINFO_LOG_ERROR) // Toggle to log encountered issues. |
| static void AvifInfoInternalLogError(const char* file, int line, |
| AvifInfoInternalStatus status) { |
| const char* kStr[] = {"Found", "NotFound", "Truncated", "Invalid", "Aborted"}; |
| fprintf(stderr, " %s:%d: %s\n", file, line, kStr[status]); |
| // Set a breakpoint here to catch the first detected issue. |
| } |
| #define AVIFINFO_RETURN(check_status) \ |
| do { \ |
| const AvifInfoInternalStatus status_checked = (check_status); \ |
| if (status_checked != kFound && status_checked != kNotFound) { \ |
| AvifInfoInternalLogError(__FILE__, __LINE__, status_checked); \ |
| } \ |
| return status_checked; \ |
| } while (0) |
| #else |
| #define AVIFINFO_RETURN(check_status) \ |
| do { \ |
| return (check_status); \ |
| } while (0) |
| #endif |
| |
| #define AVIFINFO_CHECK(check_condition, check_status) \ |
| do { \ |
| if (!(check_condition)) AVIFINFO_RETURN(check_status); \ |
| } while (0) |
| #define AVIFINFO_CHECK_STATUS_IS(check_status, expected_status) \ |
| do { \ |
| const AvifInfoInternalStatus status_returned = (check_status); \ |
| AVIFINFO_CHECK(status_returned == (expected_status), status_returned); \ |
| } while (0) |
| #define AVIFINFO_CHECK_FOUND(check_status) \ |
| AVIFINFO_CHECK_STATUS_IS((check_status), kFound) |
| #define AVIFINFO_CHECK_NOT_FOUND(check_status) \ |
| AVIFINFO_CHECK_STATUS_IS((check_status), kNotFound) |
| |
| //------------------------------------------------------------------------------ |
| // Box header parsing and various size checks. |
| |
| typedef struct { |
| uint32_t size; // In bytes. |
| const uint8_t* type; // Points to four characters. |
| uint32_t version; // 0 or actual version if this is a full box. |
| uint32_t flags; // 0 or actual value if this is a full box. |
| uint32_t content_size; // 'size' minus the header size. |
| uint32_t content_position; // Position in bytes of the 'content' of this box |
| // relative to its container. |
| const uint8_t* content; // Content bytes of this box (after its header). |
| } AvifInfoInternalBox; |
| |
| // Reads the header of a 'box' starting at 'bytes + position'. |
| // 'num_bytes' is the number of available 'bytes'. |
| // 'max_num_bytes' is the size of the container of the 'box' (either the file |
| // itself or the content of the parent of the 'box'). |
| static AvifInfoInternalStatus AvifInfoInternalParseBox( |
| const uint8_t* bytes, uint32_t num_bytes, uint32_t max_num_bytes, |
| uint32_t position, uint32_t* num_parsed_boxes, AvifInfoInternalBox* box) { |
| // See ISO/IEC 14496-12:2012(E) 4.2 |
| uint32_t box_header_size = 8; // box 32b size + 32b type (at least) |
| AVIFINFO_CHECK(position <= kAvifInfoInternalMaxSize - box_header_size, |
| kAborted); |
| AVIFINFO_CHECK(position + box_header_size <= max_num_bytes, kInvalid); |
| AVIFINFO_CHECK(position + box_header_size <= num_bytes, kTruncated); |
| box->size = AvifInfoInternalReadBigEndian(bytes + position, sizeof(uint32_t)); |
| // 'box->size==1' means 64-bit size should be read after the box type. |
| // 'box->size==0' means this box extends to all remaining bytes. |
| if (box->size == 1) { |
| box_header_size += 8; |
| AVIFINFO_CHECK(position + box_header_size <= max_num_bytes, kInvalid); |
| AVIFINFO_CHECK(position + box_header_size <= num_bytes, kTruncated); |
| // Stop the parsing if any box has a size greater than 4GB. |
| AVIFINFO_CHECK(AvifInfoInternalReadBigEndian(bytes + position + 8, |
| sizeof(uint32_t)) == 0, |
| kAborted); |
| // Read the 32 least-significant bits. |
| box->size = |
| AvifInfoInternalReadBigEndian(bytes + position + 12, sizeof(uint32_t)); |
| } else if (box->size == 0) { |
| box->size = max_num_bytes - position; |
| } |
| AVIFINFO_CHECK(box->size >= box_header_size, kInvalid); |
| AVIFINFO_CHECK(box->size <= kAvifInfoInternalMaxSize - position, kAborted); |
| AVIFINFO_CHECK(position + box->size <= max_num_bytes, kInvalid); |
| AVIFINFO_CHECK(position + box_header_size <= num_bytes, kTruncated); |
| box->type = bytes + position + 4; |
| |
| const int has_fullbox_header = |
| !memcmp(box->type, "meta", 4) || !memcmp(box->type, "pitm", 4) || |
| !memcmp(box->type, "ipma", 4) || !memcmp(box->type, "ispe", 4) || |
| !memcmp(box->type, "pixi", 4) || !memcmp(box->type, "iref", 4) || |
| !memcmp(box->type, "auxC", 4); |
| if (has_fullbox_header) box_header_size += 4; |
| AVIFINFO_CHECK(box->size >= box_header_size, kInvalid); |
| box->content_position = position + box_header_size; |
| AVIFINFO_CHECK(box->content_position <= num_bytes, kTruncated); |
| box->content_size = box->size - box_header_size; |
| box->content = bytes + box->content_position; |
| // Avoid timeouts. The maximum number of parsed boxes is arbitrary. |
| ++*num_parsed_boxes; |
| AVIFINFO_CHECK(*num_parsed_boxes < 4096, kAborted); |
| |
| box->version = 0; |
| box->flags = 0; |
| if (has_fullbox_header) { |
| box->version = AvifInfoInternalReadBigEndian(bytes + position + 8, 1); |
| box->flags = AvifInfoInternalReadBigEndian(bytes + position + 9, 3); |
| // See AV1 Image File Format (AVIF) 8.1 |
| // at https://aomediacodec.github.io/av1-avif/#avif-boxes (available when |
| // https://github.com/AOMediaCodec/av1-avif/pull/170 is merged). |
| uint32_t is_parsable = 1; |
| if (!memcmp(box->type, "meta", 4)) is_parsable = (box->version <= 0); |
| if (!memcmp(box->type, "pitm", 4)) is_parsable = (box->version <= 1); |
| if (!memcmp(box->type, "ipma", 4)) is_parsable = (box->version <= 1); |
| if (!memcmp(box->type, "ispe", 4)) is_parsable = (box->version <= 0); |
| if (!memcmp(box->type, "pixi", 4)) is_parsable = (box->version <= 0); |
| if (!memcmp(box->type, "iref", 4)) is_parsable = (box->version <= 1); |
| if (!memcmp(box->type, "auxC", 4)) is_parsable = (box->version <= 0); |
| // Instead of considering this file as invalid, skip unparsable boxes. |
| if (!is_parsable) box->type = (const uint8_t*)"\0skip"; |
| } |
| return kFound; |
| } |
| |
| // Returns kFound if 'min_size' bytes can be read from the 'box.content' now. |
| // 'num_bytes' is the number of available bytes of the parent of the 'box'. |
| static AvifInfoInternalStatus AccessContent(const AvifInfoInternalBox* box, |
| uint32_t num_bytes, |
| uint32_t min_size) { |
| AVIFINFO_CHECK(box->content_size >= min_size, kInvalid); |
| AVIFINFO_CHECK(box->content_position + min_size <= num_bytes, kTruncated); |
| return kFound; |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Search if the file identifies itself as AVIF through an "ftyp" box. |
| |
| static AvifInfoInternalStatus ParseFileForBrand(const uint8_t* bytes, |
| uint32_t num_bytes, |
| uint32_t file_size, |
| uint32_t* num_parsed_boxes) { |
| uint32_t position = 0; // Within 'bytes' (that points to first byte of file). |
| do { |
| AvifInfoInternalBox box; |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( |
| bytes, num_bytes, file_size, position, num_parsed_boxes, &box)); |
| |
| if (!memcmp(box.type, "ftyp", 4)) { |
| // Iterate over brands. See ISO/IEC 14496-12:2012(E) 4.3.1 |
| AVIFINFO_CHECK(box.content_size >= 8, kInvalid); // major_brand + version |
| for (uint32_t i = 0; i < box.content_size; i += 4) { |
| AVIFINFO_CHECK_FOUND(AccessContent(&box, num_bytes, i + 4)); |
| if (i == 4) continue; // Skip minor_version. |
| if (!memcmp(box.content + i, "avif", 4) || |
| !memcmp(box.content + i, "avis", 4)) { |
| return kFound; |
| } |
| } |
| AVIFINFO_RETURN(kInvalid); // Only one "ftyp" allowed per file. |
| } |
| position += box.size; |
| // File is valid only if the end of at least one box is at the same position |
| // as the end of the container. Oddities are caught when parsing further. |
| } while (position != file_size); |
| AVIFINFO_RETURN(kInvalid); // There should be one "ftyp" box. |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Search the primary item ID through "meta > pitm" boxes. |
| |
| static AvifInfoInternalStatus ParseMetaForPrimaryItemId( |
| const uint8_t* bytes, uint32_t num_bytes, uint32_t max_num_bytes, |
| uint32_t* num_parsed_boxes, uint32_t* primary_item_id) { |
| uint32_t position = 0; // Within 'bytes' (first byte of "meta" box content). |
| do { |
| AvifInfoInternalBox box; |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( |
| bytes, num_bytes, max_num_bytes, position, num_parsed_boxes, &box)); |
| |
| if (!memcmp(box.type, "pitm", 4)) { |
| // See ISO/IEC 14496-12:2015(E) 8.11.4.2 |
| const uint32_t num_bytes_per_id = (box.version == 0) ? 2 : 4; |
| AVIFINFO_CHECK_FOUND(AccessContent(&box, num_bytes, num_bytes_per_id)); |
| *primary_item_id = |
| AvifInfoInternalReadBigEndian(box.content + 0, num_bytes_per_id); |
| return kFound; |
| } |
| position += box.size; |
| } while (position != max_num_bytes); |
| |
| // According to ISO/IEC 14496-12:2012(E) 8.11.1.1, there is at most one "meta" |
| // per file. No "pitm" until now means never. |
| AVIFINFO_RETURN(kInvalid); |
| } |
| |
| static AvifInfoInternalStatus ParseFileForPrimaryItemId( |
| const uint8_t* bytes, uint32_t num_bytes, uint32_t file_size, |
| uint32_t* num_parsed_boxes, uint32_t* primary_item_id) { |
| uint32_t position = 0; // Within 'bytes' (that points to first byte of file). |
| do { |
| AvifInfoInternalBox box; |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( |
| bytes, num_bytes, file_size, position, num_parsed_boxes, &box)); |
| |
| if (!memcmp(box.type, "meta", 4)) { |
| return ParseMetaForPrimaryItemId( |
| box.content, num_bytes - box.content_position, box.content_size, |
| num_parsed_boxes, primary_item_id); |
| } |
| position += box.size; |
| } while (position != file_size); |
| AVIFINFO_RETURN(kInvalid); // No "meta" is an issue. |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Search the features of an item given its ID through "meta > iprp" boxes. |
| |
| static AvifInfoInternalStatus ParseIpcoForFeaturesInProperty( |
| const uint8_t* bytes, uint32_t num_bytes, uint32_t max_num_bytes, |
| uint32_t target_property_index, uint32_t* num_parsed_boxes, |
| AvifInfoFeatures* features) { |
| uint32_t position = 0; |
| uint32_t box_index = 1; // 1-based index. Used for iterating over properties. |
| do { |
| AvifInfoInternalBox box; |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( |
| bytes, num_bytes, max_num_bytes, position, num_parsed_boxes, &box)); |
| |
| if (box_index != target_property_index) { |
| // Skip. |
| } else if (features->width == 0 && !memcmp(box.type, "ispe", 4)) { |
| // See ISO/IEC 23008-12:2017(E) 6.5.3.2 |
| AVIFINFO_CHECK_FOUND(AccessContent(&box, num_bytes, 4 + 4)); |
| features->width = AvifInfoInternalReadBigEndian(box.content + 0, 4); |
| features->height = AvifInfoInternalReadBigEndian(box.content + 4, 4); |
| AVIFINFO_CHECK(features->width != 0 && features->height != 0, kInvalid); |
| return kFound; |
| } else if (features->num_channels == 0 && !memcmp(box.type, "pixi", 4)) { |
| // See ISO/IEC 23008-12:2017(E) 6.5.6.2 |
| AVIFINFO_CHECK_FOUND(AccessContent(&box, num_bytes, 1)); |
| features->num_channels = |
| AvifInfoInternalReadBigEndian(box.content + 0, 1); |
| AVIFINFO_CHECK(features->num_channels >= 1, kInvalid); |
| AVIFINFO_CHECK_FOUND( |
| AccessContent(&box, num_bytes, 1 + features->num_channels)); |
| features->bit_depth = AvifInfoInternalReadBigEndian(box.content + 1, 1); |
| AVIFINFO_CHECK(features->bit_depth >= 1, kInvalid); |
| for (uint32_t i = 1; i < features->num_channels; ++i) { |
| const uint32_t bit_depth = |
| AvifInfoInternalReadBigEndian(box.content + 1 + i, 1); |
| // Bit depth should be the same for all channels. |
| AVIFINFO_CHECK(bit_depth == features->bit_depth, kInvalid); |
| } |
| return kFound; |
| } else if (features->num_channels == 0 && !memcmp(box.type, "av1C", 4)) { |
| // See AV1 Codec ISO Media File Format Binding 2.3.1 |
| // at https://aomediacodec.github.io/av1-isobmff/#av1c |
| // Only parse the necessary third byte. Assume that the others are valid. |
| AVIFINFO_CHECK_FOUND(AccessContent(&box, num_bytes, 3)); |
| const uint32_t fields = AvifInfoInternalReadBigEndian(box.content + 2, 1); |
| const int high_bitdepth = (fields & 0x40) != 0; |
| const int twelve_bit = (fields & 0x20) != 0; |
| const int monochrome = (fields & 0x10) != 0; |
| if (twelve_bit) { |
| AVIFINFO_CHECK(high_bitdepth, kInvalid); |
| } |
| features->num_channels = monochrome ? 1 : 3; |
| features->bit_depth = high_bitdepth ? twelve_bit ? 12 : 10 : 8; |
| return kFound; |
| } |
| ++box_index; |
| position += box.size; |
| } while (position != max_num_bytes && box_index <= target_property_index); |
| AVIFINFO_RETURN(kNotFound); |
| } |
| |
| static AvifInfoInternalStatus ParseIprpForFeaturesInProperty( |
| const uint8_t* bytes, uint32_t num_bytes, uint32_t max_num_bytes, |
| uint32_t target_property_index, uint32_t* num_parsed_boxes, |
| AvifInfoFeatures* features) { |
| uint32_t position = 0; |
| do { |
| AvifInfoInternalBox box; |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( |
| bytes, num_bytes, max_num_bytes, position, num_parsed_boxes, &box)); |
| |
| if (!memcmp(box.type, "ipco", 4)) { |
| return ParseIpcoForFeaturesInProperty( |
| box.content, num_bytes - box.content_position, box.content_size, |
| target_property_index, num_parsed_boxes, features); |
| } |
| position += box.size; |
| } while (position != max_num_bytes); |
| AVIFINFO_RETURN(kInvalid); // No "ipco" in "iprp" is an issue. |
| } |
| |
| static AvifInfoInternalStatus ParseIprpForFeatures(const uint8_t* bytes, |
| uint32_t num_bytes, |
| uint32_t max_num_bytes, |
| uint32_t target_item_id, |
| uint32_t* num_parsed_boxes, |
| AvifInfoFeatures* features) { |
| uint32_t position = 0; |
| do { |
| AvifInfoInternalBox box; |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( |
| bytes, num_bytes, max_num_bytes, position, num_parsed_boxes, &box)); |
| |
| if (!memcmp(box.type, "ipma", 4)) { |
| // See ISO/IEC 23008-12:2017(E) 9.3.2 |
| AVIFINFO_CHECK_FOUND(AccessContent(&box, num_bytes, 4)); |
| const uint32_t entry_count = |
| AvifInfoInternalReadBigEndian(box.content + 0, 4); |
| uint32_t offset = 4; |
| const uint32_t id_num_bytes = (box.version < 1) ? 2 : 4; |
| const uint32_t index_num_bytes = (box.flags & 1) ? 2 : 1; |
| const uint32_t essential_bit_mask = (box.flags & 1) ? 0x8000 : 0x80; |
| |
| for (uint32_t entry = 0; entry < entry_count; ++entry) { |
| AVIFINFO_CHECK_FOUND( |
| AccessContent(&box, num_bytes, offset + id_num_bytes + 1)); |
| const uint32_t item_id = |
| AvifInfoInternalReadBigEndian(box.content + offset, id_num_bytes); |
| |
| offset += id_num_bytes; |
| const uint32_t association_count = |
| AvifInfoInternalReadBigEndian(box.content + offset, 1); |
| offset += 1; |
| |
| for (uint32_t property = 0; property < association_count; ++property) { |
| AVIFINFO_CHECK_FOUND( |
| AccessContent(&box, num_bytes, offset + index_num_bytes)); |
| const uint32_t value = AvifInfoInternalReadBigEndian( |
| box.content + offset, index_num_bytes); |
| offset += index_num_bytes; |
| |
| if (item_id == target_item_id) { |
| // const int essential = (value & essential_bit_mask); // Unused. |
| const uint32_t property_index = (value & ~essential_bit_mask); |
| |
| // Parse again at the same "iprp" level to find the associated |
| // "ipco" and the "ispe", "pixi" or "av1C" within. |
| const AvifInfoInternalStatus status = |
| ParseIprpForFeaturesInProperty(bytes, num_bytes, max_num_bytes, |
| property_index, num_parsed_boxes, |
| features); |
| if (status != kFound) { |
| // Stop in case of error, carry on if not found. |
| AVIFINFO_CHECK_NOT_FOUND(status); |
| } else if (features->width != 0 && features->height != 0 && |
| features->num_channels != 0 && |
| features->bit_depth != 0) { |
| return kFound; // Found everything. Otherwise carry on. |
| } |
| } |
| } |
| } |
| |
| // According to ISO/IEC 14496-12:2012(E) 8.11.1.1, there is at most one |
| // "meta" per file. According to ISO/IEC 23008-12:2017(E) 9.3.1, there is |
| // exactly one "ipma" per "iprp" and at most one "iprp" per "meta". |
| // The primary properties shall have been found now. |
| if (features->width != 0 && features->height != 0) { |
| // Exception: The bit depth and number of channels may be referenced |
| // in a tile and not in the primary item of item type "grid". |
| return kNotFound; // Continue the search at a higher level. |
| } |
| AVIFINFO_RETURN(kInvalid); |
| } |
| position += box.size; |
| } while (position != max_num_bytes); |
| AVIFINFO_RETURN(kInvalid); // No "ipma" in "iprp" is an issue. |
| } |
| |
| static AvifInfoInternalStatus ParseMetaForFeatures(const uint8_t* bytes, |
| uint32_t num_bytes, |
| uint32_t max_num_bytes, |
| uint32_t target_item_id, |
| uint32_t* num_parsed_boxes, |
| AvifInfoFeatures* features) { |
| uint32_t position = 0; |
| do { |
| AvifInfoInternalBox box; |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( |
| bytes, num_bytes, max_num_bytes, position, num_parsed_boxes, &box)); |
| |
| if (!memcmp(box.type, "iprp", 4)) { |
| return ParseIprpForFeatures(box.content, num_bytes - box.content_position, |
| box.content_size, target_item_id, |
| num_parsed_boxes, features); |
| } |
| position += box.size; |
| } while (position != max_num_bytes); |
| AVIFINFO_RETURN(kInvalid); // No "iprp" in "meta" is an issue. |
| } |
| |
| static AvifInfoInternalStatus ParseFileForFeatures(const uint8_t* bytes, |
| uint32_t num_bytes, |
| uint32_t file_size, |
| uint32_t target_item_id, |
| uint32_t* num_parsed_boxes, |
| AvifInfoFeatures* features) { |
| uint32_t position = 0; |
| do { |
| AvifInfoInternalBox box; |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( |
| bytes, num_bytes, file_size, position, num_parsed_boxes, &box)); |
| |
| if (!memcmp(box.type, "meta", 4)) { |
| return ParseMetaForFeatures(box.content, num_bytes - box.content_position, |
| box.content_size, target_item_id, |
| num_parsed_boxes, features); |
| } |
| position += box.size; |
| } while (position != file_size); |
| AVIFINFO_RETURN(kInvalid); // No "meta" is an issue. |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Search if a tile contains features through "meta > iref > dimg" boxes. |
| |
| static AvifInfoInternalStatus ParseIrefForFeaturesInTiles( |
| const uint8_t* bytes, uint32_t num_bytes, uint32_t max_num_bytes, |
| const uint8_t* meta_bytes, uint32_t meta_num_bytes, |
| uint32_t meta_max_num_bytes, uint32_t primary_item_id, |
| uint32_t* num_parsed_boxes, AvifInfoFeatures* features) { |
| uint32_t position = 0; |
| do { |
| AvifInfoInternalBox box; |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( |
| bytes, num_bytes, max_num_bytes, position, num_parsed_boxes, &box)); |
| |
| if (!memcmp(box.type, "dimg", 4)) { |
| // See ISO/IEC 14496-12:2015(E) 8.11.12.2 |
| const uint32_t num_bytes_per_id = (box.version == 0) ? 2 : 4; |
| uint32_t offset = 0; |
| AVIFINFO_CHECK_FOUND( |
| AccessContent(&box, num_bytes, num_bytes_per_id + 2)); |
| const uint32_t from_item_id = |
| AvifInfoInternalReadBigEndian(box.content + offset, num_bytes_per_id); |
| offset += num_bytes_per_id; |
| if (from_item_id == primary_item_id) { |
| const uint32_t reference_count = |
| AvifInfoInternalReadBigEndian(box.content + offset, 2); |
| offset += 2; |
| for (uint32_t i = 0; i < reference_count; ++i) { |
| AVIFINFO_CHECK_FOUND( |
| AccessContent(&box, num_bytes, offset + num_bytes_per_id)); |
| const uint32_t to_item_id = AvifInfoInternalReadBigEndian( |
| box.content + offset, num_bytes_per_id); |
| offset += num_bytes_per_id; |
| AVIFINFO_CHECK(meta_bytes != NULL && meta_bytes < bytes, kInvalid); |
| AVIFINFO_CHECK(meta_max_num_bytes > 0, kInvalid); |
| // Go up one level: from "dimg" among "iref" to boxes among "meta". |
| AVIFINFO_CHECK_NOT_FOUND(ParseMetaForFeatures( |
| meta_bytes, meta_num_bytes, meta_max_num_bytes, to_item_id, |
| num_parsed_boxes, features)); |
| // Trying the first tile should be enough. Check others just in case. |
| } |
| } |
| } |
| position += box.size; |
| } while (position != max_num_bytes); |
| AVIFINFO_RETURN(kNotFound); // No "dimg" in "iref" is not an issue. |
| } |
| |
| static AvifInfoInternalStatus ParseMetaForFeaturesInTiles( |
| const uint8_t* bytes, uint32_t num_bytes, uint32_t max_num_bytes, |
| uint32_t primary_item_id, uint32_t* num_parsed_boxes, |
| AvifInfoFeatures* features) { |
| uint32_t position = 0; |
| do { |
| AvifInfoInternalBox box; |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( |
| bytes, num_bytes, max_num_bytes, position, num_parsed_boxes, &box)); |
| |
| if (!memcmp(box.type, "iref", 4)) { |
| return ParseIrefForFeaturesInTiles( |
| box.content, num_bytes - box.content_position, box.content_size, |
| bytes, num_bytes, max_num_bytes, primary_item_id, num_parsed_boxes, |
| features); |
| } |
| position += box.size; |
| } while (position != max_num_bytes); |
| AVIFINFO_RETURN(kNotFound); // No "iref" in "meta" is not an issue. |
| } |
| |
| static AvifInfoInternalStatus ParseFileForFeaturesInTiles( |
| const uint8_t* bytes, uint32_t num_bytes, uint32_t file_size, |
| uint32_t primary_item_id, uint32_t* num_parsed_boxes, |
| AvifInfoFeatures* features) { |
| uint32_t position = 0; |
| do { |
| AvifInfoInternalBox box; |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( |
| bytes, num_bytes, file_size, position, num_parsed_boxes, &box)); |
| |
| if (!memcmp(box.type, "meta", 4)) { |
| return ParseMetaForFeaturesInTiles( |
| box.content, num_bytes - box.content_position, box.content_size, |
| primary_item_id, num_parsed_boxes, features); |
| } |
| position += box.size; |
| } while (position != file_size); |
| AVIFINFO_RETURN(kInvalid); // No "meta" is an issue. |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Search if there is an alpha layer through "meta > iprp > ipco > auxC" boxes. |
| |
| static AvifInfoInternalStatus ParseIpcoForAlpha(const uint8_t* bytes, |
| uint32_t num_bytes, |
| uint32_t max_num_bytes, |
| uint32_t* num_parsed_boxes) { |
| uint32_t position = 0; |
| do { |
| AvifInfoInternalBox box; |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( |
| bytes, num_bytes, max_num_bytes, position, num_parsed_boxes, &box)); |
| |
| if (!memcmp(box.type, "auxC", 4)) { |
| // See AV1 Image File Format (AVIF) 4 |
| // at https://aomediacodec.github.io/av1-avif/#auxiliary-images |
| const char* kAlphaStr = "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"; |
| const uint32_t kAlphaStrLength = 44; // Includes terminating character. |
| if (box.content_size >= kAlphaStrLength) { |
| AVIFINFO_CHECK(box.content_position + kAlphaStrLength <= num_bytes, |
| kTruncated); |
| const char* const aux_type = (const char*)box.content; |
| if (strcmp(aux_type, kAlphaStr) == 0) { |
| // Note: It is unlikely but it is possible that this alpha plane does |
| // not belong to the primary item or a tile. Ignore this issue. |
| return kFound; |
| } |
| } |
| } |
| position += box.size; |
| } while (position != max_num_bytes); |
| AVIFINFO_RETURN(kNotFound); // No "auxC" in "ipco" is not an issue. |
| } |
| |
| static AvifInfoInternalStatus ParseIprpForAlpha(const uint8_t* bytes, |
| uint32_t num_bytes, |
| uint32_t max_num_bytes, |
| uint32_t* num_parsed_boxes) { |
| uint32_t position = 0; |
| do { |
| AvifInfoInternalBox box; |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( |
| bytes, num_bytes, max_num_bytes, position, num_parsed_boxes, &box)); |
| |
| if (!memcmp(box.type, "ipco", 4)) { |
| return ParseIpcoForAlpha(box.content, num_bytes - box.content_position, |
| box.content_size, num_parsed_boxes); |
| } |
| position += box.size; |
| } while (position != max_num_bytes); |
| AVIFINFO_RETURN(kInvalid); // No "ipco" in "iprp" is an issue. |
| } |
| |
| static AvifInfoInternalStatus ParseMetaForAlpha(const uint8_t* bytes, |
| uint32_t num_bytes, |
| uint32_t max_num_bytes, |
| uint32_t* num_parsed_boxes) { |
| uint32_t position = 0; |
| do { |
| AvifInfoInternalBox box; |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( |
| bytes, num_bytes, max_num_bytes, position, num_parsed_boxes, &box)); |
| |
| if (!memcmp(box.type, "iprp", 4)) { |
| return ParseIprpForAlpha(box.content, num_bytes - box.content_position, |
| box.content_size, num_parsed_boxes); |
| } |
| position += box.size; |
| } while (position != max_num_bytes); |
| AVIFINFO_RETURN(kInvalid); // No "iprp" in "meta" is an issue. |
| } |
| |
| static AvifInfoInternalStatus ParseFileForAlpha(const uint8_t* bytes, |
| uint32_t num_bytes, |
| uint32_t file_size, |
| uint32_t* num_parsed_boxes) { |
| uint32_t position = 0; |
| do { |
| AvifInfoInternalBox box; |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( |
| bytes, num_bytes, file_size, position, num_parsed_boxes, &box)); |
| |
| if (!memcmp(box.type, "meta", 4)) { |
| return ParseMetaForAlpha(box.content, num_bytes - box.content_position, |
| box.content_size, num_parsed_boxes); |
| } |
| position += box.size; |
| } while (position != file_size); |
| AVIFINFO_RETURN(kInvalid); // No "meta" is an issue. |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Parsing starting point. |
| |
| static AvifInfoInternalStatus AvifInfoInternalParseFile( |
| const uint8_t* bytes, uint32_t num_bytes, uint32_t file_size, |
| AvifInfoFeatures* features) { |
| uint32_t num_parsed_boxes = 0; |
| AVIFINFO_CHECK_FOUND( |
| ParseFileForBrand(bytes, num_bytes, file_size, &num_parsed_boxes)); |
| |
| // 'bytes' is an AVIF file. Next step is finding the ID of the primary item. |
| uint32_t primary_item_id; |
| AVIFINFO_CHECK_FOUND(ParseFileForPrimaryItemId( |
| bytes, num_bytes, file_size, &num_parsed_boxes, &primary_item_id)); |
| |
| // Now find the 'features' of the primary item. |
| AvifInfoInternalStatus status = |
| ParseFileForFeatures(bytes, num_bytes, file_size, primary_item_id, |
| &num_parsed_boxes, features); |
| if (status == kNotFound) { |
| // It is possible that some of the 'features' are missing for the primary |
| // item. Try to look into tiles in case they are defined there. |
| status = ParseFileForFeaturesInTiles(bytes, num_bytes, file_size, |
| primary_item_id, &num_parsed_boxes, |
| features); |
| } |
| AVIFINFO_CHECK_FOUND(status); |
| |
| // If there is an alpha plane, add 1 to the number of channels. |
| status = ParseFileForAlpha(bytes, num_bytes, file_size, &num_parsed_boxes); |
| if (status == kFound) { |
| ++features->num_channels; |
| } else { |
| AVIFINFO_CHECK_NOT_FOUND(status); |
| } |
| return kFound; |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Public API |
| |
| AvifInfoStatus AvifInfoGet(const uint8_t* data, size_t data_size, |
| AvifInfoFeatures* features) { |
| // Consider the file to be of maximum size. |
| return AvifInfoGetWithSize(data, data_size, features, |
| /*file_size=*/kAvifInfoInternalMaxSize); |
| } |
| |
| AvifInfoStatus AvifInfoGetWithSize(const uint8_t* data, size_t data_size, |
| AvifInfoFeatures* features, |
| size_t file_size) { |
| if (features != NULL) memset(features, 0, sizeof(*features)); |
| if (data == NULL) return kAvifInfoNotEnoughData; |
| if (data_size > file_size) data_size = file_size; |
| |
| AvifInfoFeatures parsed_features; |
| memset(&parsed_features, 0, sizeof(parsed_features)); |
| const AvifInfoInternalStatus status = AvifInfoInternalParseFile( |
| data, |
| (data_size >= kAvifInfoInternalMaxSize) ? kAvifInfoInternalMaxSize |
| : (uint32_t)data_size, |
| (file_size >= kAvifInfoInternalMaxSize) ? kAvifInfoInternalMaxSize |
| : (uint32_t)file_size, |
| &parsed_features); |
| |
| if (status == kNotFound) { |
| return (data_size < file_size) ? kAvifInfoNotEnoughData |
| : kAvifInfoInvalidFile; |
| } |
| if (status == kTruncated) return kAvifInfoNotEnoughData; |
| if (status == kInvalid) return kAvifInfoInvalidFile; |
| if (status == kAborted) return kAvifInfoTooComplex; |
| if (features != NULL) { |
| memcpy(features, &parsed_features, sizeof(*features)); |
| } |
| return kAvifInfoOk; |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| #undef AVIFINFO_RETURN |
| #undef AVIFINFO_CHECK |
| #undef AVIFINFO_CHECK_STATUS_IS |
| #undef AVIFINFO_CHECK_FOUND |
| #undef AVIFINFO_CHECK_NOT_FOUND |