Skip to content

Commit

Permalink
Implement AV2 in MinimizedImageBox
Browse files Browse the repository at this point in the history
Add avifavmminitest.
Also fix 4:2:0 decoded samples mapped to 4:0:0 output image.
  • Loading branch information
y-guyon committed Oct 23, 2024
1 parent fe3414f commit 35390d2
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 27 deletions.
4 changes: 3 additions & 1 deletion src/codec_avm.c
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ static avifBool avmCodecGetNextImage(struct avifCodec * codec,
return AVIF_FALSE;
}
if (codec->internal->image->monochrome) {
// avm does not handle monochrome as of research-v8.0.0.
// This should not happen.
yuvFormat = AVIF_PIXEL_FORMAT_YUV400;
}

Expand Down Expand Up @@ -645,7 +647,7 @@ static avifResult avmCodecEncodeImage(avifCodec * codec,
cfg->g_threads = AVIF_MIN(encoder->maxThreads, 64);
}

// avm does not handle monochrome as of research-v4.0.0.
// avm does not handle monochrome as of research-v8.0.0.
// TODO(yguyon): Enable when fixed upstream
codec->internal->monochromeEnabled = AVIF_FALSE;

Expand Down
59 changes: 48 additions & 11 deletions src/read.c
Original file line number Diff line number Diff line change
Expand Up @@ -1214,13 +1214,19 @@ static avifResult avifDecoderItemValidateProperties(const avifDecoderItem * item
// This is a MinimizedImageBox ('mini').

if (item->miniBoxPixelFormat != avifCodecConfigurationBoxGetFormat(&configProp->u.av1C)) {
avifDiagnosticsPrintf(diag,
"Item ID %u format [%s] specified by MinimizedImageBox does not match %s property format [%s]",
item->id,
avifPixelFormatToString(item->miniBoxPixelFormat),
configPropName,
avifPixelFormatToString(avifCodecConfigurationBoxGetFormat(&configProp->u.av1C)));
return AVIF_RESULT_BMFF_PARSE_FAILED;
if (!memcmp(configPropName, "av2C", 4) && item->miniBoxPixelFormat == AVIF_PIXEL_FORMAT_YUV400 &&
avifCodecConfigurationBoxGetFormat(&configProp->u.av1C) == AVIF_PIXEL_FORMAT_YUV420) {
// avm does not handle monochrome as of research-v8.0.0.
// 4:2:0 is used instead.
} else {
avifDiagnosticsPrintf(diag,
"Item ID %u format [%s] specified by MinimizedImageBox does not match %s property format [%s]",
item->id,
avifPixelFormatToString(item->miniBoxPixelFormat),
configPropName,
avifPixelFormatToString(avifCodecConfigurationBoxGetFormat(&configProp->u.av1C)));
return AVIF_RESULT_BMFF_PARSE_FAILED;
}
}

if (configProp->u.av1C.chromaSamplePosition == /*CSP_UNKNOWN=*/0) {
Expand Down Expand Up @@ -3778,12 +3784,12 @@ static avifResult avifParseMinimizedImageBox(avifDecoderData * data,
for (int i = 0; i < 4; ++i) {
AVIF_CHECKERR(avifROStreamReadBitsU8(&s, &codecConfigType[i], 8), AVIF_RESULT_BMFF_PARSE_FAILED);
}
AVIF_CHECKERR((!memcmp(infeType, "av01", 4) && !memcmp(codecConfigType, "av1C", 4))
#if defined(AVIF_CODEC_AVM)
if (!memcmp(infeType, "av02", 4) && !memcmp(codecConfigType, "av2C", 4)) {
return AVIF_RESULT_NOT_IMPLEMENTED;
}
|| (!memcmp(infeType, "av02", 4) && !memcmp(codecConfigType, "av2C", 4))
#endif
AVIF_CHECKERR(!memcmp(infeType, "av01", 4) && !memcmp(codecConfigType, "av1C", 4), AVIF_RESULT_BMFF_PARSE_FAILED);
,
AVIF_RESULT_BMFF_PARSE_FAILED);
} else {
AVIF_CHECKERR(isAvifAccordingToMinorVersion, AVIF_RESULT_BMFF_PARSE_FAILED);
memcpy(infeType, "av01", 4);
Expand Down Expand Up @@ -6162,6 +6168,37 @@ static avifResult avifDecoderDecodeTiles(avifDecoder * decoder, uint32_t nextIma
}
}

#if defined(AVIF_CODEC_AVM)
avifDecoderItem * tileItem = NULL;
for (uint32_t itemIndex = 0; itemIndex < decoder->data->meta->items.count; ++itemIndex) {
avifDecoderItem * item = decoder->data->meta->items.item[itemIndex];
if (avifDecoderItemShouldBeSkipped(item)) {
continue;
}
if (item->id == sample->itemID) {
tileItem = item;
break;
}
}
if (tileItem != NULL) {
const avifProperty * prop = avifPropertyArrayFind(&tileItem->properties, "pixi");
// Match the decoded image format with the number of planes specified in 'pixi'.
if (prop != NULL && prop->u.pixi.planeCount == 1 && tile->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420) {
// Codecs such as avm do not support monochrome so samples were encoded as 4:2:0.
// Ignore the UV planes at decoding.
tile->image->yuvFormat = AVIF_PIXEL_FORMAT_YUV400;
if (tile->image->imageOwnsYUVPlanes) {
avifFree(tile->image->yuvPlanes[AVIF_CHAN_U]);
avifFree(tile->image->yuvPlanes[AVIF_CHAN_V]);
}
tile->image->yuvPlanes[AVIF_CHAN_U] = NULL;
tile->image->yuvRowBytes[AVIF_CHAN_U] = 0;
tile->image->yuvPlanes[AVIF_CHAN_V] = NULL;
tile->image->yuvRowBytes[AVIF_CHAN_V] = 0;
}
}
#endif

++info->decodedTileCount;

const avifBool isGrid = (info->grid.rows > 0) && (info->grid.columns > 0);
Expand Down
16 changes: 14 additions & 2 deletions src/write.c
Original file line number Diff line number Diff line change
Expand Up @@ -2596,7 +2596,14 @@ static avifResult avifEncoderWriteMiniBox(avifEncoder * encoder, avifRWStream *

const uint32_t orientationMinus1 = avifImageIrotImirToExifOrientation(image) - 1;

const avifBool hasExplicitCodecTypes = AVIF_FALSE; // 'av01' and 'av1C' known from 'avif' minor_version field of FileTypeBox.
uint8_t infeType[4] = "av01";
uint8_t codecConfigType[4] = "av1C";
avifBool hasExplicitCodecTypes = AVIF_FALSE; // 'av01' and 'av1C' known from 'avif' minor_version field of FileTypeBox.
if (encoder->codecChoice == AVIF_CODEC_CHOICE_AVM) {
memcpy(infeType, "av02", 4);
memcpy(codecConfigType, "av2C", 4); // Same syntax as 'av1C'.
hasExplicitCodecTypes = AVIF_TRUE;
}

uint32_t smallDimensionsFlag = image->width <= (1 << 7) && image->height <= (1 << 7);
const uint32_t codecConfigSize = 4; // 'av1C' always uses 4 bytes.
Expand Down Expand Up @@ -2676,8 +2683,13 @@ static avifResult avifEncoderWriteMiniBox(avifEncoder * encoder, avifRWStream *

if (hasExplicitCodecTypes) {
// bit(32) infe_type;
for (int i = 0; i < 4; ++i) {
AVIF_CHECKRES(avifRWStreamWriteBits(s, infeType[i], 8));
}
// bit(32) codec_config_type;
AVIF_ASSERT_OR_RETURN(AVIF_FALSE);
for (int i = 0; i < 4; ++i) {
AVIF_CHECKRES(avifRWStreamWriteBits(s, codecConfigType[i], 8));
}
}

// High Dynamic Range properties
Expand Down
3 changes: 3 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,9 @@ endif()
if(AVIF_CODEC_AVM_ENABLED)
if(AVIF_ENABLE_GTEST)
add_avif_gtest(avifavmtest)
if(AVIF_ENABLE_EXPERIMENTAL_MINI)
add_avif_gtest(avifavmminitest)
endif()
endif()

if(AVIF_BUILD_APPS)
Expand Down
25 changes: 14 additions & 11 deletions tests/gtest/avifavmminitest.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2023 Google LLC
// Copyright 2024 Google LLC
// SPDX-License-Identifier: BSD-2-Clause

#include "avif/avif.h"
Expand All @@ -11,11 +11,11 @@ using testing::Values;
namespace avif {
namespace {

class AvmTest : public testing::TestWithParam<
std::tuple</*width=*/int, /*height=*/int, /*depth=*/int,
avifPixelFormat, /*alpha=*/bool>> {};
class AvmMiniTest : public testing::TestWithParam<
std::tuple</*width=*/int, /*height=*/int, /*depth=*/int,
avifPixelFormat, /*alpha=*/bool>> {};

TEST_P(AvmTest, EncodeDecode) {
TEST_P(AvmMiniTest, EncodeDecode) {
const int width = std::get<0>(GetParam());
const int height = std::get<1>(GetParam());
const int depth = std::get<2>(GetParam());
Expand All @@ -30,6 +30,7 @@ TEST_P(AvmTest, EncodeDecode) {
EncoderPtr encoder(avifEncoderCreate());
ASSERT_NE(encoder, nullptr);
encoder->codecChoice = AVIF_CODEC_CHOICE_AVM;
encoder->headerFormat = AVIF_HEADER_REDUCED;
testutil::AvifRwData encoded;
ASSERT_EQ(avifEncoderWrite(encoder.get(), image.get(), &encoded),
AVIF_RESULT_OK);
Expand Down Expand Up @@ -61,29 +62,30 @@ TEST_P(AvmTest, EncodeDecode) {
}
}

INSTANTIATE_TEST_SUITE_P(Basic, AvmTest,
INSTANTIATE_TEST_SUITE_P(Basic, AvmMiniTest,
Combine(/*width=*/Values(12), /*height=*/Values(34),
/*depth=*/Values(8),
Values(AVIF_PIXEL_FORMAT_YUV420,
Values(AVIF_PIXEL_FORMAT_YUV400,
AVIF_PIXEL_FORMAT_YUV420,
AVIF_PIXEL_FORMAT_YUV444),
/*alpha=*/Values(true)));
/*alpha=*/Values(false, true)));

INSTANTIATE_TEST_SUITE_P(Tiny, AvmTest,
INSTANTIATE_TEST_SUITE_P(Tiny, AvmMiniTest,
Combine(/*width=*/Values(1), /*height=*/Values(1),
/*depth=*/Values(8),
Values(AVIF_PIXEL_FORMAT_YUV444),
/*alpha=*/Values(false)));

// TODO(yguyon): Implement or fix in avm then test the following combinations.
INSTANTIATE_TEST_SUITE_P(DISABLED_Broken, AvmTest,
INSTANTIATE_TEST_SUITE_P(DISABLED_Broken, AvmMiniTest,
Combine(/*width=*/Values(1), /*height=*/Values(34),
/*depth=*/Values(8, 10, 12),
Values(AVIF_PIXEL_FORMAT_YUV400,
AVIF_PIXEL_FORMAT_YUV420,
AVIF_PIXEL_FORMAT_YUV444),
/*alpha=*/Values(true)));

TEST(AvmTest, Av1StillWorksWhenAvmIsEnabled) {
TEST(AvmMiniTest, Av1StillWorksWhenAvmIsEnabled) {
if (!testutil::Av1EncoderAvailable() || !testutil::Av1DecoderAvailable()) {
GTEST_SKIP() << "AV1 codec unavailable, skip test.";
}
Expand All @@ -97,6 +99,7 @@ TEST(AvmTest, Av1StillWorksWhenAvmIsEnabled) {

EncoderPtr encoder(avifEncoderCreate());
ASSERT_NE(encoder, nullptr);
encoder->headerFormat = AVIF_HEADER_REDUCED;
testutil::AvifRwData encoded;
ASSERT_EQ(avifEncoderWrite(encoder.get(), image.get(), &encoded),
AVIF_RESULT_OK);
Expand Down
5 changes: 3 additions & 2 deletions tests/gtest/avifavmtest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,10 @@ TEST_P(AvmTest, EncodeDecode) {
INSTANTIATE_TEST_SUITE_P(Basic, AvmTest,
Combine(/*width=*/Values(12), /*height=*/Values(34),
/*depth=*/Values(8),
Values(AVIF_PIXEL_FORMAT_YUV420,
Values(AVIF_PIXEL_FORMAT_YUV400,
AVIF_PIXEL_FORMAT_YUV420,
AVIF_PIXEL_FORMAT_YUV444),
/*alpha=*/Values(true)));
/*alpha=*/Values(false, true)));

INSTANTIATE_TEST_SUITE_P(Tiny, AvmTest,
Combine(/*width=*/Values(1), /*height=*/Values(1),
Expand Down

0 comments on commit 35390d2

Please sign in to comment.