Line data Source code
1 : /*
2 : * Copyright (C) 2010 Google Inc. All rights reserved.
3 : *
4 : * Redistribution and use in source and binary forms, with or without
5 : * modification, are permitted provided that the following conditions
6 : * are met:
7 : *
8 : * 1. Redistributions of source code must retain the above copyright
9 : * notice, this list of conditions and the following disclaimer.
10 : * 2. Redistributions in binary form must reproduce the above copyright
11 : * notice, this list of conditions and the following disclaimer in the
12 : * documentation and/or other materials provided with the distribution.
13 : * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 : * its contributors may be used to endorse or promote products derived
15 : * from this software without specific prior written permission.
16 : *
17 : * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 : * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 : * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 : * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 : * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 : * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 : * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 : * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 : * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 : * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 : */
28 :
29 : #include "ReverbConvolver.h"
30 : #include "ReverbConvolverStage.h"
31 :
32 : using namespace mozilla;
33 :
34 : namespace WebCore {
35 :
36 : const int InputBufferSize = 8 * 16384;
37 :
38 : // We only process the leading portion of the impulse response in the real-time thread. We don't exceed this length.
39 : // It turns out then, that the background thread has about 278msec of scheduling slop.
40 : // Empirically, this has been found to be a good compromise between giving enough time for scheduling slop,
41 : // while still minimizing the amount of processing done in the primary (high-priority) thread.
42 : // This was found to be a good value on Mac OS X, and may work well on other platforms as well, assuming
43 : // the very rough scheduling latencies are similar on these time-scales. Of course, this code may need to be
44 : // tuned for individual platforms if this assumption is found to be incorrect.
45 : const size_t RealtimeFrameLimit = 8192 + 4096 // ~278msec @ 44.1KHz
46 : - WEBAUDIO_BLOCK_SIZE;
47 : // First stage will have size MinFFTSize - successive stages will double in
48 : // size each time until we hit the maximum size.
49 : const size_t MinFFTSize = 256;
50 : // If we are using background threads then don't exceed this FFT size for the
51 : // stages which run in the real-time thread. This avoids having only one or
52 : // two large stages (size 16384 or so) at the end which take a lot of time
53 : // every several processing slices. This way we amortize the cost over more
54 : // processing slices.
55 : const size_t MaxRealtimeFFTSize = 4096;
56 :
57 0 : ReverbConvolver::ReverbConvolver(const float* impulseResponseData,
58 : size_t impulseResponseLength,
59 : size_t maxFFTSize,
60 : size_t convolverRenderPhase,
61 0 : bool useBackgroundThreads)
62 : : m_impulseResponseLength(impulseResponseLength)
63 : , m_accumulationBuffer(impulseResponseLength + WEBAUDIO_BLOCK_SIZE)
64 : , m_inputBuffer(InputBufferSize)
65 : , m_backgroundThread("ConvolverWorker")
66 : , m_backgroundThreadCondition(&m_backgroundThreadLock)
67 : , m_useBackgroundThreads(useBackgroundThreads)
68 : , m_wantsToExit(false)
69 0 : , m_moreInputBuffered(false)
70 : {
71 : // For the moment, a good way to know if we have real-time constraint is to check if we're using background threads.
72 : // Otherwise, assume we're being run from a command-line tool.
73 0 : bool hasRealtimeConstraint = useBackgroundThreads;
74 :
75 0 : const float* response = impulseResponseData;
76 0 : size_t totalResponseLength = impulseResponseLength;
77 :
78 : // The total latency is zero because the first FFT stage is small enough
79 : // to return output in the first block.
80 0 : size_t reverbTotalLatency = 0;
81 :
82 0 : size_t stageOffset = 0;
83 0 : size_t stagePhase = 0;
84 0 : size_t fftSize = MinFFTSize;
85 0 : while (stageOffset < totalResponseLength) {
86 0 : size_t stageSize = fftSize / 2;
87 :
88 : // For the last stage, it's possible that stageOffset is such that we're straddling the end
89 : // of the impulse response buffer (if we use stageSize), so reduce the last stage's length...
90 0 : if (stageSize + stageOffset > totalResponseLength) {
91 0 : stageSize = totalResponseLength - stageOffset;
92 : // Use smallest FFT that is large enough to cover the last stage.
93 0 : fftSize = MinFFTSize;
94 0 : while (stageSize * 2 > fftSize) {
95 0 : fftSize *= 2;
96 : }
97 : }
98 :
99 : // This "staggers" the time when each FFT happens so they don't all happen at the same time
100 0 : int renderPhase = convolverRenderPhase + stagePhase;
101 :
102 : nsAutoPtr<ReverbConvolverStage> stage
103 : (new ReverbConvolverStage(response, totalResponseLength,
104 : reverbTotalLatency, stageOffset, stageSize,
105 : fftSize, renderPhase,
106 0 : &m_accumulationBuffer));
107 :
108 0 : bool isBackgroundStage = false;
109 :
110 0 : if (this->useBackgroundThreads() && stageOffset > RealtimeFrameLimit) {
111 0 : m_backgroundStages.AppendElement(stage.forget());
112 0 : isBackgroundStage = true;
113 : } else
114 0 : m_stages.AppendElement(stage.forget());
115 :
116 : // Figure out next FFT size
117 0 : fftSize *= 2;
118 :
119 0 : stageOffset += stageSize;
120 :
121 0 : if (hasRealtimeConstraint && !isBackgroundStage
122 0 : && fftSize > MaxRealtimeFFTSize) {
123 0 : fftSize = MaxRealtimeFFTSize;
124 : // Custom phase positions for all but the first of the realtime
125 : // stages of largest size. These spread out the work of the
126 : // larger realtime stages. None of the FFTs of size 1024, 2048 or
127 : // 4096 are performed when processing the same block. The first
128 : // MaxRealtimeFFTSize = 4096 stage, at the end of the doubling,
129 : // performs its FFT at block 7. The FFTs of size 2048 are
130 : // performed in blocks 3 + 8 * n and size 1024 at 1 + 4 * n.
131 0 : const uint32_t phaseLookup[] = { 14, 0, 10, 4 };
132 0 : stagePhase = WEBAUDIO_BLOCK_SIZE *
133 0 : phaseLookup[m_stages.Length() % ArrayLength(phaseLookup)];
134 0 : } else if (fftSize > maxFFTSize) {
135 0 : fftSize = maxFFTSize;
136 : // A prime offset spreads out FFTs in a way that all
137 : // available phase positions will be used if there are sufficient
138 : // stages.
139 0 : stagePhase += 5 * WEBAUDIO_BLOCK_SIZE;
140 0 : } else if (stageSize > WEBAUDIO_BLOCK_SIZE) {
141 : // As the stages are doubling in size, the next FFT will occur
142 : // mid-way between FFTs for this stage.
143 0 : stagePhase = stageSize - WEBAUDIO_BLOCK_SIZE;
144 : }
145 : }
146 :
147 : // Start up background thread
148 : // FIXME: would be better to up the thread priority here. It doesn't need to be real-time, but higher than the default...
149 0 : if (this->useBackgroundThreads() && m_backgroundStages.Length() > 0) {
150 0 : if (!m_backgroundThread.Start()) {
151 0 : NS_WARNING("Cannot start convolver thread.");
152 0 : return;
153 : }
154 0 : m_backgroundThread.message_loop()->PostTask(NewNonOwningRunnableMethod(
155 : "WebCore::ReverbConvolver::backgroundThreadEntry",
156 : this,
157 0 : &ReverbConvolver::backgroundThreadEntry));
158 : }
159 : }
160 :
161 0 : ReverbConvolver::~ReverbConvolver()
162 : {
163 : // Wait for background thread to stop
164 0 : if (useBackgroundThreads() && m_backgroundThread.IsRunning()) {
165 0 : m_wantsToExit = true;
166 :
167 : // Wake up thread so it can return
168 : {
169 0 : AutoLock locker(m_backgroundThreadLock);
170 0 : m_moreInputBuffered = true;
171 0 : m_backgroundThreadCondition.Signal();
172 : }
173 :
174 0 : m_backgroundThread.Stop();
175 : }
176 0 : }
177 :
178 0 : size_t ReverbConvolver::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
179 : {
180 0 : size_t amount = aMallocSizeOf(this);
181 0 : amount += m_stages.ShallowSizeOfExcludingThis(aMallocSizeOf);
182 0 : for (size_t i = 0; i < m_stages.Length(); i++) {
183 0 : if (m_stages[i]) {
184 0 : amount += m_stages[i]->sizeOfIncludingThis(aMallocSizeOf);
185 : }
186 : }
187 :
188 0 : amount += m_backgroundStages.ShallowSizeOfExcludingThis(aMallocSizeOf);
189 0 : for (size_t i = 0; i < m_backgroundStages.Length(); i++) {
190 0 : if (m_backgroundStages[i]) {
191 0 : amount += m_backgroundStages[i]->sizeOfIncludingThis(aMallocSizeOf);
192 : }
193 : }
194 :
195 : // NB: The buffer sizes are static, so even though they might be accessed
196 : // in another thread it's safe to measure them.
197 0 : amount += m_accumulationBuffer.sizeOfExcludingThis(aMallocSizeOf);
198 0 : amount += m_inputBuffer.sizeOfExcludingThis(aMallocSizeOf);
199 :
200 : // Possible future measurements:
201 : // - m_backgroundThread
202 : // - m_backgroundThreadLock
203 : // - m_backgroundThreadCondition
204 0 : return amount;
205 : }
206 :
207 0 : void ReverbConvolver::backgroundThreadEntry()
208 : {
209 0 : while (!m_wantsToExit) {
210 : // Wait for realtime thread to give us more input
211 0 : m_moreInputBuffered = false;
212 : {
213 0 : AutoLock locker(m_backgroundThreadLock);
214 0 : while (!m_moreInputBuffered && !m_wantsToExit)
215 0 : m_backgroundThreadCondition.Wait();
216 : }
217 :
218 : // Process all of the stages until their read indices reach the input buffer's write index
219 0 : int writeIndex = m_inputBuffer.writeIndex();
220 :
221 : // Even though it doesn't seem like every stage needs to maintain its own version of readIndex
222 : // we do this in case we want to run in more than one background thread.
223 : int readIndex;
224 :
225 0 : while ((readIndex = m_backgroundStages[0]->inputReadIndex()) != writeIndex) { // FIXME: do better to detect buffer overrun...
226 : // Accumulate contributions from each stage
227 0 : for (size_t i = 0; i < m_backgroundStages.Length(); ++i)
228 0 : m_backgroundStages[i]->processInBackground(this);
229 : }
230 : }
231 0 : }
232 :
233 0 : void ReverbConvolver::process(const float* sourceChannelData,
234 : float* destinationChannelData)
235 : {
236 0 : const float* source = sourceChannelData;
237 0 : float* destination = destinationChannelData;
238 0 : bool isDataSafe = source && destination;
239 0 : MOZ_ASSERT(isDataSafe);
240 0 : if (!isDataSafe)
241 0 : return;
242 :
243 : // Feed input buffer (read by all threads)
244 0 : m_inputBuffer.write(source, WEBAUDIO_BLOCK_SIZE);
245 :
246 : // Accumulate contributions from each stage
247 0 : for (size_t i = 0; i < m_stages.Length(); ++i)
248 0 : m_stages[i]->process(source);
249 :
250 : // Finally read from accumulation buffer
251 0 : m_accumulationBuffer.readAndClear(destination, WEBAUDIO_BLOCK_SIZE);
252 :
253 : // Now that we've buffered more input, wake up our background thread.
254 :
255 : // Not using a MutexLocker looks strange, but we use a tryLock() instead because this is run on the real-time
256 : // thread where it is a disaster for the lock to be contended (causes audio glitching). It's OK if we fail to
257 : // signal from time to time, since we'll get to it the next time we're called. We're called repeatedly
258 : // and frequently (around every 3ms). The background thread is processing well into the future and has a considerable amount of
259 : // leeway here...
260 0 : if (m_backgroundThreadLock.Try()) {
261 0 : m_moreInputBuffered = true;
262 0 : m_backgroundThreadCondition.Signal();
263 0 : m_backgroundThreadLock.Release();
264 : }
265 : }
266 :
267 : } // namespace WebCore
|