| // 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 |