Streamed input API
Refactor the code to:
- sequentially read all boxes once into AvifInfoInternalFeatures,
- after "ipma" and "iref", check if AvifInfoInternalFeatures contain
all necessary information to output AvifInfoFeatures.
This allows to fetch just a few bytes at a time from the stream,
in order, in a contiguous manner and never twice the same data.
The drawback is the ~250-byte long AvifInfoInternalFeatures struct
that is stored on the stack to avoid dynamic allocation. It can be
decreased or increased to adjust the variety of handled input AVIFs.
Change-Id: I98ccf4c55706f6c88d65008ccbcbefc782d2be87
diff --git a/avifinfo.c b/avifinfo.c
index 65f0b68..9630732 100644
--- a/avifinfo.c
+++ b/avifinfo.c
@@ -26,7 +26,15 @@
// 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;
+#define AVIFINFO_MAX_SIZE UINT32_MAX
+// AvifInfoInternalFeatures uses uint8_t to store values and the number of
+// values is clamped to 32 to limit the stack size.
+#define AVIFINFO_MAX_VALUE UINT8_MAX
+#define AVIFINFO_UNDEFINED 0
+// 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
// Reads an unsigned integer from 'input' with most significant bits first.
// 'input' must be at least 'num_bytes'-long.
@@ -79,54 +87,192 @@
AVIFINFO_CHECK_STATUS_IS((check_status), kNotFound)
//------------------------------------------------------------------------------
+// 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.
+ uint32_t num_read_bytes; // Total number of read bytes.
+} 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;
+} 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.
+ 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 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);
+ 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.
- 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).
+ 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 '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').
+// 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(
- const uint8_t* bytes, uint32_t num_bytes, uint32_t max_num_bytes,
- uint32_t position, uint32_t* num_parsed_boxes, AvifInfoInternalBox* box) {
+ 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(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));
+ 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(position + box_header_size <= max_num_bytes, kInvalid);
- AVIFINFO_CHECK(position + box_header_size <= num_bytes, kTruncated);
+ 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(bytes + position + 8,
- sizeof(uint32_t)) == 0,
+ AVIFINFO_CHECK(AvifInfoInternalReadBigEndian(data, sizeof(uint32_t)) == 0,
kAborted);
// Read the 32 least-significant bits.
- box->size =
- AvifInfoInternalReadBigEndian(bytes + position + 12, sizeof(uint32_t));
+ box->size = AvifInfoInternalReadBigEndian(data + 4, sizeof(uint32_t));
} else if (box->size == 0) {
- box->size = max_num_bytes - position;
+ box->size = num_remaining_bytes;
}
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;
+ AVIFINFO_CHECK(box->size <= AVIFINFO_MAX_SIZE - stream->num_read_bytes,
+ kAborted);
+ AVIFINFO_CHECK(box->size <= num_remaining_bytes, kInvalid);
const int has_fullbox_header =
!memcmp(box->type, "meta", 4) || !memcmp(box->type, "pitm", 4) ||
@@ -135,10 +281,7 @@
!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);
@@ -146,8 +289,9 @@
box->version = 0;
box->flags = 0;
if (has_fullbox_header) {
- box->version = AvifInfoInternalReadBigEndian(bytes + position + 8, 1);
- box->flags = AvifInfoInternalReadBigEndian(bytes + position + 9, 3);
+ 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).
@@ -160,572 +304,448 @@
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";
+ if (!is_parsable) memcpy(box->type, "\0skp", 4); // \0 so not a valid type
}
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_CHECK(i <= 32 * 4, kAborted); // Be reasonable.
- }
- 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;
+// 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(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(
- bytes, num_bytes, max_num_bytes, position, num_parsed_boxes, &box));
+ AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, num_remaining_bytes,
+ num_parsed_boxes, &box));
- if (box_index != target_property_index) {
- // Skip.
- } else if (features->width == 0 && !memcmp(box.type, "ispe", 4)) {
+ if (!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)) {
+ 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
- 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);
+ 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(bit_depth == features->bit_depth, kInvalid);
+ AVIFINFO_CHECK(AvifInfoInternalReadBigEndian(data, 1) == bit_depth,
+ kInvalid);
AVIFINFO_CHECK(i <= 32, kAborted); // Be reasonable.
}
- return kFound;
- } else if (features->num_channels == 0 && !memcmp(box.type, "av1C", 4)) {
+ 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.
- 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;
+ 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);
}
- 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.
- }
- }
- AVIFINFO_CHECK(property <= 32, kAborted); // Be reasonable.
- }
- AVIFINFO_CHECK(entry <= 32, kAborted); // Be reasonable.
+ 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;
}
-
- // 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.
- AVIFINFO_CHECK(i <= 32, kAborted); // Be reasonable.
- }
- }
- }
- 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)) {
+ 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.
if (box.content_size >= kAlphaStrLength) {
- AVIFINFO_CHECK(box.content_position + kAlphaStrLength <= num_bytes,
- kTruncated);
- const char* const aux_type = (const char*)box.content;
+ const uint8_t* data;
+ AVIFINFO_CHECK_FOUND(
+ AvifInfoInternalRead(stream, kAlphaStrLength, &data));
+ const char* const aux_type = (const char*)data;
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;
+ features->has_alpha = 1;
}
+ AVIFINFO_CHECK_FOUND(
+ AvifInfoInternalSkip(stream, box.content_size - kAlphaStrLength));
+ } else {
+ AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
}
+ } else {
+ AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
}
- position += box.size;
- } while (position != max_num_bytes);
- AVIFINFO_RETURN(kNotFound); // No "auxC" in "ipco" is not an issue.
+ ++box_index;
+ num_remaining_bytes -= box.size;
+ } while (num_remaining_bytes > 0);
+ AVIFINFO_RETURN(kNotFound);
}
-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;
+// 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(AvifInfoInternalStream* stream,
+ uint32_t num_remaining_bytes,
+ uint32_t* num_parsed_boxes,
+ AvifInfoInternalFeatures* features) {
do {
AvifInfoInternalBox box;
- AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(
- bytes, num_bytes, max_num_bytes, position, num_parsed_boxes, &box));
+ AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, num_remaining_bytes,
+ 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);
+ AVIFINFO_CHECK_NOT_FOUND(
+ ParseIpco(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));
+
+ AVIFINFO_CHECK_FOUND(
+ AvifInfoInternalSkip(stream, box.content_size - num_read_bytes));
+ } else {
+ AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
}
- 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.
+ num_remaining_bytes -= box.size;
+ } while (num_remaining_bytes != 0);
+ AVIFINFO_RETURN(kNotFound);
}
//------------------------------------------------------------------------------
-// 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));
+// 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(AvifInfoInternalStream* stream,
+ uint32_t num_remaining_bytes,
+ uint32_t* num_parsed_boxes,
+ AvifInfoInternalFeatures* features) {
+ do {
+ AvifInfoInternalBox box;
+ AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, num_remaining_bytes,
+ num_parsed_boxes, &box));
- // '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));
+ 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);
- // 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);
+ 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->num_tiles;
+ } else {
+ features->data_was_skipped = 1;
+ }
+ }
- // 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;
+ // If all features are available now, do not look further.
+ AVIFINFO_CHECK_NOT_FOUND(
+ AvifInfoInternalGetPrimaryItemFeatures(features));
+ } else {
+ AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
+ }
+ num_remaining_bytes -= box.size;
+ } while (num_remaining_bytes > 0);
+ AVIFINFO_RETURN(kNotFound);
}
//------------------------------------------------------------------------------
-// Public API
+
+// 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(AvifInfoInternalStream* stream,
+ uint32_t num_remaining_bytes,
+ uint32_t* num_parsed_boxes,
+ AvifInfoInternalFeatures* features) {
+ do {
+ AvifInfoInternalBox box;
+ AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(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 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;
+ AVIFINFO_CHECK_FOUND(
+ AvifInfoInternalSkip(stream, box.content_size - num_bytes_per_id));
+ } else if (!memcmp(box.type, "iprp", 4)) {
+ AVIFINFO_CHECK_NOT_FOUND(
+ ParseIprp(stream, box.content_size, num_parsed_boxes, features));
+ } else if (!memcmp(box.type, "iref", 4)) {
+ AVIFINFO_CHECK_NOT_FOUND(
+ ParseIref(stream, box.content_size, 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 and
+// 'features' are extracted through the "meta" box.
+static AvifInfoInternalStatus ParseFile(AvifInfoInternalStream* stream,
+ uint32_t num_remaining_bytes,
+ uint32_t* num_parsed_boxes,
+ AvifInfoInternalFeatures* features) {
+ do {
+ AvifInfoInternalBox box;
+ AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, num_remaining_bytes,
+ num_parsed_boxes, &box));
+ // The first box must be "ftyp" and no other box at root can be "ftyp".
+ AVIFINFO_CHECK((*num_parsed_boxes == 1) == !memcmp(box.type, "ftyp", 4),
+ kInvalid);
+
+ 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
+ uint8_t is_avif = 0;
+ 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)) {
+ is_avif = 1;
+ AVIFINFO_CHECK_FOUND(
+ AvifInfoInternalSkip(stream, box.content_size - (i + 4)));
+ break;
+ }
+ AVIFINFO_CHECK(i <= 32 * 4, kAborted); // Be reasonable.
+ }
+ AVIFINFO_CHECK(is_avif, kInvalid);
+ } else if (!memcmp(box.type, "meta", 4)) {
+ return ParseMeta(stream, box.content_size, num_parsed_boxes, features);
+ } else {
+ AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
+ }
+ num_remaining_bytes -= box.size;
+ } while (num_remaining_bytes != 0);
+ 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 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);
+ /*file_size=*/AVIFINFO_MAX_SIZE);
}
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;
+ AvifInfoInternalForward stream;
+ stream.data = data;
+ stream.data_size = data_size;
+ // Forward null 'data' as a null 'stream' to handle it the same way.
+ return AvifInfoReadWithSize(
+ (void*)&stream, (data == NULL) ? NULL : AvifInfoInternalForwardRead,
+ AvifInfoInternalForwardSkip, features, 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);
+//------------------------------------------------------------------------------
+// Streamed input API
+
+AvifInfoStatus AvifInfoRead(void* stream, read_stream_t read,
+ skip_stream_t skip, AvifInfoFeatures* features) {
+ // Consider the file to be of maximum size.
+ return AvifInfoReadWithSize(stream, read, skip, features,
+ /*file_size=*/AVIFINFO_MAX_SIZE);
+}
+
+AvifInfoStatus AvifInfoReadWithSize(void* stream, read_stream_t read,
+ skip_stream_t skip,
+ AvifInfoFeatures* features,
+ size_t file_size) {
+ 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;
+ const uint32_t size = (file_size >= AVIFINFO_MAX_SIZE) ? AVIFINFO_MAX_SIZE
+ : (uint32_t)file_size;
+ 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, size, &num_parsed_boxes, &internal_features);
if (status == kNotFound) {
- return (data_size < file_size) ? kAvifInfoNotEnoughData
- : kAvifInfoInvalidFile;
+ return (internal_stream.num_read_bytes < 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));
+ memcpy(features, &internal_features.primary_item_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
diff --git a/avifinfo.h b/avifinfo.h
index ea4d9d7..414f52e 100644
--- a/avifinfo.h
+++ b/avifinfo.h
@@ -41,6 +41,10 @@
// (1 monochrome or 3 colors) + (0 or 1 alpha)
} AvifInfoFeatures;
+//------------------------------------------------------------------------------
+// Fixed-size input API
+// Use this API if a raw byte array of fixed size is available as input.
+
// Parses the AVIF 'data' and extracts its 'features'.
// 'data' can be partial but must point to the beginning of the AVIF file.
// The 'features' can be parsed in the first 450 bytes of most AVIF files.
@@ -55,11 +59,38 @@
size_t file_size);
//------------------------------------------------------------------------------
+// Streamed input API
+// Use this API if the input bytes must be fetched and/or if the AVIF payload
+// size is unknown. Implement the two function signatures below and pass them to
+// AvifInfoRead*() with a 'stream', which can be anything (file, struct etc.).
+
+// Reads 'num_bytes' from the 'stream'.
+// The position in the 'stream' must be advanced by 'num_bytes'.
+// Returns a pointer to the 'num_bytes' or null if it cannot be fulfilled.
+// The returned data must remain valid until the next read.
+typedef const uint8_t* (*read_stream_t)(void* stream, size_t num_bytes);
+// Advances the position in the 'stream' by 'num_bytes'.
+typedef void (*skip_stream_t)(void* stream, size_t num_bytes);
+
+// Maximum number of bytes requested per read. There is no limit per skip.
+#define AVIFINFO_MAX_NUM_READ_BYTES 64
+
+// Same as AvifInfoGet*() but takes a 'stream' as input. AvifInfoRead*() does
+// not access the 'stream' directly but passes it as is to 'read' and 'skip'.
+// 'read' cannot be null. If 'skip' is null, 'read' is called instead.
+AvifInfoStatus AvifInfoRead(void* stream, read_stream_t read,
+ skip_stream_t skip, AvifInfoFeatures* features);
+AvifInfoStatus AvifInfoReadWithSize(void* stream, read_stream_t read,
+ skip_stream_t skip,
+ AvifInfoFeatures* features,
+ size_t file_size);
+
+//------------------------------------------------------------------------------
// If needed, avifinfo.h and avifinfo.c can be merged into a single file:
// 1. Replace this block comment by the content of avifinfo.c
// 2. Discard #include "./avifinfo.h" and move other includes to the top
-// 3. Mark AvifInfoGet*() declarations and definitions as static
+// 3. Mark AvifInfo*() declarations and definitions as static
// This procedure can be useful when only one translation unit uses avifinfo,
// whether it includes the merged .h or the merged code is inserted into a file.
diff --git a/tests/avifinfo_fuzz.cc b/tests/avifinfo_fuzz.cc
index 6e9cf27..bc9cb0b 100644
--- a/tests/avifinfo_fuzz.cc
+++ b/tests/avifinfo_fuzz.cc
@@ -13,6 +13,38 @@
#include "avifinfo.h"
+//------------------------------------------------------------------------------
+// Stream definition.
+
+typedef struct {
+ const uint8_t* data;
+ size_t data_size;
+} StreamData;
+
+static const uint8_t* StreamRead(void* stream, size_t num_bytes) {
+ if (stream == NULL) abort();
+ if (num_bytes < 1 || num_bytes > AVIFINFO_MAX_NUM_READ_BYTES) abort();
+
+ StreamData* stream_data = (StreamData*)stream;
+ if (num_bytes > stream_data->data_size) return NULL;
+ const uint8_t* data = stream_data->data;
+ stream_data->data += num_bytes;
+ stream_data->data_size -= num_bytes;
+ return data;
+}
+
+static void StreamSkip(void* stream, size_t num_bytes) {
+ if (stream == NULL) abort();
+ if (num_bytes < 1) abort();
+
+ StreamData* stream_data = (StreamData*)stream;
+ if (num_bytes > stream_data->data_size) num_bytes = stream_data->data_size;
+ stream_data->data += num_bytes;
+ stream_data->data_size -= num_bytes;
+}
+
+//------------------------------------------------------------------------------
+
// Test a random bitstream of random size, whether it is valid or not.
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t data_size) {
AvifInfoStatus previous_status = kAvifInfoNotEnoughData;
@@ -22,9 +54,10 @@
// for a given size and a status that is not kAvifInfoNotEnoughData, any
// bigger size (of the same data) should return the same status and features.
for (size_t size = 0; size < data_size; ++size) {
+ StreamData stream = {data, size};
AvifInfoFeatures features;
- const AvifInfoStatus status =
- AvifInfoGetWithSize(data, size, &features, data_size);
+ const AvifInfoStatus status = AvifInfoReadWithSize(
+ &stream, StreamRead, StreamSkip, &features, data_size);
if (previous_status != kAvifInfoNotEnoughData &&
status != previous_status) {
diff --git a/tests/avifinfo_test.cc b/tests/avifinfo_test.cc
index fe59b14..381a8e8 100644
--- a/tests/avifinfo_test.cc
+++ b/tests/avifinfo_test.cc
@@ -192,6 +192,17 @@
kAvifInfoTooComplex);
}
+TEST(AvifInfoReadTest, Null) {
+ AvifInfoFeatures features;
+ EXPECT_EQ(AvifInfoRead(/*stream=*/nullptr, /*read=*/nullptr, /*skip=*/nullptr,
+ &features),
+ kAvifInfoNotEnoughData);
+ EXPECT_EQ(features.width, 0u);
+ EXPECT_EQ(features.height, 0u);
+ EXPECT_EQ(features.bit_depth, 0u);
+ EXPECT_EQ(features.num_channels, 0u);
+}
+
//------------------------------------------------------------------------------
} // namespace