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 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 "config.h"
30
31#if ENABLE(WEB_AUDIO)
32
33#include "ReverbConvolver.h"
34
35#include "VectorMath.h"
36#include "AudioBus.h"
37#include <mutex>
38
39namespace WebCore {
40
41using namespace VectorMath;
42
43const 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.
52const size_t RealtimeFrameLimit = 8192 + 4096; // ~278msec @ 44.1KHz
53
54const size_t MinFFTSize = 128;
55const size_t MaxRealtimeFFTSize = 2048;
56
57ReverbConvolver::ReverbConvolver(AudioChannel* impulseResponse, size_t renderSliceSize, size_t maxFFTSize, size_t convolverRenderPhase, bool useBackgroundThreads)
58 : m_impulseResponseLength(impulseResponse->length())
59 , m_accumulationBuffer(impulseResponse->length() + 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_useBackgroundThreads(useBackgroundThreads)
64{
65 // If we are using background threads then don't exceed this FFT size for the
66 // stages which run in the real-time thread. This avoids having only one or two
67 // large stages (size 16384 or so) at the end which take a lot of time every several
68 // processing slices. This way we amortize the cost over more processing slices.
69 m_maxRealtimeFFTSize = MaxRealtimeFFTSize;
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 bool hasRealtimeConstraint = useBackgroundThreads;
74
75 const float* response = impulseResponse->data();
76 size_t totalResponseLength = impulseResponse->length();
77
78 // The total latency is zero because the direct-convolution is used in the leading portion.
79 size_t reverbTotalLatency = 0;
80
81 size_t stageOffset = 0;
82 int i = 0;
83 size_t fftSize = m_minFFTSize;
84 while (stageOffset < totalResponseLength) {
85 size_t stageSize = fftSize / 2;
86
87 // For the last stage, it's possible that stageOffset is such that we're straddling the end
88 // of the impulse response buffer (if we use stageSize), so reduce the last stage's length...
89 if (stageSize + stageOffset > totalResponseLength)
90 stageSize = totalResponseLength - stageOffset;
91
92 // This "staggers" the time when each FFT happens so they don't all happen at the same time
93 int renderPhase = convolverRenderPhase + i * renderSliceSize;
94
95 bool useDirectConvolver = !stageOffset;
96
97 auto stage = std::make_unique<ReverbConvolverStage>(response, totalResponseLength, reverbTotalLatency, stageOffset, stageSize, fftSize, renderPhase, renderSliceSize, &m_accumulationBuffer, useDirectConvolver);
98
99 bool isBackgroundStage = false;
100
101 if (this->useBackgroundThreads() && stageOffset > RealtimeFrameLimit) {
102 m_backgroundStages.append(WTFMove(stage));
103 isBackgroundStage = true;
104 } else
105 m_stages.append(WTFMove(stage));
106
107 stageOffset += stageSize;
108 ++i;
109
110 if (!useDirectConvolver) {
111 // Figure out next FFT size
112 fftSize *= 2;
113 }
114
115 if (hasRealtimeConstraint && !isBackgroundStage && fftSize > m_maxRealtimeFFTSize)
116 fftSize = m_maxRealtimeFFTSize;
117 if (fftSize > m_maxFFTSize)
118 fftSize = m_maxFFTSize;
119 }
120
121 // Start up background thread
122 // FIXME: would be better to up the thread priority here. It doesn't need to be real-time, but higher than the default...
123 if (this->useBackgroundThreads() && m_backgroundStages.size() > 0) {
124 m_backgroundThread = Thread::create("convolution background thread", [this] {
125 backgroundThreadEntry();
126 });
127 }
128}
129
130ReverbConvolver::~ReverbConvolver()
131{
132 // Wait for background thread to stop
133 if (useBackgroundThreads() && m_backgroundThread) {
134 m_wantsToExit = true;
135
136 // Wake up thread so it can return
137 {
138 std::lock_guard<Lock> lock(m_backgroundThreadMutex);
139 m_moreInputBuffered = true;
140 m_backgroundThreadConditionVariable.notifyOne();
141 }
142
143 m_backgroundThread->waitForCompletion();
144 }
145}
146
147void ReverbConvolver::backgroundThreadEntry()
148{
149 while (!m_wantsToExit) {
150 // Wait for realtime thread to give us more input
151 m_moreInputBuffered = false;
152 {
153 std::unique_lock<Lock> lock(m_backgroundThreadMutex);
154
155 m_backgroundThreadConditionVariable.wait(lock, [this] { return m_moreInputBuffered || m_wantsToExit; });
156 }
157
158 // Process all of the stages until their read indices reach the input buffer's write index
159 int writeIndex = m_inputBuffer.writeIndex();
160
161 // Even though it doesn't seem like every stage needs to maintain its own version of readIndex
162 // we do this in case we want to run in more than one background thread.
163 int readIndex;
164
165 while ((readIndex = m_backgroundStages[0]->inputReadIndex()) != writeIndex) { // FIXME: do better to detect buffer overrun...
166 // The ReverbConvolverStages need to process in amounts which evenly divide half the FFT size
167 const int SliceSize = MinFFTSize / 2;
168
169 // Accumulate contributions from each stage
170 for (size_t i = 0; i < m_backgroundStages.size(); ++i)
171 m_backgroundStages[i]->processInBackground(this, SliceSize);
172 }
173 }
174}
175
176void ReverbConvolver::process(const AudioChannel* sourceChannel, AudioChannel* destinationChannel, size_t framesToProcess)
177{
178 bool isSafe = sourceChannel && destinationChannel && sourceChannel->length() >= framesToProcess && destinationChannel->length() >= framesToProcess;
179 ASSERT(isSafe);
180 if (!isSafe)
181 return;
182
183 const float* source = sourceChannel->data();
184 float* destination = destinationChannel->mutableData();
185 bool isDataSafe = source && destination;
186 ASSERT(isDataSafe);
187 if (!isDataSafe)
188 return;
189
190 // Feed input buffer (read by all threads)
191 m_inputBuffer.write(source, framesToProcess);
192
193 // Accumulate contributions from each stage
194 for (size_t i = 0; i < m_stages.size(); ++i)
195 m_stages[i]->process(source, framesToProcess);
196
197 // Finally read from accumulation buffer
198 m_accumulationBuffer.readAndClear(destination, framesToProcess);
199
200 // Now that we've buffered more input, wake up our background thread.
201
202 // We use use std::unique_lock with std::try_lock here because this is run on the real-time
203 // thread where it is a disaster for the lock to be contended (causes audio glitching). It's OK if we fail to
204 // signal from time to time, since we'll get to it the next time we're called. We're called repeatedly
205 // and frequently (around every 3ms). The background thread is processing well into the future and has a considerable amount of
206 // leeway here...
207 std::unique_lock<Lock> lock(m_backgroundThreadMutex, std::try_to_lock);
208 if (!lock.owns_lock())
209 return;
210
211 m_moreInputBuffered = true;
212 m_backgroundThreadConditionVariable.notifyOne();
213}
214
215void ReverbConvolver::reset()
216{
217 for (size_t i = 0; i < m_stages.size(); ++i)
218 m_stages[i]->reset();
219
220 for (size_t i = 0; i < m_backgroundStages.size(); ++i)
221 m_backgroundStages[i]->reset();
222
223 m_accumulationBuffer.reset();
224 m_inputBuffer.reset();
225}
226
227size_t ReverbConvolver::latencyFrames() const
228{
229 return 0;
230}
231
232} // namespace WebCore
233
234#endif // ENABLE(WEB_AUDIO)
235