Commit 3b66f107 authored by kbr@google.com's avatar kbr@google.com
Browse files

2011-01-24 Kenneth Russell <kbr@google.com>

        Reviewed by James Robinson.

        Web Audio API: port FFTFrame to FFTW
        https://bugs.webkit.org/show_bug.cgi?id=52989

        Ported FFTFrame class to the open-source FFTW library. Tested with
        unit tests from Chris Rogers. Made preliminary changes to GYP
        files for conditional compilation of these files; will need to be
        adjusted once FFTW is integrated as third-party source.

        * WebCore.gyp/WebCore.gyp:
        * WebCore.gypi:
        * platform/audio/FFTFrame.h:
        * platform/audio/fftw: Added.
        * platform/audio/fftw/FFTFrameFFTW.cpp: Added.
        (WebCore::FFTFrame::FFTFrame):
        (WebCore::FFTFrame::~FFTFrame):
        (WebCore::FFTFrame::multiply):
        (WebCore::FFTFrame::doFFT):
        (WebCore::FFTFrame::doInverseFFT):
        (WebCore::FFTFrame::cleanup):
        (WebCore::FFTFrame::realData):
        (WebCore::FFTFrame::imagData):
        (WebCore::FFTFrame::fftwPlanForSize):


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@76562 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 34341a9c
2011-01-24 Kenneth Russell <kbr@google.com>
Reviewed by James Robinson.
Web Audio API: port FFTFrame to FFTW
https://bugs.webkit.org/show_bug.cgi?id=52989
Ported FFTFrame class to the open-source FFTW library. Tested with
unit tests from Chris Rogers. Made preliminary changes to GYP
files for conditional compilation of these files; will need to be
adjusted once FFTW is integrated as third-party source.
* WebCore.gyp/WebCore.gyp:
* WebCore.gypi:
* platform/audio/FFTFrame.h:
* platform/audio/fftw: Added.
* platform/audio/fftw/FFTFrameFFTW.cpp: Added.
(WebCore::FFTFrame::FFTFrame):
(WebCore::FFTFrame::~FFTFrame):
(WebCore::FFTFrame::multiply):
(WebCore::FFTFrame::doFFT):
(WebCore::FFTFrame::doInverseFFT):
(WebCore::FFTFrame::cleanup):
(WebCore::FFTFrame::realData):
(WebCore::FFTFrame::imagData):
(WebCore::FFTFrame::fftwPlanForSize):
2011-01-24 Anders Carlsson <andersca@apple.com>
 
Reviewed by Dan Bernstein.
......@@ -852,12 +852,17 @@
},
}],
# FIXME: (kbr) ideally this target should just depend on webcore_prerequisites
# to pick up this include directory, but I'm nervous about making that change.
# to pick up these include directories, but I'm nervous about making that change.
['(OS=="linux" or OS=="win") and "WTF_USE_WEBAUDIO_MKL=1" in feature_defines', {
'include_dirs': [
'<(chromium_src_dir)/third_party/mkl/include',
],
}],
['(OS=="linux" or OS=="win") and "WTF_USE_WEBAUDIO_FFTW=1" in feature_defines', {
'include_dirs': [
'<(chromium_src_dir)/third_party/fftw/api',
],
}],
],
},
{
......@@ -1034,6 +1039,14 @@
],
},
}],
['(OS=="linux" or OS=="win") and "WTF_USE_WEBAUDIO_FFTW=1" in feature_defines', {
# This directory needs to be on the include path for multiple sub-targets of webcore.
'direct_dependent_settings': {
'include_dirs': [
'<(chromium_src_dir)/third_party/fftw/api',
],
},
}],
],
},
{
......@@ -1096,7 +1109,7 @@
# Exclude things that don't apply to the Chromium platform on the basis
# of their enclosing directories and tags at the ends of their
# filenames.
['exclude', '(android|cairo|cf|cg|curl|gtk|haiku|linux|mac|mkl|opentype|posix|qt|soup|svg|symbian|win|wx)/'],
['exclude', '(android|cairo|cf|cg|curl|fftw|gtk|haiku|linux|mac|mkl|opentype|posix|qt|soup|svg|symbian|win|wx)/'],
['exclude', '(?<!Chromium)(Android|Cairo|CF|CG|Curl|Gtk|Linux|Mac|OpenType|POSIX|Posix|Qt|Safari|Soup|Symbian|Win|Wx)\\.(cpp|mm?)$'],
# A few things can't be excluded by patterns. List them individually.
......@@ -1270,6 +1283,11 @@
['include', 'platform/audio/mkl/FFTFrameMKL\\.cpp$'],
],
}],
['(OS=="linux" or OS=="win") and "WTF_USE_WEBAUDIO_FFTW=1" in feature_defines', {
'sources/': [
['include', 'platform/audio/fftw/FFTFrameFFTW\\.cpp$'],
],
}],
],
},
{
......@@ -1501,6 +1519,23 @@
},
},
}],
['OS=="linux" and "WTF_USE_WEBAUDIO_FFTW=1" in feature_defines', {
# FIXME: (kbr) figure out how to make these dependencies
# work in a cross-platform way. Attempts to use
# "link_settings" and "libraries" in conjunction with the
# msvs-specific settings didn't work so far.
'all_dependent_settings': {
'ldflags': [
# FIXME: (kbr) build the FFTW into PRODUCT_DIR using GYP.
'-Lthird_party/fftw/.libs',
],
'link_settings': {
'libraries': [
'-lfftw3f'
],
},
},
}],
['enable_svg!=0', {
'dependencies': [
'webcore_svg',
......
......@@ -2435,6 +2435,7 @@
'platform/audio/VectorMath.h',
'platform/audio/VectorMath.cpp',
'platform/audio/chromium/AudioBusChromium.cpp',
'platform/audio/fftw/FFTFrameFFTW.cpp',
'platform/audio/mac/AudioBusMac.mm',
'platform/audio/mac/AudioDestinationMac.h',
'platform/audio/mac/AudioDestinationMac.cpp',
......
......@@ -35,8 +35,13 @@
#include <Accelerate/Accelerate.h>
#endif
#if !OS(DARWIN) && USE(WEBAUDIO_MKL)
#if !OS(DARWIN)
#if USE(WEBAUDIO_MKL)
#include "mkl_dfti.h"
#endif // USE(WEBAUDIO_MKL)
#if USE(WEBAUDIO_FFTW)
#include "fftw3.h"
#endif // USE(WEBAUDIO_FFTW)
#endif
#include <wtf/PassOwnPtr.h>
......@@ -98,8 +103,8 @@ private:
DSPSplitComplex m_frame;
AudioFloatArray m_realData;
AudioFloatArray m_imagData;
#endif // OS(DARWIN)
#if !OS(DARWIN) && USE(WEBAUDIO_MKL)
#else // !OS(DARWIN)
#if USE(WEBAUDIO_MKL)
// Interleaves the planar real and imaginary data and returns a
// pointer to the resulting storage which can be used for in-place
// or out-of-place operations. FIXME: ideally all of the MKL
......@@ -115,7 +120,26 @@ private:
AudioFloatArray m_complexData;
AudioFloatArray m_realData;
AudioFloatArray m_imagData;
#endif // !OS(DARWIN) && USE(WEBAUDIO_MKL)
#endif // USE(WEBAUDIO_MKL)
#if USE(WEBAUDIO_FFTW)
fftwf_plan m_forwardPlan;
fftwf_plan m_backwardPlan;
enum Direction {
Forward,
Backward
};
AudioFloatArray m_realData;
AudioFloatArray m_imagData;
static fftwf_plan* fftwForwardPlans;
static fftwf_plan* fftwBackwardPlans;
static fftwf_plan fftwPlanForSize(unsigned fftSize, Direction,
float*, float*, float*);
#endif // USE(WEBAUDIO_FFTW)
#endif // !OS(DARWIN)
};
} // namespace WebCore
......
/*
* Copyright (C) 2011 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// FFTFrame implementation using the FFTW library.
#include "config.h"
#if ENABLE(WEB_AUDIO)
#include "FFTFrame.h"
#include <wtf/MathExtras.h>
namespace WebCore {
const int kMaxFFTPow2Size = 24;
fftwf_plan* FFTFrame::fftwForwardPlans = 0;
fftwf_plan* FFTFrame::fftwBackwardPlans = 0;
namespace {
unsigned unpackedFFTWDataSize(unsigned fftSize)
{
return fftSize / 2 + 1;
}
} // anonymous namespace
// Normal constructor: allocates for a given fftSize.
FFTFrame::FFTFrame(unsigned fftSize)
: m_FFTSize(fftSize)
, m_log2FFTSize(static_cast<unsigned>(log2(fftSize)))
, m_forwardPlan(0)
, m_backwardPlan(0)
, m_realData(unpackedFFTWDataSize(fftSize))
, m_imagData(unpackedFFTWDataSize(fftSize))
{
// We only allow power of two.
ASSERT(1UL << m_log2FFTSize == m_FFTSize);
// FFTW won't create a plan without being able to look at non-null
// pointers for the input and output data; it wants to be able to
// see whether these arrays are aligned properly for vector
// operations. Ideally we would use fftw_malloc and fftw_free for
// the input and output arrays to ensure proper alignment for SIMD
// operations, so that we don't have to specify FFTW_UNALIGNED
// when creating the plan. However, since we don't have control
// over the alignment of the array passed to doFFT / doInverseFFT,
// we would need to memcpy it in to or out of the FFTFrame, adding
// overhead. For the time being, we just assume unaligned data and
// pass a temporary pointer down.
// FIXME: we should probably allocate both the source and destination
// arrays in this class and memcpy the data in and out of them.
float temporary;
m_forwardPlan = fftwPlanForSize(fftSize, Forward,
&temporary, realData(), imagData());
m_backwardPlan = fftwPlanForSize(fftSize, Backward,
realData(), imagData(), &temporary);
}
// Creates a blank/empty frame (interpolate() must later be called).
FFTFrame::FFTFrame()
: m_FFTSize(0)
, m_log2FFTSize(0)
, m_forwardPlan(0)
, m_backwardPlan(0)
{
}
// Copy constructor.
FFTFrame::FFTFrame(const FFTFrame& frame)
: m_FFTSize(frame.m_FFTSize)
, m_log2FFTSize(frame.m_log2FFTSize)
, m_forwardPlan(0)
, m_backwardPlan(0)
, m_realData(unpackedFFTWDataSize(frame.m_FFTSize))
, m_imagData(unpackedFFTWDataSize(frame.m_FFTSize))
{
// See the normal constructor for an explanation of the temporary pointer.
float temporary;
m_forwardPlan = fftwPlanForSize(m_FFTSize, Forward,
&temporary, realData(), imagData());
m_backwardPlan = fftwPlanForSize(m_FFTSize, Backward,
realData(), imagData(), &temporary);
// Copy/setup frame data.
size_t nbytes = sizeof(float) * unpackedFFTWDataSize(fftSize());
memcpy(realData(), frame.realData(), nbytes);
memcpy(imagData(), frame.imagData(), nbytes);
}
FFTFrame::~FFTFrame()
{
}
void FFTFrame::multiply(const FFTFrame& frame)
{
FFTFrame& frame1 = *this;
FFTFrame& frame2 = const_cast<FFTFrame&>(frame);
float* realP1 = frame1.realData();
float* imagP1 = frame1.imagData();
const float* realP2 = frame2.realData();
const float* imagP2 = frame2.imagData();
// Scale accounts the peculiar scaling of vecLib on the Mac.
// This ensures the right scaling all the way back to inverse FFT.
// FIXME: if we change the scaling on the Mac then this scale
// factor will need to change too.
float scale = 0.5f;
// Multiply the packed DC/nyquist component
realP1[0] *= scale * realP2[0];
imagP1[0] *= scale * imagP2[0];
// Complex multiplication. If this loop turns out to be hot then
// we should use SSE or other intrinsics to accelerate it.
unsigned halfSize = fftSize() / 2;
for (unsigned i = 1; i < halfSize; ++i) {
float realResult = realP1[i] * realP2[i] - imagP1[i] * imagP2[i];
float imagResult = realP1[i] * imagP2[i] + imagP1[i] * realP2[i];
realP1[i] = scale * realResult;
imagP1[i] = scale * imagResult;
}
}
void FFTFrame::doFFT(float* data)
{
fftwf_execute_split_dft_r2c(m_forwardPlan, data, realData(), imagData());
// Scale the frequency domain data to match vecLib's scale factor
// on the Mac. FIXME: if we change the definition of FFTFrame to
// eliminate this scale factor then this code will need to change.
// Also, if this loop turns out to be hot then we should use SSE
// or other intrinsics to accelerate it.
float scaleFactor = 2;
unsigned length = unpackedFFTWDataSize(fftSize());
ASSERT(length == m_realData.size());
for (unsigned i = 0; i < length; ++i) {
m_realData[i] = m_realData[i] * scaleFactor;
m_imagData[i] = m_imagData[i] * scaleFactor;
}
// Move the Nyquist component to the location expected by the
// FFTFrame API.
m_imagData[0] = m_realData[length - 1];
}
void FFTFrame::doInverseFFT(float* data)
{
// Move the Nyquist component to the location expected by FFTW.
unsigned length = unpackedFFTWDataSize(fftSize());
ASSERT(length = m_realData.size());
m_realData[length - 1] = m_imagData[0];
m_imagData[0] = 0;
fftwf_execute_split_dft_c2r(m_backwardPlan, realData(), imagData(), data);
// Restore the original scaling of the time domain data.
// FIXME: if we change the definition of FFTFrame to eliminate the
// scale factor then this code will need to change. Also, if this
// loop turns out to be hot then we should use SSE or other
// intrinsics to accelerate it.
float scaleFactor = 1.0 / (2.0 * fftSize());
for (unsigned i = 0; i < length; ++i)
data[i] *= scaleFactor;
// Move the Nyquist component back to the location expected by the
// FFTFrame API.
m_imagData[0] = m_realData[length - 1];
}
void FFTFrame::cleanup()
{
if (!fftwForwardPlans)
return;
for (int i = 0; i < kMaxFFTPow2Size; ++i) {
if (fftwForwardPlans[i])
fftwf_destroy_plan(fftwForwardPlans[i]);
if (fftwBackwardPlans[i])
fftwf_destroy_plan(fftwBackwardPlans[i]);
}
delete[] fftwForwardPlans;
delete[] fftwBackwardPlans;
fftwForwardPlans = 0;
fftwBackwardPlans = 0;
}
float* FFTFrame::realData() const
{
return const_cast<float*>(m_realData.data());
}
float* FFTFrame::imagData() const
{
return const_cast<float*>(m_imagData.data());
}
fftwf_plan FFTFrame::fftwPlanForSize(unsigned fftSize, Direction direction,
float* data1, float* data2, float* data3)
{
if (!fftwForwardPlans) {
fftwForwardPlans = new fftwf_plan[kMaxFFTPow2Size];
fftwBackwardPlans = new fftwf_plan[kMaxFFTPow2Size];
for (int i = 0; i < kMaxFFTPow2Size; ++i) {
fftwForwardPlans[i] = 0;
fftwBackwardPlans[i] = 0;
}
}
ASSERT(fftSize);
int pow2size = static_cast<int>(log2(fftSize));
ASSERT(pow2size < kMaxFFTPow2Size);
fftwf_plan* plans = (direction == Forward) ? fftwForwardPlans : fftwBackwardPlans;
if (!plans[pow2size]) {
fftwf_iodim dimension;
dimension.n = fftSize;
dimension.is = 1;
dimension.os = 1;
// For the time being, we do not take the input data into
// account when choosing a plan, so that we can most easily
// reuse plans with different input data.
// FIXME: allocate input and output data inside this class to
// be able to take advantage of alignment and SIMD optimizations.
unsigned flags = FFTW_ESTIMATE | FFTW_PRESERVE_INPUT | FFTW_UNALIGNED;
switch (direction) {
case Forward:
plans[pow2size] = fftwf_plan_guru_split_dft_r2c(1, &dimension, 0, 0,
data1, data2, data3,
flags);
break;
case Backward:
plans[pow2size] = fftwf_plan_guru_split_dft_c2r(1, &dimension, 0, 0,
data1, data2, data3,
flags);
break;
}
}
ASSERT(plans[pow2size]);
return plans[pow2size];
}
} // namespace WebCore
#endif // ENABLE(WEB_AUDIO)
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment