| /* |
| * Copyright (c) 2014 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 "webrtc/common_audio/lapped_transform.h" |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <cstring> |
| |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using std::complex; |
| |
| namespace { |
| |
| class NoopCallback : public webrtc::LappedTransform::Callback { |
| public: |
| NoopCallback() : block_num_(0) {} |
| |
| virtual void ProcessAudioBlock(const complex<float>* const* in_block, |
| size_t in_channels, |
| size_t frames, |
| size_t out_channels, |
| complex<float>* const* out_block) { |
| RTC_CHECK_EQ(in_channels, out_channels); |
| for (size_t i = 0; i < out_channels; ++i) { |
| memcpy(out_block[i], in_block[i], sizeof(**in_block) * frames); |
| } |
| ++block_num_; |
| } |
| |
| size_t block_num() { |
| return block_num_; |
| } |
| |
| private: |
| size_t block_num_; |
| }; |
| |
| class FftCheckerCallback : public webrtc::LappedTransform::Callback { |
| public: |
| FftCheckerCallback() : block_num_(0) {} |
| |
| virtual void ProcessAudioBlock(const complex<float>* const* in_block, |
| size_t in_channels, |
| size_t frames, |
| size_t out_channels, |
| complex<float>* const* out_block) { |
| RTC_CHECK_EQ(in_channels, out_channels); |
| |
| size_t full_length = (frames - 1) * 2; |
| ++block_num_; |
| |
| if (block_num_ > 0) { |
| ASSERT_NEAR(in_block[0][0].real(), static_cast<float>(full_length), |
| 1e-5f); |
| ASSERT_NEAR(in_block[0][0].imag(), 0.0f, 1e-5f); |
| for (size_t i = 1; i < frames; ++i) { |
| ASSERT_NEAR(in_block[0][i].real(), 0.0f, 1e-5f); |
| ASSERT_NEAR(in_block[0][i].imag(), 0.0f, 1e-5f); |
| } |
| } |
| } |
| |
| size_t block_num() { |
| return block_num_; |
| } |
| |
| private: |
| size_t block_num_; |
| }; |
| |
| void SetFloatArray(float value, int rows, int cols, float* const* array) { |
| for (int i = 0; i < rows; ++i) { |
| for (int j = 0; j < cols; ++j) { |
| array[i][j] = value; |
| } |
| } |
| } |
| |
| } // namespace |
| |
| namespace webrtc { |
| |
| TEST(LappedTransformTest, Windowless) { |
| const size_t kChannels = 3; |
| const size_t kChunkLength = 512; |
| const size_t kBlockLength = 64; |
| const size_t kShiftAmount = 64; |
| NoopCallback noop; |
| |
| // Rectangular window. |
| float window[kBlockLength]; |
| std::fill(window, &window[kBlockLength], 1.0f); |
| |
| LappedTransform trans(kChannels, kChannels, kChunkLength, window, |
| kBlockLength, kShiftAmount, &noop); |
| float in_buffer[kChannels][kChunkLength]; |
| float* in_chunk[kChannels]; |
| float out_buffer[kChannels][kChunkLength]; |
| float* out_chunk[kChannels]; |
| |
| in_chunk[0] = in_buffer[0]; |
| in_chunk[1] = in_buffer[1]; |
| in_chunk[2] = in_buffer[2]; |
| out_chunk[0] = out_buffer[0]; |
| out_chunk[1] = out_buffer[1]; |
| out_chunk[2] = out_buffer[2]; |
| SetFloatArray(2.0f, kChannels, kChunkLength, in_chunk); |
| SetFloatArray(-1.0f, kChannels, kChunkLength, out_chunk); |
| |
| trans.ProcessChunk(in_chunk, out_chunk); |
| |
| for (size_t i = 0; i < kChannels; ++i) { |
| for (size_t j = 0; j < kChunkLength; ++j) { |
| ASSERT_NEAR(out_chunk[i][j], 2.0f, 1e-5f); |
| } |
| } |
| |
| ASSERT_EQ(kChunkLength / kBlockLength, noop.block_num()); |
| } |
| |
| TEST(LappedTransformTest, IdentityProcessor) { |
| const size_t kChunkLength = 512; |
| const size_t kBlockLength = 64; |
| const size_t kShiftAmount = 32; |
| NoopCallback noop; |
| |
| // Identity window for |overlap = block_size / 2|. |
| float window[kBlockLength]; |
| std::fill(window, &window[kBlockLength], std::sqrt(0.5f)); |
| |
| LappedTransform trans(1, 1, kChunkLength, window, kBlockLength, kShiftAmount, |
| &noop); |
| float in_buffer[kChunkLength]; |
| float* in_chunk = in_buffer; |
| float out_buffer[kChunkLength]; |
| float* out_chunk = out_buffer; |
| |
| SetFloatArray(2.0f, 1, kChunkLength, &in_chunk); |
| SetFloatArray(-1.0f, 1, kChunkLength, &out_chunk); |
| |
| trans.ProcessChunk(&in_chunk, &out_chunk); |
| |
| for (size_t i = 0; i < kChunkLength; ++i) { |
| ASSERT_NEAR(out_chunk[i], |
| (i < kBlockLength - kShiftAmount) ? 0.0f : 2.0f, |
| 1e-5f); |
| } |
| |
| ASSERT_EQ(kChunkLength / kShiftAmount, noop.block_num()); |
| } |
| |
| TEST(LappedTransformTest, Callbacks) { |
| const size_t kChunkLength = 512; |
| const size_t kBlockLength = 64; |
| FftCheckerCallback call; |
| |
| // Rectangular window. |
| float window[kBlockLength]; |
| std::fill(window, &window[kBlockLength], 1.0f); |
| |
| LappedTransform trans(1, 1, kChunkLength, window, kBlockLength, |
| kBlockLength, &call); |
| float in_buffer[kChunkLength]; |
| float* in_chunk = in_buffer; |
| float out_buffer[kChunkLength]; |
| float* out_chunk = out_buffer; |
| |
| SetFloatArray(1.0f, 1, kChunkLength, &in_chunk); |
| SetFloatArray(-1.0f, 1, kChunkLength, &out_chunk); |
| |
| trans.ProcessChunk(&in_chunk, &out_chunk); |
| |
| ASSERT_EQ(kChunkLength / kBlockLength, call.block_num()); |
| } |
| |
| TEST(LappedTransformTest, chunk_length) { |
| const size_t kBlockLength = 64; |
| FftCheckerCallback call; |
| const float window[kBlockLength] = {}; |
| |
| // Make sure that chunk_length returns the same value passed to the |
| // LappedTransform constructor. |
| { |
| const size_t kExpectedChunkLength = 512; |
| const LappedTransform trans(1, 1, kExpectedChunkLength, window, |
| kBlockLength, kBlockLength, &call); |
| |
| EXPECT_EQ(kExpectedChunkLength, trans.chunk_length()); |
| } |
| { |
| const size_t kExpectedChunkLength = 160; |
| const LappedTransform trans(1, 1, kExpectedChunkLength, window, |
| kBlockLength, kBlockLength, &call); |
| |
| EXPECT_EQ(kExpectedChunkLength, trans.chunk_length()); |
| } |
| } |
| |
| } // namespace webrtc |