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.

--HG--
extra : rebase_source : 34bbded736e1b385754756513c59ee27ce7552a5
This commit is contained in:
Ehsan Akhgari 2013-06-10 16:08:21 -04:00
parent bcac308338
commit 82519eedaf
5 changed files with 122 additions and 20 deletions

View File

@ -69,6 +69,24 @@ AudioBlockCopyChannelWithScale(const float* aInput,
}
}
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],

View File

@ -110,6 +110,14 @@ 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.
*/

View File

@ -10,7 +10,6 @@
#include "AudioNodeStream.h"
#include "mozilla/Mutex.h"
#include "mozilla/PodOperations.h"
#include "kiss_fft/kiss_fftr.h"
namespace mozilla {
namespace dom {
@ -80,7 +79,7 @@ AnalyserNode::AnalyserNode(AudioContext* aContext)
1,
ChannelCountMode::Explicit,
ChannelInterpretation::Speakers)
, mFFTSize(2048)
, mAnalysisBlock(2048)
, mMinDecibels(-100.)
, mMaxDecibels(-30.)
, mSmoothingTimeConstant(.8)
@ -107,8 +106,8 @@ AnalyserNode::SetFftSize(uint32_t aValue, ErrorResult& aRv)
aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
return;
}
if (mFFTSize != aValue) {
mFFTSize = aValue;
if (FftSize() != aValue) {
mAnalysisBlock.SetFFTSize(aValue);
AllocateBuffer();
}
}
@ -204,28 +203,25 @@ AnalyserNode::FFTAnalysis()
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);
ApplyBlackmanWindow(inputBuffer, FftSize());
kiss_fftr_cfg fft = kiss_fftr_alloc(mFFTSize, 0, nullptr, nullptr);
kiss_fftr(fft, inputBuffer, outputBuffer);
free(fft);
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;
@ -256,10 +252,10 @@ 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());

View File

@ -8,6 +8,7 @@
#define AnalyserNode_h_
#include "AudioNode.h"
#include "FFTBlock.h"
namespace mozilla {
namespace dom {
@ -29,7 +30,7 @@ public:
void GetByteTimeDomainData(Uint8Array& aArray);
uint32_t FftSize() const
{
return mFFTSize;
return mAnalysisBlock.FFTSize();
}
void SetFftSize(uint32_t aValue, ErrorResult& aRv);
uint32_t FrequencyBinCount() const
@ -60,7 +61,7 @@ private:
void ApplyBlackmanWindow(float* aBuffer, uint32_t aSize);
private:
uint32_t mFFTSize;
FFTBlock mAnalysisBlock;
double mMinDecibels;
double mMaxDecibels;
double mSmoothingTimeConstant;

View File

@ -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