| /* |
| * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #include "modules/audio_processing/utility/pffft_wrapper.h" |
| |
| #include <algorithm> |
| #include <cstdlib> |
| #include <memory> |
| |
| #include "test/gtest.h" |
| #include "third_party/pffft/src/pffft.h" |
| |
| namespace webrtc { |
| namespace test { |
| namespace { |
| |
| constexpr size_t kMaxValidSizeCheck = 1024; |
| |
| static constexpr int kFftSizes[] = { |
| 16, 32, 64, 96, 128, 160, 192, 256, 288, 384, 5 * 96, 512, |
| 576, 5 * 128, 800, 864, 1024, 2048, 2592, 4000, 4096, 12000, 36864}; |
| |
| void CreatePffftWrapper(size_t fft_size, Pffft::FftType fft_type) { |
| Pffft pffft_wrapper(fft_size, fft_type); |
| } |
| |
| float* AllocateScratchBuffer(size_t fft_size, bool complex_fft) { |
| return static_cast<float*>( |
| pffft_aligned_malloc(fft_size * (complex_fft ? 2 : 1) * sizeof(float))); |
| } |
| |
| double frand() { |
| return std::rand() / static_cast<double>(RAND_MAX); |
| } |
| |
| void ExpectArrayViewsEquality(rtc::ArrayView<const float> a, |
| rtc::ArrayView<const float> b) { |
| ASSERT_EQ(a.size(), b.size()); |
| for (size_t i = 0; i < a.size(); ++i) { |
| SCOPED_TRACE(i); |
| EXPECT_EQ(a[i], b[i]); |
| } |
| } |
| |
| // Compares the output of the PFFFT C++ wrapper to that of the C PFFFT. |
| // Bit-exactness is expected. |
| void PffftValidateWrapper(size_t fft_size, bool complex_fft) { |
| // Always use the same seed to avoid flakiness. |
| std::srand(0); |
| |
| // Init PFFFT. |
| PFFFT_Setup* pffft_status = |
| pffft_new_setup(fft_size, complex_fft ? PFFFT_COMPLEX : PFFFT_REAL); |
| ASSERT_TRUE(pffft_status) << "FFT size (" << fft_size << ") not supported."; |
| size_t num_floats = fft_size * (complex_fft ? 2 : 1); |
| int num_bytes = static_cast<int>(num_floats) * sizeof(float); |
| float* in = static_cast<float*>(pffft_aligned_malloc(num_bytes)); |
| float* out = static_cast<float*>(pffft_aligned_malloc(num_bytes)); |
| float* scratch = AllocateScratchBuffer(fft_size, complex_fft); |
| |
| // Init PFFFT C++ wrapper. |
| Pffft::FftType fft_type = |
| complex_fft ? Pffft::FftType::kComplex : Pffft::FftType::kReal; |
| ASSERT_TRUE(Pffft::IsValidFftSize(fft_size, fft_type)); |
| Pffft pffft_wrapper(fft_size, fft_type); |
| auto in_wrapper = pffft_wrapper.CreateBuffer(); |
| auto out_wrapper = pffft_wrapper.CreateBuffer(); |
| |
| // Input and output buffers views. |
| rtc::ArrayView<float> in_view(in, num_floats); |
| rtc::ArrayView<float> out_view(out, num_floats); |
| auto in_wrapper_view = in_wrapper->GetView(); |
| EXPECT_EQ(in_wrapper_view.size(), num_floats); |
| auto out_wrapper_view = out_wrapper->GetConstView(); |
| EXPECT_EQ(out_wrapper_view.size(), num_floats); |
| |
| // Random input data. |
| for (size_t i = 0; i < num_floats; ++i) { |
| in_wrapper_view[i] = in[i] = static_cast<float>(frand() * 2.0 - 1.0); |
| } |
| |
| // Forward transform. |
| pffft_transform(pffft_status, in, out, scratch, PFFFT_FORWARD); |
| pffft_wrapper.ForwardTransform(*in_wrapper, out_wrapper.get(), |
| /*ordered=*/false); |
| ExpectArrayViewsEquality(out_view, out_wrapper_view); |
| |
| // Copy the FFT results into the input buffers to compute the backward FFT. |
| std::copy(out_view.begin(), out_view.end(), in_view.begin()); |
| std::copy(out_wrapper_view.begin(), out_wrapper_view.end(), |
| in_wrapper_view.begin()); |
| |
| // Backward transform. |
| pffft_transform(pffft_status, in, out, scratch, PFFFT_BACKWARD); |
| pffft_wrapper.BackwardTransform(*in_wrapper, out_wrapper.get(), |
| /*ordered=*/false); |
| ExpectArrayViewsEquality(out_view, out_wrapper_view); |
| |
| pffft_destroy_setup(pffft_status); |
| pffft_aligned_free(in); |
| pffft_aligned_free(out); |
| pffft_aligned_free(scratch); |
| } |
| |
| } // namespace |
| |
| TEST(PffftTest, CreateWrapperWithValidSize) { |
| for (size_t fft_size = 0; fft_size < kMaxValidSizeCheck; ++fft_size) { |
| SCOPED_TRACE(fft_size); |
| if (Pffft::IsValidFftSize(fft_size, Pffft::FftType::kReal)) { |
| CreatePffftWrapper(fft_size, Pffft::FftType::kReal); |
| } |
| if (Pffft::IsValidFftSize(fft_size, Pffft::FftType::kComplex)) { |
| CreatePffftWrapper(fft_size, Pffft::FftType::kComplex); |
| } |
| } |
| } |
| |
| #if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) |
| |
| class PffftInvalidSizeTest : public ::testing::Test, |
| public ::testing::WithParamInterface<size_t> {}; |
| |
| TEST_P(PffftInvalidSizeTest, DoNotCreateRealWrapper) { |
| size_t fft_size = GetParam(); |
| ASSERT_FALSE(Pffft::IsValidFftSize(fft_size, Pffft::FftType::kReal)); |
| EXPECT_DEATH(CreatePffftWrapper(fft_size, Pffft::FftType::kReal), ""); |
| } |
| |
| TEST_P(PffftInvalidSizeTest, DoNotCreateComplexWrapper) { |
| size_t fft_size = GetParam(); |
| ASSERT_FALSE(Pffft::IsValidFftSize(fft_size, Pffft::FftType::kComplex)); |
| EXPECT_DEATH(CreatePffftWrapper(fft_size, Pffft::FftType::kComplex), ""); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(PffftTest, |
| PffftInvalidSizeTest, |
| ::testing::Values(17, |
| 33, |
| 65, |
| 97, |
| 129, |
| 161, |
| 193, |
| 257, |
| 289, |
| 385, |
| 481, |
| 513, |
| 577, |
| 641, |
| 801, |
| 865, |
| 1025)); |
| |
| #endif |
| |
| // TODO(https://crbug.com/webrtc/9577): Enable once SIMD is always enabled. |
| TEST(PffftTest, DISABLED_CheckSimd) { |
| EXPECT_TRUE(Pffft::IsSimdEnabled()); |
| } |
| |
| TEST(PffftTest, FftBitExactness) { |
| for (int fft_size : kFftSizes) { |
| SCOPED_TRACE(fft_size); |
| if (fft_size != 16) { |
| PffftValidateWrapper(fft_size, /*complex_fft=*/false); |
| } |
| PffftValidateWrapper(fft_size, /*complex_fft=*/true); |
| } |
| } |
| |
| } // namespace test |
| } // namespace webrtc |