Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement AV2 in MinimizedImageBox #2486

Merged
merged 2 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
60 changes: 49 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 @@ -3779,11 +3785,12 @@ static avifResult avifParseMinimizedImageBox(avifDecoderData * data,
AVIF_CHECKERR(avifROStreamReadBitsU8(&s, &codecConfigType[i], 8), AVIF_RESULT_BMFF_PARSE_FAILED);
}
#if defined(AVIF_CODEC_AVM)
if (!memcmp(infeType, "av02", 4) && !memcmp(codecConfigType, "av2C", 4)) {
return AVIF_RESULT_NOT_IMPLEMENTED;
}
#endif
AVIF_CHECKERR((!memcmp(infeType, "av01", 4) && !memcmp(codecConfigType, "av1C", 4)) ||
(!memcmp(infeType, "av02", 4) && !memcmp(codecConfigType, "av2C", 4)),
AVIF_RESULT_BMFF_PARSE_FAILED);
#else
AVIF_CHECKERR(!memcmp(infeType, "av01", 4) && !memcmp(codecConfigType, "av1C", 4), AVIF_RESULT_BMFF_PARSE_FAILED);
#endif
} else {
AVIF_CHECKERR(isAvifAccordingToMinorVersion, AVIF_RESULT_BMFF_PARSE_FAILED);
memcpy(infeType, "av01", 4);
Expand Down Expand Up @@ -6162,6 +6169,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
21 changes: 19 additions & 2 deletions src/write.c
Original file line number Diff line number Diff line change
Expand Up @@ -2596,7 +2596,19 @@ 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];
uint8_t codecConfigType[4];
avifBool hasExplicitCodecTypes;
if (encoder->codecChoice == AVIF_CODEC_CHOICE_AVM) {
memcpy(infeType, "av02", 4);
memcpy(codecConfigType, "av2C", 4); // Same syntax as 'av1C'.
hasExplicitCodecTypes = AVIF_TRUE;
} else {
memcpy(infeType, "av01", 4);
memcpy(codecConfigType, "av1C", 4);
// 'av01' and 'av1C' are implied by 'avif' minor_version field of FileTypeBox. No need to write them.
hasExplicitCodecTypes = AVIF_FALSE;
}

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 +2688,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
126 changes: 126 additions & 0 deletions tests/gtest/avifavmminitest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright 2024 Google LLC
// SPDX-License-Identifier: BSD-2-Clause

#include "avif/avif.h"
#include "aviftest_helpers.h"
#include "gtest/gtest.h"

using testing::Combine;
using testing::Values;

namespace avif {
namespace {

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

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());
const avifPixelFormat format = std::get<3>(GetParam());
const bool alpha = std::get<4>(GetParam());

ImagePtr image = testutil::CreateImage(
width, height, depth, format, alpha ? AVIF_PLANES_ALL : AVIF_PLANES_YUV);
ASSERT_NE(image, nullptr);
testutil::FillImageGradient(image.get());

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);

ImagePtr decoded(avifImageCreateEmpty());
ASSERT_NE(decoded, nullptr);
DecoderPtr decoder(avifDecoderCreate());
ASSERT_NE(decoder, nullptr);
// No need to set AVIF_CODEC_CHOICE_AVM. The decoder should recognize AV2.
ASSERT_EQ(avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
encoded.size),
AVIF_RESULT_OK);

// Verify that the input and decoded images are close.
EXPECT_GT(testutil::GetPsnr(*image, *decoded), 40.0);

// Forcing an AV1 decoding codec should fail.
for (avifCodecChoice av1_codec :
{AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_CHOICE_DAV1D,
AVIF_CODEC_CHOICE_LIBGAV1}) {
decoder->codecChoice = av1_codec;
// An error is expected because av1_codec is not enabled or because we are
// trying to decode an AV2 file with an AV1 codec.
ASSERT_EQ(avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
encoded.size),
avifCodecName(av1_codec, AVIF_CODEC_FLAG_CAN_DECODE)
? AVIF_RESULT_DECODE_COLOR_FAILED
: AVIF_RESULT_NO_CODEC_AVAILABLE);
}
}

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

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, 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(AvmMiniTest, Av1StillWorksWhenAvmIsEnabled) {
if (!testutil::Av1EncoderAvailable() || !testutil::Av1DecoderAvailable()) {
GTEST_SKIP() << "AV1 codec unavailable, skip test.";
}
// avm is the only AV2 codec, so the default codec will be an AV1 one.

ImagePtr image =
testutil::CreateImage(/*width=*/64, /*height=*/64, /*depth=*/8,
AVIF_PIXEL_FORMAT_YUV420, AVIF_PLANES_ALL);
ASSERT_NE(image, nullptr);
testutil::FillImageGradient(image.get());

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);

ImagePtr decoded(avifImageCreateEmpty());
ASSERT_NE(decoded, nullptr);
DecoderPtr decoder(avifDecoderCreate());
ASSERT_NE(decoder, nullptr);
ASSERT_EQ(avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
encoded.size),
AVIF_RESULT_OK);

// Verify that the input and decoded images are close.
EXPECT_GT(testutil::GetPsnr(*image, *decoded), 40.0);

// Forcing an AV2 decoding codec should fail.
decoder->codecChoice = AVIF_CODEC_CHOICE_AVM;
ASSERT_EQ(avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
encoded.size),
AVIF_RESULT_DECODE_COLOR_FAILED);
}

} // namespace
} // namespace avif
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
Loading