Skip to content

Commit

Permalink
Improve 'mini' read and write of float and brand (#2400)
Browse files Browse the repository at this point in the history
Check minor_version field of FileTypeBox.
Return proper error for samples in floating point format.
Verify codec config length.
  • Loading branch information
y-guyon authored Aug 20, 2024
1 parent 56512dd commit 14d8e3c
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 27 deletions.
47 changes: 32 additions & 15 deletions src/read.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ static const char * avifGetConfigurationPropertyName(avifCodecType codecType)
typedef struct avifFileType
{
uint8_t majorBrand[4];
uint32_t minorVersion;
uint8_t minorVersion[4];
// If not null, points to a memory block of 4 * compatibleBrandsCount bytes.
const uint8_t * compatibleBrands;
int compatibleBrandsCount;
Expand Down Expand Up @@ -2254,6 +2254,8 @@ static avifBool avifParseContentLightLevelInformationBox(avifProperty * prop, co
// See https://aomediacodec.github.io/av1-isobmff/v1.2.0.html#av1codecconfigurationbox-syntax.
static avifBool avifParseCodecConfiguration(avifROStream * s, avifCodecConfigurationBox * config, const char * configPropName, avifDiagnostics * diag)
{
const size_t av1COffset = s->offset;

uint32_t marker, version;
AVIF_CHECK(avifROStreamReadBits(s, &marker, /*bitCount=*/1)); // unsigned int (1) marker = 1;
if (!marker) {
Expand Down Expand Up @@ -2295,6 +2297,8 @@ static avifBool avifParseCodecConfiguration(avifROStream * s, avifCodecConfigura
// For simplicity, the constraints above are not enforced.
// The following is skipped by avifParseItemPropertyContainerBox().
// unsigned int (8) configOBUs[];

AVIF_CHECK(s->offset - av1COffset == 4); // Make sure avifParseCodecConfiguration() reads exactly 4 bytes.
return AVIF_TRUE;
}

Expand Down Expand Up @@ -3565,7 +3569,12 @@ static avifProperty * avifDecoderItemAddProperty(avifDecoderItem * item, const a
return itemProperty;
}

static avifResult avifParseMinimizedImageBox(avifMeta * meta, uint64_t rawOffset, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
static avifResult avifParseMinimizedImageBox(avifMeta * meta,
uint64_t rawOffset,
const uint8_t * raw,
size_t rawLen,
avifBool isAvifAccordingToMinorVersion,
avifDiagnostics * diag)
{
BEGIN_STREAM(s, raw, rawLen, diag, "Box[mini]");

Expand Down Expand Up @@ -3614,7 +3623,7 @@ static avifResult avifParseMinimizedImageBox(avifMeta * meta, uint64_t rawOffset
uint32_t bitDepth;
if (floatFlag) {
// bit(2) bit_depth_log2_minus4;
return AVIF_RESULT_NOT_IMPLEMENTED;
return AVIF_RESULT_BMFF_PARSE_FAILED; // Either invalid AVIF or unsupported non-AVIF.
} else {
uint32_t highBitDepthFlag;
AVIF_CHECKERR(avifROStreamReadBits(&s, &highBitDepthFlag, 1), AVIF_RESULT_BMFF_PARSE_FAILED); // bit(1) high_bit_depth_flag;
Expand Down Expand Up @@ -3652,7 +3661,6 @@ static avifResult avifParseMinimizedImageBox(avifMeta * meta, uint64_t rawOffset
: AVIF_MATRIX_COEFFICIENTS_BT601; // 6
}

// Optional unless minor_version of FileTypeBox is a brand defining these
uint8_t infeType[4];
uint8_t codecConfigType[4];
if (hasExplicitCodecTypes) {
Expand All @@ -3669,9 +3677,9 @@ static avifResult avifParseMinimizedImageBox(avifMeta * meta, uint64_t rawOffset
return AVIF_RESULT_NOT_IMPLEMENTED;
}
#endif
AVIF_CHECKERR(!memcmp(infeType, "av01", 4), AVIF_RESULT_BMFF_PARSE_FAILED);
AVIF_CHECKERR(!memcmp(codecConfigType, "av1C", 4), AVIF_RESULT_BMFF_PARSE_FAILED);
AVIF_CHECKERR(!memcmp(infeType, "av01", 4) && !memcmp(codecConfigType, "av1C", 4), AVIF_RESULT_BMFF_PARSE_FAILED);
} else {
AVIF_CHECKERR(isAvifAccordingToMinorVersion, AVIF_RESULT_BMFF_PARSE_FAILED);
memcpy(infeType, "av01", 4);
memcpy(codecConfigType, "av1C", 4);
}
Expand Down Expand Up @@ -3825,7 +3833,7 @@ static avifResult avifParseMinimizedImageBox(avifMeta * meta, uint64_t rawOffset
}
// if (hdr_flag && gainmap_flag && gainmap_item_codec_config_size > 0)
// unsigned int(8) gainmap_item_codec_config[gainmap_item_codec_config_size];
avifCodecConfigurationBox mainItemCodecConfig = { 0 };
avifCodecConfigurationBox mainItemCodecConfig;
// 'av1C' always uses 4 bytes.
AVIF_CHECKERR(mainItemCodecConfigSize == 4, AVIF_RESULT_BMFF_PARSE_FAILED);
AVIF_CHECKERR(avifParseCodecConfiguration(&s, &mainItemCodecConfig, (const char *)codecConfigType, diag),
Expand Down Expand Up @@ -4051,7 +4059,7 @@ static avifBool avifParseFileTypeBox(avifFileType * ftyp, const uint8_t * raw, s
BEGIN_STREAM(s, raw, rawLen, diag, "Box[ftyp]");

AVIF_CHECK(avifROStreamRead(&s, ftyp->majorBrand, 4));
AVIF_CHECK(avifROStreamReadU32(&s, &ftyp->minorVersion));
AVIF_CHECK(avifROStreamRead(&s, ftyp->minorVersion, 4));

size_t compatibleBrandsBytes = avifROStreamRemainingBytes(&s);
if ((compatibleBrandsBytes % 4) != 0) {
Expand Down Expand Up @@ -4085,6 +4093,7 @@ static avifResult avifParse(avifDecoder * decoder)
avifBool miniSeen = AVIF_FALSE;
avifBool needsMini = AVIF_FALSE;
#endif
avifFileType ftyp = {};

for (;;) {
// Read just enough to get the next box header (a max of 32 bytes)
Expand Down Expand Up @@ -4173,19 +4182,25 @@ static avifResult avifParse(avifDecoder * decoder)

if (isFtyp) {
AVIF_CHECKERR(!ftypSeen, AVIF_RESULT_BMFF_PARSE_FAILED);
avifFileType ftyp;
AVIF_CHECKERR(avifParseFileTypeBox(&ftyp, boxContents.data, boxContents.size, data->diag), AVIF_RESULT_BMFF_PARSE_FAILED);
if (!avifFileTypeIsCompatible(&ftyp)) {
return AVIF_RESULT_INVALID_FTYP;
}
AVIF_CHECKERR(avifFileTypeIsCompatible(&ftyp), AVIF_RESULT_INVALID_FTYP);
ftypSeen = AVIF_TRUE;
memcpy(data->majorBrand, ftyp.majorBrand, 4); // Remember the major brand for future AVIF_DECODER_SOURCE_AUTO decisions
needsMeta = avifFileTypeHasBrand(&ftyp, "avif");
needsMoov = avifFileTypeHasBrand(&ftyp, "avis");
#if defined(AVIF_ENABLE_EXPERIMENTAL_MINI)
needsMini = avifFileTypeHasBrand(&ftyp, "mif3");
if (needsMini && needsMeta) {
return AVIF_RESULT_INVALID_FTYP;
if (needsMini) {
AVIF_CHECKERR(!needsMeta, AVIF_RESULT_INVALID_FTYP);
// Section O.2.1.2 of ISO/IEC 23008-12:2014, CDAM 2:
// When the 'mif3' brand is present as the major_brand of the FileTypeBox,
// the minor_version of the FileTypeBox shall be 0 or a brand that is either
// structurally compatible with the 'mif3' brand, such as a codec brand
// complying with the 'mif3' structural brand, or a brand to which the file
// conforms after the equivalent MetaBox has been transformed from
// MinimizedImageBox as specified in Clause O.4.
AVIF_CHECKERR(!memcmp(ftyp.minorVersion, "\0\0\0\0", 4) || !memcmp(ftyp.minorVersion, "avif", 4),
AVIF_RESULT_BMFF_PARSE_FAILED);
}
#endif // AVIF_ENABLE_EXPERIMENTAL_MINI
} else if (isMeta) {
Expand All @@ -4199,7 +4214,9 @@ static avifResult avifParse(avifDecoder * decoder)
} else if (isMini) {
AVIF_CHECKERR(!metaSeen, AVIF_RESULT_BMFF_PARSE_FAILED);
AVIF_CHECKERR(!miniSeen, AVIF_RESULT_BMFF_PARSE_FAILED);
AVIF_CHECKRES(avifParseMinimizedImageBox(data->meta, boxOffset, boxContents.data, boxContents.size, data->diag));
const avifBool isAvifAccordingToMinorVersion = !memcmp(ftyp.minorVersion, "avif", 4);
AVIF_CHECKRES(
avifParseMinimizedImageBox(data->meta, boxOffset, boxContents.data, boxContents.size, isAvifAccordingToMinorVersion, data->diag));
miniSeen = AVIF_TRUE;
#endif
} else if (isMoov) {
Expand Down
27 changes: 15 additions & 12 deletions src/write.c
Original file line number Diff line number Diff line change
Expand Up @@ -2472,15 +2472,15 @@ static avifResult avifEncoderWriteMiniBox(avifEncoder * encoder, avifRWStream *
AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, 2)); // bit(2) version = 0;

// flags
AVIF_CHECKRES(avifRWStreamWriteBits(s, hasExplicitCodecTypes, 1)); // bit(1) explicit_codec_types_flag;
AVIF_CHECKRES(avifRWStreamWriteBits(s, floatFlag, 1)); // bit(1) float_flag;
AVIF_CHECKRES(avifRWStreamWriteBits(s, fullRange, 1)); // bit(1) full_range_flag;
AVIF_CHECKRES(avifRWStreamWriteBits(s, alphaItem ? 1 : 0, 1)); // bit(1) alpha_flag;
AVIF_CHECKRES(avifRWStreamWriteBits(s, hasExplicitCicp, 1)); // bit(1) explicit_cicp_flag;
AVIF_CHECKRES(avifRWStreamWriteBits(s, hasHdr, 1)); // bit(1) hdr_flag;
AVIF_CHECKRES(avifRWStreamWriteBits(s, hasIcc, 1)); // bit(1) icc_flag;
AVIF_CHECKRES(avifRWStreamWriteBits(s, image->exif.size ? 1 : 0, 1)); // bit(1) exif_flag;
AVIF_CHECKRES(avifRWStreamWriteBits(s, image->xmp.size ? 1 : 0, 1)); // bit(1) xmp_flag;
AVIF_CHECKRES(avifRWStreamWriteBits(s, hasExplicitCodecTypes, 1)); // bit(1) explicit_codec_types_flag;
AVIF_CHECKRES(avifRWStreamWriteBits(s, floatFlag, 1)); // bit(1) float_flag;
AVIF_CHECKRES(avifRWStreamWriteBits(s, fullRange, 1)); // bit(1) full_range_flag;
AVIF_CHECKRES(avifRWStreamWriteBits(s, alphaItem != 0, 1)); // bit(1) alpha_flag;
AVIF_CHECKRES(avifRWStreamWriteBits(s, hasExplicitCicp, 1)); // bit(1) explicit_cicp_flag;
AVIF_CHECKRES(avifRWStreamWriteBits(s, hasHdr, 1)); // bit(1) hdr_flag;
AVIF_CHECKRES(avifRWStreamWriteBits(s, hasIcc, 1)); // bit(1) icc_flag;
AVIF_CHECKRES(avifRWStreamWriteBits(s, image->exif.size != 0, 1)); // bit(1) exif_flag;
AVIF_CHECKRES(avifRWStreamWriteBits(s, image->xmp.size != 0, 1)); // bit(1) xmp_flag;

AVIF_CHECKRES(avifRWStreamWriteBits(s, chromaSubsampling, 2)); // bit(2) chroma_subsampling;
AVIF_CHECKRES(avifRWStreamWriteBits(s, orientationMinus1, 3)); // bit(3) orientation_minus1;
Expand All @@ -2500,7 +2500,7 @@ static avifResult avifEncoderWriteMiniBox(avifEncoder * encoder, avifRWStream *

if (floatFlag) {
// bit(2) bit_depth_log2_minus4;
return AVIF_RESULT_NOT_IMPLEMENTED;
AVIF_ASSERT_OR_RETURN(AVIF_FALSE);
} else {
AVIF_CHECKRES(avifRWStreamWriteBits(s, image->depth > 8, 1)); // bit(1) high_bit_depth_flag;
if (image->depth > 8) {
Expand All @@ -2523,11 +2523,10 @@ static avifResult avifEncoderWriteMiniBox(avifEncoder * encoder, avifRWStream *
}
}

// Optional unless minor_version of FileTypeBox is a brand defining these
if (hasExplicitCodecTypes) {
// bit(32) infe_type;
// bit(32) codec_config_type;
return AVIF_RESULT_NOT_IMPLEMENTED;
AVIF_ASSERT_OR_RETURN(AVIF_FALSE);
}

// High Dynamic Range properties
Expand Down Expand Up @@ -3654,6 +3653,8 @@ avifResult avifEncoderWrite(avifEncoder * encoder, const avifImage * image, avif
// See https://aomediacodec.github.io/av1-isobmff/v1.2.0.html#av1codecconfigurationbox-syntax.
static avifResult writeCodecConfig(avifRWStream * s, const avifCodecConfigurationBox * cfg)
{
const size_t av1COffset = s->offset;

AVIF_CHECKRES(avifRWStreamWriteBits(s, 1, /*bitCount=*/1)); // unsigned int (1) marker = 1;
AVIF_CHECKRES(avifRWStreamWriteBits(s, 1, /*bitCount=*/7)); // unsigned int (7) version = 1;

Expand All @@ -3676,6 +3677,8 @@ static avifResult writeCodecConfig(avifRWStream * s, const avifCodecConfiguratio
// there is no need to write any OBU here.
// See https://aomediacodec.github.io/av1-avif/v1.1.0.html#av1-configuration-item-property.
// unsigned int (8) configOBUs[];

AVIF_ASSERT_OR_RETURN(s->offset - av1COffset == 4); // Make sure writeCodecConfig() writes exactly 4 bytes.
return AVIF_RESULT_OK;
}

Expand Down

0 comments on commit 14d8e3c

Please sign in to comment.