|
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 template<> |
|
35 struct RunnableMethodTraits<WebCore::ReverbConvolver> |
|
36 { |
|
37 static void RetainCallee(WebCore::ReverbConvolver* obj) {} |
|
38 static void ReleaseCallee(WebCore::ReverbConvolver* obj) {} |
|
39 }; |
|
40 |
|
41 namespace WebCore { |
|
42 |
|
43 const int InputBufferSize = 8 * 16384; |
|
44 |
|
45 // We only process the leading portion of the impulse response in the real-time thread. We don't exceed this length. |
|
46 // It turns out then, that the background thread has about 278msec of scheduling slop. |
|
47 // Empirically, this has been found to be a good compromise between giving enough time for scheduling slop, |
|
48 // while still minimizing the amount of processing done in the primary (high-priority) thread. |
|
49 // This was found to be a good value on Mac OS X, and may work well on other platforms as well, assuming |
|
50 // the very rough scheduling latencies are similar on these time-scales. Of course, this code may need to be |
|
51 // tuned for individual platforms if this assumption is found to be incorrect. |
|
52 const size_t RealtimeFrameLimit = 8192 + 4096; // ~278msec @ 44.1KHz |
|
53 |
|
54 const size_t MinFFTSize = 128; |
|
55 const size_t MaxRealtimeFFTSize = 2048; |
|
56 |
|
57 ReverbConvolver::ReverbConvolver(const float* impulseResponseData, size_t impulseResponseLength, size_t renderSliceSize, size_t maxFFTSize, size_t convolverRenderPhase, bool useBackgroundThreads) |
|
58 : m_impulseResponseLength(impulseResponseLength) |
|
59 , m_accumulationBuffer(impulseResponseLength + renderSliceSize) |
|
60 , m_inputBuffer(InputBufferSize) |
|
61 , m_minFFTSize(MinFFTSize) // First stage will have this size - successive stages will double in size each time |
|
62 , m_maxFFTSize(maxFFTSize) // until we hit m_maxFFTSize |
|
63 , m_backgroundThread("ConvolverWorker") |
|
64 , m_backgroundThreadCondition(&m_backgroundThreadLock) |
|
65 , m_useBackgroundThreads(useBackgroundThreads) |
|
66 , m_wantsToExit(false) |
|
67 , m_moreInputBuffered(false) |
|
68 { |
|
69 // If we are using background threads then don't exceed this FFT size for the |
|
70 // stages which run in the real-time thread. This avoids having only one or two |
|
71 // large stages (size 16384 or so) at the end which take a lot of time every several |
|
72 // processing slices. This way we amortize the cost over more processing slices. |
|
73 m_maxRealtimeFFTSize = MaxRealtimeFFTSize; |
|
74 |
|
75 // For the moment, a good way to know if we have real-time constraint is to check if we're using background threads. |
|
76 // Otherwise, assume we're being run from a command-line tool. |
|
77 bool hasRealtimeConstraint = useBackgroundThreads; |
|
78 |
|
79 const float* response = impulseResponseData; |
|
80 size_t totalResponseLength = impulseResponseLength; |
|
81 |
|
82 // The total latency is zero because the direct-convolution is used in the leading portion. |
|
83 size_t reverbTotalLatency = 0; |
|
84 |
|
85 size_t stageOffset = 0; |
|
86 int i = 0; |
|
87 size_t fftSize = m_minFFTSize; |
|
88 while (stageOffset < totalResponseLength) { |
|
89 size_t stageSize = fftSize / 2; |
|
90 |
|
91 // For the last stage, it's possible that stageOffset is such that we're straddling the end |
|
92 // of the impulse response buffer (if we use stageSize), so reduce the last stage's length... |
|
93 if (stageSize + stageOffset > totalResponseLength) |
|
94 stageSize = totalResponseLength - stageOffset; |
|
95 |
|
96 // This "staggers" the time when each FFT happens so they don't all happen at the same time |
|
97 int renderPhase = convolverRenderPhase + i * renderSliceSize; |
|
98 |
|
99 bool useDirectConvolver = !stageOffset; |
|
100 |
|
101 nsAutoPtr<ReverbConvolverStage> stage(new ReverbConvolverStage(response, totalResponseLength, reverbTotalLatency, stageOffset, stageSize, fftSize, renderPhase, renderSliceSize, &m_accumulationBuffer, useDirectConvolver)); |
|
102 |
|
103 bool isBackgroundStage = false; |
|
104 |
|
105 if (this->useBackgroundThreads() && stageOffset > RealtimeFrameLimit) { |
|
106 m_backgroundStages.AppendElement(stage.forget()); |
|
107 isBackgroundStage = true; |
|
108 } else |
|
109 m_stages.AppendElement(stage.forget()); |
|
110 |
|
111 stageOffset += stageSize; |
|
112 ++i; |
|
113 |
|
114 if (!useDirectConvolver) { |
|
115 // Figure out next FFT size |
|
116 fftSize *= 2; |
|
117 } |
|
118 |
|
119 if (hasRealtimeConstraint && !isBackgroundStage && fftSize > m_maxRealtimeFFTSize) |
|
120 fftSize = m_maxRealtimeFFTSize; |
|
121 if (fftSize > m_maxFFTSize) |
|
122 fftSize = m_maxFFTSize; |
|
123 } |
|
124 |
|
125 // Start up background thread |
|
126 // FIXME: would be better to up the thread priority here. It doesn't need to be real-time, but higher than the default... |
|
127 if (this->useBackgroundThreads() && m_backgroundStages.Length() > 0) { |
|
128 if (!m_backgroundThread.Start()) { |
|
129 NS_WARNING("Cannot start convolver thread."); |
|
130 return; |
|
131 } |
|
132 CancelableTask* task = NewRunnableMethod(this, &ReverbConvolver::backgroundThreadEntry); |
|
133 m_backgroundThread.message_loop()->PostTask(FROM_HERE, task); |
|
134 } |
|
135 } |
|
136 |
|
137 ReverbConvolver::~ReverbConvolver() |
|
138 { |
|
139 // Wait for background thread to stop |
|
140 if (useBackgroundThreads() && m_backgroundThread.IsRunning()) { |
|
141 m_wantsToExit = true; |
|
142 |
|
143 // Wake up thread so it can return |
|
144 { |
|
145 AutoLock locker(m_backgroundThreadLock); |
|
146 m_moreInputBuffered = true; |
|
147 m_backgroundThreadCondition.Signal(); |
|
148 } |
|
149 |
|
150 m_backgroundThread.Stop(); |
|
151 } |
|
152 } |
|
153 |
|
154 size_t ReverbConvolver::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const |
|
155 { |
|
156 size_t amount = aMallocSizeOf(this); |
|
157 amount += m_stages.SizeOfExcludingThis(aMallocSizeOf); |
|
158 for (size_t i = 0; i < m_stages.Length(); i++) { |
|
159 if (m_stages[i]) { |
|
160 amount += m_stages[i]->sizeOfIncludingThis(aMallocSizeOf); |
|
161 } |
|
162 } |
|
163 |
|
164 amount += m_backgroundStages.SizeOfExcludingThis(aMallocSizeOf); |
|
165 for (size_t i = 0; i < m_backgroundStages.Length(); i++) { |
|
166 if (m_backgroundStages[i]) { |
|
167 amount += m_backgroundStages[i]->sizeOfIncludingThis(aMallocSizeOf); |
|
168 } |
|
169 } |
|
170 |
|
171 // NB: The buffer sizes are static, so even though they might be accessed |
|
172 // in another thread it's safe to measure them. |
|
173 amount += m_accumulationBuffer.sizeOfExcludingThis(aMallocSizeOf); |
|
174 amount += m_inputBuffer.sizeOfExcludingThis(aMallocSizeOf); |
|
175 |
|
176 // Possible future measurements: |
|
177 // - m_backgroundThread |
|
178 // - m_backgroundThreadLock |
|
179 // - m_backgroundThreadCondition |
|
180 return amount; |
|
181 } |
|
182 |
|
183 void ReverbConvolver::backgroundThreadEntry() |
|
184 { |
|
185 while (!m_wantsToExit) { |
|
186 // Wait for realtime thread to give us more input |
|
187 m_moreInputBuffered = false; |
|
188 { |
|
189 AutoLock locker(m_backgroundThreadLock); |
|
190 while (!m_moreInputBuffered && !m_wantsToExit) |
|
191 m_backgroundThreadCondition.Wait(); |
|
192 } |
|
193 |
|
194 // Process all of the stages until their read indices reach the input buffer's write index |
|
195 int writeIndex = m_inputBuffer.writeIndex(); |
|
196 |
|
197 // Even though it doesn't seem like every stage needs to maintain its own version of readIndex |
|
198 // we do this in case we want to run in more than one background thread. |
|
199 int readIndex; |
|
200 |
|
201 while ((readIndex = m_backgroundStages[0]->inputReadIndex()) != writeIndex) { // FIXME: do better to detect buffer overrun... |
|
202 // The ReverbConvolverStages need to process in amounts which evenly divide half the FFT size |
|
203 const int SliceSize = MinFFTSize / 2; |
|
204 |
|
205 // Accumulate contributions from each stage |
|
206 for (size_t i = 0; i < m_backgroundStages.Length(); ++i) |
|
207 m_backgroundStages[i]->processInBackground(this, SliceSize); |
|
208 } |
|
209 } |
|
210 } |
|
211 |
|
212 void ReverbConvolver::process(const float* sourceChannelData, size_t sourceChannelLength, |
|
213 float* destinationChannelData, size_t destinationChannelLength, |
|
214 size_t framesToProcess) |
|
215 { |
|
216 bool isSafe = sourceChannelData && destinationChannelData && sourceChannelLength >= framesToProcess && destinationChannelLength >= framesToProcess; |
|
217 MOZ_ASSERT(isSafe); |
|
218 if (!isSafe) |
|
219 return; |
|
220 |
|
221 const float* source = sourceChannelData; |
|
222 float* destination = destinationChannelData; |
|
223 bool isDataSafe = source && destination; |
|
224 MOZ_ASSERT(isDataSafe); |
|
225 if (!isDataSafe) |
|
226 return; |
|
227 |
|
228 // Feed input buffer (read by all threads) |
|
229 m_inputBuffer.write(source, framesToProcess); |
|
230 |
|
231 // Accumulate contributions from each stage |
|
232 for (size_t i = 0; i < m_stages.Length(); ++i) |
|
233 m_stages[i]->process(source, framesToProcess); |
|
234 |
|
235 // Finally read from accumulation buffer |
|
236 m_accumulationBuffer.readAndClear(destination, framesToProcess); |
|
237 |
|
238 // Now that we've buffered more input, wake up our background thread. |
|
239 |
|
240 // Not using a MutexLocker looks strange, but we use a tryLock() instead because this is run on the real-time |
|
241 // thread where it is a disaster for the lock to be contended (causes audio glitching). It's OK if we fail to |
|
242 // signal from time to time, since we'll get to it the next time we're called. We're called repeatedly |
|
243 // and frequently (around every 3ms). The background thread is processing well into the future and has a considerable amount of |
|
244 // leeway here... |
|
245 if (m_backgroundThreadLock.Try()) { |
|
246 m_moreInputBuffered = true; |
|
247 m_backgroundThreadCondition.Signal(); |
|
248 m_backgroundThreadLock.Release(); |
|
249 } |
|
250 } |
|
251 |
|
252 void ReverbConvolver::reset() |
|
253 { |
|
254 for (size_t i = 0; i < m_stages.Length(); ++i) |
|
255 m_stages[i]->reset(); |
|
256 |
|
257 for (size_t i = 0; i < m_backgroundStages.Length(); ++i) |
|
258 m_backgroundStages[i]->reset(); |
|
259 |
|
260 m_accumulationBuffer.reset(); |
|
261 m_inputBuffer.reset(); |
|
262 } |
|
263 |
|
264 size_t ReverbConvolver::latencyFrames() const |
|
265 { |
|
266 return 0; |
|
267 } |
|
268 |
|
269 } // namespace WebCore |