diff options
author | Teng-Hui Zhu <ztenghui@google.com> | 2010-11-10 15:31:59 -0800 |
---|---|---|
committer | Teng-Hui Zhu <ztenghui@google.com> | 2010-11-17 13:35:59 -0800 |
commit | 28040489d744e0c5d475a88663056c9040ed5320 (patch) | |
tree | c463676791e4a63e452a95f0a12b2a8519730693 /WebCore/webaudio/RealtimeAnalyser.cpp | |
parent | eff9be92c41913c92fb1d3b7983c071f3e718678 (diff) | |
download | external_webkit-28040489d744e0c5d475a88663056c9040ed5320.zip external_webkit-28040489d744e0c5d475a88663056c9040ed5320.tar.gz external_webkit-28040489d744e0c5d475a88663056c9040ed5320.tar.bz2 |
Merge WebKit at r71558: Initial merge by git.
Change-Id: Ib345578fa29df7e4bc72b4f00e4a6fddcb754c4c
Diffstat (limited to 'WebCore/webaudio/RealtimeAnalyser.cpp')
-rw-r--r-- | WebCore/webaudio/RealtimeAnalyser.cpp | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/WebCore/webaudio/RealtimeAnalyser.cpp b/WebCore/webaudio/RealtimeAnalyser.cpp new file mode 100644 index 0000000..282f299 --- /dev/null +++ b/WebCore/webaudio/RealtimeAnalyser.cpp @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2010, 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 INC. 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 INC. 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. + */ + +#include "config.h" + +#if ENABLE(WEB_AUDIO) + +#include "RealtimeAnalyser.h" + +#include "AudioBus.h" +#include "AudioUtilities.h" +#include "FFTFrame.h" + +#if ENABLE(3D_CANVAS) +#include "Float32Array.h" +#include "Uint8Array.h" +#endif + +#include <algorithm> +#include <limits.h> +#include <wtf/Complex.h> +#include <wtf/Threading.h> + +using namespace std; + +namespace WebCore { + +const double RealtimeAnalyser::DefaultSmoothingTimeConstant = 0.8; +const double RealtimeAnalyser::DefaultMinDecibels = -100.0; +const double RealtimeAnalyser::DefaultMaxDecibels = -30.0; + +const unsigned RealtimeAnalyser::DefaultFFTSize = 2048; +const unsigned RealtimeAnalyser::MaxFFTSize = 2048; +const unsigned RealtimeAnalyser::InputBufferSize = RealtimeAnalyser::MaxFFTSize * 2; + +RealtimeAnalyser::RealtimeAnalyser() + : m_inputBuffer(InputBufferSize) + , m_writeIndex(0) + , m_fftSize(DefaultFFTSize) + , m_magnitudeBuffer(DefaultFFTSize / 2) + , m_smoothingTimeConstant(DefaultSmoothingTimeConstant) + , m_minDecibels(DefaultMinDecibels) + , m_maxDecibels(DefaultMaxDecibels) +{ + m_analysisFrame = adoptPtr(new FFTFrame(DefaultFFTSize)); +} + +RealtimeAnalyser::~RealtimeAnalyser() +{ +} + +void RealtimeAnalyser::reset() +{ + m_writeIndex = 0; + m_inputBuffer.zero(); + m_magnitudeBuffer.zero(); +} + +void RealtimeAnalyser::setFftSize(size_t size) +{ + ASSERT(isMainThread()); + + // Only allow powers of two. + unsigned log2size = static_cast<unsigned>(log2(size)); + bool isPOT(1UL << log2size == size); + + if (!isPOT || size > MaxFFTSize) { + // FIXME: It would be good to also set an exception. + return; + } + + if (m_fftSize != size) { + m_analysisFrame = adoptPtr(new FFTFrame(m_fftSize)); + m_magnitudeBuffer.resize(size); + m_fftSize = size; + } +} + +void RealtimeAnalyser::writeInput(AudioBus* bus, size_t framesToProcess) +{ + bool isBusGood = bus && bus->numberOfChannels() > 0 && bus->channel(0)->length() >= framesToProcess; + ASSERT(isBusGood); + if (!isBusGood) + return; + + // FIXME : allow to work with non-FFTSize divisible chunking + bool isDestinationGood = m_writeIndex < m_inputBuffer.size() && m_writeIndex + framesToProcess <= m_inputBuffer.size(); + ASSERT(isDestinationGood); + if (!isDestinationGood) + return; + + // Perform real-time analysis + // FIXME : for now just use left channel (must mix if stereo source) + float* source = bus->channel(0)->data(); + + // The source has already been sanity checked with isBusGood above. + + memcpy(m_inputBuffer.data() + m_writeIndex, source, sizeof(float) * framesToProcess); + + m_writeIndex += framesToProcess; + if (m_writeIndex >= InputBufferSize) + m_writeIndex = 0; +} + +namespace { + +void applyWindow(float* p, size_t n) +{ + ASSERT(isMainThread()); + + // Blackman window + double alpha = 0.16; + double a0 = 0.5 * (1.0 - alpha); + double a1 = 0.5; + double a2 = 0.5 * alpha; + + for (unsigned i = 0; i < n; ++i) { + double x = static_cast<double>(i) / static_cast<double>(n); + double window = a0 - a1 * cos(2.0 * M_PI * x) + a2 * cos(4.0 * M_PI * x); + p[i] *= float(window); + } +} + +} // namespace + +void RealtimeAnalyser::doFFTAnalysis() +{ + ASSERT(isMainThread()); + + // Unroll the input buffer into a temporary buffer, where we'll apply an analysis window followed by an FFT. + size_t fftSize = this->fftSize(); + + AudioFloatArray temporaryBuffer(fftSize); + float* inputBuffer = m_inputBuffer.data(); + float* tempP = temporaryBuffer.data(); + + // Take the previous fftSize values from the input buffer and copy into the temporary buffer. + // FIXME : optimize with memcpy(). + unsigned writeIndex = m_writeIndex; + for (unsigned i = 0; i < fftSize; ++i) + tempP[i] = inputBuffer[(i + writeIndex - fftSize + InputBufferSize) % InputBufferSize]; + + // Window the input samples. + applyWindow(tempP, fftSize); + + // Do the analysis. + m_analysisFrame->doFFT(tempP); + + size_t n = DefaultFFTSize / 2; + + float* realP = m_analysisFrame->realData(); + float* imagP = m_analysisFrame->imagData(); + + // Blow away the packed nyquist component. + imagP[0] = 0.0f; + + // Normalize so than an input sine wave at 0dBfs registers as 0dBfs (undo FFT scaling factor). + const double MagnitudeScale = 1.0 / DefaultFFTSize; + + // A value of 0 does no averaging with the previous result. Larger values produce slower, but smoother changes. + double k = m_smoothingTimeConstant; + k = max(0.0, k); + k = min(1.0, k); + + // Convert the analysis data from complex to magnitude and average with the previous result. + float* destination = magnitudeBuffer().data(); + for (unsigned i = 0; i < n; ++i) { + Complex c(realP[i], imagP[i]); + double scalarMagnitude = abs(c) * MagnitudeScale; + destination[i] = float(k * destination[i] + (1.0 - k) * scalarMagnitude); + } +} + +#if ENABLE(3D_CANVAS) + +void RealtimeAnalyser::getFloatFrequencyData(Float32Array* destinationArray) +{ + ASSERT(isMainThread()); + + if (!destinationArray) + return; + + doFFTAnalysis(); + + // Convert from linear magnitude to floating-point decibels. + const double MinDecibels = m_minDecibels; + unsigned sourceLength = magnitudeBuffer().size(); + size_t len = min(sourceLength, destinationArray->length()); + if (len > 0) { + const float* source = magnitudeBuffer().data(); + float* destination = destinationArray->data(); + + for (unsigned i = 0; i < len; ++i) { + float linearValue = source[i]; + double dbMag = !linearValue ? MinDecibels : AudioUtilities::linearToDecibels(linearValue); + destination[i] = float(dbMag); + } + } +} + +void RealtimeAnalyser::getByteFrequencyData(Uint8Array* destinationArray) +{ + ASSERT(isMainThread()); + + if (!destinationArray) + return; + + doFFTAnalysis(); + + // Convert from linear magnitude to unsigned-byte decibels. + unsigned sourceLength = magnitudeBuffer().size(); + size_t len = min(sourceLength, destinationArray->length()); + if (len > 0) { + const double RangeScaleFactor = m_maxDecibels == m_minDecibels ? 1.0 : 1.0 / (m_maxDecibels - m_minDecibels); + + const float* source = magnitudeBuffer().data(); + unsigned char* destination = destinationArray->data(); + + for (unsigned i = 0; i < len; ++i) { + float linearValue = source[i]; + double dbMag = !linearValue ? m_minDecibels : AudioUtilities::linearToDecibels(linearValue); + + // The range m_minDecibels to m_maxDecibels will be scaled to byte values from 0 to UCHAR_MAX. + double scaledValue = UCHAR_MAX * (dbMag - m_minDecibels) * RangeScaleFactor; + + // Clip to valid range. + if (scaledValue < 0.0) + scaledValue = 0.0; + if (scaledValue > UCHAR_MAX) + scaledValue = UCHAR_MAX; + + destination[i] = static_cast<unsigned char>(scaledValue); + } + } +} + +void RealtimeAnalyser::getByteTimeDomainData(Uint8Array* destinationArray) +{ + ASSERT(isMainThread()); + + if (!destinationArray) + return; + + unsigned fftSize = this->fftSize(); + size_t len = min(fftSize, destinationArray->length()); + if (len > 0) { + bool isInputBufferGood = m_inputBuffer.size() == InputBufferSize && m_inputBuffer.size() > fftSize; + ASSERT(isInputBufferGood); + if (!isInputBufferGood) + return; + + float* inputBuffer = m_inputBuffer.data(); + unsigned char* destination = destinationArray->data(); + + unsigned writeIndex = m_writeIndex; + + for (unsigned i = 0; i < len; ++i) { + // Buffer access is protected due to modulo operation. + float value = inputBuffer[(i + writeIndex - fftSize + InputBufferSize) % InputBufferSize]; + + // Scale from nominal -1.0 -> +1.0 to unsigned byte. + double scaledValue = 128.0 * (value + 1.0); + + // Clip to valid range. + if (scaledValue < 0.0) + scaledValue = 0.0; + if (scaledValue > UCHAR_MAX) + scaledValue = UCHAR_MAX; + + destination[i] = static_cast<unsigned char>(scaledValue); + } + } +} + +#endif // 3D_CANVAS + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) |