Bug 815643 - Part 2: Refactor our Web Audio FFT code into the FFTBlock class; r=roc
authorEhsan Akhgari <ehsan@mozilla.com>
Mon, 10 Jun 2013 16:08:21 -0400
changeset 134586 dc80f47a7123
parent 134585 6058da103d45
child 134587 6bed30223d8f
push id24805
push useremorley@mozilla.com
push dateTue, 11 Jun 2013 08:32:39 +0000
treeherdermozilla-central@81b227f1a522 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs815643
milestone24.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 815643 - Part 2: Refactor our Web Audio FFT code into the FFTBlock class; r=roc This is useful in order for us to be able to borrow code from Blink with fewer changes, and also to explore faster platform dependent FFT implementations in the future if needed.
content/media/AudioNodeEngine.cpp
content/media/AudioNodeEngine.h
content/media/webaudio/AnalyserNode.cpp
content/media/webaudio/AnalyserNode.h
content/media/webaudio/FFTBlock.h
--- a/content/media/AudioNodeEngine.cpp
+++ b/content/media/AudioNodeEngine.cpp
@@ -65,16 +65,34 @@ AudioBlockCopyChannelWithScale(const flo
   } else {
     for (uint32_t i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) {
       aOutput[i] = aInput[i]*aScale;
     }
   }
 }
 
 void
+BufferComplexMultiply(const float* aInput,
+                      const float* aScale,
+                      float* aOutput,
+                      uint32_t aSize)
+{
+  for (uint32_t i = 0; i < aSize * 2; i += 2) {
+    float real1 = aInput[i];
+    float imag1 = aInput[i + 1];
+    float real2 = aScale[i];
+    float imag2 = aScale[i + 1];
+    float realResult = real1 * real2 - imag1 * imag2;
+    float imagResult = real1 * imag2 + imag1 * real2;
+    aOutput[i] = realResult;
+    aOutput[i + 1] = imagResult;
+  }
+}
+
+void
 AudioBlockCopyChannelWithScale(const float aInput[WEBAUDIO_BLOCK_SIZE],
                                const float aScale[WEBAUDIO_BLOCK_SIZE],
                                float aOutput[WEBAUDIO_BLOCK_SIZE])
 {
   for (uint32_t i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) {
     aOutput[i] = aInput[i]*aScale[i];
   }
 }
--- a/content/media/AudioNodeEngine.h
+++ b/content/media/AudioNodeEngine.h
@@ -106,16 +106,24 @@ void AudioBlockCopyChannelWithScale(cons
 /**
  * Vector copy-scaled operation.
  */
 void AudioBlockCopyChannelWithScale(const float aInput[WEBAUDIO_BLOCK_SIZE],
                                     const float aScale[WEBAUDIO_BLOCK_SIZE],
                                     float aOutput[WEBAUDIO_BLOCK_SIZE]);
 
 /**
+ * Vector complex multiplication on arbitrary sized buffers.
+ */
+void BufferComplexMultiply(const float* aInput,
+                           const float* aScale,
+                           float* aOutput,
+                           uint32_t aSize);
+
+/**
  * In place gain. aScale == 1.0f should be optimized.
  */
 void AudioBlockInPlaceScale(float aBlock[WEBAUDIO_BLOCK_SIZE],
                             uint32_t aChannelCount,
                             float aScale);
 
 /**
  * Upmix a mono input to a stereo output, scaling the two output channels by two
--- a/content/media/webaudio/AnalyserNode.cpp
+++ b/content/media/webaudio/AnalyserNode.cpp
@@ -5,17 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/AnalyserNode.h"
 #include "mozilla/dom/AnalyserNodeBinding.h"
 #include "AudioNodeEngine.h"
 #include "AudioNodeStream.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/PodOperations.h"
-#include "kiss_fft/kiss_fftr.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_ISUPPORTS_INHERITED0(AnalyserNode, AudioNode)
 
 class AnalyserNodeEngine : public AudioNodeEngine
 {
@@ -75,17 +74,17 @@ public:
   }
 };
 
 AnalyserNode::AnalyserNode(AudioContext* aContext)
   : AudioNode(aContext,
               1,
               ChannelCountMode::Explicit,
               ChannelInterpretation::Speakers)
-  , mFFTSize(2048)
+  , mAnalysisBlock(2048)
   , mMinDecibels(-100.)
   , mMaxDecibels(-30.)
   , mSmoothingTimeConstant(.8)
   , mWriteIndex(0)
 {
   mStream = aContext->Graph()->CreateAudioNodeStream(new AnalyserNodeEngine(this),
                                                      MediaStreamGraph::INTERNAL_STREAM);
   AllocateBuffer();
@@ -102,18 +101,18 @@ AnalyserNode::SetFftSize(uint32_t aValue
 {
   // Disallow values that are not a power of 2 and outside the [32,2048] range
   if (aValue < 32 ||
       aValue > 2048 ||
       (aValue & (aValue - 1)) != 0) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return;
   }
-  if (mFFTSize != aValue) {
-    mFFTSize = aValue;
+  if (FftSize() != aValue) {
+    mAnalysisBlock.SetFFTSize(aValue);
     AllocateBuffer();
   }
 }
 
 void
 AnalyserNode::SetMinDecibels(double aValue, ErrorResult& aRv)
 {
   if (aValue >= mMaxDecibels) {
@@ -199,38 +198,35 @@ AnalyserNode::GetByteTimeDomainData(Uint
 bool
 AnalyserNode::FFTAnalysis()
 {
   float* inputBuffer;
   bool allocated = false;
   if (mWriteIndex == 0) {
     inputBuffer = mBuffer.Elements();
   } else {
-    inputBuffer = static_cast<float*>(moz_malloc(mFFTSize * sizeof(float)));
+    inputBuffer = static_cast<float*>(moz_malloc(FftSize() * sizeof(float)));
     if (!inputBuffer) {
       return false;
     }
-    memcpy(inputBuffer, mBuffer.Elements() + mWriteIndex, sizeof(float) * (mFFTSize - mWriteIndex));
-    memcpy(inputBuffer + mFFTSize - mWriteIndex, mBuffer.Elements(), sizeof(float) * mWriteIndex);
+    memcpy(inputBuffer, mBuffer.Elements() + mWriteIndex, sizeof(float) * (FftSize() - mWriteIndex));
+    memcpy(inputBuffer + FftSize() - mWriteIndex, mBuffer.Elements(), sizeof(float) * mWriteIndex);
     allocated = true;
   }
-  nsAutoArrayPtr<kiss_fft_cpx> outputBuffer(new kiss_fft_cpx[FrequencyBinCount() + 1]);
-
-  ApplyBlackmanWindow(inputBuffer, mFFTSize);
 
-  kiss_fftr_cfg fft = kiss_fftr_alloc(mFFTSize, 0, nullptr, nullptr);
-  kiss_fftr(fft, inputBuffer, outputBuffer);
-  free(fft);
+  ApplyBlackmanWindow(inputBuffer, FftSize());
+
+  mAnalysisBlock.PerformFFT(inputBuffer);
 
   // Normalize so than an input sine wave at 0dBfs registers as 0dBfs (undo FFT scaling factor).
-  const double magnitudeScale = 1.0 / mFFTSize;
+  const double magnitudeScale = 1.0 / FftSize();
 
   for (uint32_t i = 0; i < mOutputBuffer.Length(); ++i) {
-    double scalarMagnitude = sqrt(outputBuffer[i].r * outputBuffer[i].r +
-                                  outputBuffer[i].i * outputBuffer[i].i) *
+    double scalarMagnitude = NS_hypot(mAnalysisBlock.RealData(i),
+                                      mAnalysisBlock.ImagData(i)) *
                              magnitudeScale;
     mOutputBuffer[i] = mSmoothingTimeConstant * mOutputBuffer[i] +
                        (1.0 - mSmoothingTimeConstant) * scalarMagnitude;
   }
 
   if (allocated) {
     moz_free(inputBuffer);
   }
@@ -251,20 +247,20 @@ AnalyserNode::ApplyBlackmanWindow(float*
     aBuffer[i] *= window;
   }
 }
 
 bool
 AnalyserNode::AllocateBuffer()
 {
   bool result = true;
-  if (mBuffer.Length() != mFFTSize) {
-    result = mBuffer.SetLength(mFFTSize);
+  if (mBuffer.Length() != FftSize()) {
+    result = mBuffer.SetLength(FftSize());
     if (result) {
-      memset(mBuffer.Elements(), 0, sizeof(float) * mFFTSize);
+      memset(mBuffer.Elements(), 0, sizeof(float) * FftSize());
       mWriteIndex = 0;
 
       result = mOutputBuffer.SetLength(FrequencyBinCount());
       if (result) {
         memset(mOutputBuffer.Elements(), 0, sizeof(float) * FrequencyBinCount());
       }
     }
   }
--- a/content/media/webaudio/AnalyserNode.h
+++ b/content/media/webaudio/AnalyserNode.h
@@ -3,16 +3,17 @@
 /* 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 AnalyserNode_h_
 #define AnalyserNode_h_
 
 #include "AudioNode.h"
+#include "FFTBlock.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioContext;
 
 class AnalyserNode : public AudioNode
 {
@@ -24,17 +25,17 @@ public:
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
 
   void GetFloatFrequencyData(Float32Array& aArray);
   void GetByteFrequencyData(Uint8Array& aArray);
   void GetByteTimeDomainData(Uint8Array& aArray);
   uint32_t FftSize() const
   {
-    return mFFTSize;
+    return mAnalysisBlock.FFTSize();
   }
   void SetFftSize(uint32_t aValue, ErrorResult& aRv);
   uint32_t FrequencyBinCount() const
   {
     return FftSize() / 2;
   }
   double MinDecibels() const
   {
@@ -55,17 +56,17 @@ public:
 private:
   friend class AnalyserNodeEngine;
   void AppendChunk(const AudioChunk& aChunk);
   bool AllocateBuffer();
   bool FFTAnalysis();
   void ApplyBlackmanWindow(float* aBuffer, uint32_t aSize);
 
 private:
-  uint32_t mFFTSize;
+  FFTBlock mAnalysisBlock;
   double mMinDecibels;
   double mMaxDecibels;
   double mSmoothingTimeConstant;
   uint32_t mWriteIndex;
   FallibleTArray<float> mBuffer;
   FallibleTArray<float> mOutputBuffer;
 };
 
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/FFTBlock.h
@@ -0,0 +1,79 @@
+/* -*- 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_
+
+#include "nsTArray.h"
+#include "AudioNodeEngine.h"
+#include "kiss_fft/kiss_fftr.h"
+
+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 {
+public:
+  explicit FFTBlock(uint32_t aFFTSize)
+    : mFFTSize(aFFTSize)
+  {
+    mOutputBuffer.SetLength(aFFTSize / 2 + 1);
+    PodZero(mOutputBuffer.Elements(), aFFTSize / 2 + 1);
+  }
+
+  void PerformFFT(const float* aData)
+  {
+    kiss_fftr_cfg fft = kiss_fftr_alloc(mFFTSize, 0, nullptr, nullptr);
+    kiss_fftr(fft, aData, mOutputBuffer.Elements());
+    free(fft);
+  }
+  void PerformInverseFFT(float* aData) const
+  {
+    kiss_fftr_cfg fft = kiss_fftr_alloc(mFFTSize, 1, nullptr, nullptr);
+    kiss_fftri(fft, mOutputBuffer.Elements(), aData);
+    free(fft);
+    for (uint32_t i = 0; i < mFFTSize; ++i) {
+      aData[i] /= mFFTSize;
+    }
+  }
+  void Multiply(const FFTBlock& aFrame)
+  {
+    BufferComplexMultiply(reinterpret_cast<const float*>(mOutputBuffer.Elements()),
+                          reinterpret_cast<const float*>(aFrame.mOutputBuffer.Elements()),
+                          reinterpret_cast<float*>(mOutputBuffer.Elements()),
+                          mFFTSize / 2 + 1);
+  }
+
+  void SetFFTSize(uint32_t aSize)
+  {
+    mFFTSize = aSize;
+    mOutputBuffer.SetLength(aSize / 2 + 1);
+    PodZero(mOutputBuffer.Elements(), aSize / 2 + 1);
+  }
+
+  float FFTSize() const
+  {
+    return mFFTSize;
+  }
+  float RealData(uint32_t aIndex) const
+  {
+    return mOutputBuffer[aIndex].r;
+  }
+  float ImagData(uint32_t aIndex) const
+  {
+    return mOutputBuffer[aIndex].i;
+  }
+
+private:
+  nsTArray<kiss_fft_cpx> mOutputBuffer;
+  uint32_t mFFTSize;
+};
+
+}
+
+#endif
+