blob: 23ffde6db0ccc45311449802a9b17787e0885497 [file] [log] [blame]
// Copyright 2025 Google LLC
// SPDX-License-Identifier: BSD-2-Clause
#include <cstdlib>
#include <tuple>
#include <vector>
#include "avif/avif.h"
#include "avif/internal.h"
#include "aviftest_helpers.h"
#include "gtest/gtest.h"
namespace avif {
namespace {
bool SvtAv1SupportsLossless() {
const char* version = avifCodecVersionSvt(); // "vX.Y.Z" expected.
if (version == nullptr || version[0] != 'v') return false;
const int major_version = std::atoi(version + 1);
// EbSvtAv1EncConfiguration::lossless was introduced in SVT-AV1 version 3.0.0.
return major_version >= 3;
}
class SvtAv1Test : public testing::TestWithParam<std::tuple</*quality=*/int>> {
};
TEST_P(SvtAv1Test, EncodeDecodeStillImage) {
const int quality = std::get<0>(GetParam());
ASSERT_NE(avifCodecName(AVIF_CODEC_CHOICE_SVT, AVIF_CODEC_FLAG_CAN_ENCODE),
nullptr);
if (!testutil::Av1DecoderAvailable()) {
GTEST_SKIP() << "Decoder unavailable, skip test.";
}
// AVIF_CODEC_CHOICE_SVT requires dimensions to be at least 64 pixels.
ImagePtr image =
testutil::CreateImage(/*width=*/64, /*height=*/64, /*depth=*/8,
AVIF_PIXEL_FORMAT_YUV420, AVIF_PLANES_YUV);
ASSERT_NE(image, nullptr);
testutil::FillImageGradient(image.get());
if (quality == AVIF_QUALITY_LOSSLESS) {
image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY;
}
EncoderPtr encoder(avifEncoderCreate());
ASSERT_NE(encoder, nullptr);
encoder->codecChoice = AVIF_CODEC_CHOICE_SVT;
encoder->quality = quality;
encoder->qualityAlpha = quality;
testutil::AvifRwData encoded;
ASSERT_EQ(avifEncoderWrite(encoder.get(), image.get(), &encoded),
AVIF_RESULT_OK);
DecoderPtr decoder(avifDecoderCreate());
ASSERT_NE(decoder, nullptr);
ImagePtr decoded(avifImageCreateEmpty());
ASSERT_NE(decoded, nullptr);
ASSERT_EQ(avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
encoded.size),
AVIF_RESULT_OK);
if (quality == AVIF_QUALITY_LOSSLESS && SvtAv1SupportsLossless()) {
EXPECT_TRUE(testutil::AreImagesEqual(*image, *decoded));
} else {
EXPECT_GT(testutil::GetPsnr(*image, *decoded), 20.0);
}
}
TEST_P(SvtAv1Test, EncodeDecodeSequence) {
const int quality = std::get<0>(GetParam());
ASSERT_NE(avifCodecName(AVIF_CODEC_CHOICE_SVT, AVIF_CODEC_FLAG_CAN_ENCODE),
nullptr);
if (!testutil::Av1DecoderAvailable()) {
GTEST_SKIP() << "Decoder unavailable, skip test.";
}
std::vector<ImagePtr> sequence;
sequence.reserve(3);
for (uint32_t i = 0; i < sequence.capacity(); ++i) {
// AVIF_CODEC_CHOICE_SVT requires dimensions to be at least 64 pixels.
sequence.push_back(
testutil::CreateImage(/*width=*/64, /*height=*/64, /*depth=*/8,
AVIF_PIXEL_FORMAT_YUV420, AVIF_PLANES_ALL));
ASSERT_NE(sequence.back(), nullptr);
// Generate different frames.
testutil::FillImageGradient(sequence.back().get(),
/*offset=*/static_cast<int>(i) * 100);
if (quality == AVIF_QUALITY_LOSSLESS) {
sequence.back()->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY;
}
}
EncoderPtr encoder(avifEncoderCreate());
ASSERT_NE(encoder, nullptr);
encoder->codecChoice = AVIF_CODEC_CHOICE_SVT;
encoder->quality = quality;
encoder->qualityAlpha = quality;
for (const ImagePtr& image : sequence) {
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(),
/*durationInTimescales=*/1,
AVIF_ADD_IMAGE_FLAG_NONE),
AVIF_RESULT_OK);
}
testutil::AvifRwData encoded;
ASSERT_EQ(avifEncoderFinish(encoder.get(), &encoded), AVIF_RESULT_OK);
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);
for (const ImagePtr& image : sequence) {
ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK);
if (quality == AVIF_QUALITY_LOSSLESS && SvtAv1SupportsLossless()) {
EXPECT_TRUE(testutil::AreImagesEqual(*image, *decoder->image));
} else {
EXPECT_GT(testutil::GetPsnr(*image, *decoder->image), 20.0);
}
}
}
INSTANTIATE_TEST_SUITE_P(All, SvtAv1Test,
testing::Combine(testing::Values(
AVIF_QUALITY_DEFAULT, AVIF_QUALITY_WORST,
AVIF_QUALITY_BEST - 1, AVIF_QUALITY_LOSSLESS)));
} // namespace
} // namespace avif