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