Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(380)

Side by Side Diff: Source/modules/webaudio/OfflineAudioContext.cpp

Issue 1140723003: Implement suspend() and resume() for OfflineAudioContext (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: Resolving the render thread issue (WIP) Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 /* 1 /*
2 * Copyright (C) 2012, Google Inc. All rights reserved. 2 * Copyright (C) 2012, 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 13 matching lines...) Expand all
24 24
25 #include "config.h" 25 #include "config.h"
26 #if ENABLE(WEB_AUDIO) 26 #if ENABLE(WEB_AUDIO)
27 #include "modules/webaudio/OfflineAudioContext.h" 27 #include "modules/webaudio/OfflineAudioContext.h"
28 28
29 #include "bindings/core/v8/ExceptionMessages.h" 29 #include "bindings/core/v8/ExceptionMessages.h"
30 #include "bindings/core/v8/ExceptionState.h" 30 #include "bindings/core/v8/ExceptionState.h"
31 #include "core/dom/Document.h" 31 #include "core/dom/Document.h"
32 #include "core/dom/ExceptionCode.h" 32 #include "core/dom/ExceptionCode.h"
33 #include "core/dom/ExecutionContext.h" 33 #include "core/dom/ExecutionContext.h"
34 #include "modules/webaudio/OfflineAudioCompletionEvent.h"
35 #include "modules/webaudio/OfflineAudioDestinationNode.h"
36 #include "platform/Task.h"
37 #include "platform/ThreadSafeFunctional.h"
34 #include "platform/audio/AudioUtilities.h" 38 #include "platform/audio/AudioUtilities.h"
39 #include "public/platform/Platform.h"
40
35 41
36 namespace blink { 42 namespace blink {
37 43
38 OfflineAudioContext* OfflineAudioContext::create(ExecutionContext* context, unsi gned numberOfChannels, size_t numberOfFrames, float sampleRate, ExceptionState& exceptionState) 44 OfflineAudioContext* OfflineAudioContext::create(ExecutionContext* context, unsi gned numberOfChannels, size_t numberOfFrames, float sampleRate, ExceptionState& exceptionState)
39 { 45 {
40 // FIXME: add support for workers. 46 // FIXME: add support for workers.
41 if (!context || !context->isDocument()) { 47 if (!context || !context->isDocument()) {
42 exceptionState.throwDOMException( 48 exceptionState.throwDOMException(
43 NotSupportedError, 49 NotSupportedError,
44 "Workers are not supported."); 50 "Workers are not supported.");
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
85 + ", " + String::number(sampleRate) 91 + ", " + String::number(sampleRate)
86 + ")"); 92 + ")");
87 } 93 }
88 94
89 audioContext->suspendIfNeeded(); 95 audioContext->suspendIfNeeded();
90 return audioContext; 96 return audioContext;
91 } 97 }
92 98
93 OfflineAudioContext::OfflineAudioContext(Document* document, unsigned numberOfCh annels, size_t numberOfFrames, float sampleRate) 99 OfflineAudioContext::OfflineAudioContext(Document* document, unsigned numberOfCh annels, size_t numberOfFrames, float sampleRate)
94 : AudioContext(document, numberOfChannels, numberOfFrames, sampleRate) 100 : AudioContext(document, numberOfChannels, numberOfFrames, sampleRate)
101 , m_isRenderingStarted(false)
102 , m_totalRenderFrames(numberOfFrames)
95 { 103 {
96 } 104 }
97 105
98 OfflineAudioContext::~OfflineAudioContext() 106 OfflineAudioContext::~OfflineAudioContext()
99 { 107 {
100 } 108 }
101 109
110 DEFINE_TRACE(OfflineAudioContext)
111 {
112 visitor->trace(m_scheduledSuspends);
113 visitor->trace(m_completeResolver);
114 AudioContext::trace(visitor);
115 }
116
117 bool OfflineAudioContext::shouldSuspendNow()
118 {
119 ASSERT(!isMainThread());
120
121 // Suspend if necessary and mark the associated promise as pending. Marked
122 // promises will be resolved later. Note that duplicate entries in the
123 // suspend list are prohibited so it returns immediately when a valid
124 // suspend is found. This duplicate check is done by
125 // |suspendOfflineRendering|.
126 size_t nowFrame = currentSampleFrame();
127 for (unsigned index = 0; index < m_scheduledSuspends.size(); ++index) {
128 if (m_scheduledSuspends.at(index)->shouldSuspendAtFrame(nowFrame)) {
129 m_scheduledSuspends.at(index)->markAsPending();
130 return true;
131 }
132 }
133
134 return false;
135 }
136
137 void OfflineAudioContext::resolvePendingSuspendPromises()
138 {
139 ASSERT(!isMainThread());
140
141 // Resolve promises marked as 'pending'.
142 if (m_scheduledSuspends.size() > 0) {
143 Platform::current()->mainThread()->postTask(FROM_HERE,
144 threadSafeBind(&OfflineAudioContext::resolvePendingSuspendPromisesOn MainThread, this));
145 }
146 }
147
148 void OfflineAudioContext::fireCompletionEvent()
149 {
150 ASSERT(isMainThread());
151
152 // We set the state to closed here so that the oncomplete event handler sees
153 // that the context has been closed.
154 setContextState(Closed);
155
156 AudioBuffer* renderedBuffer = renderTarget();
157
158 ASSERT(renderedBuffer);
159 if (!renderedBuffer)
160 return;
161
162 // Avoid firing the event if the document has already gone away.
163 if (executionContext()) {
164 // Call the offline rendering completion event listener and resolve the
165 // promise too.
166 dispatchEvent(OfflineAudioCompletionEvent::create(renderedBuffer));
167 m_completeResolver->resolve(renderedBuffer);
168 }
169 }
170
102 ScriptPromise OfflineAudioContext::startOfflineRendering(ScriptState* scriptStat e) 171 ScriptPromise OfflineAudioContext::startOfflineRendering(ScriptState* scriptStat e)
103 { 172 {
173 ASSERT(isMainThread());
174
104 // Calling close() on an OfflineAudioContext is not supported/allowed, 175 // Calling close() on an OfflineAudioContext is not supported/allowed,
105 // but it might well have been stopped by its execution context. 176 // but it might well have been stopped by its execution context.
106 if (isContextClosed()) { 177 if (isContextClosed()) {
107 return ScriptPromise::rejectWithDOMException( 178 return ScriptPromise::rejectWithDOMException(
108 scriptState, 179 scriptState,
109 DOMException::create( 180 DOMException::create(
110 InvalidStateError, 181 InvalidStateError,
111 "cannot call startRendering on an OfflineAudioContext in a stopp ed state.")); 182 "cannot call startRendering on an OfflineAudioContext in a stopp ed state."));
112 } 183 }
113 184
114 if (m_offlineResolver) { 185 if (m_completeResolver) {
115 // Can't call startRendering more than once. Return a rejected promise now. 186 // Can't call startRendering more than once. Return a rejected promise now.
116 return ScriptPromise::rejectWithDOMException( 187 return ScriptPromise::rejectWithDOMException(
117 scriptState, 188 scriptState,
118 DOMException::create( 189 DOMException::create(
119 InvalidStateError, 190 InvalidStateError,
120 "cannot call startRendering more than once")); 191 "cannot call startRendering more than once"));
121 } 192 }
122 193
123 m_offlineResolver = ScriptPromiseResolver::create(scriptState); 194 // If the context is not in the suspended state, reject the promise.
124 startRendering(); 195 if (contextState() != AudioContextState::Suspended) {
125 return m_offlineResolver->promise(); 196 return ScriptPromise::rejectWithDOMException(
197 scriptState,
198 DOMException::create(
199 InvalidStateError,
200 "cannot startRendering when an OfflineAudioContext is not in a s uspended state"));
201 }
202
203 m_completeResolver = ScriptPromiseResolver::create(scriptState);
204
205 // Start rendering and return the promise.
206 m_isRenderingStarted = true;
207 setContextState(Running);
208 destination()->audioDestinationHandler().startRendering();
209
210 return m_completeResolver->promise();
211 }
212
213 ScriptPromise OfflineAudioContext::suspendOfflineRendering(ScriptState* scriptSt ate, double when)
214 {
215 ASSERT(isMainThread());
216 ASSERT(destination()->audioDestinationHandler().offlineRenderThread());
217
218 RefPtrWillBeRawPtr<ScriptPromiseResolver> resolver = ScriptPromiseResolver:: create(scriptState);
219 ScriptPromise promise = resolver->promise();
220
221 // The specified suspend time is negative, reject the promise.
222 if (when < 0) {
223 resolver->reject(DOMException::create(InvalidStateError,
224 "negative suspend time (" + String::number(when) + ") is not allowed "));
225 return promise;
226 }
227
228 // Quantize the suspend time to the rendering block boundary.
229 size_t quantizedFrame = destination()->audioDestinationHandler().quantizeTim eToRenderQuantum(when);
230
231 // The specified suspend time is in the past, reject the promise.
232 if (quantizedFrame < currentSampleFrame()) {
233 resolver->reject(DOMException::create(InvalidStateError,
234 "cannot schedule a suspend at frame " + String::number(quantizedFram e) +
235 " (" + String::number(when) + " seconds) because it is earlier than the current frame of " +
236 String::number(currentSampleFrame())));
237 return promise;
238 }
239
240 // The suspend time should be earlier than the total render frame. If the
241 // requested suspension time is equal to the total render frame, the promise
242 // will be rejected.
243 if (m_totalRenderFrames <= quantizedFrame) {
244 resolver->reject(DOMException::create(InvalidStateError,
245 "cannot schedule a suspend at frame " + String::number(quantizedFram e) +
246 " (" + String::number(when) + " seconds) because it is greater than or equal to the total render duration of " +
247 String::number(m_totalRenderFrames) + " frames"));
248 return promise;
249 }
250
251 ScheduledSuspendContainer* suspendContainer = ScheduledSuspendContainer::cre ate(when, quantizedFrame, resolver);
252
253 // Validate the suspend and append if necessary on the render thread.
254 destination()->audioDestinationHandler().offlineRenderThread()->postTask(FRO M_HERE,
255 threadSafeBind(&OfflineAudioContext::checkDuplicateSuspend, this, suspen dContainer));
256
257 return promise;
258 }
259
260 ScriptPromise OfflineAudioContext::resumeOfflineRendering(ScriptState* scriptSta te)
261 {
262 ASSERT(isMainThread());
263
264 RefPtrWillBeRawPtr<ScriptPromiseResolver> resolver = ScriptPromiseResolver:: create(scriptState);
265 ScriptPromise promise = resolver->promise();
266
267 // If the context is not in a suspended state, reject the promise.
268 if (contextState() != AudioContextState::Suspended) {
269 resolver->reject(DOMException::create(InvalidStateError,
270 "cannot resume a context that is not suspended"));
271 return promise;
272 }
273
274 // If the rendering has not started, reject the promise.
275 if (!m_isRenderingStarted) {
276 resolver->reject(DOMException::create(InvalidStateError,
277 "cannot resume a context that has not started"));
278 return promise;
279 }
280
281 // If the context is suspended, resume rendering by setting the state to
282 // "Running." and calling startRendering(). Note that resuming is possible
283 // only after the rendering started.
284 setContextState(Running);
285 destination()->audioDestinationHandler().startRendering();
286
287 // Resolve the promise immediately.
288 resolver->resolve();
289
290 return promise;
291 }
292
293 void OfflineAudioContext::checkDuplicateSuspend(ScheduledSuspendContainer* suspe ndContainer)
294 {
295 ASSERT(!isMainThread());
296
297 // If there is a duplicate suspension at the same quantize frame, reject the
298 // promise. The rejection of promise should happen in the main thread, so we
299 // post a task to it.
300 for (unsigned index = 0; index < m_scheduledSuspends.size(); ++index) {
301 if (m_scheduledSuspends.at(index)->suspendFrame() == suspendContainer->s uspendFrame()) {
302 Platform::current()->mainThread()->postTask(FROM_HERE,
303 threadSafeBind(&OfflineAudioContext::rejectSuspendOnMainThread, this, suspendContainer));
304 return;
305 }
306 }
307
308 // If duplicate check passes, we can add the container safely here in the
309 // render thread.
310 MutexLocker locker(m_suspendMutex);
311 fprintf(stderr, "adding suspend...\n");
312 // TODO: this does not work. The tab crashes here.
313 m_scheduledSuspends.append(suspendContainer);
hongchan 2015/06/22 18:23:59 This actually crashes the tab (no error message in
yhirano 2015/06/23 02:33:42 I didn't notice that the render thread doesn't sup
hongchan 2015/06/23 17:57:38 yhirano@ haraken@ I guess we can decide what we w
314 fprintf(stderr, "suspend added.\n");
315 }
316
317 void OfflineAudioContext::rejectSuspendOnMainThread(ScheduledSuspendContainer* s uspendContainer)
318 {
319 ASSERT(isMainThread());
320
321 suspendContainer->resolver()->reject(DOMException::create(InvalidStateError,
322 "cannot schedule more than one suspend at frame " +
323 String::number(suspendContainer->suspendFrame()) + " (" +
324 String::number(suspendContainer->suspendTime()) + " seconds)"));
325 }
326
327 void OfflineAudioContext::resolvePendingSuspendPromisesOnMainThread()
328 {
329 ASSERT(isMainThread());
330 AutoLocker locker(this);
331
332 // Suspend the context first. This will fire onstatechange event.
333 setContextState(Suspended);
334
335 // TODO: is removing elements efficient? What if there are 10K suspends?
336 // TODO: move this to the render thread.
337 // Resolve any pending suspend and remove it from the list.
338 bool pendingPromiseResolved = false;
339 for (unsigned index = 0; index < m_scheduledSuspends.size();) {
340 if (m_scheduledSuspends.at(index)->isPending()) {
341
342 // We should never have more than one pending suspend at any time.
343 ASSERT(!pendingPromiseResolved);
344
345 m_scheduledSuspends.at(index)->resolver()->resolve();
346 m_scheduledSuspends.remove(index);
347
348 pendingPromiseResolved = true;
349 } else {
350 ++index;
351 }
352 }
353 }
354
355 ScheduledSuspendContainer::ScheduledSuspendContainer(
356 double suspendTime, size_t suspendFrame, PassRefPtrWillBeRawPtr<ScriptPromis eResolver> resolver)
357 : m_suspendTime(suspendTime)
358 , m_suspendFrame(suspendFrame)
359 , m_resolver(resolver)
360 , m_isPending(false)
361 {
362 }
363
364 ScheduledSuspendContainer::~ScheduledSuspendContainer()
365 {
366 }
367
368 DEFINE_TRACE(ScheduledSuspendContainer)
369 {
370 visitor->trace(m_resolver);
371 }
372
373 ScheduledSuspendContainer* ScheduledSuspendContainer::create(
374 double suspendTime, size_t suspendFrame, PassRefPtrWillBeRawPtr<ScriptPromis eResolver> resolver)
375 {
376 return new ScheduledSuspendContainer(suspendTime, suspendFrame, resolver);
377 }
378
379 bool ScheduledSuspendContainer::shouldSuspendAtFrame(size_t whenFrame) const
380 {
381 if (m_suspendFrame != whenFrame)
382 return false;
383
384 return true;
385 }
386
387 bool ScheduledSuspendContainer::isPending() const
388 {
389 return m_isPending;
390 }
391
392 void ScheduledSuspendContainer::markAsPending()
393 {
394 m_isPending = true;
126 } 395 }
127 396
128 } // namespace blink 397 } // namespace blink
129 398
130 #endif // ENABLE(WEB_AUDIO) 399 #endif // ENABLE(WEB_AUDIO)
OLDNEW
« no previous file with comments | « Source/modules/webaudio/OfflineAudioContext.h ('k') | Source/modules/webaudio/OfflineAudioContext.idl » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698