OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2011, Google Inc. All rights reserved. | 2 * Copyright (C) 2011, 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 * 1. Redistributions of source code must retain the above copyright | 7 * 1. Redistributions of source code must retain the above copyright |
8 * notice, this list of conditions and the following disclaimer. | 8 * notice, this list of conditions and the following disclaimer. |
9 * 2. Redistributions in binary form must reproduce the above copyright | 9 * 2. Redistributions in binary form must reproduce the above copyright |
10 * notice, this list of conditions and the following disclaimer in the | 10 * notice, this list of conditions and the following disclaimer in the |
(...skipping 10 matching lines...) Expand all Loading... |
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | 21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
22 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 22 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
23 */ | 23 */ |
24 | 24 |
25 #include "config.h" | 25 #include "config.h" |
26 #if ENABLE(WEB_AUDIO) | 26 #if ENABLE(WEB_AUDIO) |
27 #include "modules/webaudio/OfflineAudioDestinationNode.h" | 27 #include "modules/webaudio/OfflineAudioDestinationNode.h" |
28 | 28 |
29 #include "core/dom/CrossThreadTask.h" | 29 #include "core/dom/CrossThreadTask.h" |
30 #include "modules/webaudio/AbstractAudioContext.h" | 30 #include "modules/webaudio/AbstractAudioContext.h" |
| 31 #include "modules/webaudio/AudioNodeInput.h" |
| 32 #include "modules/webaudio/AudioNodeOutput.h" |
| 33 #include "modules/webaudio/OfflineAudioContext.h" |
31 #include "platform/Task.h" | 34 #include "platform/Task.h" |
32 #include "platform/audio/AudioBus.h" | 35 #include "platform/audio/AudioBus.h" |
| 36 #include "platform/audio/DenormalDisabler.h" |
33 #include "platform/audio/HRTFDatabaseLoader.h" | 37 #include "platform/audio/HRTFDatabaseLoader.h" |
34 #include "public/platform/Platform.h" | 38 #include "public/platform/Platform.h" |
35 #include <algorithm> | 39 #include <algorithm> |
36 | 40 |
37 namespace blink { | 41 namespace blink { |
38 | 42 |
39 const size_t renderQuantumSize = 128; | 43 const size_t OfflineAudioDestinationHandler::renderQuantumSize = 128; |
40 | 44 |
41 OfflineAudioDestinationHandler::OfflineAudioDestinationHandler(AudioNode& node,
AudioBuffer* renderTarget) | 45 OfflineAudioDestinationHandler::OfflineAudioDestinationHandler(AudioNode& node,
AudioBuffer* renderTarget) |
42 : AudioDestinationHandler(node, renderTarget->sampleRate()) | 46 : AudioDestinationHandler(node, renderTarget->sampleRate()) |
43 , m_renderTarget(renderTarget) | 47 , m_renderTarget(renderTarget) |
44 , m_startedRendering(false) | 48 , m_renderThread(adoptPtr(Platform::current()->createThread("offline audio r
enderer"))) |
| 49 , m_framesProcessed(0) |
| 50 , m_framesToProcess(0) |
| 51 , m_isRenderingStarted(false) |
| 52 , m_shouldSuspend(false) |
45 { | 53 { |
46 m_renderBus = AudioBus::create(renderTarget->numberOfChannels(), renderQuant
umSize); | 54 m_renderBus = AudioBus::create(renderTarget->numberOfChannels(), renderQuant
umSize); |
| 55 m_framesToProcess = m_renderTarget->length(); |
47 } | 56 } |
48 | 57 |
49 PassRefPtr<OfflineAudioDestinationHandler> OfflineAudioDestinationHandler::creat
e(AudioNode& node, AudioBuffer* renderTarget) | 58 PassRefPtr<OfflineAudioDestinationHandler> OfflineAudioDestinationHandler::creat
e(AudioNode& node, AudioBuffer* renderTarget) |
50 { | 59 { |
51 return adoptRef(new OfflineAudioDestinationHandler(node, renderTarget)); | 60 return adoptRef(new OfflineAudioDestinationHandler(node, renderTarget)); |
52 } | 61 } |
53 | 62 |
54 OfflineAudioDestinationHandler::~OfflineAudioDestinationHandler() | 63 OfflineAudioDestinationHandler::~OfflineAudioDestinationHandler() |
55 { | 64 { |
56 ASSERT(!isInitialized()); | 65 ASSERT(!isInitialized()); |
(...skipping 17 matching lines...) Expand all Loading... |
74 { | 83 { |
75 if (!isInitialized()) | 84 if (!isInitialized()) |
76 return; | 85 return; |
77 | 86 |
78 if (m_renderThread) | 87 if (m_renderThread) |
79 m_renderThread.clear(); | 88 m_renderThread.clear(); |
80 | 89 |
81 AudioHandler::uninitialize(); | 90 AudioHandler::uninitialize(); |
82 } | 91 } |
83 | 92 |
| 93 OfflineAudioContext* OfflineAudioDestinationHandler::context() const |
| 94 { |
| 95 return static_cast<OfflineAudioContext*>(m_context); |
| 96 } |
| 97 |
84 void OfflineAudioDestinationHandler::startRendering() | 98 void OfflineAudioDestinationHandler::startRendering() |
85 { | 99 { |
86 ASSERT(isMainThread()); | 100 ASSERT(isMainThread()); |
| 101 ASSERT(m_renderThread); |
87 ASSERT(m_renderTarget); | 102 ASSERT(m_renderTarget); |
| 103 |
88 if (!m_renderTarget) | 104 if (!m_renderTarget) |
89 return; | 105 return; |
90 | 106 |
91 if (!m_startedRendering) { | 107 // Rendering was not started. Starting now. |
92 m_startedRendering = true; | 108 if (!m_isRenderingStarted) { |
93 m_renderThread = adoptPtr(Platform::current()->createThread("Offline Aud
io Renderer")); | 109 m_isRenderingStarted = true; |
94 m_renderThread->taskRunner()->postTask(BLINK_FROM_HERE, new Task(threadS
afeBind(&OfflineAudioDestinationHandler::offlineRender, PassRefPtr<OfflineAudioD
estinationHandler>(this)))); | 110 m_renderThread->taskRunner()->postTask(BLINK_FROM_HERE, |
| 111 new Task(threadSafeBind(&OfflineAudioDestinationHandler::startOfflin
eRendering, this))); |
| 112 return; |
95 } | 113 } |
| 114 |
| 115 // Rendering is already started, which implicitly means we resume the |
| 116 // rendering by calling |doOfflineRendering| on the render thread. |
| 117 m_renderThread->taskRunner()->postTask(BLINK_FROM_HERE, |
| 118 threadSafeBind(&OfflineAudioDestinationHandler::doOfflineRendering, this
)); |
96 } | 119 } |
97 | 120 |
98 void OfflineAudioDestinationHandler::stopRendering() | 121 void OfflineAudioDestinationHandler::stopRendering() |
99 { | 122 { |
| 123 // offline audio rendering CANNOT BE stopped by JavaScript. |
100 ASSERT_NOT_REACHED(); | 124 ASSERT_NOT_REACHED(); |
101 } | 125 } |
102 | 126 |
103 void OfflineAudioDestinationHandler::offlineRender() | 127 WebThread* OfflineAudioDestinationHandler::offlineRenderThread() |
104 { | 128 { |
105 offlineRenderInternal(); | 129 ASSERT(m_renderThread); |
106 context()->handlePostRenderTasks(); | 130 |
| 131 return m_renderThread.get(); |
107 } | 132 } |
108 | 133 |
109 void OfflineAudioDestinationHandler::offlineRenderInternal() | 134 void OfflineAudioDestinationHandler::startOfflineRendering() |
110 { | 135 { |
111 ASSERT(!isMainThread()); | 136 ASSERT(!isMainThread()); |
| 137 |
112 ASSERT(m_renderBus); | 138 ASSERT(m_renderBus); |
113 if (!m_renderBus) | 139 if (!m_renderBus) |
114 return; | 140 return; |
115 | 141 |
116 bool isAudioContextInitialized = context()->isDestinationInitialized(); | 142 bool isAudioContextInitialized = context()->isDestinationInitialized(); |
117 ASSERT(isAudioContextInitialized); | 143 ASSERT(isAudioContextInitialized); |
118 if (!isAudioContextInitialized) | 144 if (!isAudioContextInitialized) |
119 return; | 145 return; |
120 | 146 |
121 bool channelsMatch = m_renderBus->numberOfChannels() == m_renderTarget->numb
erOfChannels(); | 147 bool channelsMatch = m_renderBus->numberOfChannels() == m_renderTarget->numb
erOfChannels(); |
122 ASSERT(channelsMatch); | 148 ASSERT(channelsMatch); |
123 if (!channelsMatch) | 149 if (!channelsMatch) |
124 return; | 150 return; |
125 | 151 |
126 bool isRenderBusAllocated = m_renderBus->length() >= renderQuantumSize; | 152 bool isRenderBusAllocated = m_renderBus->length() >= renderQuantumSize; |
127 ASSERT(isRenderBusAllocated); | 153 ASSERT(isRenderBusAllocated); |
128 if (!isRenderBusAllocated) | 154 if (!isRenderBusAllocated) |
129 return; | 155 return; |
130 | 156 |
131 // Break up the render target into smaller "render quantize" sized pieces. | 157 // Start rendering. |
132 // Render until we're finished. | 158 doOfflineRendering(); |
133 size_t framesToProcess = m_renderTarget->length(); | 159 } |
| 160 |
| 161 void OfflineAudioDestinationHandler::doOfflineRendering() |
| 162 { |
| 163 ASSERT(!isMainThread()); |
| 164 |
134 unsigned numberOfChannels = m_renderTarget->numberOfChannels(); | 165 unsigned numberOfChannels = m_renderTarget->numberOfChannels(); |
135 | 166 |
136 unsigned n = 0; | 167 // Reset the suspend flag. |
137 while (framesToProcess > 0) { | 168 m_shouldSuspend = false; |
138 // Render one render quantum. | |
139 render(0, m_renderBus.get(), renderQuantumSize); | |
140 | 169 |
141 size_t framesAvailableToCopy = std::min(framesToProcess, renderQuantumSi
ze); | 170 // If there is more to process and there is no suspension at the moment, |
| 171 // do continue to render quanta. Then calling OfflineAudioContext.resume() w
ill pick up |
| 172 // the render loop again from where it was suspended. |
| 173 while (m_framesToProcess > 0 && !m_shouldSuspend) { |
| 174 |
| 175 // Suspend the rendering and update m_shouldSuspend if a scheduled |
| 176 // suspend found at the current sample frame. Otherwise render one |
| 177 // quantum and return false. |
| 178 m_shouldSuspend = renderIfNotSuspended(0, m_renderBus.get(), renderQuant
umSize); |
| 179 |
| 180 if (m_shouldSuspend) |
| 181 return; |
| 182 |
| 183 size_t framesAvailableToCopy = std::min(m_framesToProcess, renderQuantum
Size); |
142 | 184 |
143 for (unsigned channelIndex = 0; channelIndex < numberOfChannels; ++chann
elIndex) { | 185 for (unsigned channelIndex = 0; channelIndex < numberOfChannels; ++chann
elIndex) { |
144 const float* source = m_renderBus->channel(channelIndex)->data(); | 186 const float* source = m_renderBus->channel(channelIndex)->data(); |
145 float* destination = m_renderTarget->getChannelData(channelIndex)->d
ata(); | 187 float* destination = m_renderTarget->getChannelData(channelIndex)->d
ata(); |
146 memcpy(destination + n, source, sizeof(float) * framesAvailableToCop
y); | 188 memcpy(destination + m_framesProcessed, source, sizeof(float) * fram
esAvailableToCopy); |
147 } | 189 } |
148 | 190 |
149 n += framesAvailableToCopy; | 191 m_framesProcessed += framesAvailableToCopy; |
150 framesToProcess -= framesAvailableToCopy; | 192 |
| 193 ASSERT(m_framesToProcess >= framesAvailableToCopy); |
| 194 m_framesToProcess -= framesAvailableToCopy; |
151 } | 195 } |
152 | 196 |
153 // Our work is done. Let the AbstractAudioContext know. | 197 // Finish up the rendering loop if there is no more to process. |
154 if (context()->executionContext()) | 198 if (!m_framesToProcess) |
155 context()->executionContext()->postTask(BLINK_FROM_HERE, createCrossThre
adTask(&OfflineAudioDestinationHandler::notifyComplete, PassRefPtr<OfflineAudioD
estinationHandler>(this))); | 199 finishOfflineRendering(); |
| 200 } |
| 201 |
| 202 void OfflineAudioDestinationHandler::suspendOfflineRendering() |
| 203 { |
| 204 ASSERT(!isMainThread()); |
| 205 |
| 206 // The actual rendering has been suspended. Notify the context. |
| 207 if (context()->executionContext()) { |
| 208 context()->executionContext()->postTask(BLINK_FROM_HERE, |
| 209 createCrossThreadTask(&OfflineAudioDestinationHandler::notifySuspend
, this)); |
| 210 } |
| 211 } |
| 212 |
| 213 void OfflineAudioDestinationHandler::finishOfflineRendering() |
| 214 { |
| 215 ASSERT(!isMainThread()); |
| 216 |
| 217 // The actual rendering has been completed. Notify the context. |
| 218 if (context()->executionContext()) { |
| 219 context()->executionContext()->postTask(BLINK_FROM_HERE, |
| 220 createCrossThreadTask(&OfflineAudioDestinationHandler::notifyComplet
e, this)); |
| 221 } |
| 222 } |
| 223 |
| 224 void OfflineAudioDestinationHandler::notifySuspend() |
| 225 { |
| 226 if (context()) |
| 227 context()->resolveSuspendOnMainThread(context()->currentSampleFrame()); |
156 } | 228 } |
157 | 229 |
158 void OfflineAudioDestinationHandler::notifyComplete() | 230 void OfflineAudioDestinationHandler::notifyComplete() |
159 { | 231 { |
160 // The AbstractAudioContext might be gone. | 232 // The OfflineAudioContext might be gone. |
161 if (context()) | 233 if (context()) |
162 context()->fireCompletionEvent(); | 234 context()->fireCompletionEvent(); |
163 } | 235 } |
164 | 236 |
| 237 bool OfflineAudioDestinationHandler::renderIfNotSuspended(AudioBus* sourceBus, A
udioBus* destinationBus, size_t numberOfFrames) |
| 238 { |
| 239 // We don't want denormals slowing down any of the audio processing |
| 240 // since they can very seriously hurt performance. |
| 241 // This will take care of all AudioNodes because they all process within thi
s scope. |
| 242 DenormalDisabler denormalDisabler; |
| 243 |
| 244 context()->deferredTaskHandler().setAudioThread(currentThread()); |
| 245 |
| 246 if (!context()->isDestinationInitialized()) { |
| 247 destinationBus->zero(); |
| 248 return false; |
| 249 } |
| 250 |
| 251 // Take care pre-render tasks at the beginning of each render quantum. Then |
| 252 // it will stop the rendering loop if the context needs to be suspended |
| 253 // at the beginning of the next render quantum. |
| 254 if (context()->handlePreOfflineRenderTasks()) { |
| 255 suspendOfflineRendering(); |
| 256 return true; |
| 257 } |
| 258 |
| 259 // Prepare the local audio input provider for this render quantum. |
| 260 if (sourceBus) |
| 261 m_localAudioInputProvider.set(sourceBus); |
| 262 |
| 263 ASSERT(numberOfInputs() >= 1); |
| 264 if (numberOfInputs() < 1) { |
| 265 destinationBus->zero(); |
| 266 return false; |
| 267 } |
| 268 // This will cause the node(s) connected to us to process, which in turn wil
l pull on their input(s), |
| 269 // all the way backwards through the rendering graph. |
| 270 AudioBus* renderedBus = input(0).pull(destinationBus, numberOfFrames); |
| 271 |
| 272 if (!renderedBus) { |
| 273 destinationBus->zero(); |
| 274 } else if (renderedBus != destinationBus) { |
| 275 // in-place processing was not possible - so copy |
| 276 destinationBus->copyFrom(*renderedBus); |
| 277 } |
| 278 |
| 279 // Process nodes which need a little extra help because they are not connect
ed to anything, but still need to process. |
| 280 context()->deferredTaskHandler().processAutomaticPullNodes(numberOfFrames); |
| 281 |
| 282 // Let the context take care of any business at the end of each render quant
um. |
| 283 context()->handlePostOfflineRenderTasks(); |
| 284 |
| 285 // Advance current sample-frame. |
| 286 size_t newSampleFrame = m_currentSampleFrame + numberOfFrames; |
| 287 releaseStore(&m_currentSampleFrame, newSampleFrame); |
| 288 |
| 289 return false; |
| 290 } |
| 291 |
165 // ---------------------------------------------------------------- | 292 // ---------------------------------------------------------------- |
166 | 293 |
167 OfflineAudioDestinationNode::OfflineAudioDestinationNode(AbstractAudioContext& c
ontext, AudioBuffer* renderTarget) | 294 OfflineAudioDestinationNode::OfflineAudioDestinationNode(AbstractAudioContext& c
ontext, AudioBuffer* renderTarget) |
168 : AudioDestinationNode(context) | 295 : AudioDestinationNode(context) |
169 { | 296 { |
170 setHandler(OfflineAudioDestinationHandler::create(*this, renderTarget)); | 297 setHandler(OfflineAudioDestinationHandler::create(*this, renderTarget)); |
171 } | 298 } |
172 | 299 |
173 OfflineAudioDestinationNode* OfflineAudioDestinationNode::create(AbstractAudioCo
ntext* context, AudioBuffer* renderTarget) | 300 OfflineAudioDestinationNode* OfflineAudioDestinationNode::create(AbstractAudioCo
ntext* context, AudioBuffer* renderTarget) |
174 { | 301 { |
175 return new OfflineAudioDestinationNode(*context, renderTarget); | 302 return new OfflineAudioDestinationNode(*context, renderTarget); |
176 } | 303 } |
177 | 304 |
178 } // namespace blink | 305 } // namespace blink |
179 | 306 |
180 #endif // ENABLE(WEB_AUDIO) | 307 #endif // ENABLE(WEB_AUDIO) |
OLD | NEW |