OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2010 Google Inc. All rights reserved. | 2 * Copyright (C) 2010 Google Inc. All rights reserved. |
3 * | 3 * |
4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
5 * modification, are permitted provided that the following conditions | 5 * modification, are permitted provided that the following conditions |
6 * are met: | 6 * are met: |
7 * | 7 * |
8 * 1. Redistributions of source code must retain the above copyright | 8 * 1. Redistributions of source code must retain the above copyright |
9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
10 * 2. Redistributions in binary form must reproduce the above copyright | 10 * 2. Redistributions in binary form must reproduce the above copyright |
(...skipping 26 matching lines...) Expand all Loading... |
37 #include "public/platform/WebTraceLocation.h" | 37 #include "public/platform/WebTraceLocation.h" |
38 #include "wtf/PtrUtil.h" | 38 #include "wtf/PtrUtil.h" |
39 #include <memory> | 39 #include <memory> |
40 | 40 |
41 namespace blink { | 41 namespace blink { |
42 | 42 |
43 using namespace VectorMath; | 43 using namespace VectorMath; |
44 | 44 |
45 const int InputBufferSize = 8 * 16384; | 45 const int InputBufferSize = 8 * 16384; |
46 | 46 |
47 // We only process the leading portion of the impulse response in the real-time
thread. We don't exceed this length. | 47 // We only process the leading portion of the impulse response in the real-time |
48 // It turns out then, that the background thread has about 278msec of scheduling
slop. | 48 // thread. We don't exceed this length. It turns out then, that the |
49 // Empirically, this has been found to be a good compromise between giving enoug
h time for scheduling slop, | 49 // background thread has about 278msec of scheduling slop. Empirically, this |
50 // while still minimizing the amount of processing done in the primary (high-pri
ority) thread. | 50 // has been found to be a good compromise between giving enough time for |
51 // This was found to be a good value on Mac OS X, and may work well on other pla
tforms as well, assuming | 51 // scheduling slop, while still minimizing the amount of processing done in the |
52 // the very rough scheduling latencies are similar on these time-scales. Of cou
rse, this code may need to be | 52 // primary (high-priority) thread. This was found to be a good value on Mac OS |
53 // tuned for individual platforms if this assumption is found to be incorrect. | 53 // X, and may work well on other platforms as well, assuming the very rough |
| 54 // scheduling latencies are similar on these time-scales. Of course, this code |
| 55 // may need to be tuned for individual platforms if this assumption is found to |
| 56 // be incorrect. |
54 const size_t RealtimeFrameLimit = 8192 + 4096; // ~278msec @ 44.1KHz | 57 const size_t RealtimeFrameLimit = 8192 + 4096; // ~278msec @ 44.1KHz |
55 | 58 |
56 const size_t MinFFTSize = 128; | 59 const size_t MinFFTSize = 128; |
57 const size_t MaxRealtimeFFTSize = 2048; | 60 const size_t MaxRealtimeFFTSize = 2048; |
58 | 61 |
59 ReverbConvolver::ReverbConvolver(AudioChannel* impulseResponse, | 62 ReverbConvolver::ReverbConvolver(AudioChannel* impulseResponse, |
60 size_t renderSliceSize, | 63 size_t renderSliceSize, |
61 size_t maxFFTSize, | 64 size_t maxFFTSize, |
62 size_t convolverRenderPhase, | 65 size_t convolverRenderPhase, |
63 bool useBackgroundThreads) | 66 bool useBackgroundThreads) |
64 : m_impulseResponseLength(impulseResponse->length()), | 67 : m_impulseResponseLength(impulseResponse->length()), |
65 m_accumulationBuffer(impulseResponse->length() + renderSliceSize), | 68 m_accumulationBuffer(impulseResponse->length() + renderSliceSize), |
66 m_inputBuffer(InputBufferSize), | 69 m_inputBuffer(InputBufferSize), |
67 m_minFFTSize( | 70 m_minFFTSize(MinFFTSize), // First stage will have this size - successive |
68 MinFFTSize) // First stage will have this size - successive stages wi
ll double in size each time | 71 // stages will double in size each time |
69 , | 72 m_maxFFTSize(maxFFTSize) // until we hit m_maxFFTSize |
70 m_maxFFTSize(maxFFTSize) // until we hit m_maxFFTSize | |
71 { | 73 { |
72 // If we are using background threads then don't exceed this FFT size for the | 74 // If we are using background threads then don't exceed this FFT size for the |
73 // stages which run in the real-time thread. This avoids having only one or t
wo | 75 // stages which run in the real-time thread. This avoids having only one or |
74 // large stages (size 16384 or so) at the end which take a lot of time every s
everal | 76 // two large stages (size 16384 or so) at the end which take a lot of time |
75 // processing slices. This way we amortize the cost over more processing slic
es. | 77 // every several processing slices. This way we amortize the cost over more |
| 78 // processing slices. |
76 m_maxRealtimeFFTSize = MaxRealtimeFFTSize; | 79 m_maxRealtimeFFTSize = MaxRealtimeFFTSize; |
77 | 80 |
78 const float* response = impulseResponse->data(); | 81 const float* response = impulseResponse->data(); |
79 size_t totalResponseLength = impulseResponse->length(); | 82 size_t totalResponseLength = impulseResponse->length(); |
80 | 83 |
81 // The total latency is zero because the direct-convolution is used in the lea
ding portion. | 84 // The total latency is zero because the direct-convolution is used in the |
| 85 // leading portion. |
82 size_t reverbTotalLatency = 0; | 86 size_t reverbTotalLatency = 0; |
83 | 87 |
84 size_t stageOffset = 0; | 88 size_t stageOffset = 0; |
85 int i = 0; | 89 int i = 0; |
86 size_t fftSize = m_minFFTSize; | 90 size_t fftSize = m_minFFTSize; |
87 while (stageOffset < totalResponseLength) { | 91 while (stageOffset < totalResponseLength) { |
88 size_t stageSize = fftSize / 2; | 92 size_t stageSize = fftSize / 2; |
89 | 93 |
90 // For the last stage, it's possible that stageOffset is such that we're str
addling the end | 94 // For the last stage, it's possible that stageOffset is such that we're |
91 // of the impulse response buffer (if we use stageSize), so reduce the last
stage's length... | 95 // straddling the end of the impulse response buffer (if we use stageSize), |
| 96 // so reduce the last stage's length... |
92 if (stageSize + stageOffset > totalResponseLength) | 97 if (stageSize + stageOffset > totalResponseLength) |
93 stageSize = totalResponseLength - stageOffset; | 98 stageSize = totalResponseLength - stageOffset; |
94 | 99 |
95 // This "staggers" the time when each FFT happens so they don't all happen a
t the same time | 100 // This "staggers" the time when each FFT happens so they don't all happen |
| 101 // at the same time |
96 int renderPhase = convolverRenderPhase + i * renderSliceSize; | 102 int renderPhase = convolverRenderPhase + i * renderSliceSize; |
97 | 103 |
98 bool useDirectConvolver = !stageOffset; | 104 bool useDirectConvolver = !stageOffset; |
99 | 105 |
100 std::unique_ptr<ReverbConvolverStage> stage = | 106 std::unique_ptr<ReverbConvolverStage> stage = |
101 wrapUnique(new ReverbConvolverStage( | 107 wrapUnique(new ReverbConvolverStage( |
102 response, totalResponseLength, reverbTotalLatency, stageOffset, | 108 response, totalResponseLength, reverbTotalLatency, stageOffset, |
103 stageSize, fftSize, renderPhase, renderSliceSize, | 109 stageSize, fftSize, renderPhase, renderSliceSize, |
104 &m_accumulationBuffer, useDirectConvolver)); | 110 &m_accumulationBuffer, useDirectConvolver)); |
105 | 111 |
(...skipping 15 matching lines...) Expand all Loading... |
121 } | 127 } |
122 | 128 |
123 if (useBackgroundThreads && !isBackgroundStage && | 129 if (useBackgroundThreads && !isBackgroundStage && |
124 fftSize > m_maxRealtimeFFTSize) | 130 fftSize > m_maxRealtimeFFTSize) |
125 fftSize = m_maxRealtimeFFTSize; | 131 fftSize = m_maxRealtimeFFTSize; |
126 if (fftSize > m_maxFFTSize) | 132 if (fftSize > m_maxFFTSize) |
127 fftSize = m_maxFFTSize; | 133 fftSize = m_maxFFTSize; |
128 } | 134 } |
129 | 135 |
130 // Start up background thread | 136 // Start up background thread |
131 // FIXME: would be better to up the thread priority here. It doesn't need to
be real-time, but higher than the default... | 137 // FIXME: would be better to up the thread priority here. It doesn't need to |
| 138 // be real-time, but higher than the default... |
132 if (useBackgroundThreads && m_backgroundStages.size() > 0) | 139 if (useBackgroundThreads && m_backgroundStages.size() > 0) |
133 m_backgroundThread = wrapUnique(Platform::current()->createThread( | 140 m_backgroundThread = wrapUnique(Platform::current()->createThread( |
134 "Reverb convolution background thread")); | 141 "Reverb convolution background thread")); |
135 } | 142 } |
136 | 143 |
137 ReverbConvolver::~ReverbConvolver() { | 144 ReverbConvolver::~ReverbConvolver() { |
138 // Wait for background thread to stop | 145 // Wait for background thread to stop |
139 m_backgroundThread.reset(); | 146 m_backgroundThread.reset(); |
140 } | 147 } |
141 | 148 |
142 void ReverbConvolver::processInBackground() { | 149 void ReverbConvolver::processInBackground() { |
143 // Process all of the stages until their read indices reach the input buffer's
write index | 150 // Process all of the stages until their read indices reach the input buffer's |
| 151 // write index |
144 int writeIndex = m_inputBuffer.writeIndex(); | 152 int writeIndex = m_inputBuffer.writeIndex(); |
145 | 153 |
146 // Even though it doesn't seem like every stage needs to maintain its own vers
ion of readIndex | 154 // Even though it doesn't seem like every stage needs to maintain its own |
147 // we do this in case we want to run in more than one background thread. | 155 // version of readIndex we do this in case we want to run in more than one |
| 156 // background thread. |
148 int readIndex; | 157 int readIndex; |
149 | 158 |
150 while ((readIndex = m_backgroundStages[0]->inputReadIndex()) != | 159 while ((readIndex = m_backgroundStages[0]->inputReadIndex()) != |
151 writeIndex) { // FIXME: do better to detect buffer overrun... | 160 writeIndex) { // FIXME: do better to detect buffer overrun... |
152 // The ReverbConvolverStages need to process in amounts which evenly divide
half the FFT size | 161 // The ReverbConvolverStages need to process in amounts which evenly divide |
| 162 // half the FFT size |
153 const int SliceSize = MinFFTSize / 2; | 163 const int SliceSize = MinFFTSize / 2; |
154 | 164 |
155 // Accumulate contributions from each stage | 165 // Accumulate contributions from each stage |
156 for (size_t i = 0; i < m_backgroundStages.size(); ++i) | 166 for (size_t i = 0; i < m_backgroundStages.size(); ++i) |
157 m_backgroundStages[i]->processInBackground(this, SliceSize); | 167 m_backgroundStages[i]->processInBackground(this, SliceSize); |
158 } | 168 } |
159 } | 169 } |
160 | 170 |
161 void ReverbConvolver::process(const AudioChannel* sourceChannel, | 171 void ReverbConvolver::process(const AudioChannel* sourceChannel, |
162 AudioChannel* destinationChannel, | 172 AudioChannel* destinationChannel, |
(...skipping 15 matching lines...) Expand all Loading... |
178 // Feed input buffer (read by all threads) | 188 // Feed input buffer (read by all threads) |
179 m_inputBuffer.write(source, framesToProcess); | 189 m_inputBuffer.write(source, framesToProcess); |
180 | 190 |
181 // Accumulate contributions from each stage | 191 // Accumulate contributions from each stage |
182 for (size_t i = 0; i < m_stages.size(); ++i) | 192 for (size_t i = 0; i < m_stages.size(); ++i) |
183 m_stages[i]->process(source, framesToProcess); | 193 m_stages[i]->process(source, framesToProcess); |
184 | 194 |
185 // Finally read from accumulation buffer | 195 // Finally read from accumulation buffer |
186 m_accumulationBuffer.readAndClear(destination, framesToProcess); | 196 m_accumulationBuffer.readAndClear(destination, framesToProcess); |
187 | 197 |
188 // Now that we've buffered more input, post another task to the background thr
ead. | 198 // Now that we've buffered more input, post another task to the background |
| 199 // thread. |
189 if (m_backgroundThread) | 200 if (m_backgroundThread) |
190 m_backgroundThread->getWebTaskRunner()->postTask( | 201 m_backgroundThread->getWebTaskRunner()->postTask( |
191 BLINK_FROM_HERE, crossThreadBind(&ReverbConvolver::processInBackground, | 202 BLINK_FROM_HERE, crossThreadBind(&ReverbConvolver::processInBackground, |
192 crossThreadUnretained(this))); | 203 crossThreadUnretained(this))); |
193 } | 204 } |
194 | 205 |
195 void ReverbConvolver::reset() { | 206 void ReverbConvolver::reset() { |
196 for (size_t i = 0; i < m_stages.size(); ++i) | 207 for (size_t i = 0; i < m_stages.size(); ++i) |
197 m_stages[i]->reset(); | 208 m_stages[i]->reset(); |
198 | 209 |
199 for (size_t i = 0; i < m_backgroundStages.size(); ++i) | 210 for (size_t i = 0; i < m_backgroundStages.size(); ++i) |
200 m_backgroundStages[i]->reset(); | 211 m_backgroundStages[i]->reset(); |
201 | 212 |
202 m_accumulationBuffer.reset(); | 213 m_accumulationBuffer.reset(); |
203 m_inputBuffer.reset(); | 214 m_inputBuffer.reset(); |
204 } | 215 } |
205 | 216 |
206 size_t ReverbConvolver::latencyFrames() const { | 217 size_t ReverbConvolver::latencyFrames() const { |
207 return 0; | 218 return 0; |
208 } | 219 } |
209 | 220 |
210 } // namespace blink | 221 } // namespace blink |
OLD | NEW |