929 lines
41 KiB
C
929 lines
41 KiB
C
// 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 iref_parsed; // True if the "iref" (item reference) 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 || (f->tone_mapped_item_id && !f->iref_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) {
|
|
// ISO/IEC 14496-12 4.2.2:
|
|
// if size is 0, then this box shall be in a top-level box
|
|
// (i.e. not contained in another box)
|
|
AVIFINFO_CHECK(nesting_level == 0, kInvalid);
|
|
box->size = num_remaining_bytes;
|
|
}
|
|
AVIFINFO_CHECK(box->size >= box_header_size, kInvalid);
|
|
AVIFINFO_CHECK(box->size <= num_remaining_bytes, kInvalid);
|
|
|
|
// 16 bytes of usertype should be read here if the box type is 'uuid'.
|
|
// 'uuid' boxes are skipped so usertype is part of the skipped body.
|
|
|
|
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, "skip", 4); // FreeSpaceBox
|
|
}
|
|
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.
|
|
if (box_index <= AVIFINFO_MAX_VALUE) {
|
|
features->gainmap_property_index = (uint8_t)box_index;
|
|
} else {
|
|
features->data_was_skipped = 1;
|
|
}
|
|
} 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 contains
|
|
// 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) {
|
|
features->iref_parsed = 1;
|
|
|
|
while (num_remaining_bytes > 0) {
|
|
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;
|
|
}
|
|
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
|
|
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 + 2 + 4 <= box.content_size, kInvalid);
|
|
AVIFINFO_CHECK_FOUND(
|
|
AvifInfoInternalRead(stream, num_bytes_per_id, &data));
|
|
const uint32_t item_id =
|
|
AvifInfoInternalReadBigEndian(data, num_bytes_per_id);
|
|
|
|
// Skip item_protection_index.
|
|
AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, 2));
|
|
|
|
const uint8_t* item_type;
|
|
AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &item_type));
|
|
if (!memcmp(item_type, "tmap", 4)) {
|
|
// Tone Mapped Image: indicates the presence of a gain map.
|
|
if (item_id <= AVIFINFO_MAX_VALUE) {
|
|
features->tone_mapped_item_id = (uint8_t)item_id;
|
|
} else {
|
|
features->data_was_skipped = 1;
|
|
}
|
|
}
|
|
|
|
AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(
|
|
stream, box.content_size - (num_bytes_per_id + 2 + 4)));
|
|
} else {
|
|
AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
|
|
}
|
|
|
|
num_remaining_bytes -= box.size;
|
|
if (num_remaining_bytes == 0) break; // Ignore entry_count bigger than box.
|
|
}
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// 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);
|
|
}
|