/src/mozilla-central/dom/media/webaudio/blink/ReverbConvolver.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | ReverbConvolver::ReverbConvolver(const float* impulseResponseData, |
58 | | size_t impulseResponseLength, |
59 | | size_t maxFFTSize, |
60 | | size_t convolverRenderPhase, |
61 | | 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 | | , m_moreInputBuffered(false) |
70 | 0 | { |
71 | 0 | // For the moment, a good way to know if we have real-time constraint is to check if we're using background threads. |
72 | 0 | // Otherwise, assume we're being run from a command-line tool. |
73 | 0 | bool hasRealtimeConstraint = useBackgroundThreads; |
74 | 0 |
|
75 | 0 | const float* response = impulseResponseData; |
76 | 0 | size_t totalResponseLength = impulseResponseLength; |
77 | 0 |
|
78 | 0 | // The total latency is zero because the first FFT stage is small enough |
79 | 0 | // to return output in the first block. |
80 | 0 | size_t reverbTotalLatency = 0; |
81 | 0 |
|
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 | 0 |
|
88 | 0 | // For the last stage, it's possible that stageOffset is such that we're straddling the end |
89 | 0 | // 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 | 0 | // 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 | 0 | } |
97 | 0 | } |
98 | 0 |
|
99 | 0 | // 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 | 0 |
|
102 | 0 | nsAutoPtr<ReverbConvolverStage> stage |
103 | 0 | (new ReverbConvolverStage(response, totalResponseLength, |
104 | 0 | reverbTotalLatency, stageOffset, stageSize, |
105 | 0 | fftSize, renderPhase, |
106 | 0 | &m_accumulationBuffer)); |
107 | 0 |
|
108 | 0 | bool isBackgroundStage = false; |
109 | 0 |
|
110 | 0 | if (this->useBackgroundThreads() && stageOffset > RealtimeFrameLimit) { |
111 | 0 | m_backgroundStages.AppendElement(stage.forget()); |
112 | 0 | isBackgroundStage = true; |
113 | 0 | } else |
114 | 0 | m_stages.AppendElement(stage.forget()); |
115 | 0 |
|
116 | 0 | // Figure out next FFT size |
117 | 0 | fftSize *= 2; |
118 | 0 |
|
119 | 0 | stageOffset += stageSize; |
120 | 0 |
|
121 | 0 | if (hasRealtimeConstraint && !isBackgroundStage && |
122 | 0 | fftSize > MaxRealtimeFFTSize) { |
123 | 0 | fftSize = MaxRealtimeFFTSize; |
124 | 0 | // Custom phase positions for all but the first of the realtime |
125 | 0 | // stages of largest size. These spread out the work of the |
126 | 0 | // larger realtime stages. None of the FFTs of size 1024, 2048 or |
127 | 0 | // 4096 are performed when processing the same block. The first |
128 | 0 | // MaxRealtimeFFTSize = 4096 stage, at the end of the doubling, |
129 | 0 | // performs its FFT at block 7. The FFTs of size 2048 are |
130 | 0 | // 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 = |
133 | 0 | WEBAUDIO_BLOCK_SIZE * |
134 | 0 | phaseLookup[m_stages.Length() % ArrayLength(phaseLookup)]; |
135 | 0 | } else if (fftSize > maxFFTSize) { |
136 | 0 | fftSize = maxFFTSize; |
137 | 0 | // A prime offset spreads out FFTs in a way that all |
138 | 0 | // available phase positions will be used if there are sufficient |
139 | 0 | // stages. |
140 | 0 | stagePhase += 5 * WEBAUDIO_BLOCK_SIZE; |
141 | 0 | } else if (stageSize > WEBAUDIO_BLOCK_SIZE) { |
142 | 0 | // As the stages are doubling in size, the next FFT will occur |
143 | 0 | // mid-way between FFTs for this stage. |
144 | 0 | stagePhase = stageSize - WEBAUDIO_BLOCK_SIZE; |
145 | 0 | } |
146 | 0 | } |
147 | 0 |
|
148 | 0 | // Start up background thread |
149 | 0 | // FIXME: would be better to up the thread priority here. It doesn't need to be real-time, but higher than the default... |
150 | 0 | if (this->useBackgroundThreads() && m_backgroundStages.Length() > 0) { |
151 | 0 | if (!m_backgroundThread.Start()) { |
152 | 0 | NS_WARNING("Cannot start convolver thread."); |
153 | 0 | return; |
154 | 0 | } |
155 | 0 | m_backgroundThread.message_loop()->PostTask(NewNonOwningRunnableMethod( |
156 | 0 | "WebCore::ReverbConvolver::backgroundThreadEntry", |
157 | 0 | this, |
158 | 0 | &ReverbConvolver::backgroundThreadEntry)); |
159 | 0 | } |
160 | 0 | } |
161 | | |
162 | | ReverbConvolver::~ReverbConvolver() |
163 | 0 | { |
164 | 0 | // Wait for background thread to stop |
165 | 0 | if (useBackgroundThreads() && m_backgroundThread.IsRunning()) { |
166 | 0 | m_wantsToExit = true; |
167 | 0 |
|
168 | 0 | // Wake up thread so it can return |
169 | 0 | { |
170 | 0 | AutoLock locker(m_backgroundThreadLock); |
171 | 0 | m_moreInputBuffered = true; |
172 | 0 | m_backgroundThreadCondition.Signal(); |
173 | 0 | } |
174 | 0 |
|
175 | 0 | m_backgroundThread.Stop(); |
176 | 0 | } |
177 | 0 | } |
178 | | |
179 | | size_t ReverbConvolver::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const |
180 | 0 | { |
181 | 0 | size_t amount = aMallocSizeOf(this); |
182 | 0 | amount += m_stages.ShallowSizeOfExcludingThis(aMallocSizeOf); |
183 | 0 | for (size_t i = 0; i < m_stages.Length(); i++) { |
184 | 0 | if (m_stages[i]) { |
185 | 0 | amount += m_stages[i]->sizeOfIncludingThis(aMallocSizeOf); |
186 | 0 | } |
187 | 0 | } |
188 | 0 |
|
189 | 0 | amount += m_backgroundStages.ShallowSizeOfExcludingThis(aMallocSizeOf); |
190 | 0 | for (size_t i = 0; i < m_backgroundStages.Length(); i++) { |
191 | 0 | if (m_backgroundStages[i]) { |
192 | 0 | amount += m_backgroundStages[i]->sizeOfIncludingThis(aMallocSizeOf); |
193 | 0 | } |
194 | 0 | } |
195 | 0 |
|
196 | 0 | // NB: The buffer sizes are static, so even though they might be accessed |
197 | 0 | // in another thread it's safe to measure them. |
198 | 0 | amount += m_accumulationBuffer.sizeOfExcludingThis(aMallocSizeOf); |
199 | 0 | amount += m_inputBuffer.sizeOfExcludingThis(aMallocSizeOf); |
200 | 0 |
|
201 | 0 | // Possible future measurements: |
202 | 0 | // - m_backgroundThread |
203 | 0 | // - m_backgroundThreadLock |
204 | 0 | // - m_backgroundThreadCondition |
205 | 0 | return amount; |
206 | 0 | } |
207 | | |
208 | | void ReverbConvolver::backgroundThreadEntry() |
209 | 0 | { |
210 | 0 | while (!m_wantsToExit) { |
211 | 0 | // Wait for realtime thread to give us more input |
212 | 0 | m_moreInputBuffered = false; |
213 | 0 | { |
214 | 0 | AutoLock locker(m_backgroundThreadLock); |
215 | 0 | while (!m_moreInputBuffered && !m_wantsToExit) |
216 | 0 | m_backgroundThreadCondition.Wait(); |
217 | 0 | } |
218 | 0 |
|
219 | 0 | // Process all of the stages until their read indices reach the input buffer's write index |
220 | 0 | int writeIndex = m_inputBuffer.writeIndex(); |
221 | 0 |
|
222 | 0 | // Even though it doesn't seem like every stage needs to maintain its own version of readIndex |
223 | 0 | // we do this in case we want to run in more than one background thread. |
224 | 0 | int readIndex; |
225 | 0 |
|
226 | 0 | while ((readIndex = m_backgroundStages[0]->inputReadIndex()) != writeIndex) { // FIXME: do better to detect buffer overrun... |
227 | 0 | // Accumulate contributions from each stage |
228 | 0 | for (size_t i = 0; i < m_backgroundStages.Length(); ++i) |
229 | 0 | m_backgroundStages[i]->processInBackground(this); |
230 | 0 | } |
231 | 0 | } |
232 | 0 | } |
233 | | |
234 | | void ReverbConvolver::process(const float* sourceChannelData, |
235 | | float* destinationChannelData) |
236 | 0 | { |
237 | 0 | const float* source = sourceChannelData; |
238 | 0 | float* destination = destinationChannelData; |
239 | 0 | bool isDataSafe = source && destination; |
240 | 0 | MOZ_ASSERT(isDataSafe); |
241 | 0 | if (!isDataSafe) |
242 | 0 | return; |
243 | 0 | |
244 | 0 | // Feed input buffer (read by all threads) |
245 | 0 | m_inputBuffer.write(source, WEBAUDIO_BLOCK_SIZE); |
246 | 0 |
|
247 | 0 | // Accumulate contributions from each stage |
248 | 0 | for (size_t i = 0; i < m_stages.Length(); ++i) |
249 | 0 | m_stages[i]->process(source); |
250 | 0 |
|
251 | 0 | // Finally read from accumulation buffer |
252 | 0 | m_accumulationBuffer.readAndClear(destination, WEBAUDIO_BLOCK_SIZE); |
253 | 0 |
|
254 | 0 | // Now that we've buffered more input, wake up our background thread. |
255 | 0 |
|
256 | 0 | // Not using a MutexLocker looks strange, but we use a tryLock() instead because this is run on the real-time |
257 | 0 | // thread where it is a disaster for the lock to be contended (causes audio glitching). It's OK if we fail to |
258 | 0 | // signal from time to time, since we'll get to it the next time we're called. We're called repeatedly |
259 | 0 | // and frequently (around every 3ms). The background thread is processing well into the future and has a considerable amount of |
260 | 0 | // leeway here... |
261 | 0 | if (m_backgroundThreadLock.Try()) { |
262 | 0 | m_moreInputBuffered = true; |
263 | 0 | m_backgroundThreadCondition.Signal(); |
264 | 0 | m_backgroundThreadLock.Release(); |
265 | 0 | } |
266 | 0 | } |
267 | | |
268 | | } // namespace WebCore |