blob: 3317e8ad054109c575666fb1ab46aa5ffa557078 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#if defined(WTF_USE_WEBAUDIO_PFFFT)
#include "third_party/blink/renderer/platform/audio/fft_frame.h"
#include "third_party/blink/renderer/platform/audio/audio_array.h"
#include "third_party/blink/renderer/platform/audio/hrtf_panner.h"
#include "third_party/blink/renderer/platform/audio/vector_math.h"
#include "third_party/blink/renderer/platform/wtf/math_extras.h"
#include "third_party/blink/renderer/platform/wtf/threading_primitives.h"
#include "third_party/pffft/src/pffft.h"
namespace blink {
// Not really clear what the largest size of FFT PFFFT supports, but the docs
// indicate it can go up to at least 1048576 (order 20). Since we're using
// single-floats, accuracy decreases quite a bit at that size. Plus we only
// need 32K (order 15) for WebAudio.
const unsigned kMaxFFTPow2Size = 20;
// PFFFT has a minimum real FFT order of 5 (32-point transforms).
const unsigned kMinFFTPow2Size = 5;
FFTFrame::FFTSetup::FFTSetup(unsigned fft_size) {
DCHECK_LE(fft_size, 1U << kMaxFFTPow2Size);
DCHECK_GE(fft_size, 1U << kMinFFTPow2Size);
// All FFTs we need are FFTs of real signals, and the inverse FFTs produce
// real signals. Hence |PFFFT_REAL|.
setup_ = pffft_new_setup(fft_size, PFFFT_REAL);
DCHECK(setup_);
}
FFTFrame::FFTSetup::~FFTSetup() {
DCHECK(setup_);
pffft_destroy_setup(setup_);
}
HashMap<unsigned, std::unique_ptr<FFTFrame::FFTSetup>>& FFTFrame::FFTSetups() {
// TODO(rtoy): Let this bake for a bit and then remove the assertions after
// we're confident the first call is from the main thread.
static bool first_call = true;
// A HashMap to hold all of the possible FFT setups we need. The setups are
// initialized lazily. The key is the fft size, and the value is the setup
// data.
typedef HashMap<unsigned, std::unique_ptr<FFTSetup>> FFTHashMap_t;
DEFINE_THREAD_SAFE_STATIC_LOCAL(FFTHashMap_t, fft_setups, ());
if (first_call) {
DEFINE_STATIC_LOCAL(Mutex, setup_lock, ());
// Make sure we construct the fft_setups vector below on the main thread.
// Once constructed, we can access it from any thread.
DCHECK(IsMainThread());
first_call = false;
MutexLocker locker(setup_lock);
// Initialize the hash map with all the possible keys (FFT sizes), with a
// value of nullptr because we want to initialize the setup data lazily. The
// set of valid FFT sizes for PFFFT are of the form 2^k*3^m*5*n where k >=
// 5, m >= 0, n >= 0. We only go up to a max size of 32768, because we need
// at least an FFT size of 32768 for the convolver node.
// TODO(crbug.com/988121): Sync this with kMaxFFTPow2Size.
const int kMaxConvolverFFTSize = 32768;
for (int n = 1; n <= kMaxConvolverFFTSize; n *= 5) {
for (int m = 1; m <= kMaxConvolverFFTSize / n; m *= 3) {
for (int k = 32; k <= kMaxConvolverFFTSize / (n * m); k *= 2) {
int size = k * m * n;
if (size <= kMaxConvolverFFTSize && !fft_setups.Contains(size)) {
fft_setups.insert(size, nullptr);
}
}
}
}
// There should be 87 entries when we're done.
DCHECK_EQ(fft_setups.size(), 87u);
}
return fft_setups;
}
void FFTFrame::InitializeFFTSetupForSize(wtf_size_t fft_size) {
auto& setup = FFTSetups();
DCHECK(setup.Contains(fft_size));
if (setup.find(fft_size)->value == nullptr) {
DEFINE_STATIC_LOCAL(Mutex, setup_lock, ());
// Make sure allocation of a new setup only occurs on the main thread so we
// don't have a race condition with multiple threads trying to write to the
// same element of the vector.
DCHECK(IsMainThread());
auto fft_data = std::make_unique<FFTSetup>(fft_size);
MutexLocker locker(setup_lock);
setup.find(fft_size)->value = std::move(fft_data);
}
}
PFFFT_Setup* FFTFrame::FFTSetupForSize(wtf_size_t fft_size) {
auto& setup = FFTSetups();
DCHECK(setup.Contains(fft_size));
DCHECK(setup.find(fft_size)->value);
return setup.find(fft_size)->value->GetSetup();
}
FFTFrame::FFTFrame(unsigned fft_size)
: fft_size_(fft_size),
log2fft_size_(static_cast<unsigned>(log2(fft_size))),
real_data_(fft_size / 2),
imag_data_(fft_size / 2),
complex_data_(fft_size),
pffft_work_(fft_size) {
// Initialize the PFFFT_Setup object here so that it will be ready when we
// compute FFTs.
InitializeFFTSetupForSize(fft_size);
}
// Creates a blank/empty frame (interpolate() must later be called).
FFTFrame::FFTFrame() : fft_size_(0), log2fft_size_(0) {}
// Copy constructor.
FFTFrame::FFTFrame(const FFTFrame& frame)
: fft_size_(frame.fft_size_),
log2fft_size_(frame.log2fft_size_),
real_data_(frame.fft_size_ / 2),
imag_data_(frame.fft_size_ / 2),
complex_data_(frame.fft_size_),
pffft_work_(frame.fft_size_) {
// Initialize the PFFFT_Setup object here wo that it will be ready when we
// compute FFTs.
InitializeFFTSetupForSize(fft_size_);
// Copy/setup frame data.
unsigned nbytes = sizeof(float) * (fft_size_ / 2);
memcpy(RealData().Data(), frame.RealData().Data(), nbytes);
memcpy(ImagData().Data(), frame.ImagData().Data(), nbytes);
}
int FFTFrame::MinFFTSize() {
return 1 << kMinFFTPow2Size;
}
int FFTFrame::MaxFFTSize() {
return 1 << kMaxFFTPow2Size;
}
void FFTFrame::Initialize(float sample_rate) {
// Initialize the vector now so it's ready for use when we construct
// FFTFrames.
FFTSetups();
// Determine the order of the convolvers used by the HRTF kernel. Allocate
// FFT setups for that size and for half that size. The HRTF kernel uses half
// size for analysis FFTs.
//
// TODO(rtoy): Try to come up with some way so that |Initialize()| doesn't
// need to know about how the HRTF panner uses FFTs.
unsigned hrtf_fft_size =
static_cast<unsigned>(HRTFPanner::FftSizeForSampleRate(sample_rate));
DCHECK_GT(hrtf_fft_size, 1U << kMinFFTPow2Size);
DCHECK_LE(hrtf_fft_size, 1U << kMaxFFTPow2Size);
InitializeFFTSetupForSize(hrtf_fft_size);
InitializeFFTSetupForSize(hrtf_fft_size / 2);
}
void FFTFrame::Cleanup() {
for (auto& setup : FFTSetups()) {
setup.value.reset();
}
}
FFTFrame::~FFTFrame() {
}
void FFTFrame::DoFFT(const float* data) {
DCHECK_EQ(pffft_work_.size(), fft_size_);
PFFFT_Setup* setup = FFTSetupForSize(fft_size_);
DCHECK(setup);
pffft_transform_ordered(setup, data, complex_data_.Data(), pffft_work_.Data(),
PFFFT_FORWARD);
unsigned len = fft_size_ / 2;
// Split FFT data into real and imaginary arrays. PFFFT transform already
// uses the desired format; we just need to split out the real and imaginary
// parts.
const float* c = complex_data_.Data();
float* real = real_data_.Data();
float* imag = imag_data_.Data();
for (unsigned k = 0; k < len; ++k) {
int index = 2 * k;
real[k] = c[index];
imag[k] = c[index + 1];
}
}
void FFTFrame::DoInverseFFT(float* data) {
DCHECK_EQ(complex_data_.size(), fft_size_);
unsigned len = fft_size_ / 2;
// Pack the real and imaginary data into the complex array format. PFFFT
// already uses the desired format; we just need to pack the parts together.
float* fft_data = complex_data_.Data();
const float* real = real_data_.Data();
const float* imag = imag_data_.Data();
for (unsigned k = 0; k < len; ++k) {
int index = 2 * k;
fft_data[index] = real[k];
fft_data[index + 1] = imag[k];
}
PFFFT_Setup* setup = FFTSetupForSize(fft_size_);
DCHECK(setup);
pffft_transform_ordered(setup, fft_data, data, pffft_work_.Data(),
PFFFT_BACKWARD);
// The inverse transform needs to be scaled because PFFFT doesn't.
float scale = 1.0 / fft_size_;
vector_math::Vsmul(data, 1, &scale, data, 1, fft_size_);
}
} // namespace blink
#endif // #if defined(WTF_USE_WEBAUDIO_PFFFT)