summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/platform/audio/pffft/fft_frame_pffft.cc
blob: 3317e8ad054109c575666fb1ab46aa5ffa557078 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
// 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)