PFFFT C++ wrapper for APM

Pretty-Fast Fast Fourier Transform is a 3rd party FFT C library meant to
replace other FFT libraries in WebRTC (see https://crbug.com/webrtc/9577).

This CL adds a WebRTC wrapper meant to be used inside the Audio Processing
Module (APM). As a first step, it only supports aligned memory allocated
via PFFFT. Support for the C++ standard library containers will be done
afterwards since it requires careful investigation and benchmarking (because
PFFFT uses SIMD optimizations).

The wrapper pre-allocates a scratch buffer to avoid VLA.

Bug: webrtc:9577
Change-Id: Ied00c3d3b1df292024f608ccf0ed1917d6e92e56
Reviewed-on: https://webrtc-review.googlesource.com/c/122563
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Reviewed-by: Max Morin <maxmorin@webrtc.org>
Commit-Queue: Alessio Bazzica <alessiob@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#26808}
This commit is contained in:
Alessio Bazzica 2019-02-21 17:35:43 +01:00 committed by Commit Bot
parent 45af00f33b
commit caa499b207
6 changed files with 378 additions and 0 deletions

View file

@ -449,6 +449,7 @@ if (rtc_include_tests) {
"test/conversational_speech:unittest",
"utility:block_mean_calculator_unittest",
"utility:legacy_delay_estimator_unittest",
"utility:pffft_wrapper_unittest",
"vad:vad_unittests",
"//testing/gtest",
"//third_party/abseil-cpp/absl/memory",

View file

@ -83,6 +83,18 @@ rtc_source_set("ooura_fft") {
}
}
rtc_source_set("pffft_wrapper") {
sources = [
"pffft_wrapper.cc",
"pffft_wrapper.h",
]
deps = [
"../../../api:array_view",
"../../../rtc_base:checks",
"//third_party/pffft",
]
}
if (rtc_include_tests) {
rtc_source_set("block_mean_calculator_unittest") {
testonly = true
@ -111,4 +123,17 @@ if (rtc_include_tests) {
"//testing/gtest",
]
}
rtc_source_set("pffft_wrapper_unittest") {
testonly = true
sources = [
"pffft_wrapper_unittest.cc",
]
deps = [
":pffft_wrapper",
"../../../test:test_support",
"//testing/gtest",
"//third_party/pffft",
]
}
}

View file

@ -0,0 +1,3 @@
include_rules = [
"+third_party/pffft",
]

View file

@ -0,0 +1,110 @@
/*
* 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 "rtc_base/checks.h"
#include "third_party/pffft/src/pffft.h"
namespace webrtc {
namespace {
size_t GetBufferSize(size_t fft_size, Pffft::FftType fft_type) {
return fft_size * (fft_type == Pffft::FftType::kReal ? 1 : 2);
}
float* AllocatePffftBuffer(size_t size) {
return static_cast<float*>(pffft_aligned_malloc(size * sizeof(float)));
}
} // namespace
Pffft::FloatBuffer::FloatBuffer(size_t fft_size, FftType fft_type)
: size_(GetBufferSize(fft_size, fft_type)),
data_(AllocatePffftBuffer(size_)) {}
Pffft::FloatBuffer::~FloatBuffer() {
pffft_aligned_free(data_);
}
rtc::ArrayView<const float> Pffft::FloatBuffer::GetConstView() const {
return {data_, size_};
}
rtc::ArrayView<float> Pffft::FloatBuffer::GetView() {
return {data_, size_};
}
Pffft::Pffft(size_t fft_size, FftType fft_type)
: fft_size_(fft_size),
fft_type_(fft_type),
pffft_status_(pffft_new_setup(
fft_size_,
fft_type == Pffft::FftType::kReal ? PFFFT_REAL : PFFFT_COMPLEX)),
scratch_buffer_(
AllocatePffftBuffer(GetBufferSize(fft_size_, fft_type_))) {
RTC_DCHECK(pffft_status_);
RTC_DCHECK(scratch_buffer_);
}
Pffft::~Pffft() {
pffft_destroy_setup(pffft_status_);
pffft_aligned_free(scratch_buffer_);
}
bool Pffft::IsValidFftSize(size_t fft_size, FftType fft_type) {
if (fft_size == 0) {
return false;
}
// PFFFT only supports transforms for inputs of length N of the form
// N = (2^a)*(3^b)*(5^c) where b >=0 and c >= 0 and a >= 5 for the real FFT
// and a >= 4 for the complex FFT.
constexpr int kFactors[] = {2, 3, 5};
int factorization[] = {0, 0, 0};
int n = static_cast<int>(fft_size);
for (int i = 0; i < 3; ++i) {
while (n % kFactors[i] == 0) {
n = n / kFactors[i];
factorization[i]++;
}
}
int a_min = (fft_type == Pffft::FftType::kReal) ? 5 : 4;
return factorization[0] >= a_min && n == 1;
}
bool Pffft::IsSimdEnabled() {
return pffft_simd_size() > 1;
}
std::unique_ptr<Pffft::FloatBuffer> Pffft::CreateBuffer() const {
// Cannot use make_unique from absl because Pffft is the only friend of
// Pffft::FloatBuffer.
std::unique_ptr<Pffft::FloatBuffer> buffer(
new Pffft::FloatBuffer(fft_size_, fft_type_));
return buffer;
}
void Pffft::ForwardTransform(const FloatBuffer& in, FloatBuffer* out) {
RTC_DCHECK_EQ(in.size(), GetBufferSize(fft_size_, fft_type_));
RTC_DCHECK_EQ(in.size(), out->size());
RTC_DCHECK(scratch_buffer_);
pffft_transform(pffft_status_, in.const_data(), out->data(), scratch_buffer_,
PFFFT_FORWARD);
}
void Pffft::BackwardTransform(const FloatBuffer& in, FloatBuffer* out) {
RTC_DCHECK_EQ(in.size(), GetBufferSize(fft_size_, fft_type_));
RTC_DCHECK_EQ(in.size(), out->size());
RTC_DCHECK(scratch_buffer_);
pffft_transform(pffft_status_, in.const_data(), out->data(), scratch_buffer_,
PFFFT_BACKWARD);
}
} // namespace webrtc

View file

@ -0,0 +1,85 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_PROCESSING_UTILITY_PFFFT_WRAPPER_H_
#define MODULES_AUDIO_PROCESSING_UTILITY_PFFFT_WRAPPER_H_
#include <memory>
#include "api/array_view.h"
// Forward declaration.
struct PFFFT_Setup;
namespace webrtc {
// Pretty-Fast Fast Fourier Transform (PFFFT) wrapper class.
// Not thread safe.
class Pffft {
public:
enum class FftType { kReal, kComplex };
// 1D floating point buffer used as input/output data type for the FFT ops.
// It must be constructed using Pffft::CreateBuffer().
class FloatBuffer {
public:
FloatBuffer(const FloatBuffer&) = delete;
FloatBuffer& operator=(const FloatBuffer&) = delete;
~FloatBuffer();
rtc::ArrayView<const float> GetConstView() const;
rtc::ArrayView<float> GetView();
private:
friend class Pffft;
FloatBuffer(size_t fft_size, FftType fft_type);
const float* const_data() const { return data_; }
float* data() { return data_; }
size_t size() const { return size_; }
const size_t size_;
float* const data_;
};
// TODO(https://crbug.com/webrtc/9577): Consider adding a factory and making
// the ctor private.
// static std::unique_ptr<Pffft> Create(size_t fft_size,
// FftType fft_type); Ctor. |fft_size| must be a supported size (see
// Pffft::IsValidFftSize()). If not supported, the code will crash.
Pffft(size_t fft_size, FftType fft_type);
Pffft(const Pffft&) = delete;
Pffft& operator=(const Pffft&) = delete;
~Pffft();
// Returns true if the FFT size is supported.
static bool IsValidFftSize(size_t fft_size, FftType fft_type);
// Returns true if SIMD code optimizations are being used.
static bool IsSimdEnabled();
// Creates a buffer of the right size.
std::unique_ptr<FloatBuffer> CreateBuffer() const;
// TODO(https://crbug.com/webrtc/9577): Overload with rtc::ArrayView args.
// Computes the forward fast Fourier transform.
void ForwardTransform(const FloatBuffer& in, FloatBuffer* out);
// Computes the backward fast Fourier transform.
void BackwardTransform(const FloatBuffer& in, FloatBuffer* out);
private:
const size_t fft_size_;
const FftType fft_type_;
PFFFT_Setup* pffft_status_;
float* const scratch_buffer_;
};
} // namespace webrtc
#endif // MODULES_AUDIO_PROCESSING_UTILITY_PFFFT_WRAPPER_H_

View file

@ -0,0 +1,154 @@
/*
* 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());
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());
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)
TEST(PffftTest, DoNotCreateWrapperWithInvalidSize) {
for (size_t fft_size = 0; fft_size < kMaxValidSizeCheck; ++fft_size) {
SCOPED_TRACE(fft_size);
if (!Pffft::IsValidFftSize(fft_size, Pffft::FftType::kReal)) {
EXPECT_DEATH(CreatePffftWrapper(fft_size, Pffft::FftType::kReal), "");
}
if (!Pffft::IsValidFftSize(fft_size, Pffft::FftType::kComplex)) {
EXPECT_DEATH(CreatePffftWrapper(fft_size, Pffft::FftType::kComplex), "");
}
}
}
#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