Chromium Code Reviews| 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 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); |
|
Raymond Toy
2015/10/21 18:22:46
What if AudioBus::create fails to allocate space?
hongchan
2015/10/22 18:23:49
That would be an exception.
Also without a valid
| |
| 47 } | 55 } |
| 48 | 56 |
| 49 PassRefPtr<OfflineAudioDestinationHandler> OfflineAudioDestinationHandler::creat e(AudioNode& node, AudioBuffer* renderTarget) | 57 PassRefPtr<OfflineAudioDestinationHandler> OfflineAudioDestinationHandler::creat e(AudioNode& node, AudioBuffer* renderTarget) |
| 50 { | 58 { |
| 51 return adoptRef(new OfflineAudioDestinationHandler(node, renderTarget)); | 59 return adoptRef(new OfflineAudioDestinationHandler(node, renderTarget)); |
| 52 } | 60 } |
| 53 | 61 |
| 54 OfflineAudioDestinationHandler::~OfflineAudioDestinationHandler() | 62 OfflineAudioDestinationHandler::~OfflineAudioDestinationHandler() |
| 55 { | 63 { |
| 56 ASSERT(!isInitialized()); | 64 ASSERT(!isInitialized()); |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 74 { | 82 { |
| 75 if (!isInitialized()) | 83 if (!isInitialized()) |
| 76 return; | 84 return; |
| 77 | 85 |
| 78 if (m_renderThread) | 86 if (m_renderThread) |
| 79 m_renderThread.clear(); | 87 m_renderThread.clear(); |
| 80 | 88 |
| 81 AudioHandler::uninitialize(); | 89 AudioHandler::uninitialize(); |
| 82 } | 90 } |
| 83 | 91 |
| 92 OfflineAudioContext* OfflineAudioDestinationHandler::context() const | |
| 93 { | |
| 94 return static_cast<OfflineAudioContext*>(m_context); | |
| 95 } | |
| 96 | |
| 84 void OfflineAudioDestinationHandler::startRendering() | 97 void OfflineAudioDestinationHandler::startRendering() |
| 85 { | 98 { |
| 86 ASSERT(isMainThread()); | 99 ASSERT(isMainThread()); |
| 87 ASSERT(m_renderTarget); | 100 ASSERT(m_renderTarget); |
| 101 ASSERT(m_renderThread); | |
|
Raymond Toy
2015/10/21 18:22:46
I would reverse these so that ASSERT(m_renderTarge
hongchan
2015/10/22 18:23:49
Done.
| |
| 102 | |
| 88 if (!m_renderTarget) | 103 if (!m_renderTarget) |
| 89 return; | 104 return; |
| 90 | 105 |
| 91 if (!m_startedRendering) { | 106 // Rendering was not started. Starting now. |
| 92 m_startedRendering = true; | 107 if (!m_isRenderingStarted) { |
| 93 m_renderThread = adoptPtr(Platform::current()->createThread("Offline Aud io Renderer")); | 108 m_renderThread->taskRunner()->postTask(BLINK_FROM_HERE, |
| 94 m_renderThread->taskRunner()->postTask(BLINK_FROM_HERE, new Task(threadS afeBind(&OfflineAudioDestinationHandler::offlineRender, PassRefPtr<OfflineAudioD estinationHandler>(this)))); | 109 new Task(threadSafeBind(&OfflineAudioDestinationHandler::startOfflin eRendering, this))); |
| 110 m_isRenderingStarted = true; | |
|
Raymond Toy
2015/10/21 18:22:46
Does it matter if m_isRenderingStarted is set to t
hongchan
2015/10/22 18:23:49
m_isRenderingStart can only be read/modified by st
| |
| 111 return; | |
| 95 } | 112 } |
| 113 | |
| 114 // Rendering is already started, which implicitly means we resume the | |
| 115 // rendering by calling |doOfflineRendering| on the render thread. | |
| 116 m_renderThread->taskRunner()->postTask(BLINK_FROM_HERE, | |
| 117 threadSafeBind(&OfflineAudioDestinationHandler::doOfflineRendering, this )); | |
| 96 } | 118 } |
| 97 | 119 |
| 98 void OfflineAudioDestinationHandler::stopRendering() | 120 void OfflineAudioDestinationHandler::stopRendering() |
| 99 { | 121 { |
| 122 // OfflineAudioContext CANNOT BE stopped by JavaScript. | |
| 100 ASSERT_NOT_REACHED(); | 123 ASSERT_NOT_REACHED(); |
| 101 } | 124 } |
| 102 | 125 |
| 103 void OfflineAudioDestinationHandler::offlineRender() | 126 WebThread* OfflineAudioDestinationHandler::offlineRenderThread() |
| 104 { | 127 { |
| 105 offlineRenderInternal(); | 128 ASSERT(m_renderThread); |
| 106 context()->handlePostRenderTasks(); | 129 |
| 130 return m_renderThread.get(); | |
| 107 } | 131 } |
| 108 | 132 |
| 109 void OfflineAudioDestinationHandler::offlineRenderInternal() | 133 size_t OfflineAudioDestinationHandler::renderQuantumFrames() const |
|
Raymond Toy
2015/10/21 18:22:45
Should this be inline? It's so small. Or maybe m
hongchan
2015/10/22 18:23:49
Done.
| |
| 134 { | |
| 135 return renderQuantumSize; | |
| 136 } | |
| 137 | |
| 138 void OfflineAudioDestinationHandler::startOfflineRendering() | |
| 110 { | 139 { |
| 111 ASSERT(!isMainThread()); | 140 ASSERT(!isMainThread()); |
| 141 | |
| 112 ASSERT(m_renderBus); | 142 ASSERT(m_renderBus); |
| 113 if (!m_renderBus) | 143 if (!m_renderBus) |
| 114 return; | 144 return; |
| 115 | 145 |
| 116 bool isAudioContextInitialized = context()->isDestinationInitialized(); | 146 bool isAudioContextInitialized = context()->isDestinationInitialized(); |
| 117 ASSERT(isAudioContextInitialized); | 147 ASSERT(isAudioContextInitialized); |
| 118 if (!isAudioContextInitialized) | 148 if (!isAudioContextInitialized) |
| 119 return; | 149 return; |
| 120 | 150 |
| 121 bool channelsMatch = m_renderBus->numberOfChannels() == m_renderTarget->numb erOfChannels(); | 151 bool channelsMatch = m_renderBus->numberOfChannels() == m_renderTarget->numb erOfChannels(); |
| 122 ASSERT(channelsMatch); | 152 ASSERT(channelsMatch); |
| 123 if (!channelsMatch) | 153 if (!channelsMatch) |
| 124 return; | 154 return; |
| 125 | 155 |
| 126 bool isRenderBusAllocated = m_renderBus->length() >= renderQuantumSize; | 156 bool isRenderBusAllocated = m_renderBus->length() >= renderQuantumSize; |
| 127 ASSERT(isRenderBusAllocated); | 157 ASSERT(isRenderBusAllocated); |
| 128 if (!isRenderBusAllocated) | 158 if (!isRenderBusAllocated) |
| 129 return; | 159 return; |
| 130 | 160 |
| 131 // Break up the render target into smaller "render quantize" sized pieces. | 161 // Break up the render target into smaller "render quantize" sized pieces. |
| 132 // Render until we're finished. | 162 // Render until we're finished. |
| 133 size_t framesToProcess = m_renderTarget->length(); | 163 m_framesToProcess = m_renderTarget->length(); |
|
Raymond Toy
2015/10/21 18:22:46
Shouldn't this be set when the offline context is
hongchan
2015/10/22 18:23:49
Done, and I mistakenly kept the old comment. I wil
| |
| 164 | |
| 165 // Start rendering. | |
| 166 doOfflineRendering(); | |
| 167 } | |
| 168 | |
| 169 void OfflineAudioDestinationHandler::doOfflineRendering() | |
| 170 { | |
| 171 ASSERT(!isMainThread()); | |
| 172 | |
| 134 unsigned numberOfChannels = m_renderTarget->numberOfChannels(); | 173 unsigned numberOfChannels = m_renderTarget->numberOfChannels(); |
| 135 | 174 |
| 136 unsigned n = 0; | 175 // Reset the suspend flag. |
| 137 while (framesToProcess > 0) { | 176 m_shouldSuspend = false; |
| 138 // Render one render quantum. | |
| 139 render(0, m_renderBus.get(), renderQuantumSize); | |
| 140 | 177 |
| 141 size_t framesAvailableToCopy = std::min(framesToProcess, renderQuantumSi ze); | 178 // If there is more to process and there is no suspension at the moment, |
| 179 // do continue to render quanta. Then calling OfflineAudioContext.resume() w ill pick up | |
| 180 // the render loop again from where it was suspended. | |
| 181 while (m_framesToProcess > 0 && !m_shouldSuspend) { | |
| 182 | |
| 183 // Render one render quantum. Note that this includes pre/post render | |
| 184 // tasks from the online audio context. Note that this method will | |
| 185 // change |m_shouldSuspend| internally according to the scheduled | |
|
Raymond Toy
2015/10/21 18:22:46
This comment about changing m_shouldSuspend intern
hongchan
2015/10/22 18:23:49
Oops. Done.
| |
| 186 // suspends. | |
| 187 m_shouldSuspend = checkSuspendsAndRender(0, m_renderBus.get(), renderQua ntumSize); | |
| 188 | |
| 189 if (m_shouldSuspend) { | |
| 190 suspendOfflineRendering(); | |
| 191 return; | |
| 192 } | |
| 193 | |
| 194 size_t framesAvailableToCopy = std::min(m_framesToProcess, renderQuantum Size); | |
| 142 | 195 |
| 143 for (unsigned channelIndex = 0; channelIndex < numberOfChannels; ++chann elIndex) { | 196 for (unsigned channelIndex = 0; channelIndex < numberOfChannels; ++chann elIndex) { |
| 144 const float* source = m_renderBus->channel(channelIndex)->data(); | 197 const float* source = m_renderBus->channel(channelIndex)->data(); |
| 145 float* destination = m_renderTarget->getChannelData(channelIndex)->d ata(); | 198 float* destination = m_renderTarget->getChannelData(channelIndex)->d ata(); |
| 146 memcpy(destination + n, source, sizeof(float) * framesAvailableToCop y); | 199 memcpy(destination + m_framesProcessed, source, sizeof(float) * fram esAvailableToCopy); |
| 147 } | 200 } |
| 148 | 201 |
| 149 n += framesAvailableToCopy; | 202 m_framesProcessed += framesAvailableToCopy; |
| 150 framesToProcess -= framesAvailableToCopy; | 203 |
| 204 ASSERT(m_framesToProcess >= framesAvailableToCopy); | |
| 205 m_framesToProcess -= framesAvailableToCopy; | |
| 151 } | 206 } |
| 152 | 207 |
| 153 // Our work is done. Let the AbstractAudioContext know. | 208 // Finish up the rendering loop if there is no more to process. |
| 154 if (context()->executionContext()) | 209 if (m_framesToProcess == 0) { |
|
Raymond Toy
2015/10/21 18:22:46
I hate the style, but you're not supposed to compa
hongchan
2015/10/22 18:23:49
Done.
| |
| 155 context()->executionContext()->postTask(BLINK_FROM_HERE, createCrossThre adTask(&OfflineAudioDestinationHandler::notifyComplete, PassRefPtr<OfflineAudioD estinationHandler>(this))); | 210 ASSERT(m_framesToProcess == 0); |
|
Raymond Toy
2015/10/21 18:22:46
How can this assert ever fail? Delete it.
hongchan
2015/10/22 18:23:49
This was suggested by haraken@ before, but I think
| |
| 211 finishOfflineRendering(); | |
| 212 } | |
| 213 } | |
| 214 | |
| 215 void OfflineAudioDestinationHandler::suspendOfflineRendering() | |
| 216 { | |
| 217 ASSERT(!isMainThread()); | |
| 218 | |
| 219 // The actual rendering has been suspended. Notify the context. | |
| 220 if (context()->executionContext()) { | |
| 221 context()->executionContext()->postTask(BLINK_FROM_HERE, | |
| 222 createCrossThreadTask(&OfflineAudioDestinationHandler::notifySuspend , this)); | |
| 223 } | |
| 224 } | |
| 225 | |
| 226 void OfflineAudioDestinationHandler::finishOfflineRendering() | |
| 227 { | |
| 228 ASSERT(!isMainThread()); | |
| 229 | |
| 230 // The actual rendering has been completed. Notify the context. | |
| 231 if (context()->executionContext()) { | |
| 232 context()->executionContext()->postTask(BLINK_FROM_HERE, | |
| 233 createCrossThreadTask(&OfflineAudioDestinationHandler::notifyComplet e, this)); | |
| 234 } | |
| 235 } | |
| 236 | |
| 237 void OfflineAudioDestinationHandler::notifySuspend() | |
| 238 { | |
| 239 if (context()) | |
| 240 context()->resolveSuspendOnMainThread(context()->currentSampleFrame()); | |
| 156 } | 241 } |
| 157 | 242 |
| 158 void OfflineAudioDestinationHandler::notifyComplete() | 243 void OfflineAudioDestinationHandler::notifyComplete() |
| 159 { | 244 { |
| 160 // The AbstractAudioContext might be gone. | 245 // The OfflineAudioContext might be gone. |
| 161 if (context()) | 246 if (context()) |
| 162 context()->fireCompletionEvent(); | 247 context()->fireCompletionEvent(); |
| 163 } | 248 } |
| 164 | 249 |
| 250 bool OfflineAudioDestinationHandler::checkSuspendsAndRender(AudioBus* sourceBus, AudioBus* destinationBus, size_t numberOfFrames) | |
| 251 { | |
| 252 // We don't want denormals slowing down any of the audio processing | |
| 253 // since they can very seriously hurt performance. | |
| 254 // This will take care of all AudioNodes because they all process within thi s scope. | |
| 255 DenormalDisabler denormalDisabler; | |
| 256 | |
| 257 context()->deferredTaskHandler().setAudioThread(currentThread()); | |
| 258 | |
| 259 if (!context()->isDestinationInitialized()) { | |
|
Raymond Toy
2015/10/21 18:22:45
For an offline context, is it actually possible fo
hongchan
2015/10/22 18:23:49
This method is just a ported version of render() m
| |
| 260 destinationBus->zero(); | |
| 261 return false; | |
| 262 } | |
| 263 | |
| 264 // Take care pre-render tasks at the beginning of each render quantum. This | |
| 265 // will change |m_shouldSuspend| flag if there is a suspend scheduled at the | |
|
Raymond Toy
2015/10/21 18:22:46
Comment is wrong? m_shouldSuspend isn't modified.
hongchan
2015/10/22 18:23:49
Oops. Done.
| |
| 266 // current frame. | |
| 267 if (context()->handlePreOfflineRenderTasks()) | |
| 268 return true; | |
| 269 | |
| 270 // Prepare the local audio input provider for this render quantum. | |
| 271 if (sourceBus) | |
| 272 m_localAudioInputProvider.set(sourceBus); | |
| 273 | |
| 274 ASSERT(numberOfInputs() >= 1); | |
| 275 if (numberOfInputs() < 1) { | |
| 276 destinationBus->zero(); | |
| 277 return false; | |
| 278 } | |
| 279 // This will cause the node(s) connected to us to process, which in turn wil l pull on their input(s), | |
| 280 // all the way backwards through the rendering graph. | |
| 281 AudioBus* renderedBus = input(0).pull(destinationBus, numberOfFrames); | |
| 282 | |
| 283 if (!renderedBus) { | |
| 284 destinationBus->zero(); | |
| 285 } else if (renderedBus != destinationBus) { | |
| 286 // in-place processing was not possible - so copy | |
| 287 destinationBus->copyFrom(*renderedBus); | |
| 288 } | |
| 289 | |
| 290 // Process nodes which need a little extra help because they are not connect ed to anything, but still need to process. | |
| 291 context()->deferredTaskHandler().processAutomaticPullNodes(numberOfFrames); | |
| 292 | |
| 293 // Let the context take care of any business at the end of each render quant um. | |
| 294 context()->handlePostOfflineRenderTasks(); | |
| 295 | |
| 296 // Advance current sample-frame. | |
| 297 size_t newSampleFrame = m_currentSampleFrame + numberOfFrames; | |
| 298 releaseStore(&m_currentSampleFrame, newSampleFrame); | |
| 299 | |
| 300 return false; | |
| 301 } | |
| 302 | |
| 165 // ---------------------------------------------------------------- | 303 // ---------------------------------------------------------------- |
| 166 | 304 |
| 167 OfflineAudioDestinationNode::OfflineAudioDestinationNode(AbstractAudioContext& c ontext, AudioBuffer* renderTarget) | 305 OfflineAudioDestinationNode::OfflineAudioDestinationNode(AbstractAudioContext& c ontext, AudioBuffer* renderTarget) |
| 168 : AudioDestinationNode(context) | 306 : AudioDestinationNode(context) |
| 169 { | 307 { |
| 170 setHandler(OfflineAudioDestinationHandler::create(*this, renderTarget)); | 308 setHandler(OfflineAudioDestinationHandler::create(*this, renderTarget)); |
| 171 } | 309 } |
| 172 | 310 |
| 173 OfflineAudioDestinationNode* OfflineAudioDestinationNode::create(AbstractAudioCo ntext* context, AudioBuffer* renderTarget) | 311 OfflineAudioDestinationNode* OfflineAudioDestinationNode::create(AbstractAudioCo ntext* context, AudioBuffer* renderTarget) |
| 174 { | 312 { |
| 175 return new OfflineAudioDestinationNode(*context, renderTarget); | 313 return new OfflineAudioDestinationNode(*context, renderTarget); |
| 176 } | 314 } |
| 177 | 315 |
| 178 } // namespace blink | 316 } // namespace blink |
| 179 | 317 |
| 180 #endif // ENABLE(WEB_AUDIO) | 318 #endif // ENABLE(WEB_AUDIO) |
| OLD | NEW |