diff options
Diffstat (limited to 'src/dsp/inverse_transform_test.cc')
-rw-r--r-- | src/dsp/inverse_transform_test.cc | 536 |
1 files changed, 536 insertions, 0 deletions
diff --git a/src/dsp/inverse_transform_test.cc b/src/dsp/inverse_transform_test.cc new file mode 100644 index 0000000..623e203 --- /dev/null +++ b/src/dsp/inverse_transform_test.cc @@ -0,0 +1,536 @@ +// Copyright 2021 The libgav1 Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/dsp/inverse_transform.h" + +#include <algorithm> +#include <cstdint> +#include <cstdio> +#include <cstring> +#include <ostream> + +#include "absl/strings/match.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "gtest/gtest.h" +#include "src/dsp/constants.h" +#include "src/dsp/dsp.h" +#include "src/utils/array_2d.h" +#include "src/utils/bit_mask_set.h" +#include "src/utils/common.h" +#include "src/utils/constants.h" +#include "src/utils/cpu.h" +#include "src/utils/memory.h" +#include "tests/block_utils.h" +#include "tests/third_party/libvpx/acm_random.h" +#include "tests/utils.h" + +namespace libgav1 { +namespace dsp { +namespace { + +constexpr int kMaxBlockSize = 64; +constexpr int kTotalPixels = kMaxBlockSize * kMaxBlockSize; + +const char* const kTransformSize1DNames[kNum1DTransformSizes] = { + "k1DTransformSize4", "k1DTransformSize8", "k1DTransformSize16", + "k1DTransformSize32", "k1DTransformSize64"}; + +constexpr TransformSize1D kRow1DTransformSizes[] = { + k1DTransformSize4, k1DTransformSize4, k1DTransformSize4, + k1DTransformSize8, k1DTransformSize8, k1DTransformSize8, + k1DTransformSize8, k1DTransformSize16, k1DTransformSize16, + k1DTransformSize16, k1DTransformSize16, k1DTransformSize16, + k1DTransformSize32, k1DTransformSize32, k1DTransformSize32, + k1DTransformSize32, k1DTransformSize64, k1DTransformSize64, + k1DTransformSize64}; + +constexpr TransformSize1D kCol1DTransformSizes[] = { + k1DTransformSize4, k1DTransformSize8, k1DTransformSize16, + k1DTransformSize4, k1DTransformSize8, k1DTransformSize16, + k1DTransformSize32, k1DTransformSize4, k1DTransformSize8, + k1DTransformSize16, k1DTransformSize32, k1DTransformSize64, + k1DTransformSize8, k1DTransformSize16, k1DTransformSize32, + k1DTransformSize64, k1DTransformSize16, k1DTransformSize32, + k1DTransformSize64}; + +template <int bitdepth, typename SrcPixel, typename DstPixel> +class InverseTransformTestBase : public testing::TestWithParam<TransformSize>, + public test_utils::MaxAlignedAllocable { + public: + InverseTransformTestBase() { + switch (tx_size_) { + case kNumTransformSizes: + EXPECT_NE(tx_size_, kNumTransformSizes); + break; + default: + block_width_ = kTransformWidth[tx_size_]; + block_height_ = kTransformHeight[tx_size_]; + break; + } + } + + InverseTransformTestBase(const InverseTransformTestBase&) = delete; + InverseTransformTestBase& operator=(const InverseTransformTestBase&) = delete; + ~InverseTransformTestBase() override = default; + + protected: + struct InverseTransformMem { + void Reset(libvpx_test::ACMRandom* rnd, int width, int height) { + ASSERT_NE(rnd, nullptr); + // Limit the size of the residual values to bitdepth + sign in order + // to prevent outranging in the transforms. + const int num_bits = bitdepth + 1; + const int sign_shift = (bitdepth == 8 ? 16 : 32) - num_bits; + const int mask = (1 << num_bits) - 1; + // Fill residual with random data. For widths == 64, only fill the upper + // left 32 x min(block_height_, 32). + memset(ref_src, 0, sizeof(ref_src)); + SrcPixel* r = ref_src; + const int stride = width; + for (int y = 0; y < std::min(height, 32); ++y) { + for (int x = 0; x < std::min(width, 32); ++x) { + r[x] = rnd->Rand16() & mask; + // The msb of num_bits is the sign bit, so force each 16 bit value to + // the correct sign. + r[x] = (r[x] << sign_shift) >> sign_shift; + } + r += stride; + } + + // Set frame data to random values. + for (int y = 0; y < kMaxBlockSize; ++y) { + for (int x = 0; x < kMaxBlockSize; ++x) { + const int mask = (1 << bitdepth) - 1; + cur_frame[y * kMaxBlockSize + x] = base_frame[y * kMaxBlockSize + x] = + rnd->Rand16() & mask; + } + } + } + + // Set ref_src to |pixel|. + void Set(const SrcPixel pixel) { + for (auto& r : ref_src) r = pixel; + } + + alignas(kMaxAlignment) DstPixel base_frame[kTotalPixels]; + alignas(kMaxAlignment) DstPixel cur_frame[kTotalPixels]; + + alignas(kMaxAlignment) SrcPixel base_residual[kTotalPixels]; + alignas(kMaxAlignment) SrcPixel cur_residual[kTotalPixels]; + + alignas(kMaxAlignment) SrcPixel ref_src[kTotalPixels]; + }; + + void SetUp() override { test_utils::ResetDspTable(bitdepth); } + + const TransformSize tx_size_ = GetParam(); + int block_width_; + int block_height_; + InverseTransformMem inverse_transform_mem_; +}; + +//------------------------------------------------------------------------------ +// InverseTransformTest + +template <int bitdepth, typename Pixel, typename DstPixel> +class InverseTransformTest + : public InverseTransformTestBase<bitdepth, Pixel, DstPixel> { + public: + InverseTransformTest() = default; + InverseTransformTest(const InverseTransformTest&) = delete; + InverseTransformTest& operator=(const InverseTransformTest&) = delete; + ~InverseTransformTest() override = default; + + protected: + using InverseTransformTestBase<bitdepth, Pixel, DstPixel>::tx_size_; + using InverseTransformTestBase<bitdepth, Pixel, DstPixel>::block_width_; + using InverseTransformTestBase<bitdepth, Pixel, DstPixel>::block_height_; + using InverseTransformTestBase<bitdepth, Pixel, + DstPixel>::inverse_transform_mem_; + + void SetUp() override { + InverseTransformTestBase<bitdepth, Pixel, DstPixel>::SetUp(); + InverseTransformInit_C(); + + const Dsp* const dsp = GetDspTable(bitdepth); + ASSERT_NE(dsp, nullptr); + + tx_size_1d_row_ = kRow1DTransformSizes[tx_size_]; + tx_size_1d_column_ = kCol1DTransformSizes[tx_size_]; + + memcpy(base_inverse_transforms_, dsp->inverse_transforms, + sizeof(base_inverse_transforms_)); + + const testing::TestInfo* const test_info = + testing::UnitTest::GetInstance()->current_test_info(); + const char* const test_case = test_info->test_suite_name(); + if (absl::StartsWith(test_case, "C/")) { + memset(base_inverse_transforms_, 0, sizeof(base_inverse_transforms_)); + } else if (absl::StartsWith(test_case, "SSE41/")) { + if ((GetCpuInfo() & kSSE4_1) != 0) { + InverseTransformInit_SSE4_1(); + } + } else if (absl::StartsWith(test_case, "NEON/")) { + InverseTransformInit_NEON(); + InverseTransformInit10bpp_NEON(); + } else { + FAIL() << "Unrecognized architecture prefix in test case name: " + << test_case; + } + + memcpy(cur_inverse_transforms_, dsp->inverse_transforms, + sizeof(cur_inverse_transforms_)); + + for (int i = 0; i < kNum1DTransforms; ++i) { + // skip functions that haven't been specialized for this particular + // architecture. + if (cur_inverse_transforms_[i][tx_size_1d_row_][kRow] == + base_inverse_transforms_[i][tx_size_1d_row_][kRow]) { + cur_inverse_transforms_[i][tx_size_1d_row_][kRow] = nullptr; + } + if (cur_inverse_transforms_[i][tx_size_1d_column_][kColumn] == + base_inverse_transforms_[i][tx_size_1d_column_][kColumn]) { + cur_inverse_transforms_[i][tx_size_1d_column_][kColumn] = nullptr; + } + } + + base_frame_buffer_.Reset(kMaxBlockSize, kMaxBlockSize, + inverse_transform_mem_.base_frame); + + cur_frame_buffer_.Reset(kMaxBlockSize, kMaxBlockSize, + inverse_transform_mem_.cur_frame); + } + + // These tests modify inverse_transform_mem_. + void TestRandomValues(int num_tests); + void TestDcOnlyRandomValue(int num_tests); + + Array2DView<DstPixel> base_frame_buffer_; + Array2DView<DstPixel> cur_frame_buffer_; + + TransformSize1D tx_size_1d_row_ = k1DTransformSize4; + TransformSize1D tx_size_1d_column_ = k1DTransformSize4; + + InverseTransformAddFuncs base_inverse_transforms_; + InverseTransformAddFuncs cur_inverse_transforms_; +}; + +constexpr TransformType kLibgav1TxType[kNumTransformTypes] = { + kTransformTypeDctDct, kTransformTypeAdstDct, + kTransformTypeDctAdst, kTransformTypeAdstAdst, + kTransformTypeFlipadstDct, kTransformTypeDctFlipadst, + kTransformTypeFlipadstFlipadst, kTransformTypeAdstFlipadst, + kTransformTypeFlipadstAdst, kTransformTypeIdentityIdentity, + kTransformTypeIdentityDct, kTransformTypeDctIdentity, + kTransformTypeIdentityAdst, kTransformTypeAdstIdentity, + kTransformTypeIdentityFlipadst, kTransformTypeFlipadstIdentity}; + +// Maps TransformType to dsp::Transform1D for the row transforms. +constexpr Transform1D kRowTransform[kNumTransformTypes] = { + k1DTransformDct, k1DTransformAdst, k1DTransformDct, + k1DTransformAdst, k1DTransformAdst, k1DTransformDct, + k1DTransformAdst, k1DTransformAdst, k1DTransformAdst, + k1DTransformIdentity, k1DTransformIdentity, k1DTransformDct, + k1DTransformIdentity, k1DTransformAdst, k1DTransformIdentity, + k1DTransformAdst}; + +// Maps TransformType to dsp::Transform1D for the column transforms. +constexpr Transform1D kColumnTransform[kNumTransformTypes] = { + k1DTransformDct, k1DTransformDct, k1DTransformAdst, + k1DTransformAdst, k1DTransformDct, k1DTransformAdst, + k1DTransformAdst, k1DTransformAdst, k1DTransformAdst, + k1DTransformIdentity, k1DTransformDct, k1DTransformIdentity, + k1DTransformAdst, k1DTransformIdentity, k1DTransformAdst, + k1DTransformIdentity}; + +// Mask indicating whether the transform sets contain a particular transform +// type. If |tx_type| is present in |tx_set|, then the |tx_type|th LSB is set. +constexpr BitMaskSet kTransformTypeInSetMask[kNumTransformSets] = { + BitMaskSet(0x1), BitMaskSet(0xE0F), BitMaskSet(0x20F), + BitMaskSet(0xFFFF), BitMaskSet(0xFFF), BitMaskSet(0x201)}; + +bool IsTxSizeTypeValid(TransformSize tx_size, TransformType tx_type) { + const TransformSize tx_size_square_max = kTransformSizeSquareMax[tx_size]; + TransformSet tx_set; + if (tx_size_square_max > kTransformSize32x32) { + tx_set = kTransformSetDctOnly; + } else if (tx_size_square_max == kTransformSize32x32) { + tx_set = kTransformSetInter3; + } else if (tx_size_square_max == kTransformSize16x16) { + tx_set = kTransformSetInter2; + } else { + tx_set = kTransformSetInter1; + } + return kTransformTypeInSetMask[tx_set].Contains(tx_type); +} + +template <int bitdepth, typename Pixel, typename DstPixel> +void InverseTransformTest<bitdepth, Pixel, DstPixel>::TestRandomValues( + int num_tests) { + libvpx_test::ACMRandom rnd(libvpx_test::ACMRandom::DeterministicSeed()); + + for (int tx_type_idx = 0; tx_type_idx < kNumTransformTypes; ++tx_type_idx) { + const TransformType tx_type = kLibgav1TxType[tx_type_idx]; + const Transform1D row_transform = kRowTransform[tx_type]; + const Transform1D column_transform = kColumnTransform[tx_type]; + + // Skip the 'C' test case as this is used as the reference. + if (base_inverse_transforms_[row_transform][tx_size_1d_row_][kRow] == + nullptr || + cur_inverse_transforms_[row_transform][tx_size_1d_row_][kRow] == + nullptr || + base_inverse_transforms_[column_transform][tx_size_1d_column_] + [kColumn] == nullptr || + cur_inverse_transforms_[column_transform][tx_size_1d_column_] + [kColumn] == nullptr) { + continue; + } + + // Only test valid tx_size for given tx_type. See 5.11.40. + if (!IsTxSizeTypeValid(tx_size_, tx_type)) continue; + + absl::Duration base_elapsed_time[2]; + absl::Duration cur_elapsed_time[2]; + + for (int n = 0; n < num_tests; ++n) { + const int tx_height = std::min(block_height_, 32); + const int start_x = 0; + const int start_y = 0; + + inverse_transform_mem_.Reset(&rnd, block_width_, block_height_); + memcpy(inverse_transform_mem_.base_residual, + inverse_transform_mem_.ref_src, + sizeof(inverse_transform_mem_.ref_src)); + memcpy(inverse_transform_mem_.cur_residual, + inverse_transform_mem_.ref_src, + sizeof(inverse_transform_mem_.ref_src)); + + const absl::Time base_row_start = absl::Now(); + base_inverse_transforms_[row_transform][tx_size_1d_row_][kRow]( + tx_type, tx_size_, tx_height, inverse_transform_mem_.base_residual, + start_x, start_y, &base_frame_buffer_); + base_elapsed_time[kRow] += absl::Now() - base_row_start; + + const absl::Time cur_row_start = absl::Now(); + cur_inverse_transforms_[row_transform][tx_size_1d_row_][kRow]( + tx_type, tx_size_, tx_height, inverse_transform_mem_.cur_residual, + start_x, start_y, &cur_frame_buffer_); + cur_elapsed_time[kRow] += absl::Now() - cur_row_start; + + const absl::Time base_column_start = absl::Now(); + base_inverse_transforms_[column_transform][tx_size_1d_column_][kColumn]( + tx_type, tx_size_, tx_height, inverse_transform_mem_.base_residual, + start_x, start_y, &base_frame_buffer_); + base_elapsed_time[kColumn] += absl::Now() - base_column_start; + + const absl::Time cur_column_start = absl::Now(); + cur_inverse_transforms_[column_transform][tx_size_1d_column_][kColumn]( + tx_type, tx_size_, tx_height, inverse_transform_mem_.cur_residual, + start_x, start_y, &cur_frame_buffer_); + cur_elapsed_time[kColumn] += absl::Now() - cur_column_start; + + if (!test_utils::CompareBlocks(inverse_transform_mem_.base_frame, + inverse_transform_mem_.cur_frame, + block_width_, block_height_, kMaxBlockSize, + kMaxBlockSize, false)) { + ADD_FAILURE() << "Result from optimized version of " + << ToString( + static_cast<TransformSize1D>(tx_size_1d_column_)) + << " differs from reference in iteration #" << n + << "tx_type_idx:" << tx_type_idx; + break; + } + } + + if (num_tests > 1) { + const auto base_row_elapsed_time_us = + static_cast<int>(absl::ToInt64Microseconds(base_elapsed_time[kRow])); + const auto cur_row_elapsed_time_us = + static_cast<int>(absl::ToInt64Microseconds(cur_elapsed_time[kRow])); + printf("TxType %30s[%19s]:: base_row: %5d us cur_row: %5d us %2.2fx \n", + ToString(tx_type), kTransformSize1DNames[tx_size_1d_row_], + base_row_elapsed_time_us, cur_row_elapsed_time_us, + static_cast<float>(base_row_elapsed_time_us) / + static_cast<float>(cur_row_elapsed_time_us)); + const auto base_column_elapsed_time_us = static_cast<int>( + absl::ToInt64Microseconds(base_elapsed_time[kColumn])); + const auto cur_column_elapsed_time_us = static_cast<int>( + absl::ToInt64Microseconds(cur_elapsed_time[kColumn])); + printf("TxType %30s[%19s]:: base_col: %5d us cur_col: %5d us %2.2fx \n", + ToString(tx_type), kTransformSize1DNames[tx_size_1d_column_], + base_column_elapsed_time_us, cur_column_elapsed_time_us, + static_cast<float>(base_column_elapsed_time_us) / + static_cast<float>(cur_column_elapsed_time_us)); + } + } +} + +template <int bitdepth, typename Pixel, typename DstPixel> +void InverseTransformTest<bitdepth, Pixel, DstPixel>::TestDcOnlyRandomValue( + int num_tests) { + libvpx_test::ACMRandom rnd(libvpx_test::ACMRandom::DeterministicSeed()); + + for (int tx_type_idx = 0; tx_type_idx < kNumTransformTypes; ++tx_type_idx) { + const TransformType tx_type = kLibgav1TxType[tx_type_idx]; + const Transform1D row_transform = kRowTransform[tx_type]; + const Transform1D column_transform = kColumnTransform[tx_type]; + + if (cur_inverse_transforms_[row_transform][tx_size_1d_row_][kRow] == + nullptr || + cur_inverse_transforms_[column_transform][tx_size_1d_column_] + [kColumn] == nullptr) { + continue; + } + + // Only test valid tx_size for given tx_type. See 5.11.40. + if (IsTxSizeTypeValid(tx_size_, tx_type) == 0) continue; + + absl::Duration base_elapsed_time[2]; + absl::Duration cur_elapsed_time[2]; + + for (int n = 0; n < num_tests; ++n) { + const int tx_height = std::min(block_height_, 32); + const int start_x = 0; + const int start_y = 0; + + // Using width == 1 and height == 1 will reset only the dc value. + inverse_transform_mem_.Reset(&rnd, 1, 1); + memcpy(inverse_transform_mem_.base_residual, + inverse_transform_mem_.ref_src, + sizeof(inverse_transform_mem_.ref_src)); + memcpy(inverse_transform_mem_.cur_residual, + inverse_transform_mem_.ref_src, + sizeof(inverse_transform_mem_.ref_src)); + + // For this test, the "base" contains the output when the + // tx_height is set to the max for the given block size. The + // "cur" contains the output when the passed in tx_height is 1. + // Compare the outputs for match. + const absl::Time base_row_start = absl::Now(); + cur_inverse_transforms_[row_transform][tx_size_1d_row_][kRow]( + tx_type, tx_size_, tx_height, inverse_transform_mem_.base_residual, + start_x, start_y, &base_frame_buffer_); + base_elapsed_time[kRow] += absl::Now() - base_row_start; + + const absl::Time cur_row_start = absl::Now(); + cur_inverse_transforms_[row_transform][tx_size_1d_row_][kRow]( + tx_type, tx_size_, /*adjusted_tx_height=*/1, + inverse_transform_mem_.cur_residual, start_x, start_y, + &cur_frame_buffer_); + cur_elapsed_time[kRow] += absl::Now() - cur_row_start; + + const absl::Time base_column_start = absl::Now(); + cur_inverse_transforms_[column_transform][tx_size_1d_column_][kColumn]( + tx_type, tx_size_, tx_height, inverse_transform_mem_.base_residual, + start_x, start_y, &base_frame_buffer_); + base_elapsed_time[kColumn] += absl::Now() - base_column_start; + + const absl::Time cur_column_start = absl::Now(); + cur_inverse_transforms_[column_transform][tx_size_1d_column_][kColumn]( + tx_type, tx_size_, /*adjusted_tx_height=*/1, + inverse_transform_mem_.cur_residual, start_x, start_y, + &cur_frame_buffer_); + cur_elapsed_time[kColumn] += absl::Now() - cur_column_start; + + if (!test_utils::CompareBlocks(inverse_transform_mem_.base_frame, + inverse_transform_mem_.cur_frame, + block_width_, block_height_, kMaxBlockSize, + kMaxBlockSize, false)) { + ADD_FAILURE() << "Result from dc only version of " + << ToString( + static_cast<TransformSize1D>(tx_size_1d_column_)) + << " differs from reference in iteration #" << n + << "tx_type_idx:" << tx_type_idx; + break; + } + } + + if (num_tests > 1) { + const auto base_row_elapsed_time_us = + static_cast<int>(absl::ToInt64Microseconds(base_elapsed_time[kRow])); + const auto cur_row_elapsed_time_us = + static_cast<int>(absl::ToInt64Microseconds(cur_elapsed_time[kRow])); + printf("TxType %30s[%19s]:: base_row: %5d us cur_row: %5d us %2.2fx \n", + ToString(tx_type), kTransformSize1DNames[tx_size_1d_row_], + base_row_elapsed_time_us, cur_row_elapsed_time_us, + static_cast<float>(base_row_elapsed_time_us) / + static_cast<float>(cur_row_elapsed_time_us)); + const auto base_column_elapsed_time_us = static_cast<int>( + absl::ToInt64Microseconds(base_elapsed_time[kColumn])); + const auto cur_column_elapsed_time_us = static_cast<int>( + absl::ToInt64Microseconds(cur_elapsed_time[kColumn])); + printf("TxType %30s[%19s]:: base_col: %5d us cur_col: %5d us %2.2fx \n", + ToString(tx_type), kTransformSize1DNames[tx_size_1d_column_], + base_column_elapsed_time_us, cur_column_elapsed_time_us, + static_cast<float>(base_column_elapsed_time_us) / + static_cast<float>(cur_column_elapsed_time_us)); + } + } +} + +using InverseTransformTest8bpp = InverseTransformTest<8, int16_t, uint8_t>; + +TEST_P(InverseTransformTest8bpp, Random) { TestRandomValues(1); } + +TEST_P(InverseTransformTest8bpp, DISABLED_Speed) { TestRandomValues(10000); } + +TEST_P(InverseTransformTest8bpp, DcRandom) { TestDcOnlyRandomValue(1); } + +constexpr TransformSize kTransformSizesAll[] = { + kTransformSize4x4, kTransformSize4x8, kTransformSize4x16, + kTransformSize8x4, kTransformSize8x8, kTransformSize8x16, + kTransformSize8x32, kTransformSize16x4, kTransformSize16x8, + kTransformSize16x16, kTransformSize16x32, kTransformSize16x64, + kTransformSize32x8, kTransformSize32x16, kTransformSize32x32, + kTransformSize32x64, kTransformSize64x16, kTransformSize64x32, + kTransformSize64x64}; + +INSTANTIATE_TEST_SUITE_P(C, InverseTransformTest8bpp, + testing::ValuesIn(kTransformSizesAll)); +#if LIBGAV1_ENABLE_NEON +INSTANTIATE_TEST_SUITE_P(NEON, InverseTransformTest8bpp, + testing::ValuesIn(kTransformSizesAll)); +#endif +#if LIBGAV1_ENABLE_SSE4_1 +INSTANTIATE_TEST_SUITE_P(SSE41, InverseTransformTest8bpp, + testing::ValuesIn(kTransformSizesAll)); +#endif + +#if LIBGAV1_MAX_BITDEPTH >= 10 +using InverseTransformTest10bpp = InverseTransformTest<10, int32_t, uint16_t>; + +TEST_P(InverseTransformTest10bpp, Random) { TestRandomValues(1); } + +TEST_P(InverseTransformTest10bpp, DISABLED_Speed) { TestRandomValues(10000); } + +TEST_P(InverseTransformTest10bpp, DcRandom) { TestDcOnlyRandomValue(1); } + +INSTANTIATE_TEST_SUITE_P(C, InverseTransformTest10bpp, + testing::ValuesIn(kTransformSizesAll)); + +#if LIBGAV1_ENABLE_NEON +INSTANTIATE_TEST_SUITE_P(NEON, InverseTransformTest10bpp, + testing::ValuesIn(kTransformSizesAll)); +#endif +#endif // LIBGAV1_MAX_BITDEPTH >= 10 + +} // namespace +} // namespace dsp + +static std::ostream& operator<<(std::ostream& os, const TransformSize param) { + return os << ToString(param); +} + +} // namespace libgav1 |