2011-01-24 Kenneth Russell <kbr@google.com>
authorkbr@google.com <kbr@google.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 25 Jan 2011 02:39:50 +0000 (02:39 +0000)
committerkbr@google.com <kbr@google.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 25 Jan 2011 02:39:50 +0000 (02:39 +0000)
        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: https://svn.webkit.org/repository/webkit/trunk@76562 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/WebCore.gyp/WebCore.gyp
Source/WebCore/WebCore.gypi
Source/WebCore/platform/audio/FFTFrame.h
Source/WebCore/platform/audio/fftw/FFTFrameFFTW.cpp [new file with mode: 0644]

index 5de637b..053094c 100644 (file)
@@ -1,3 +1,30 @@
+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.
index f96753c..2594d29 100644 (file)
           },
         }],
         # 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',
+          ],
+        }],
       ],
     },
     {
             ],
           },
         }],
+        ['(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',
+            ],
+          },
+        }],
       ],
     },
     {
         # 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.
             ['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$'],
+          ],
+        }],
       ],
     },
     {
             },
           },
         }],
+        ['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',
index 6198c1f..ac6fe6b 100644 (file)
             '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',
index 1a82ef0..bfaa98d 100644 (file)
 #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
diff --git a/Source/WebCore/platform/audio/fftw/FFTFrameFFTW.cpp b/Source/WebCore/platform/audio/fftw/FFTFrameFFTW.cpp
new file mode 100644 (file)
index 0000000..c1f7ad1
--- /dev/null
@@ -0,0 +1,277 @@
+/*
+ * 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)