blob: 485a87be2c7fcd57724f0608932a489829953a8f [file] [log] [blame]
// Copyright 2022 Yuan Tong. All rights reserved.
// SPDX-License-Identifier: BSD-2-Clause
#include <map>
#include <string>
#include "avif/avif.h"
#include "aviftest_helpers.h"
#include "gtest/gtest.h"
namespace avif {
namespace {
// Used to pass the data folder path to the GoogleTest suites.
const char* data_path = nullptr;
//------------------------------------------------------------------------------
void TestEncodeDecode(avifCodecChoice codec,
const std::map<std::string, std::string>& init_cs_options,
bool can_encode, bool use_cq) {
if (avifCodecName(codec, AVIF_CODEC_FLAG_CAN_ENCODE) == nullptr) {
GTEST_SKIP() << "Codec unavailable, skip test.";
}
constexpr uint32_t kImageSize = 512;
ImagePtr image = testutil::CreateImage(kImageSize, kImageSize, /*depth=*/8,
AVIF_PIXEL_FORMAT_YUV420,
AVIF_PLANES_YUV, AVIF_RANGE_FULL);
ASSERT_NE(image, nullptr);
testutil::FillImageGradient(image.get());
// Encode
EncoderPtr encoder(avifEncoderCreate());
ASSERT_NE(encoder, nullptr);
encoder->codecChoice = codec;
encoder->speed = AVIF_SPEED_FASTEST;
encoder->timescale = 1;
for (const auto& option : init_cs_options) {
ASSERT_EQ(avifEncoderSetCodecSpecificOption(
encoder.get(), option.first.c_str(), option.second.c_str()),
AVIF_RESULT_OK);
}
if (use_cq) {
encoder->minQuantizer = 0;
encoder->maxQuantizer = 63;
ASSERT_EQ(
avifEncoderSetCodecSpecificOption(encoder.get(), "end-usage", "q"),
AVIF_RESULT_OK);
ASSERT_EQ(
avifEncoderSetCodecSpecificOption(encoder.get(), "cq-level", "63"),
AVIF_RESULT_OK);
} else {
encoder->minQuantizer = 63;
encoder->maxQuantizer = 63;
}
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
AVIF_RESULT_OK);
if (use_cq) {
ASSERT_EQ(avifEncoderSetCodecSpecificOption(encoder.get(), "cq-level", "0"),
AVIF_RESULT_OK);
} else {
encoder->minQuantizer = 0;
encoder->maxQuantizer = 0;
}
if (!can_encode) {
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
AVIF_RESULT_NOT_IMPLEMENTED);
return;
}
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
AVIF_RESULT_OK);
testutil::AvifRwData encodedAvif;
ASSERT_EQ(avifEncoderFinish(encoder.get(), &encodedAvif), AVIF_RESULT_OK);
// Decode
DecoderPtr decoder(avifDecoderCreate());
ASSERT_NE(decoder, nullptr);
// The second frame is set to have far better quality,
// and should be much bigger, so small amount of data at beginning
// should be enough to decode the first frame.
avifIO* io = testutil::AvifIOCreateLimitedReader(
avifIOCreateMemoryReader(encodedAvif.data, encodedAvif.size),
encodedAvif.size / 10);
ASSERT_NE(io, nullptr);
avifDecoderSetIO(decoder.get(), io);
ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK);
ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK);
ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_WAITING_ON_IO);
reinterpret_cast<testutil::AvifIOLimitedReader*>(io)->clamp =
testutil::AvifIOLimitedReader::kNoClamp;
ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK);
ASSERT_EQ(avifDecoderNextImage(decoder.get()),
AVIF_RESULT_NO_IMAGES_REMAINING);
}
TEST(ChangeSettingTest, AOM) {
// Test if changes to AV1 encode settings are detected.
TestEncodeDecode(AVIF_CODEC_CHOICE_AOM, {{"end-usage", "cbr"}}, true, false);
// Test if changes to codec specific options are detected.
TestEncodeDecode(AVIF_CODEC_CHOICE_AOM, {}, true, true);
}
TEST(ChangeSettingTest, RAV1E) {
TestEncodeDecode(AVIF_CODEC_CHOICE_RAV1E, {}, false, false);
}
TEST(ChangeSettingTest, SVT) {
TestEncodeDecode(AVIF_CODEC_CHOICE_SVT, {}, false, false);
}
TEST(ChangeSettingTest, UnchangeableSetting) {
if (avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE) ==
nullptr) {
GTEST_SKIP() << "Codec unavailable, skip test.";
}
constexpr uint32_t kImageSize = 512;
ImagePtr image = testutil::CreateImage(kImageSize, kImageSize, /*depth=*/8,
AVIF_PIXEL_FORMAT_YUV420,
AVIF_PLANES_YUV, AVIF_RANGE_FULL);
ASSERT_NE(image, nullptr);
testutil::FillImageGradient(image.get());
// Encode
EncoderPtr encoder(avifEncoderCreate());
ASSERT_NE(encoder, nullptr);
encoder->codecChoice = AVIF_CODEC_CHOICE_AOM;
encoder->speed = AVIF_SPEED_FASTEST;
encoder->timescale = 1;
ASSERT_EQ(encoder->repetitionCount, AVIF_REPETITION_COUNT_INFINITE);
encoder->minQuantizer = 63;
encoder->maxQuantizer = 63;
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
AVIF_RESULT_OK);
encoder->timescale = 2;
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
AVIF_RESULT_CANNOT_CHANGE_SETTING);
encoder->timescale = 1;
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
AVIF_RESULT_OK);
encoder->repetitionCount = 0;
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
AVIF_RESULT_CANNOT_CHANGE_SETTING);
}
TEST(ChangeSettingTest, UnchangeableImageColorRange) {
if (avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE) ==
nullptr) {
GTEST_SKIP() << "Codec unavailable, skip test.";
}
constexpr uint32_t kImageSize = 512;
ImagePtr image = testutil::CreateImage(kImageSize, kImageSize, /*depth=*/8,
AVIF_PIXEL_FORMAT_YUV420,
AVIF_PLANES_YUV, AVIF_RANGE_FULL);
ASSERT_NE(image, nullptr);
const uint32_t yuva[] = {128, 128, 128, 255};
testutil::FillImagePlain(image.get(), yuva);
// Encode
EncoderPtr encoder(avifEncoderCreate());
ASSERT_NE(encoder, nullptr);
encoder->codecChoice = AVIF_CODEC_CHOICE_AOM;
encoder->speed = AVIF_SPEED_FASTEST;
encoder->timescale = 1;
ASSERT_EQ(encoder->repetitionCount, AVIF_REPETITION_COUNT_INFINITE);
encoder->quality = AVIF_QUALITY_WORST;
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
AVIF_ADD_IMAGE_FLAG_NONE),
AVIF_RESULT_OK);
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
AVIF_ADD_IMAGE_FLAG_NONE),
AVIF_RESULT_OK);
image->yuvRange = AVIF_RANGE_LIMITED;
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
AVIF_ADD_IMAGE_FLAG_NONE),
AVIF_RESULT_INCOMPATIBLE_IMAGE);
}
TEST(ChangeSettingTest, UnchangeableImageChromaSamplePosition) {
if (avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE) ==
nullptr) {
GTEST_SKIP() << "Codec unavailable, skip test.";
}
constexpr uint32_t kImageSize = 512;
ImagePtr image = testutil::CreateImage(kImageSize, kImageSize, /*depth=*/8,
AVIF_PIXEL_FORMAT_YUV420,
AVIF_PLANES_YUV, AVIF_RANGE_FULL);
ASSERT_NE(image, nullptr);
const uint32_t yuva[] = {128, 128, 128, 255};
testutil::FillImagePlain(image.get(), yuva);
// Encode
EncoderPtr encoder(avifEncoderCreate());
ASSERT_NE(encoder, nullptr);
encoder->codecChoice = AVIF_CODEC_CHOICE_AOM;
encoder->speed = AVIF_SPEED_FASTEST;
encoder->timescale = 1;
ASSERT_EQ(encoder->repetitionCount, AVIF_REPETITION_COUNT_INFINITE);
encoder->quality = AVIF_QUALITY_WORST;
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
AVIF_ADD_IMAGE_FLAG_NONE),
AVIF_RESULT_OK);
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
AVIF_ADD_IMAGE_FLAG_NONE),
AVIF_RESULT_OK);
ASSERT_EQ(image->yuvChromaSamplePosition,
AVIF_CHROMA_SAMPLE_POSITION_UNKNOWN);
image->yuvChromaSamplePosition = AVIF_CHROMA_SAMPLE_POSITION_VERTICAL;
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
AVIF_ADD_IMAGE_FLAG_NONE),
AVIF_RESULT_INCOMPATIBLE_IMAGE);
}
void EncodeAnimation(const char* key, const char* value_before_first_frame,
const char* value_after_first_frame,
const char* value_before_second_frame,
std::vector<uint8_t>& encoded_bitstream) {
// Generate an animation with two different frames.
const ImagePtr first_frame =
testutil::ReadImage(data_path, "paris_exif_xmp_icc.jpg");
ASSERT_NE(first_frame, nullptr);
// Speed up the test.
first_frame->width = 64;
first_frame->height = 64;
const ImagePtr second_frame(avifImageCreateEmpty());
ASSERT_NE(second_frame, nullptr);
ASSERT_EQ(
avifImageCopy(second_frame.get(), first_frame.get(), AVIF_PLANES_ALL),
AVIF_RESULT_OK);
testutil::FillImageGradient(first_frame.get());
EncoderPtr encoder(avifEncoderCreate());
ASSERT_NE(encoder, nullptr);
encoder->codecChoice = AVIF_CODEC_CHOICE_AOM;
encoder->creationTime = encoder->modificationTime = 1; // Deterministic.
const avifAddImageFlag flag = AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME;
// First frame.
constexpr uint64_t kDuration = 1;
ASSERT_EQ(avifEncoderSetCodecSpecificOption(encoder.get(), key,
value_before_first_frame),
AVIF_RESULT_OK);
ASSERT_EQ(
avifEncoderAddImage(encoder.get(), first_frame.get(), kDuration, flag),
AVIF_RESULT_OK);
ASSERT_EQ(avifEncoderSetCodecSpecificOption(encoder.get(), key,
value_after_first_frame),
AVIF_RESULT_OK);
// Second frame.
ASSERT_EQ(avifEncoderSetCodecSpecificOption(encoder.get(), key,
value_before_second_frame),
AVIF_RESULT_OK);
ASSERT_EQ(
avifEncoderAddImage(encoder.get(), second_frame.get(), kDuration, flag),
AVIF_RESULT_OK);
testutil::AvifRwData encoded;
ASSERT_EQ(avifEncoderFinish(encoder.get(), &encoded), AVIF_RESULT_OK);
// Make sure it decodes fine, even if unrelated to the current test.
DecoderPtr decoder(avifDecoderCreate());
ASSERT_NE(decoder, nullptr);
ASSERT_EQ(avifDecoderSetIOMemory(decoder.get(), encoded.data, encoded.size),
AVIF_RESULT_OK);
ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK);
ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK);
ASSERT_GT(testutil::GetPsnr(*first_frame, *decoder->image), 32.0);
ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK);
ASSERT_GT(testutil::GetPsnr(*second_frame, *decoder->image), 32.0);
encoded_bitstream = std::vector(encoded.data, encoded.data + encoded.size);
}
TEST(ChangeSettingTest, SetCodecSpecificOptionWithNull) {
if (avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE) ==
nullptr) {
GTEST_SKIP() << "Codec unavailable, skip test.";
}
std::vector<uint8_t> a, b;
EncodeAnimation("sharpness", nullptr, nullptr, nullptr, a);
EncodeAnimation("sharpness", nullptr, nullptr, nullptr, b);
// Make sure the comparison works as intended for identical input.
EXPECT_EQ(a, b);
EncodeAnimation("sharpness", "7", nullptr, nullptr, a);
EncodeAnimation("sharpness", nullptr, nullptr, nullptr, b);
// 7 is not the default.
EXPECT_NE(a, b);
EncodeAnimation("sharpness", nullptr, nullptr, nullptr, a);
EncodeAnimation("sharpness", nullptr, nullptr, "7", b);
// The second frame differs.
EXPECT_NE(a, b);
EncodeAnimation("sharpness", "7", nullptr, "0", a);
EncodeAnimation("sharpness", "7", nullptr, "5", b);
// The second frame differs.
EXPECT_NE(a, b);
EncodeAnimation("sharpness", nullptr, "6", "7", a);
EncodeAnimation("sharpness", nullptr, nullptr, "7", b);
// The option is overwritten successfully.
EXPECT_EQ(a, b);
EncodeAnimation("sharpness", nullptr, nullptr, nullptr, a);
EncodeAnimation("sharpness", nullptr, "7", nullptr, b);
// The pending key is successfully deleted.
EXPECT_EQ(a, b);
EncodeAnimation("sharpness", "7", "7", "7", a);
EncodeAnimation("sharpness", "7", nullptr, nullptr, b);
// avifEncoderSetCodecSpecificOption(NULL) only deletes the *pending* key.
EXPECT_EQ(a, b);
}
//------------------------------------------------------------------------------
} // namespace
} // namespace avif
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
if (argc != 2) {
std::cerr << "There must be exactly one argument containing the path to "
"the test data folder"
<< std::endl;
return 1;
}
avif::data_path = argv[1];
return RUN_ALL_TESTS();
}