Skip to content

Commit 6451b9d

Browse files
authored
Use new SVT 3.0.0 EbSvtAv1EncConfig::lossless (#2630)
Add test.
1 parent c3a9234 commit 6451b9d

6 files changed

Lines changed: 176 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ The changes are relative to the previous release, unless the baseline is specifi
5454
* Update libjpeg.cmd/LocalJpeg.cmake: v3.0.4
5555
* Update libxml2.cmd/LocalLibXml2.cmake: v2.13.5
5656
* Update libyuv.cmd: ccdf87034 (1903)
57-
* Update svt.cmd/svt.sh/LocalSvt.cmake: v3.0.0
57+
* Update svt.cmd/svt.sh/LocalSvt.cmake to v3.0.0 and use
58+
EbSvtAv1EncConfiguration::lossless in libavif when available.
5859
* Remove AVIF_ENABLE_GTEST CMake option. It's now implied by
5960
AVIF_GTEST=LOCAL/SYSTEM.
6061
* Deprecate `avifEncoder`'s `minQuantizer`, `maxQuantizer`, `minQuantizerAlpha`,

apps/avifenc.c

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2009,8 +2009,22 @@ int main(int argc, char * argv[])
20092009
fprintf(stderr, "rav1e doesn't support lossless encoding yet: https://github.com/xiph/rav1e/issues/151\n");
20102010
goto cleanup;
20112011
} else if (codecName && !strcmp(codecName, "svt")) {
2012-
fprintf(stderr, "SVT-AV1 doesn't support lossless encoding yet: https://gitlab.com/AOMediaCodec/SVT-AV1/-/issues/1636\n");
2013-
goto cleanup;
2012+
char versions[256];
2013+
avifCodecVersions(versions);
2014+
const char svtVersionPrefix[] = "svt [enc]:v";
2015+
const char * svtVersionPrefixPos = strstr(versions, svtVersionPrefix);
2016+
if (svtVersionPrefixPos == NULL) {
2017+
// Make sure the syntax returned by avifCodecVersions() did not change.
2018+
assert(strstr(versions, "svt") == NULL && strstr(versions, "SVT") == NULL);
2019+
// Let the encode fail later because SVT was not included in the build.
2020+
} else {
2021+
const char * svtVersion = svtVersionPrefixPos + strlen(svtVersionPrefix);
2022+
const int svtMajorVersion = atoi(svtVersion);
2023+
if (svtMajorVersion < 3) {
2024+
fprintf(stderr, "SVT-AV1 lossless support was added in version 3.0.0, current major version is only %d\n", svtMajorVersion);
2025+
goto cleanup;
2026+
}
2027+
}
20142028
}
20152029
// Range.
20162030
if (requestedRange != AVIF_RANGE_FULL) {

src/codec_svt.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,12 @@ static avifResult svtCodecEncodeImage(avifCodec * codec,
205205
}
206206
#endif
207207

208+
#if SVT_AV1_CHECK_VERSION(3, 0, 0)
209+
svt_config->lossless = quantizer == AVIF_QUANTIZER_LOSSLESS;
210+
// TODO: https://gitlab.com/AOMediaCodec/SVT-AV1/-/issues/2245 - Enable when resolved.
211+
// svt_config->avif = (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) != 0;
212+
#endif
213+
208214
res = svt_av1_enc_set_parameter(codec->internal->svt_encoder, svt_config);
209215
if (res == EB_ErrorBadParameter) {
210216
goto cleanup;

tests/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,11 @@ if(AVIF_GTEST)
151151
add_avif_gtest_with_data(avifscaletest)
152152
add_avif_gtest_with_data(avifsize0test)
153153
add_avif_internal_gtest(avifstreamtest)
154+
155+
if(AVIF_CODEC_SVT_ENABLED)
156+
add_avif_internal_gtest(avifsvttest)
157+
endif()
158+
154159
add_avif_internal_gtest(aviftilingtest)
155160
add_avif_gtest_with_data(aviftransformtest)
156161
add_avif_internal_gtest(avifutilstest)

tests/gtest/avifsvttest.cc

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright 2025 Google LLC
2+
// SPDX-License-Identifier: BSD-2-Clause
3+
4+
#include <cstdlib>
5+
#include <tuple>
6+
#include <vector>
7+
8+
#include "avif/avif.h"
9+
#include "avif/internal.h"
10+
#include "aviftest_helpers.h"
11+
#include "gtest/gtest.h"
12+
13+
namespace avif {
14+
namespace {
15+
16+
bool SvtAv1SupportsLossless() {
17+
const char* version = avifCodecVersionSvt(); // "vX.Y.Z" expected.
18+
if (version == nullptr || version[0] != 'v') return false;
19+
const int major_version = std::atoi(version + 1);
20+
// EbSvtAv1EncConfiguration::lossless was introduced in SVT-AV1 version 3.0.0.
21+
return major_version >= 3;
22+
}
23+
24+
class SvtAv1Test : public testing::TestWithParam<std::tuple</*quality=*/int>> {
25+
};
26+
27+
TEST_P(SvtAv1Test, EncodeDecodeStillImage) {
28+
const int quality = std::get<0>(GetParam());
29+
30+
ASSERT_NE(avifCodecName(AVIF_CODEC_CHOICE_SVT, AVIF_CODEC_FLAG_CAN_ENCODE),
31+
nullptr);
32+
if (!testutil::Av1DecoderAvailable()) {
33+
GTEST_SKIP() << "Decoder unavailable, skip test.";
34+
}
35+
36+
// AVIF_CODEC_CHOICE_SVT requires dimensions to be at least 64 pixels.
37+
ImagePtr image =
38+
testutil::CreateImage(/*width=*/64, /*height=*/64, /*depth=*/8,
39+
AVIF_PIXEL_FORMAT_YUV420, AVIF_PLANES_YUV);
40+
ASSERT_NE(image, nullptr);
41+
testutil::FillImageGradient(image.get());
42+
if (quality == AVIF_QUALITY_LOSSLESS) {
43+
image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY;
44+
}
45+
46+
EncoderPtr encoder(avifEncoderCreate());
47+
ASSERT_NE(encoder, nullptr);
48+
encoder->codecChoice = AVIF_CODEC_CHOICE_SVT;
49+
encoder->quality = quality;
50+
encoder->qualityAlpha = quality;
51+
testutil::AvifRwData encoded;
52+
ASSERT_EQ(avifEncoderWrite(encoder.get(), image.get(), &encoded),
53+
AVIF_RESULT_OK);
54+
55+
DecoderPtr decoder(avifDecoderCreate());
56+
ASSERT_NE(decoder, nullptr);
57+
ImagePtr decoded(avifImageCreateEmpty());
58+
ASSERT_NE(decoded, nullptr);
59+
ASSERT_EQ(avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
60+
encoded.size),
61+
AVIF_RESULT_OK);
62+
63+
if (quality == AVIF_QUALITY_LOSSLESS && SvtAv1SupportsLossless()) {
64+
EXPECT_TRUE(testutil::AreImagesEqual(*image, *decoded));
65+
} else {
66+
EXPECT_GT(testutil::GetPsnr(*image, *decoded), 20.0);
67+
}
68+
}
69+
70+
TEST_P(SvtAv1Test, EncodeDecodeSequence) {
71+
const int quality = std::get<0>(GetParam());
72+
73+
ASSERT_NE(avifCodecName(AVIF_CODEC_CHOICE_SVT, AVIF_CODEC_FLAG_CAN_ENCODE),
74+
nullptr);
75+
if (!testutil::Av1DecoderAvailable()) {
76+
GTEST_SKIP() << "Decoder unavailable, skip test.";
77+
}
78+
79+
std::vector<ImagePtr> sequence;
80+
sequence.reserve(3);
81+
for (uint32_t i = 0; i < sequence.capacity(); ++i) {
82+
// AVIF_CODEC_CHOICE_SVT requires dimensions to be at least 64 pixels.
83+
sequence.push_back(
84+
testutil::CreateImage(/*width=*/64, /*height=*/64, /*depth=*/8,
85+
AVIF_PIXEL_FORMAT_YUV420, AVIF_PLANES_ALL));
86+
ASSERT_NE(sequence.back(), nullptr);
87+
// Generate different frames.
88+
testutil::FillImageGradient(sequence.back().get(),
89+
/*offset=*/static_cast<int>(i) * 100);
90+
if (quality == AVIF_QUALITY_LOSSLESS) {
91+
sequence.back()->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY;
92+
}
93+
}
94+
95+
EncoderPtr encoder(avifEncoderCreate());
96+
ASSERT_NE(encoder, nullptr);
97+
encoder->codecChoice = AVIF_CODEC_CHOICE_SVT;
98+
encoder->quality = quality;
99+
encoder->qualityAlpha = quality;
100+
for (const ImagePtr& image : sequence) {
101+
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(),
102+
/*durationInTimescales=*/1,
103+
AVIF_ADD_IMAGE_FLAG_NONE),
104+
AVIF_RESULT_OK);
105+
}
106+
testutil::AvifRwData encoded;
107+
ASSERT_EQ(avifEncoderFinish(encoder.get(), &encoded), AVIF_RESULT_OK);
108+
109+
DecoderPtr decoder(avifDecoderCreate());
110+
ASSERT_NE(decoder, nullptr);
111+
ASSERT_EQ(avifDecoderSetIOMemory(decoder.get(), encoded.data, encoded.size),
112+
AVIF_RESULT_OK);
113+
ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK);
114+
for (const ImagePtr& image : sequence) {
115+
ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK);
116+
if (quality == AVIF_QUALITY_LOSSLESS && SvtAv1SupportsLossless()) {
117+
EXPECT_TRUE(testutil::AreImagesEqual(*image, *decoder->image));
118+
} else {
119+
EXPECT_GT(testutil::GetPsnr(*image, *decoder->image), 20.0);
120+
}
121+
}
122+
}
123+
124+
INSTANTIATE_TEST_SUITE_P(All, SvtAv1Test,
125+
testing::Combine(testing::Values(
126+
AVIF_QUALITY_DEFAULT, AVIF_QUALITY_WORST,
127+
AVIF_QUALITY_BEST - 1, AVIF_QUALITY_LOSSLESS)));
128+
129+
} // namespace
130+
} // namespace avif

tests/test_cmd_lossless.sh

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,19 @@ source $(dirname "$0")/cmd_test_common.sh
2121
# Input file paths.
2222
INPUT_PNG="${TESTDATA_DIR}/paris_icc_exif_xmp.png"
2323
INPUT_GRAY_PNG="${TESTDATA_DIR}/kodim03_grayscale_gamma1.6.png"
24+
INPUT_Y4M="${TESTDATA_DIR}/kodim03_yuv420_8bpc.y4m"
2425
# Output file names.
2526
ENCODED_FILE="avif_test_cmd_lossless_encoded.avif"
2627
DECODED_FILE="avif_test_cmd_lossless_decoded.png"
2728
DECODED_FILE_LOSSLESS="avif_test_cmd_lossless_decoded_lossless.png"
29+
DECODED_FILE_Y4M="avif_test_cmd_lossless_decoded_lossless.y4m"
30+
OUT_MSG="avif_test_cmd_lossless_out_msg.txt"
2831

2932
# Cleanup
3033
cleanup() {
3134
pushd ${TMP_DIR}
32-
rm -f -- "${ENCODED_FILE}" "${DECODED_FILE}" "${DECODED_FILE_LOSSLESS}"
35+
rm -f -- "${ENCODED_FILE}" "${DECODED_FILE}" "${DECODED_FILE_LOSSLESS}" \
36+
"${DECODED_FILE_Y4M}" "${OUT_MSG}"
3337
popd
3438
}
3539
trap cleanup EXIT
@@ -68,6 +72,18 @@ pushd ${TMP_DIR}
6872
"${AVIFENC}" -y 400 -s 10 -l "${INPUT_GRAY_PNG}" -o "${ENCODED_FILE}"
6973
"${AVIFDEC}" "${ENCODED_FILE}" "${DECODED_FILE_LOSSLESS}"
7074
"${ARE_IMAGES_EQUAL}" "${INPUT_GRAY_PNG}" "${DECODED_FILE_LOSSLESS}" 0
75+
76+
# SVT-AV1 supports 4:2:0 lossless starting with version 3.0.0.
77+
# SVT-AV1 does not support 4:4:4 so far, so use a 4:2:0 y4m as test input.
78+
if "${AVIFENC}" --version | grep 'svt \['; then
79+
# Depending on the version, either succeeds or prints an error message.
80+
if "${AVIFENC}" -c svt --lossless --cicp 2/2/16 -s 9 "${INPUT_Y4M}" "${ENCODED_FILE}" 2> "${OUT_MSG}"; then
81+
"${AVIFDEC}" "${ENCODED_FILE}" "${DECODED_FILE_Y4M}"
82+
"${ARE_IMAGES_EQUAL}" "${INPUT_Y4M}" "${DECODED_FILE_Y4M}" 0
83+
else
84+
grep "lossless support was added in version 3" "${OUT_MSG}"
85+
fi
86+
fi
7187
popd
7288

7389
exit 0

0 commit comments

Comments
 (0)