summaryrefslogtreecommitdiffstats
path: root/dom/media/webaudio/FFTBlock.h
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/webaudio/FFTBlock.h')
-rw-r--r--dom/media/webaudio/FFTBlock.h348
1 files changed, 348 insertions, 0 deletions
diff --git a/dom/media/webaudio/FFTBlock.h b/dom/media/webaudio/FFTBlock.h
new file mode 100644
index 0000000000..6af882f9af
--- /dev/null
+++ b/dom/media/webaudio/FFTBlock.h
@@ -0,0 +1,348 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef FFTBlock_h_
+#define FFTBlock_h_
+
+#ifdef BUILD_ARM_NEON
+# include <cmath>
+# include "mozilla/arm.h"
+# include "dl/sp/api/omxSP.h"
+#endif
+
+#include "AlignedTArray.h"
+#include "AudioNodeEngine.h"
+#if defined(MOZ_LIBAV_FFT)
+# include "FFmpegRDFTTypes.h"
+# include "FFVPXRuntimeLinker.h"
+#else
+# include "kiss_fft/kiss_fftr.h"
+#endif
+
+namespace mozilla {
+
+// This class defines an FFT block, loosely modeled after Blink's FFTFrame
+// class to make sharing code with Blink easy.
+// Currently it's implemented on top of KissFFT on all platforms.
+class FFTBlock final {
+ union ComplexU {
+#if !defined(MOZ_LIBAV_FFT)
+ kiss_fft_cpx c;
+#endif
+ float f[2];
+ struct {
+ float r;
+ float i;
+ };
+ };
+
+ public:
+ static void MainThreadInit() {
+#ifdef MOZ_LIBAV_FFT
+ FFVPXRuntimeLinker::Init();
+ if (!sRDFTFuncs.init) {
+ FFVPXRuntimeLinker::GetRDFTFuncs(&sRDFTFuncs);
+ }
+#endif
+ }
+
+ explicit FFTBlock(uint32_t aFFTSize)
+#if defined(MOZ_LIBAV_FFT)
+ : mAvRDFT(nullptr),
+ mAvIRDFT(nullptr)
+#else
+ : mKissFFT(nullptr),
+ mKissIFFT(nullptr)
+# ifdef BUILD_ARM_NEON
+ ,
+ mOmxFFT(nullptr),
+ mOmxIFFT(nullptr)
+# endif
+#endif
+ {
+ MOZ_COUNT_CTOR(FFTBlock);
+ SetFFTSize(aFFTSize);
+ }
+ ~FFTBlock() {
+ MOZ_COUNT_DTOR(FFTBlock);
+ Clear();
+ }
+
+ // Return a new FFTBlock with frequency components interpolated between
+ // |block0| and |block1| with |interp| between 0.0 and 1.0.
+ static FFTBlock* CreateInterpolatedBlock(const FFTBlock& block0,
+ const FFTBlock& block1,
+ double interp);
+
+ // Transform FFTSize() points of aData and store the result internally.
+ void PerformFFT(const float* aData) {
+ if (!EnsureFFT()) {
+ return;
+ }
+
+#if defined(MOZ_LIBAV_FFT)
+ PodCopy(mOutputBuffer.Elements()->f, aData, mFFTSize);
+ sRDFTFuncs.calc(mAvRDFT, mOutputBuffer.Elements()->f);
+ // Recover packed Nyquist.
+ mOutputBuffer[mFFTSize / 2].r = mOutputBuffer[0].i;
+ mOutputBuffer[0].i = 0.0f;
+#else
+# ifdef BUILD_ARM_NEON
+ if (mozilla::supports_neon()) {
+ omxSP_FFTFwd_RToCCS_F32_Sfs(aData, mOutputBuffer.Elements()->f, mOmxFFT);
+ } else
+# endif
+ {
+ kiss_fftr(mKissFFT, aData, &(mOutputBuffer.Elements()->c));
+ }
+#endif
+ }
+ // Inverse-transform internal data and store the resulting FFTSize()
+ // points in aDataOut.
+ void GetInverse(float* aDataOut) {
+ GetInverseWithoutScaling(aDataOut);
+ AudioBufferInPlaceScale(aDataOut, 1.0f / mFFTSize, mFFTSize);
+ }
+
+ // Inverse-transform internal frequency data and store the resulting
+ // FFTSize() points in |aDataOut|. If frequency data has not already been
+ // scaled, then the output will need scaling by 1/FFTSize().
+ void GetInverseWithoutScaling(float* aDataOut) {
+ if (!EnsureIFFT()) {
+ std::fill_n(aDataOut, mFFTSize, 0.0f);
+ return;
+ };
+
+#if defined(MOZ_LIBAV_FFT)
+ {
+ // Even though this function doesn't scale, the libav forward transform
+ // gives a value that needs scaling by 2 in order for things to turn out
+ // similar to how we expect from kissfft/openmax.
+ AudioBufferCopyWithScale(mOutputBuffer.Elements()->f, 2.0f, aDataOut,
+ mFFTSize);
+ aDataOut[1] = 2.0f * mOutputBuffer[mFFTSize / 2].r; // Packed Nyquist
+ sRDFTFuncs.calc(mAvIRDFT, aDataOut);
+ }
+#else
+# ifdef BUILD_ARM_NEON
+ if (mozilla::supports_neon()) {
+ omxSP_FFTInv_CCSToR_F32_Sfs_unscaled(mOutputBuffer.Elements()->f,
+ aDataOut, mOmxIFFT);
+ } else
+# endif
+ {
+ kiss_fftri(mKissIFFT, &(mOutputBuffer.Elements()->c), aDataOut);
+ }
+#endif
+ }
+
+ void Multiply(const FFTBlock& aFrame) {
+ uint32_t halfSize = mFFTSize / 2;
+ // DFTs are not packed.
+ MOZ_ASSERT(mOutputBuffer[0].i == 0);
+ MOZ_ASSERT(aFrame.mOutputBuffer[0].i == 0);
+
+ BufferComplexMultiply(mOutputBuffer.Elements()->f,
+ aFrame.mOutputBuffer.Elements()->f,
+ mOutputBuffer.Elements()->f, halfSize);
+ mOutputBuffer[halfSize].r *= aFrame.mOutputBuffer[halfSize].r;
+ // This would have been set to NaN if either real component was NaN.
+ mOutputBuffer[0].i = 0.0f;
+ }
+
+ // Perform a forward FFT on |aData|, assuming zeros after dataSize samples,
+ // and pre-scale the generated internal frequency domain coefficients so
+ // that GetInverseWithoutScaling() can be used to transform to the time
+ // domain. This is useful for convolution kernels.
+ void PadAndMakeScaledDFT(const float* aData, size_t dataSize) {
+ MOZ_ASSERT(dataSize <= FFTSize());
+ AlignedTArray<float> paddedData;
+ paddedData.SetLength(FFTSize());
+ AudioBufferCopyWithScale(aData, 1.0f / FFTSize(), paddedData.Elements(),
+ dataSize);
+ PodZero(paddedData.Elements() + dataSize, mFFTSize - dataSize);
+ PerformFFT(paddedData.Elements());
+ }
+
+ // aSize must be a power of 2
+ void SetFFTSize(uint32_t aSize) {
+ MOZ_ASSERT(CountPopulation32(aSize) == 1);
+ mFFTSize = aSize;
+ mOutputBuffer.SetLength(aSize / 2 + 1);
+ PodZero(mOutputBuffer.Elements(), aSize / 2 + 1);
+ Clear();
+ }
+
+ // Return the average group delay and removes this from the frequency data.
+ double ExtractAverageGroupDelay();
+
+ uint32_t FFTSize() const { return mFFTSize; }
+ float RealData(uint32_t aIndex) const { return mOutputBuffer[aIndex].r; }
+ float& RealData(uint32_t aIndex) { return mOutputBuffer[aIndex].r; }
+ float ImagData(uint32_t aIndex) const { return mOutputBuffer[aIndex].i; }
+ float& ImagData(uint32_t aIndex) { return mOutputBuffer[aIndex].i; }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t amount = 0;
+
+#if defined(MOZ_LIBAV_FFT)
+ auto ComputedSizeOfContextIfSet = [this](void* aContext) -> size_t {
+ if (!aContext) {
+ return 0;
+ }
+ // RDFTContext is only forward declared in public headers, but this is
+ // an estimate based on a value of 231 seen requested from
+ // _aligned_alloc on Win64. Don't use malloc_usable_size() because the
+ // context pointer is not necessarily from malloc.
+ size_t amount = 232;
+ // Add size of allocations performed in ff_fft_init().
+ // The maximum FFT size used is 32768 = 2^15 and so revtab32 is not
+ // allocated.
+ MOZ_ASSERT(mFFTSize <= 32768);
+ amount += mFFTSize * (sizeof(uint16_t) + 2 * sizeof(float));
+
+ return amount;
+ };
+
+ amount += ComputedSizeOfContextIfSet(mAvRDFT);
+ amount += ComputedSizeOfContextIfSet(mAvIRDFT);
+#else
+# ifdef BUILD_ARM_NEON
+ amount += aMallocSizeOf(mOmxFFT);
+ amount += aMallocSizeOf(mOmxIFFT);
+# endif
+# ifdef USE_SIMD
+# error kiss fft uses malloc only when USE_SIMD is not defined
+# endif
+ amount += aMallocSizeOf(mKissFFT);
+ amount += aMallocSizeOf(mKissIFFT);
+#endif
+ amount += mOutputBuffer.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ return amount;
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ FFTBlock(const FFTBlock& other) = delete;
+ void operator=(const FFTBlock& other) = delete;
+
+ bool EnsureFFT() {
+#if defined(MOZ_LIBAV_FFT)
+ if (!mAvRDFT) {
+ if (!sRDFTFuncs.init) {
+ return false;
+ }
+
+ mAvRDFT = sRDFTFuncs.init(FloorLog2(mFFTSize), DFT_R2C);
+ }
+#else
+# ifdef BUILD_ARM_NEON
+ if (mozilla::supports_neon()) {
+ if (!mOmxFFT) {
+ mOmxFFT = createOmxFFT(mFFTSize);
+ }
+ } else
+# endif
+ {
+ if (!mKissFFT) {
+ mKissFFT = kiss_fftr_alloc(mFFTSize, 0, nullptr, nullptr);
+ }
+ }
+#endif
+ return true;
+ }
+
+ bool EnsureIFFT() {
+#if defined(MOZ_LIBAV_FFT)
+ if (!mAvIRDFT) {
+ if (!sRDFTFuncs.init) {
+ return false;
+ }
+
+ mAvIRDFT = sRDFTFuncs.init(FloorLog2(mFFTSize), IDFT_C2R);
+ }
+#else
+# ifdef BUILD_ARM_NEON
+ if (mozilla::supports_neon()) {
+ if (!mOmxIFFT) {
+ mOmxIFFT = createOmxFFT(mFFTSize);
+ }
+ } else
+# endif
+ {
+ if (!mKissIFFT) {
+ mKissIFFT = kiss_fftr_alloc(mFFTSize, 1, nullptr, nullptr);
+ }
+ }
+#endif
+ return true;
+ }
+
+#ifdef BUILD_ARM_NEON
+ static OMXFFTSpec_R_F32* createOmxFFT(uint32_t aFFTSize) {
+ MOZ_ASSERT((aFFTSize & (aFFTSize - 1)) == 0);
+ OMX_INT bufSize;
+ OMX_INT order = FloorLog2(aFFTSize);
+ MOZ_ASSERT(aFFTSize >> order == 1);
+ OMXResult status = omxSP_FFTGetBufSize_R_F32(order, &bufSize);
+ if (status == OMX_Sts_NoErr) {
+ OMXFFTSpec_R_F32* context =
+ static_cast<OMXFFTSpec_R_F32*>(malloc(bufSize));
+ if (omxSP_FFTInit_R_F32(context, order) != OMX_Sts_NoErr) {
+ return nullptr;
+ }
+ return context;
+ }
+ return nullptr;
+ }
+#endif
+
+ void Clear() {
+#if defined(MOZ_LIBAV_FFT)
+ if (mAvRDFT) {
+ sRDFTFuncs.end(mAvRDFT);
+ mAvRDFT = nullptr;
+ }
+ if (mAvIRDFT) {
+ sRDFTFuncs.end(mAvIRDFT);
+ mAvIRDFT = nullptr;
+ }
+#else
+# ifdef BUILD_ARM_NEON
+ free(mOmxFFT);
+ free(mOmxIFFT);
+ mOmxFFT = mOmxIFFT = nullptr;
+# endif
+ free(mKissFFT);
+ free(mKissIFFT);
+ mKissFFT = mKissIFFT = nullptr;
+#endif
+ }
+ void AddConstantGroupDelay(double sampleFrameDelay);
+ void InterpolateFrequencyComponents(const FFTBlock& block0,
+ const FFTBlock& block1, double interp);
+#if defined(MOZ_LIBAV_FFT)
+ static FFmpegRDFTFuncs sRDFTFuncs;
+ RDFTContext* mAvRDFT;
+ RDFTContext* mAvIRDFT;
+#else
+ kiss_fftr_cfg mKissFFT;
+ kiss_fftr_cfg mKissIFFT;
+# ifdef BUILD_ARM_NEON
+ OMXFFTSpec_R_F32* mOmxFFT;
+ OMXFFTSpec_R_F32* mOmxIFFT;
+# endif
+#endif
+ AlignedTArray<ComplexU> mOutputBuffer;
+ uint32_t mFFTSize;
+};
+
+} // namespace mozilla
+
+#endif