content/media/webaudio/blink/ReverbConvolver.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/content/media/webaudio/blink/ReverbConvolver.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,269 @@
     1.4 +/*
     1.5 + * Copyright (C) 2010 Google Inc. All rights reserved.
     1.6 + *
     1.7 + * Redistribution and use in source and binary forms, with or without
     1.8 + * modification, are permitted provided that the following conditions
     1.9 + * are met:
    1.10 + *
    1.11 + * 1.  Redistributions of source code must retain the above copyright
    1.12 + *     notice, this list of conditions and the following disclaimer.
    1.13 + * 2.  Redistributions in binary form must reproduce the above copyright
    1.14 + *     notice, this list of conditions and the following disclaimer in the
    1.15 + *     documentation and/or other materials provided with the distribution.
    1.16 + * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
    1.17 + *     its contributors may be used to endorse or promote products derived
    1.18 + *     from this software without specific prior written permission.
    1.19 + *
    1.20 + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
    1.21 + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    1.22 + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    1.23 + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
    1.24 + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    1.25 + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    1.26 + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    1.27 + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    1.28 + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    1.29 + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    1.30 + */
    1.31 +
    1.32 +#include "ReverbConvolver.h"
    1.33 +#include "ReverbConvolverStage.h"
    1.34 +
    1.35 +using namespace mozilla;
    1.36 +
    1.37 +template<>
    1.38 +struct RunnableMethodTraits<WebCore::ReverbConvolver>
    1.39 +{
    1.40 +  static void RetainCallee(WebCore::ReverbConvolver* obj) {}
    1.41 +  static void ReleaseCallee(WebCore::ReverbConvolver* obj) {}
    1.42 +};
    1.43 +
    1.44 +namespace WebCore {
    1.45 +
    1.46 +const int InputBufferSize = 8 * 16384;
    1.47 +
    1.48 +// We only process the leading portion of the impulse response in the real-time thread.  We don't exceed this length.
    1.49 +// It turns out then, that the background thread has about 278msec of scheduling slop.
    1.50 +// Empirically, this has been found to be a good compromise between giving enough time for scheduling slop,
    1.51 +// while still minimizing the amount of processing done in the primary (high-priority) thread.
    1.52 +// This was found to be a good value on Mac OS X, and may work well on other platforms as well, assuming
    1.53 +// the very rough scheduling latencies are similar on these time-scales.  Of course, this code may need to be
    1.54 +// tuned for individual platforms if this assumption is found to be incorrect.
    1.55 +const size_t RealtimeFrameLimit = 8192  + 4096; // ~278msec @ 44.1KHz
    1.56 +
    1.57 +const size_t MinFFTSize = 128;
    1.58 +const size_t MaxRealtimeFFTSize = 2048;
    1.59 +
    1.60 +ReverbConvolver::ReverbConvolver(const float* impulseResponseData, size_t impulseResponseLength, size_t renderSliceSize, size_t maxFFTSize, size_t convolverRenderPhase, bool useBackgroundThreads)
    1.61 +    : m_impulseResponseLength(impulseResponseLength)
    1.62 +    , m_accumulationBuffer(impulseResponseLength + renderSliceSize)
    1.63 +    , m_inputBuffer(InputBufferSize)
    1.64 +    , m_minFFTSize(MinFFTSize) // First stage will have this size - successive stages will double in size each time
    1.65 +    , m_maxFFTSize(maxFFTSize) // until we hit m_maxFFTSize
    1.66 +    , m_backgroundThread("ConvolverWorker")
    1.67 +    , m_backgroundThreadCondition(&m_backgroundThreadLock)
    1.68 +    , m_useBackgroundThreads(useBackgroundThreads)
    1.69 +    , m_wantsToExit(false)
    1.70 +    , m_moreInputBuffered(false)
    1.71 +{
    1.72 +    // If we are using background threads then don't exceed this FFT size for the
    1.73 +    // stages which run in the real-time thread.  This avoids having only one or two
    1.74 +    // large stages (size 16384 or so) at the end which take a lot of time every several
    1.75 +    // processing slices.  This way we amortize the cost over more processing slices.
    1.76 +    m_maxRealtimeFFTSize = MaxRealtimeFFTSize;
    1.77 +
    1.78 +    // For the moment, a good way to know if we have real-time constraint is to check if we're using background threads.
    1.79 +    // Otherwise, assume we're being run from a command-line tool.
    1.80 +    bool hasRealtimeConstraint = useBackgroundThreads;
    1.81 +
    1.82 +    const float* response = impulseResponseData;
    1.83 +    size_t totalResponseLength = impulseResponseLength;
    1.84 +
    1.85 +    // The total latency is zero because the direct-convolution is used in the leading portion.
    1.86 +    size_t reverbTotalLatency = 0;
    1.87 +
    1.88 +    size_t stageOffset = 0;
    1.89 +    int i = 0;
    1.90 +    size_t fftSize = m_minFFTSize;
    1.91 +    while (stageOffset < totalResponseLength) {
    1.92 +        size_t stageSize = fftSize / 2;
    1.93 +
    1.94 +        // For the last stage, it's possible that stageOffset is such that we're straddling the end
    1.95 +        // of the impulse response buffer (if we use stageSize), so reduce the last stage's length...
    1.96 +        if (stageSize + stageOffset > totalResponseLength)
    1.97 +            stageSize = totalResponseLength - stageOffset;
    1.98 +
    1.99 +        // This "staggers" the time when each FFT happens so they don't all happen at the same time
   1.100 +        int renderPhase = convolverRenderPhase + i * renderSliceSize;
   1.101 +
   1.102 +        bool useDirectConvolver = !stageOffset;
   1.103 +
   1.104 +        nsAutoPtr<ReverbConvolverStage> stage(new ReverbConvolverStage(response, totalResponseLength, reverbTotalLatency, stageOffset, stageSize, fftSize, renderPhase, renderSliceSize, &m_accumulationBuffer, useDirectConvolver));
   1.105 +
   1.106 +        bool isBackgroundStage = false;
   1.107 +
   1.108 +        if (this->useBackgroundThreads() && stageOffset > RealtimeFrameLimit) {
   1.109 +            m_backgroundStages.AppendElement(stage.forget());
   1.110 +            isBackgroundStage = true;
   1.111 +        } else
   1.112 +            m_stages.AppendElement(stage.forget());
   1.113 +
   1.114 +        stageOffset += stageSize;
   1.115 +        ++i;
   1.116 +
   1.117 +        if (!useDirectConvolver) {
   1.118 +            // Figure out next FFT size
   1.119 +            fftSize *= 2;
   1.120 +        }
   1.121 +
   1.122 +        if (hasRealtimeConstraint && !isBackgroundStage && fftSize > m_maxRealtimeFFTSize)
   1.123 +            fftSize = m_maxRealtimeFFTSize;
   1.124 +        if (fftSize > m_maxFFTSize)
   1.125 +            fftSize = m_maxFFTSize;
   1.126 +    }
   1.127 +
   1.128 +    // Start up background thread
   1.129 +    // FIXME: would be better to up the thread priority here.  It doesn't need to be real-time, but higher than the default...
   1.130 +    if (this->useBackgroundThreads() && m_backgroundStages.Length() > 0) {
   1.131 +        if (!m_backgroundThread.Start()) {
   1.132 +          NS_WARNING("Cannot start convolver thread.");
   1.133 +          return;
   1.134 +        }
   1.135 +        CancelableTask* task = NewRunnableMethod(this, &ReverbConvolver::backgroundThreadEntry);
   1.136 +        m_backgroundThread.message_loop()->PostTask(FROM_HERE, task);
   1.137 +    }
   1.138 +}
   1.139 +
   1.140 +ReverbConvolver::~ReverbConvolver()
   1.141 +{
   1.142 +    // Wait for background thread to stop
   1.143 +    if (useBackgroundThreads() && m_backgroundThread.IsRunning()) {
   1.144 +        m_wantsToExit = true;
   1.145 +
   1.146 +        // Wake up thread so it can return
   1.147 +        {
   1.148 +            AutoLock locker(m_backgroundThreadLock);
   1.149 +            m_moreInputBuffered = true;
   1.150 +            m_backgroundThreadCondition.Signal();
   1.151 +        }
   1.152 +
   1.153 +        m_backgroundThread.Stop();
   1.154 +    }
   1.155 +}
   1.156 +
   1.157 +size_t ReverbConvolver::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
   1.158 +{
   1.159 +    size_t amount = aMallocSizeOf(this);
   1.160 +    amount += m_stages.SizeOfExcludingThis(aMallocSizeOf);
   1.161 +    for (size_t i = 0; i < m_stages.Length(); i++) {
   1.162 +        if (m_stages[i]) {
   1.163 +            amount += m_stages[i]->sizeOfIncludingThis(aMallocSizeOf);
   1.164 +        }
   1.165 +    }
   1.166 +
   1.167 +    amount += m_backgroundStages.SizeOfExcludingThis(aMallocSizeOf);
   1.168 +    for (size_t i = 0; i < m_backgroundStages.Length(); i++) {
   1.169 +        if (m_backgroundStages[i]) {
   1.170 +            amount += m_backgroundStages[i]->sizeOfIncludingThis(aMallocSizeOf);
   1.171 +        }
   1.172 +    }
   1.173 +
   1.174 +    // NB: The buffer sizes are static, so even though they might be accessed
   1.175 +    //     in another thread it's safe to measure them.
   1.176 +    amount += m_accumulationBuffer.sizeOfExcludingThis(aMallocSizeOf);
   1.177 +    amount += m_inputBuffer.sizeOfExcludingThis(aMallocSizeOf);
   1.178 +
   1.179 +    // Possible future measurements:
   1.180 +    // - m_backgroundThread
   1.181 +    // - m_backgroundThreadLock
   1.182 +    // - m_backgroundThreadCondition
   1.183 +    return amount;
   1.184 +}
   1.185 +
   1.186 +void ReverbConvolver::backgroundThreadEntry()
   1.187 +{
   1.188 +    while (!m_wantsToExit) {
   1.189 +        // Wait for realtime thread to give us more input
   1.190 +        m_moreInputBuffered = false;
   1.191 +        {
   1.192 +            AutoLock locker(m_backgroundThreadLock);
   1.193 +            while (!m_moreInputBuffered && !m_wantsToExit)
   1.194 +                m_backgroundThreadCondition.Wait();
   1.195 +        }
   1.196 +
   1.197 +        // Process all of the stages until their read indices reach the input buffer's write index
   1.198 +        int writeIndex = m_inputBuffer.writeIndex();
   1.199 +
   1.200 +        // Even though it doesn't seem like every stage needs to maintain its own version of readIndex 
   1.201 +        // we do this in case we want to run in more than one background thread.
   1.202 +        int readIndex;
   1.203 +
   1.204 +        while ((readIndex = m_backgroundStages[0]->inputReadIndex()) != writeIndex) { // FIXME: do better to detect buffer overrun...
   1.205 +            // The ReverbConvolverStages need to process in amounts which evenly divide half the FFT size
   1.206 +            const int SliceSize = MinFFTSize / 2;
   1.207 +
   1.208 +            // Accumulate contributions from each stage
   1.209 +            for (size_t i = 0; i < m_backgroundStages.Length(); ++i)
   1.210 +                m_backgroundStages[i]->processInBackground(this, SliceSize);
   1.211 +        }
   1.212 +    }
   1.213 +}
   1.214 +
   1.215 +void ReverbConvolver::process(const float* sourceChannelData, size_t sourceChannelLength,
   1.216 +                              float* destinationChannelData, size_t destinationChannelLength,
   1.217 +                              size_t framesToProcess)
   1.218 +{
   1.219 +    bool isSafe = sourceChannelData && destinationChannelData && sourceChannelLength >= framesToProcess && destinationChannelLength >= framesToProcess;
   1.220 +    MOZ_ASSERT(isSafe);
   1.221 +    if (!isSafe)
   1.222 +        return;
   1.223 +
   1.224 +    const float* source = sourceChannelData;
   1.225 +    float* destination = destinationChannelData;
   1.226 +    bool isDataSafe = source && destination;
   1.227 +    MOZ_ASSERT(isDataSafe);
   1.228 +    if (!isDataSafe)
   1.229 +        return;
   1.230 +
   1.231 +    // Feed input buffer (read by all threads)
   1.232 +    m_inputBuffer.write(source, framesToProcess);
   1.233 +
   1.234 +    // Accumulate contributions from each stage
   1.235 +    for (size_t i = 0; i < m_stages.Length(); ++i)
   1.236 +        m_stages[i]->process(source, framesToProcess);
   1.237 +
   1.238 +    // Finally read from accumulation buffer
   1.239 +    m_accumulationBuffer.readAndClear(destination, framesToProcess);
   1.240 +
   1.241 +    // Now that we've buffered more input, wake up our background thread.
   1.242 +
   1.243 +    // Not using a MutexLocker looks strange, but we use a tryLock() instead because this is run on the real-time
   1.244 +    // thread where it is a disaster for the lock to be contended (causes audio glitching).  It's OK if we fail to
   1.245 +    // signal from time to time, since we'll get to it the next time we're called.  We're called repeatedly
   1.246 +    // and frequently (around every 3ms).  The background thread is processing well into the future and has a considerable amount of 
   1.247 +    // leeway here...
   1.248 +    if (m_backgroundThreadLock.Try()) {
   1.249 +        m_moreInputBuffered = true;
   1.250 +        m_backgroundThreadCondition.Signal();
   1.251 +        m_backgroundThreadLock.Release();
   1.252 +    }
   1.253 +}
   1.254 +
   1.255 +void ReverbConvolver::reset()
   1.256 +{
   1.257 +    for (size_t i = 0; i < m_stages.Length(); ++i)
   1.258 +        m_stages[i]->reset();
   1.259 +
   1.260 +    for (size_t i = 0; i < m_backgroundStages.Length(); ++i)
   1.261 +        m_backgroundStages[i]->reset();
   1.262 +
   1.263 +    m_accumulationBuffer.reset();
   1.264 +    m_inputBuffer.reset();
   1.265 +}
   1.266 +
   1.267 +size_t ReverbConvolver::latencyFrames() const
   1.268 +{
   1.269 +    return 0;
   1.270 +}
   1.271 +
   1.272 +} // namespace WebCore

mercurial