| // 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; |
| |
| static AvifInfoStatus AvifInfoInternalConvertStatus(AvifInfoInternalStatus s) { |
| return (s == kFound) ? kAvifInfoOk |
| : (s == kNotFound || s == kTruncated) ? kAvifInfoNotEnoughData |
| : (s == kAborted) ? kAvifInfoTooComplex |
| : kAvifInfoInvalidFile; |
| } |
| |
| // uint32_t is used everywhere in this file. It is unlikely to be insufficient |
| // to parse AVIF headers. |
| #define AVIFINFO_MAX_SIZE UINT32_MAX |
| // Be reasonable. Avoid timeouts and out-of-memory. |
| #define AVIFINFO_MAX_NUM_BOXES 4096 |
| // AvifInfoInternalFeatures uses uint8_t to store values. |
| #define AVIFINFO_MAX_VALUE UINT8_MAX |
| // Maximum number of stored associations. Past that, they are skipped. |
| #define AVIFINFO_MAX_TILES 16 |
| #define AVIFINFO_MAX_PROPS 32 |
| #define AVIFINFO_MAX_FEATURES 8 |
| #define AVIFINFO_UNDEFINED 0 |
| |
| // 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) |
| |
| #if defined(AVIFINFO_ENABLE_DEBUG_LOG) |
| #define AVIF_DEBUG_LOG(...) printf(__VA_ARGS__) |
| #else |
| #define AVIF_DEBUG_LOG(...) |
| #endif |
| |
| //------------------------------------------------------------------------------ |
| // Streamed input struct and helper functions. |
| |
| typedef struct { |
| void* stream; // User-defined data. |
| read_stream_t read; // Used to fetch more bytes from the 'stream'. |
| skip_stream_t skip; // Used to advance the position in the 'stream'. |
| // Fallback to 'read' if 'skip' is null. |
| uint64_t num_read_bytes; // Number of bytes read or skipped. |
| } AvifInfoInternalStream; |
| |
| // Reads 'num_bytes' from the 'stream'. They are available at '*data'. |
| // 'num_bytes' must be greater than zero. |
| static AvifInfoInternalStatus AvifInfoInternalRead( |
| AvifInfoInternalStream* stream, uint32_t num_bytes, const uint8_t** data) { |
| *data = stream->read(stream->stream, num_bytes); |
| AVIFINFO_CHECK(*data != NULL, kTruncated); |
| stream->num_read_bytes += num_bytes; |
| return kFound; |
| } |
| |
| // Skips 'num_bytes' from the 'stream'. 'num_bytes' can be zero. |
| static AvifInfoInternalStatus AvifInfoInternalSkip( |
| AvifInfoInternalStream* stream, uint32_t num_bytes) { |
| // Avoid a call to the user-defined function for nothing. |
| if (num_bytes > 0) { |
| if (stream->skip == NULL) { |
| const uint8_t* unused; |
| while (num_bytes > AVIFINFO_MAX_NUM_READ_BYTES) { |
| AVIFINFO_CHECK_FOUND( |
| AvifInfoInternalRead(stream, AVIFINFO_MAX_NUM_READ_BYTES, &unused)); |
| num_bytes -= AVIFINFO_MAX_NUM_READ_BYTES; |
| } |
| return AvifInfoInternalRead(stream, num_bytes, &unused); |
| } |
| stream->skip(stream->stream, num_bytes); |
| stream->num_read_bytes += num_bytes; |
| } |
| return kFound; |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Features are parsed into temporary property associations. |
| |
| typedef struct { |
| uint8_t tile_item_id; |
| uint8_t parent_item_id; |
| uint8_t dimg_idx; // Index of this association in the dimg box (0-based). |
| } AvifInfoInternalTile; // Tile item id <-> parent item id associations. |
| |
| typedef struct { |
| uint8_t property_index; |
| uint8_t item_id; |
| } AvifInfoInternalProp; // Property index <-> item id associations. |
| |
| typedef struct { |
| uint8_t property_index; |
| uint32_t width, height; |
| } AvifInfoInternalDimProp; // Property <-> features associations. |
| |
| typedef struct { |
| uint8_t property_index; |
| uint8_t bit_depth, num_channels; |
| } AvifInfoInternalChanProp; // Property <-> features associations. |
| |
| typedef struct { |
| uint8_t has_primary_item; // True if "pitm" was parsed. |
| uint8_t has_alpha; // True if an alpha "auxC" was parsed. |
| // Index of the gain map auxC property. |
| uint8_t gainmap_property_index; |
| uint8_t primary_item_id; |
| AvifInfoFeatures primary_item_features; // Deduced from the data below. |
| uint8_t data_was_skipped; // True if some loops/indices were skipped. |
| uint8_t tone_mapped_item_id; // Id of the "tmap" box, > 0 if present. |
| uint8_t iinf_parsed; // True if the "iinf" (item info) box was parsed. |
| |
| uint8_t num_tiles; |
| AvifInfoInternalTile tiles[AVIFINFO_MAX_TILES]; |
| uint8_t num_props; |
| AvifInfoInternalProp props[AVIFINFO_MAX_PROPS]; |
| uint8_t num_dim_props; |
| AvifInfoInternalDimProp dim_props[AVIFINFO_MAX_FEATURES]; |
| uint8_t num_chan_props; |
| AvifInfoInternalChanProp chan_props[AVIFINFO_MAX_FEATURES]; |
| } AvifInfoInternalFeatures; |
| |
| // Generates the features of a given 'target_item_id' from internal features. |
| static AvifInfoInternalStatus AvifInfoInternalGetItemFeatures( |
| AvifInfoInternalFeatures* f, uint32_t target_item_id, uint32_t tile_depth) { |
| for (uint32_t prop_item = 0; prop_item < f->num_props; ++prop_item) { |
| if (f->props[prop_item].item_id != target_item_id) continue; |
| const uint32_t property_index = f->props[prop_item].property_index; |
| |
| // Retrieve the width and height of the primary item if not already done. |
| if (target_item_id == f->primary_item_id && |
| (f->primary_item_features.width == AVIFINFO_UNDEFINED || |
| f->primary_item_features.height == AVIFINFO_UNDEFINED)) { |
| for (uint32_t i = 0; i < f->num_dim_props; ++i) { |
| if (f->dim_props[i].property_index != property_index) continue; |
| f->primary_item_features.width = f->dim_props[i].width; |
| f->primary_item_features.height = f->dim_props[i].height; |
| if (f->primary_item_features.bit_depth != AVIFINFO_UNDEFINED && |
| f->primary_item_features.num_channels != AVIFINFO_UNDEFINED) { |
| return kFound; |
| } |
| break; |
| } |
| } |
| // Retrieve the bit depth and number of channels of the target item if not |
| // already done. |
| if (f->primary_item_features.bit_depth == AVIFINFO_UNDEFINED || |
| f->primary_item_features.num_channels == AVIFINFO_UNDEFINED) { |
| for (uint32_t i = 0; i < f->num_chan_props; ++i) { |
| if (f->chan_props[i].property_index != property_index) continue; |
| f->primary_item_features.bit_depth = f->chan_props[i].bit_depth; |
| f->primary_item_features.num_channels = f->chan_props[i].num_channels; |
| if (f->primary_item_features.width != AVIFINFO_UNDEFINED && |
| f->primary_item_features.height != AVIFINFO_UNDEFINED) { |
| return kFound; |
| } |
| break; |
| } |
| } |
| } |
| |
| // Check for the bit_depth and num_channels in a tile if not yet found. |
| for (uint32_t tile = 0; tile < f->num_tiles && tile_depth < 3; ++tile) { |
| if (f->tiles[tile].parent_item_id != target_item_id) continue; |
| AVIFINFO_CHECK_NOT_FOUND(AvifInfoInternalGetItemFeatures( |
| f, f->tiles[tile].tile_item_id, tile_depth + 1)); |
| } |
| AVIFINFO_RETURN(kNotFound); |
| } |
| |
| // Generates the 'f->primary_item_features' from the AvifInfoInternalFeatures. |
| // Returns kNotFound if there is not enough information. |
| static AvifInfoInternalStatus AvifInfoInternalGetPrimaryItemFeatures( |
| AvifInfoInternalFeatures* f) { |
| // Nothing to do without the primary item ID. |
| AVIFINFO_CHECK(f->has_primary_item, kNotFound); |
| // Early exit. |
| AVIFINFO_CHECK(f->num_dim_props > 0 && f->num_chan_props, kNotFound); |
| |
| // Look for a gain map. |
| // HEIF scheme: gain map is a hidden input of a derived item. |
| if (f->tone_mapped_item_id) { |
| for (uint32_t tile = 0; tile < f->num_tiles; ++tile) { |
| if (f->tiles[tile].parent_item_id == f->tone_mapped_item_id && |
| f->tiles[tile].dimg_idx == 1) { |
| f->primary_item_features.has_gainmap = 1; |
| f->primary_item_features.gainmap_item_id = f->tiles[tile].tile_item_id; |
| break; |
| } |
| } |
| } |
| // Adobe scheme: gain map is an auxiliary item. |
| if (!f->primary_item_features.has_gainmap && f->gainmap_property_index > 0) { |
| for (uint32_t prop_item = 0; prop_item < f->num_props; ++prop_item) { |
| if (f->props[prop_item].property_index == f->gainmap_property_index) { |
| f->primary_item_features.has_gainmap = 1; |
| f->primary_item_features.gainmap_item_id = f->props[prop_item].item_id; |
| break; |
| } |
| } |
| } |
| // If the gain map has not been found but we haven't read all the relevant |
| // metadata, we might still find one later and cannot stop now. |
| if (!f->primary_item_features.has_gainmap && !f->iinf_parsed) { |
| return kNotFound; |
| } |
| |
| AVIFINFO_CHECK_FOUND( |
| AvifInfoInternalGetItemFeatures(f, f->primary_item_id, /*tile_depth=*/0)); |
| |
| // "auxC" is parsed before the "ipma" properties so it is known now, if any. |
| if (f->has_alpha) ++f->primary_item_features.num_channels; |
| return kFound; |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Box header parsing and various size checks. |
| |
| typedef struct { |
| uint32_t size; // In bytes. |
| uint8_t type[4]; // 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. |
| } AvifInfoInternalBox; |
| |
| // Reads the header of a 'box' starting at the beginning of a 'stream'. |
| // 'num_remaining_bytes' is the remaining size of the container of the 'box' |
| // (either the file size itself or the content size of the parent of the 'box'). |
| static AvifInfoInternalStatus AvifInfoInternalParseBox( |
| int nesting_level, AvifInfoInternalStream* stream, |
| uint32_t num_remaining_bytes, uint32_t* num_parsed_boxes, |
| AvifInfoInternalBox* box) { |
| const uint8_t* data; |
| // See ISO/IEC 14496-12:2012(E) 4.2 |
| uint32_t box_header_size = 8; // box 32b size + 32b type (at least) |
| AVIFINFO_CHECK(box_header_size <= num_remaining_bytes, kInvalid); |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 8, &data)); |
| box->size = AvifInfoInternalReadBigEndian(data, sizeof(uint32_t)); |
| memcpy(box->type, data + 4, 4); |
| // '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(box_header_size <= num_remaining_bytes, kInvalid); |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 8, &data)); |
| // Stop the parsing if any box has a size greater than 4GB. |
| AVIFINFO_CHECK(AvifInfoInternalReadBigEndian(data, sizeof(uint32_t)) == 0, |
| kAborted); |
| // Read the 32 least-significant bits. |
| box->size = AvifInfoInternalReadBigEndian(data + 4, sizeof(uint32_t)); |
| } else if (box->size == 0) { |
| box->size = num_remaining_bytes; |
| } |
| AVIFINFO_CHECK(box->size >= box_header_size, kInvalid); |
| AVIFINFO_CHECK(box->size <= num_remaining_bytes, kInvalid); |
| |
| 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) || !memcmp(box->type, "iinf", 4) || |
| !memcmp(box->type, "infe", 4); |
| if (has_fullbox_header) box_header_size += 4; |
| AVIFINFO_CHECK(box->size >= box_header_size, kInvalid); |
| box->content_size = box->size - box_header_size; |
| // AvifInfoGetFeaturesStream() can be called on a full stream or on a stream |
| // where the 'ftyp' box was already read. Do not count 'ftyp' boxes towards |
| // AVIFINFO_MAX_NUM_BOXES, so that this function returns the same status in |
| // both situations (because of the AVIFINFO_MAX_NUM_BOXES check that would |
| // compare a different box count otherwise). This is fine because top-level |
| // 'ftyp' boxes are just skipped anyway. |
| if (nesting_level != 0 || memcmp(box->type, "ftyp", 4)) { |
| // Avoid timeouts. The maximum number of parsed boxes is arbitrary. |
| ++*num_parsed_boxes; |
| AVIFINFO_CHECK(*num_parsed_boxes < AVIFINFO_MAX_NUM_BOXES, kAborted); |
| } |
| |
| box->version = 0; |
| box->flags = 0; |
| if (has_fullbox_header) { |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &data)); |
| box->version = AvifInfoInternalReadBigEndian(data, 1); |
| box->flags = AvifInfoInternalReadBigEndian(data + 1, 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). |
| const uint32_t is_parsable = |
| (!memcmp(box->type, "meta", 4) && box->version <= 0) || |
| (!memcmp(box->type, "pitm", 4) && box->version <= 1) || |
| (!memcmp(box->type, "ipma", 4) && box->version <= 1) || |
| (!memcmp(box->type, "ispe", 4) && box->version <= 0) || |
| (!memcmp(box->type, "pixi", 4) && box->version <= 0) || |
| (!memcmp(box->type, "iref", 4) && box->version <= 1) || |
| (!memcmp(box->type, "auxC", 4) && box->version <= 0) || |
| (!memcmp(box->type, "iinf", 4) && box->version <= 1) || |
| (!memcmp(box->type, "infe", 4) && box->version >= 2 && |
| box->version <= 3); |
| // Instead of considering this file as invalid, skip unparsable boxes. |
| if (!is_parsable) memcpy(box->type, "\0skp", 4); // \0 so not a valid type |
| } |
| AVIF_DEBUG_LOG("%*c", nesting_level * 2, ' '); |
| AVIF_DEBUG_LOG("Box type %.4s size %d\n", box->type, box->size); |
| return kFound; |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| // Parses a 'stream' of an "ipco" box into 'features'. |
| // "ispe" is used for width and height, "pixi" and "av1C" are used for bit depth |
| // and number of channels, and "auxC" is used for alpha. |
| static AvifInfoInternalStatus ParseIpco(int nesting_level, |
| AvifInfoInternalStream* stream, |
| uint32_t num_remaining_bytes, |
| uint32_t* num_parsed_boxes, |
| AvifInfoInternalFeatures* features) { |
| uint32_t box_index = 1; // 1-based index. Used for iterating over properties. |
| do { |
| AvifInfoInternalBox box; |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( |
| nesting_level, stream, num_remaining_bytes, num_parsed_boxes, &box)); |
| |
| if (!memcmp(box.type, "ispe", 4)) { |
| // See ISO/IEC 23008-12:2017(E) 6.5.3.2 |
| const uint8_t* data; |
| AVIFINFO_CHECK(box.content_size >= 8, kInvalid); |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 8, &data)); |
| const uint32_t width = AvifInfoInternalReadBigEndian(data + 0, 4); |
| const uint32_t height = AvifInfoInternalReadBigEndian(data + 4, 4); |
| AVIFINFO_CHECK(width != 0 && height != 0, kInvalid); |
| if (features->num_dim_props < AVIFINFO_MAX_FEATURES && |
| box_index <= AVIFINFO_MAX_VALUE) { |
| features->dim_props[features->num_dim_props].property_index = box_index; |
| features->dim_props[features->num_dim_props].width = width; |
| features->dim_props[features->num_dim_props].height = height; |
| ++features->num_dim_props; |
| } else { |
| features->data_was_skipped = 1; |
| } |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size - 8)); |
| } else if (!memcmp(box.type, "pixi", 4)) { |
| // See ISO/IEC 23008-12:2017(E) 6.5.6.2 |
| const uint8_t* data; |
| AVIFINFO_CHECK(box.content_size >= 1, kInvalid); |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 1, &data)); |
| const uint32_t num_channels = AvifInfoInternalReadBigEndian(data + 0, 1); |
| AVIFINFO_CHECK(num_channels >= 1, kInvalid); |
| AVIFINFO_CHECK(box.content_size >= 1 + num_channels, kInvalid); |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 1, &data)); |
| const uint32_t bit_depth = AvifInfoInternalReadBigEndian(data, 1); |
| AVIFINFO_CHECK(bit_depth >= 1, kInvalid); |
| for (uint32_t i = 1; i < num_channels; ++i) { |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 1, &data)); |
| // Bit depth should be the same for all channels. |
| AVIFINFO_CHECK(AvifInfoInternalReadBigEndian(data, 1) == bit_depth, |
| kInvalid); |
| AVIFINFO_CHECK(i <= 32, kAborted); // Be reasonable. |
| } |
| if (features->num_chan_props < AVIFINFO_MAX_FEATURES && |
| box_index <= AVIFINFO_MAX_VALUE && bit_depth <= AVIFINFO_MAX_VALUE && |
| num_channels <= AVIFINFO_MAX_VALUE) { |
| features->chan_props[features->num_chan_props].property_index = |
| box_index; |
| features->chan_props[features->num_chan_props].bit_depth = bit_depth; |
| features->chan_props[features->num_chan_props].num_channels = |
| num_channels; |
| ++features->num_chan_props; |
| } else { |
| features->data_was_skipped = 1; |
| } |
| AVIFINFO_CHECK_FOUND( |
| AvifInfoInternalSkip(stream, box.content_size - (1 + num_channels))); |
| } else if (!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. |
| const uint8_t* data; |
| AVIFINFO_CHECK(box.content_size >= 3, kInvalid); |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 3, &data)); |
| const int high_bitdepth = (data[2] & 0x40) != 0; |
| const int twelve_bit = (data[2] & 0x20) != 0; |
| const int monochrome = (data[2] & 0x10) != 0; |
| if (twelve_bit) { |
| AVIFINFO_CHECK(high_bitdepth, kInvalid); |
| } |
| if (features->num_chan_props < AVIFINFO_MAX_FEATURES && |
| box_index <= AVIFINFO_MAX_VALUE) { |
| features->chan_props[features->num_chan_props].property_index = |
| box_index; |
| features->chan_props[features->num_chan_props].bit_depth = |
| high_bitdepth ? twelve_bit ? 12 : 10 : 8; |
| features->chan_props[features->num_chan_props].num_channels = |
| monochrome ? 1 : 3; |
| ++features->num_chan_props; |
| } else { |
| features->data_was_skipped = 1; |
| } |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size - 3)); |
| } else 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. |
| const char* kGainmapStr = "urn:com:photo:aux:hdrgainmap"; |
| const uint32_t kGainmapStrLength = 29; // Includes terminating character. |
| uint32_t num_read_bytes = 0; |
| // Check for a gain map or for an alpha plane. Start with the gain map |
| // since the identifier is shorter. |
| if (box.content_size >= kGainmapStrLength) { |
| const uint8_t* data; |
| AVIFINFO_CHECK_FOUND( |
| AvifInfoInternalRead(stream, kGainmapStrLength, &data)); |
| num_read_bytes = kGainmapStrLength; |
| const char* const aux_type = (const char*)data; |
| if (strcmp(aux_type, kGainmapStr) == 0) { |
| // Note: It is unlikely but it is possible that this gain map |
| // does not belong to the primary item or a tile. Ignore this issue. |
| features->gainmap_property_index = box_index; |
| } else if (box.content_size >= kAlphaStrLength && |
| memcmp(aux_type, kAlphaStr, kGainmapStrLength) == 0) { |
| // The beginning of the aux type matches the alpha aux type string. |
| // Check the end as well. |
| const uint8_t* data2; |
| const uint32_t kEndLength = kAlphaStrLength - kGainmapStrLength; |
| AVIFINFO_CHECK_FOUND( |
| AvifInfoInternalRead(stream, kEndLength, &data2)); |
| num_read_bytes = kAlphaStrLength; |
| if (strcmp((const char*)data2, &kAlphaStr[kGainmapStrLength]) == 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. |
| features->has_alpha = 1; |
| } |
| } |
| } |
| AVIFINFO_CHECK_FOUND( |
| AvifInfoInternalSkip(stream, box.content_size - num_read_bytes)); |
| } else { |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size)); |
| } |
| ++box_index; |
| num_remaining_bytes -= box.size; |
| } while (num_remaining_bytes > 0); |
| AVIFINFO_RETURN(kNotFound); |
| } |
| |
| // Parses a 'stream' of an "iprp" box into 'features'. The "ipco" box contain |
| // the properties which are linked to items by the "ipma" box. |
| static AvifInfoInternalStatus ParseIprp(int nesting_level, |
| AvifInfoInternalStream* stream, |
| uint32_t num_remaining_bytes, |
| uint32_t* num_parsed_boxes, |
| AvifInfoInternalFeatures* features) { |
| do { |
| AvifInfoInternalBox box; |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( |
| nesting_level, stream, num_remaining_bytes, num_parsed_boxes, &box)); |
| |
| if (!memcmp(box.type, "ipco", 4)) { |
| AVIFINFO_CHECK_NOT_FOUND(ParseIpco(nesting_level + 1, stream, |
| box.content_size, num_parsed_boxes, |
| features)); |
| } else if (!memcmp(box.type, "ipma", 4)) { |
| // See ISO/IEC 23008-12:2017(E) 9.3.2 |
| uint32_t num_read_bytes = 4; |
| const uint8_t* data; |
| AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid); |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &data)); |
| const uint32_t entry_count = AvifInfoInternalReadBigEndian(data, 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) { |
| if (entry >= AVIFINFO_MAX_PROPS || |
| features->num_props >= AVIFINFO_MAX_PROPS) { |
| features->data_was_skipped = 1; |
| break; |
| } |
| num_read_bytes += id_num_bytes + 1; |
| AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid); |
| AVIFINFO_CHECK_FOUND( |
| AvifInfoInternalRead(stream, id_num_bytes + 1, &data)); |
| const uint32_t item_id = |
| AvifInfoInternalReadBigEndian(data, id_num_bytes); |
| const uint32_t association_count = |
| AvifInfoInternalReadBigEndian(data + id_num_bytes, 1); |
| |
| uint32_t property; |
| for (property = 0; property < association_count; ++property) { |
| if (property >= AVIFINFO_MAX_PROPS || |
| features->num_props >= AVIFINFO_MAX_PROPS) { |
| features->data_was_skipped = 1; |
| break; |
| } |
| num_read_bytes += index_num_bytes; |
| AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid); |
| AVIFINFO_CHECK_FOUND( |
| AvifInfoInternalRead(stream, index_num_bytes, &data)); |
| const uint32_t value = |
| AvifInfoInternalReadBigEndian(data, index_num_bytes); |
| // const int essential = (value & essential_bit_mask); // Unused. |
| const uint32_t property_index = (value & ~essential_bit_mask); |
| if (property_index <= AVIFINFO_MAX_VALUE && |
| item_id <= AVIFINFO_MAX_VALUE) { |
| features->props[features->num_props].property_index = |
| property_index; |
| features->props[features->num_props].item_id = item_id; |
| ++features->num_props; |
| } else { |
| features->data_was_skipped = 1; |
| } |
| } |
| if (property < association_count) break; // Do not read garbage. |
| } |
| |
| // If all features are available now, do not look further. |
| AVIFINFO_CHECK_NOT_FOUND( |
| AvifInfoInternalGetPrimaryItemFeatures(features)); |
| |
| // Mostly if 'data_was_skipped'. |
| AVIFINFO_CHECK_FOUND( |
| AvifInfoInternalSkip(stream, box.content_size - num_read_bytes)); |
| } else { |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size)); |
| } |
| num_remaining_bytes -= box.size; |
| } while (num_remaining_bytes != 0); |
| AVIFINFO_RETURN(kNotFound); |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| // Parses a 'stream' of an "iref" box into 'features'. |
| // The "dimg" boxes contain links between tiles and their parent items, which |
| // can be used to infer bit depth and number of channels for the primary item |
| // when the latter does not have these properties. |
| static AvifInfoInternalStatus ParseIref(int nesting_level, |
| AvifInfoInternalStream* stream, |
| uint32_t num_remaining_bytes, |
| uint32_t* num_parsed_boxes, |
| AvifInfoInternalFeatures* features) { |
| do { |
| AvifInfoInternalBox box; |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( |
| nesting_level, stream, num_remaining_bytes, 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 num_read_bytes = num_bytes_per_id + 2; |
| const uint8_t* data; |
| AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid); |
| AVIFINFO_CHECK_FOUND( |
| AvifInfoInternalRead(stream, num_bytes_per_id + 2, &data)); |
| const uint32_t from_item_id = |
| AvifInfoInternalReadBigEndian(data, num_bytes_per_id); |
| const uint32_t reference_count = |
| AvifInfoInternalReadBigEndian(data + num_bytes_per_id, 2); |
| |
| for (uint32_t i = 0; i < reference_count; ++i) { |
| if (i >= AVIFINFO_MAX_TILES) { |
| features->data_was_skipped = 1; |
| break; |
| } |
| num_read_bytes += num_bytes_per_id; |
| AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid); |
| AVIFINFO_CHECK_FOUND( |
| AvifInfoInternalRead(stream, num_bytes_per_id, &data)); |
| const uint32_t to_item_id = |
| AvifInfoInternalReadBigEndian(data, num_bytes_per_id); |
| if (from_item_id <= AVIFINFO_MAX_VALUE && |
| to_item_id <= AVIFINFO_MAX_VALUE && |
| features->num_tiles < AVIFINFO_MAX_TILES) { |
| features->tiles[features->num_tiles].tile_item_id = to_item_id; |
| features->tiles[features->num_tiles].parent_item_id = from_item_id; |
| features->tiles[features->num_tiles].dimg_idx = i; |
| ++features->num_tiles; |
| } else { |
| features->data_was_skipped = 1; |
| } |
| } |
| |
| // If all features are available now, do not look further. |
| AVIFINFO_CHECK_NOT_FOUND( |
| AvifInfoInternalGetPrimaryItemFeatures(features)); |
| |
| // Mostly if 'data_was_skipped'. |
| AVIFINFO_CHECK_FOUND( |
| AvifInfoInternalSkip(stream, box.content_size - num_read_bytes)); |
| } else { |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size)); |
| } |
| num_remaining_bytes -= box.size; |
| } while (num_remaining_bytes > 0); |
| AVIFINFO_RETURN(kNotFound); |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| // Parses a 'stream' of an "iinf" box into 'features'. |
| static AvifInfoInternalStatus ParseIinf(int nesting_level, |
| AvifInfoInternalStream* stream, |
| uint32_t num_remaining_bytes, |
| uint32_t box_version, |
| uint32_t* num_parsed_boxes, |
| AvifInfoInternalFeatures* features) { |
| features->iinf_parsed = 1; |
| |
| const uint32_t num_bytes_per_entry_count = box_version == 0 ? 2 : 4; |
| AVIFINFO_CHECK(num_bytes_per_entry_count <= num_remaining_bytes, kInvalid); |
| const uint8_t* data; |
| AVIFINFO_CHECK_FOUND( |
| AvifInfoInternalRead(stream, num_bytes_per_entry_count, &data)); |
| num_remaining_bytes -= num_bytes_per_entry_count; |
| const uint32_t entry_count = |
| AvifInfoInternalReadBigEndian(data, num_bytes_per_entry_count); |
| |
| for (int i = 0; i < entry_count; ++i) { |
| AvifInfoInternalBox box; |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( |
| nesting_level, stream, num_remaining_bytes, num_parsed_boxes, &box)); |
| |
| if (!memcmp(box.type, "infe", 4)) { |
| // See ISO/IEC 14496-12:2015(E) 8.11.6.2 |
| uint32_t num_read_bytes = 0; |
| |
| const uint32_t num_bytes_per_id = (box.version == 2) ? 2 : 4; |
| const uint8_t* data; |
| // item_ID (16 or 32) + item_protection_index (16) + item_type (32). |
| AVIFINFO_CHECK((num_bytes_per_id + 6) <= num_remaining_bytes, kInvalid); |
| AVIFINFO_CHECK_FOUND( |
| AvifInfoInternalRead(stream, num_bytes_per_id, &data)); |
| num_read_bytes += num_bytes_per_id; |
| const uint32_t item_id = |
| AvifInfoInternalReadBigEndian(data, num_bytes_per_id); |
| |
| // Skip item_protection_index |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, 2)); |
| num_read_bytes += 2; |
| |
| const uint8_t* item_type; |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &item_type)); |
| num_read_bytes += 4; |
| if (!memcmp(item_type, "tmap", 4)) { |
| // Tone Mapped Image: indicates the presence of a gain map. |
| features->tone_mapped_item_id = item_id; |
| } |
| |
| AVIFINFO_CHECK_FOUND( |
| AvifInfoInternalSkip(stream, box.content_size - num_read_bytes)); |
| } else { |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size)); |
| } |
| |
| num_remaining_bytes -= box.size; |
| if (num_remaining_bytes <= 0) break; |
| } |
| AVIFINFO_RETURN(kNotFound); |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| // Parses a 'stream' of a "meta" box. It looks for the primary item ID in the |
| // "pitm" box and recurses into other boxes to find its 'features'. |
| static AvifInfoInternalStatus ParseMeta(int nesting_level, |
| AvifInfoInternalStream* stream, |
| uint32_t num_remaining_bytes, |
| uint32_t* num_parsed_boxes, |
| AvifInfoInternalFeatures* features) { |
| do { |
| AvifInfoInternalBox box; |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( |
| nesting_level, stream, num_remaining_bytes, 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; |
| const uint64_t primary_item_id_location = stream->num_read_bytes; |
| const uint8_t* data; |
| AVIFINFO_CHECK(num_bytes_per_id <= num_remaining_bytes, kInvalid); |
| AVIFINFO_CHECK_FOUND( |
| AvifInfoInternalRead(stream, num_bytes_per_id, &data)); |
| const uint32_t primary_item_id = |
| AvifInfoInternalReadBigEndian(data, num_bytes_per_id); |
| AVIFINFO_CHECK(primary_item_id <= AVIFINFO_MAX_VALUE, kAborted); |
| features->has_primary_item = 1; |
| features->primary_item_id = primary_item_id; |
| features->primary_item_features.primary_item_id_location = |
| primary_item_id_location; |
| features->primary_item_features.primary_item_id_bytes = num_bytes_per_id; |
| AVIFINFO_CHECK_FOUND( |
| AvifInfoInternalSkip(stream, box.content_size - num_bytes_per_id)); |
| } else if (!memcmp(box.type, "iprp", 4)) { |
| AVIFINFO_CHECK_NOT_FOUND(ParseIprp(nesting_level + 1, stream, |
| box.content_size, num_parsed_boxes, |
| features)); |
| } else if (!memcmp(box.type, "iref", 4)) { |
| AVIFINFO_CHECK_NOT_FOUND(ParseIref(nesting_level + 1, stream, |
| box.content_size, num_parsed_boxes, |
| features)); |
| } else if (!memcmp(box.type, "iinf", 4)) { |
| AVIFINFO_CHECK_NOT_FOUND(ParseIinf(nesting_level + 1, stream, |
| box.content_size, box.version, |
| num_parsed_boxes, features)); |
| } else { |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size)); |
| } |
| num_remaining_bytes -= box.size; |
| } while (num_remaining_bytes != 0); |
| // According to ISO/IEC 14496-12:2012(E) 8.11.1.1 there is at most one "meta". |
| AVIFINFO_RETURN(features->data_was_skipped ? kAborted : kInvalid); |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| // Parses a file 'stream'. The file type is checked through the "ftyp" box. |
| static AvifInfoInternalStatus ParseFtyp(AvifInfoInternalStream* stream) { |
| AvifInfoInternalBox box; |
| uint32_t num_parsed_boxes = 0; |
| const int nesting_level = 0; |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( |
| nesting_level, stream, AVIFINFO_MAX_SIZE, &num_parsed_boxes, &box)); |
| AVIFINFO_CHECK(!memcmp(box.type, "ftyp", 4), kInvalid); |
| // Iterate over brands. See ISO/IEC 14496-12:2012(E) 4.3.1 |
| AVIFINFO_CHECK(box.content_size >= 8, kInvalid); // major_brand,minor_version |
| for (uint32_t i = 0; i + 4 <= box.content_size; i += 4) { |
| const uint8_t* data; |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &data)); |
| if (i == 4) continue; // Skip minor_version. |
| if (!memcmp(data, "avif", 4) || !memcmp(data, "avis", 4)) { |
| AVIFINFO_CHECK_FOUND( |
| AvifInfoInternalSkip(stream, box.content_size - (i + 4))); |
| return kFound; |
| } |
| AVIFINFO_CHECK(i <= 32 * 4, kAborted); // Be reasonable. |
| } |
| AVIFINFO_RETURN(kInvalid); // No AVIF brand no good. |
| } |
| |
| // Parses a file 'stream'. 'features' are extracted from the "meta" box. |
| static AvifInfoInternalStatus ParseFile(AvifInfoInternalStream* stream, |
| uint32_t* num_parsed_boxes, |
| AvifInfoInternalFeatures* features) { |
| while (1) { |
| AvifInfoInternalBox box; |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( |
| /*nesting_level=*/0, stream, AVIFINFO_MAX_SIZE, num_parsed_boxes, |
| &box)); |
| if (!memcmp(box.type, "meta", 4)) { |
| return ParseMeta(/*nesting_level=*/1, stream, box.content_size, |
| num_parsed_boxes, features); |
| } else { |
| AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size)); |
| } |
| } |
| AVIFINFO_RETURN(kInvalid); // No "meta" no good. |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Helpers for converting the fixed-size input public API to the streamed one. |
| |
| typedef struct { |
| const uint8_t* data; |
| size_t data_size; |
| } AvifInfoInternalForward; |
| |
| static const uint8_t* AvifInfoInternalForwardRead(void* stream, |
| size_t num_bytes) { |
| AvifInfoInternalForward* forward = (AvifInfoInternalForward*)stream; |
| if (num_bytes > forward->data_size) return NULL; |
| const uint8_t* data = forward->data; |
| forward->data += num_bytes; |
| forward->data_size -= num_bytes; |
| return data; |
| } |
| |
| static void AvifInfoInternalForwardSkip(void* stream, size_t num_bytes) { |
| AvifInfoInternalForward* forward = (AvifInfoInternalForward*)stream; |
| if (num_bytes > forward->data_size) num_bytes = forward->data_size; |
| forward->data += num_bytes; |
| forward->data_size -= num_bytes; |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Fixed-size input public API |
| |
| AvifInfoStatus AvifInfoIdentify(const uint8_t* data, size_t data_size) { |
| AvifInfoInternalForward stream; |
| stream.data = data; |
| stream.data_size = data_size; |
| // Forward null 'data' as a null 'stream' to handle it the same way. |
| return AvifInfoIdentifyStream( |
| (void*)&stream, (data == NULL) ? NULL : AvifInfoInternalForwardRead, |
| AvifInfoInternalForwardSkip); |
| } |
| |
| AvifInfoStatus AvifInfoGetFeatures(const uint8_t* data, size_t data_size, |
| AvifInfoFeatures* features) { |
| const AvifInfoStatus status = AvifInfoIdentify(data, data_size); |
| if (status != kAvifInfoOk) { |
| if (features != NULL) memset(features, 0, sizeof(*features)); |
| return status; |
| } |
| AvifInfoInternalForward stream; |
| stream.data = data; |
| stream.data_size = data_size; |
| return AvifInfoGetFeaturesStream( |
| (void*)&stream, (data == NULL) ? NULL : AvifInfoInternalForwardRead, |
| AvifInfoInternalForwardSkip, features); |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Streamed input API |
| |
| AvifInfoStatus AvifInfoIdentifyStream(void* stream, read_stream_t read, |
| skip_stream_t skip) { |
| if (read == NULL) return kAvifInfoNotEnoughData; |
| |
| AvifInfoInternalStream internal_stream; |
| internal_stream.stream = stream; |
| internal_stream.read = read; |
| internal_stream.skip = skip; // Fallbacks to 'read' if null. |
| internal_stream.num_read_bytes = 0; |
| return AvifInfoInternalConvertStatus(ParseFtyp(&internal_stream)); |
| } |
| |
| AvifInfoStatus AvifInfoGetFeaturesStream(void* stream, read_stream_t read, |
| skip_stream_t skip, |
| AvifInfoFeatures* features) { |
| if (features != NULL) memset(features, 0, sizeof(*features)); |
| if (read == NULL) return kAvifInfoNotEnoughData; |
| |
| AvifInfoInternalStream internal_stream; |
| internal_stream.stream = stream; |
| internal_stream.read = read; |
| internal_stream.skip = skip; // Fallbacks to 'read' if null. |
| internal_stream.num_read_bytes = 0; |
| uint32_t num_parsed_boxes = 0; |
| AvifInfoInternalFeatures internal_features; |
| memset(&internal_features, AVIFINFO_UNDEFINED, sizeof(internal_features)); |
| |
| // Go through all relevant boxes sequentially. |
| const AvifInfoInternalStatus status = |
| ParseFile(&internal_stream, &num_parsed_boxes, &internal_features); |
| if (status == kFound && features != NULL) { |
| memcpy(features, &internal_features.primary_item_features, |
| sizeof(*features)); |
| } |
| return AvifInfoInternalConvertStatus(status); |
| } |