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

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

Issue 625363004: Implement suspend/resume for AudioContext (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: Update according to new webaudio spec Created 6 years, 1 month 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 | Annotate | Revision Log
« no previous file with comments | « Source/modules/webaudio/AudioContext.h ('k') | Source/modules/webaudio/AudioContext.idl » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 * 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 12 matching lines...) Expand all
23 */ 23 */
24 24
25 #include "config.h" 25 #include "config.h"
26 26
27 #if ENABLE(WEB_AUDIO) 27 #if ENABLE(WEB_AUDIO)
28 28
29 #include "modules/webaudio/AudioContext.h" 29 #include "modules/webaudio/AudioContext.h"
30 30
31 #include "bindings/core/v8/ExceptionMessages.h" 31 #include "bindings/core/v8/ExceptionMessages.h"
32 #include "bindings/core/v8/ExceptionState.h" 32 #include "bindings/core/v8/ExceptionState.h"
33 #include "bindings/core/v8/ScriptState.h"
34 #include "core/dom/DOMException.h"
33 #include "core/dom/Document.h" 35 #include "core/dom/Document.h"
34 #include "core/dom/ExceptionCode.h" 36 #include "core/dom/ExceptionCode.h"
35 #include "core/html/HTMLMediaElement.h" 37 #include "core/html/HTMLMediaElement.h"
36 #include "core/inspector/ScriptCallStack.h" 38 #include "core/inspector/ScriptCallStack.h"
37 #include "modules/mediastream/MediaStream.h" 39 #include "modules/mediastream/MediaStream.h"
38 #include "modules/webaudio/AnalyserNode.h" 40 #include "modules/webaudio/AnalyserNode.h"
39 #include "modules/webaudio/AudioBuffer.h" 41 #include "modules/webaudio/AudioBuffer.h"
40 #include "modules/webaudio/AudioBufferCallback.h" 42 #include "modules/webaudio/AudioBufferCallback.h"
41 #include "modules/webaudio/AudioBufferSourceNode.h" 43 #include "modules/webaudio/AudioBufferSourceNode.h"
42 #include "modules/webaudio/AudioListener.h" 44 #include "modules/webaudio/AudioListener.h"
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
92 return audioContext; 94 return audioContext;
93 } 95 }
94 96
95 // Constructor for rendering to the audio hardware. 97 // Constructor for rendering to the audio hardware.
96 AudioContext::AudioContext(Document* document) 98 AudioContext::AudioContext(Document* document)
97 : ActiveDOMObject(document) 99 : ActiveDOMObject(document)
98 , m_isStopScheduled(false) 100 , m_isStopScheduled(false)
99 , m_isCleared(false) 101 , m_isCleared(false)
100 , m_isInitialized(false) 102 , m_isInitialized(false)
101 , m_destinationNode(nullptr) 103 , m_destinationNode(nullptr)
104 , m_isResolvingResumePromises(false)
102 , m_automaticPullNodesNeedUpdating(false) 105 , m_automaticPullNodesNeedUpdating(false)
103 , m_connectionCount(0) 106 , m_connectionCount(0)
104 , m_audioThread(0) 107 , m_audioThread(0)
105 , m_isOfflineContext(false) 108 , m_isOfflineContext(false)
109 , m_contextState(Suspended)
106 { 110 {
107 m_destinationNode = DefaultAudioDestinationNode::create(this); 111 m_destinationNode = DefaultAudioDestinationNode::create(this);
108 112
109 initialize(); 113 initialize();
110 #if DEBUG_AUDIONODE_REFERENCES 114 #if DEBUG_AUDIONODE_REFERENCES
111 fprintf(stderr, "%p: AudioContext::AudioContext() #%u\n", this, AudioContext ::s_hardwareContextCount); 115 fprintf(stderr, "%p: AudioContext::AudioContext() #%u\n", this, AudioContext ::s_hardwareContextCount);
112 #endif 116 #endif
113 } 117 }
114 118
115 // Constructor for offline (non-realtime) rendering. 119 // Constructor for offline (non-realtime) rendering.
116 AudioContext::AudioContext(Document* document, unsigned numberOfChannels, size_t numberOfFrames, float sampleRate) 120 AudioContext::AudioContext(Document* document, unsigned numberOfChannels, size_t numberOfFrames, float sampleRate)
117 : ActiveDOMObject(document) 121 : ActiveDOMObject(document)
118 , m_isStopScheduled(false) 122 , m_isStopScheduled(false)
119 , m_isCleared(false) 123 , m_isCleared(false)
120 , m_isInitialized(false) 124 , m_isInitialized(false)
121 , m_destinationNode(nullptr) 125 , m_destinationNode(nullptr)
126 , m_isResolvingResumePromises(false)
122 , m_automaticPullNodesNeedUpdating(false) 127 , m_automaticPullNodesNeedUpdating(false)
123 , m_connectionCount(0) 128 , m_connectionCount(0)
124 , m_audioThread(0) 129 , m_audioThread(0)
125 , m_isOfflineContext(true) 130 , m_isOfflineContext(true)
131 , m_contextState(Suspended)
126 { 132 {
127 // Create a new destination for offline rendering. 133 // Create a new destination for offline rendering.
128 m_renderTarget = AudioBuffer::create(numberOfChannels, numberOfFrames, sampl eRate); 134 m_renderTarget = AudioBuffer::create(numberOfChannels, numberOfFrames, sampl eRate);
129 if (m_renderTarget.get()) 135 if (m_renderTarget.get())
130 m_destinationNode = OfflineAudioDestinationNode::create(this, m_renderTa rget.get()); 136 m_destinationNode = OfflineAudioDestinationNode::create(this, m_renderTa rget.get());
131 137
132 initialize(); 138 initialize();
133 } 139 }
134 140
135 AudioContext::~AudioContext() 141 AudioContext::~AudioContext()
136 { 142 {
137 #if DEBUG_AUDIONODE_REFERENCES 143 #if DEBUG_AUDIONODE_REFERENCES
138 fprintf(stderr, "%p: AudioContext::~AudioContext()\n", this); 144 fprintf(stderr, "%p: AudioContext::~AudioContext()\n", this);
139 #endif 145 #endif
140 // AudioNodes keep a reference to their context, so there should be no way t o be in the destructor if there are still AudioNodes around. 146 // AudioNodes keep a reference to their context, so there should be no way t o be in the destructor if there are still AudioNodes around.
141 ASSERT(!m_isInitialized); 147 ASSERT(!m_isInitialized);
142 ASSERT(!m_referencedNodes.size()); 148 ASSERT(!m_referencedNodes.size());
143 ASSERT(!m_finishedNodes.size()); 149 ASSERT(!m_finishedNodes.size());
144 ASSERT(!m_automaticPullNodes.size()); 150 ASSERT(!m_automaticPullNodes.size());
145 if (m_automaticPullNodesNeedUpdating) 151 if (m_automaticPullNodesNeedUpdating)
146 m_renderingAutomaticPullNodes.resize(m_automaticPullNodes.size()); 152 m_renderingAutomaticPullNodes.resize(m_automaticPullNodes.size());
147 ASSERT(!m_renderingAutomaticPullNodes.size()); 153 ASSERT(!m_renderingAutomaticPullNodes.size());
154 ASSERT(!m_resumePromises.size());
148 } 155 }
149 156
150 void AudioContext::initialize() 157 void AudioContext::initialize()
151 { 158 {
152 if (isInitialized()) 159 if (isInitialized())
153 return; 160 return;
154 161
155 FFTFrame::initialize(); 162 FFTFrame::initialize();
156 m_listener = AudioListener::create(); 163 m_listener = AudioListener::create();
157 164
158 if (m_destinationNode.get()) { 165 if (m_destinationNode.get()) {
159 m_destinationNode->initialize(); 166 m_destinationNode->initialize();
160 167
161 if (!isOfflineContext()) { 168 if (!isOfflineContext()) {
162 // This starts the audio thread. The destination node's provideInput () method will now be called repeatedly to render audio. 169 // This starts the audio thread. The destination node's provideInput () method will now be called repeatedly to render audio.
163 // Each time provideInput() is called, a portion of the audio stream is rendered. Let's call this time period a "render quantum". 170 // Each time provideInput() is called, a portion of the audio stream is rendered. Let's call this time period a "render quantum".
164 // NOTE: for now default AudioContext does not need an explicit star tRendering() call from JavaScript. 171 // NOTE: for now default AudioContext does not need an explicit star tRendering() call from JavaScript.
165 // We may want to consider requiring it for symmetry with OfflineAud ioContext. 172 // We may want to consider requiring it for symmetry with OfflineAud ioContext.
166 m_destinationNode->startRendering(); 173 startRendering();
167 ++s_hardwareContextCount; 174 ++s_hardwareContextCount;
168 } 175 }
169 176
170 m_isInitialized = true; 177 m_isInitialized = true;
171 } 178 }
172 } 179 }
173 180
174 void AudioContext::clear() 181 void AudioContext::clear()
175 { 182 {
176 // We need to run disposers before destructing m_contextGraphMutex. 183 // We need to run disposers before destructing m_contextGraphMutex.
(...skipping 345 matching lines...) Expand 10 before | Expand all | Expand 10 after
522 exceptionState.throwDOMException( 529 exceptionState.throwDOMException(
523 IndexSizeError, 530 IndexSizeError,
524 "length of imaginary array (" + String::number(imag->length()) 531 "length of imaginary array (" + String::number(imag->length())
525 + ") exceeds allowed maximum of 4096"); 532 + ") exceeds allowed maximum of 4096");
526 return 0; 533 return 0;
527 } 534 }
528 535
529 return PeriodicWave::create(sampleRate(), real->view(), imag->view()); 536 return PeriodicWave::create(sampleRate(), real->view(), imag->view());
530 } 537 }
531 538
539 String AudioContext::state() const
540 {
541 // These strings had better match the strings for AudioContextState in Audio Context.idl.
542 switch (m_contextState) {
543 case Suspended:
544 return "suspended";
545 case Running:
546 return "running";
547 case Closed:
548 return "closed";
549 }
550 ASSERT_NOT_REACHED();
551 return "";
552 }
553
554 void AudioContext::setContextState(AudioContextState newState)
555 {
556 // Validate the transitions
557 switch (newState) {
558 case Suspended:
559 ASSERT(m_contextState == Running);
560 break;
561 case Running:
562 ASSERT(m_contextState == Suspended);
563 break;
564 case Closed:
565 ASSERT(m_contextState != Closed);
566 break;
567 }
568
569 m_contextState = newState;
570 }
571
572 ScriptPromise AudioContext::suspendContext(ScriptState* scriptState)
573 {
574 ASSERT(isMainThread());
575 AutoLocker locker(this);
576
577 RefPtr<ScriptPromiseResolver> resolver = ScriptPromiseResolver::create(scrip tState);
578
579 ScriptPromise promise = resolver->promise();
580
581 if (isOfflineContext()) {
582 resolver->reject(
583 DOMException::create(
584 InvalidStateError,
585 "cannot suspend an OfflineAudioContext"));
586 } else {
587 // Save the promise which will get resolved at the end of the rendering quantum.
588 m_suspendPromises.append(resolver);
589 }
590
591 return promise;
592 }
593
594 ScriptPromise AudioContext::resumeContext(ScriptState* scriptState)
595 {
596 ASSERT(isMainThread());
597 AutoLocker locker(this);
598
599 RefPtr<ScriptPromiseResolver> resolver = ScriptPromiseResolver::create(scrip tState);
600
601 ScriptPromise promise = resolver->promise();
602
603 if (isOfflineContext()) {
604 resolver->reject(
605 DOMException::create(
606 InvalidStateError,
607 "cannot resume an OfflineAudioContext"));
608 } else {
609 // Restart the destination node to pull on the audio graph.
610 if (m_destinationNode)
611 startRendering();
612
613 // Save the promise which will get resolved when the destination node st arts pulling on the
614 // graph again.
615 m_resumePromises.append(resolver);
616 }
617
618 return promise;
619 }
620
532 void AudioContext::notifyNodeFinishedProcessing(AudioNode* node) 621 void AudioContext::notifyNodeFinishedProcessing(AudioNode* node)
533 { 622 {
534 ASSERT(isAudioThread()); 623 ASSERT(isAudioThread());
535 m_finishedNodes.append(node); 624 m_finishedNodes.append(node);
536 } 625 }
537 626
538 void AudioContext::derefFinishedSourceNodes() 627 void AudioContext::derefFinishedSourceNodes()
539 { 628 {
540 ASSERT(isGraphOwner()); 629 ASSERT(isGraphOwner());
541 ASSERT(isAudioThread()); 630 ASSERT(isAudioThread());
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after
628 // It's OK if the tryLock() fails, we'll just take slightly longer to pick u p the changes. 717 // It's OK if the tryLock() fails, we'll just take slightly longer to pick u p the changes.
629 if (tryLock()) { 718 if (tryLock()) {
630 // Update the channel count mode. 719 // Update the channel count mode.
631 updateChangedChannelCountMode(); 720 updateChangedChannelCountMode();
632 721
633 // Fixup the state of any dirty AudioSummingJunctions and AudioNodeOutpu ts. 722 // Fixup the state of any dirty AudioSummingJunctions and AudioNodeOutpu ts.
634 handleDirtyAudioSummingJunctions(); 723 handleDirtyAudioSummingJunctions();
635 handleDirtyAudioNodeOutputs(); 724 handleDirtyAudioNodeOutputs();
636 725
637 updateAutomaticPullNodes(); 726 updateAutomaticPullNodes();
727 resolvePromisesForResume();
728
638 unlock(); 729 unlock();
639 } 730 }
640 } 731 }
641 732
642 void AudioContext::handlePostRenderTasks() 733 void AudioContext::handlePostRenderTasks()
643 { 734 {
644 ASSERT(isAudioThread()); 735 ASSERT(isAudioThread());
645 736
646 // Must use a tryLock() here too. Don't worry, the lock will very rarely be contended and this method is called frequently. 737 // Must use a tryLock() here too. Don't worry, the lock will very rarely be contended and this method is called frequently.
647 // The worst that can happen is that there will be some nodes which will tak e slightly longer than usual to be deleted or removed 738 // The worst that can happen is that there will be some nodes which will tak e slightly longer than usual to be deleted or removed
648 // from the render graph (in which case they'll render silence). 739 // from the render graph (in which case they'll render silence).
649 if (tryLock()) { 740 if (tryLock()) {
650 // Update the channel count mode. 741 // Update the channel count mode.
651 updateChangedChannelCountMode(); 742 updateChangedChannelCountMode();
652 743
653 // Take care of AudioNode tasks where the tryLock() failed previously. 744 // Take care of AudioNode tasks where the tryLock() failed previously.
654 handleDeferredAudioNodeTasks(); 745 handleDeferredAudioNodeTasks();
655 746
656 // Dynamically clean up nodes which are no longer needed. 747 // Dynamically clean up nodes which are no longer needed.
657 derefFinishedSourceNodes(); 748 derefFinishedSourceNodes();
658 749
659 // Fixup the state of any dirty AudioSummingJunctions and AudioNodeOutpu ts. 750 // Fixup the state of any dirty AudioSummingJunctions and AudioNodeOutpu ts.
660 handleDirtyAudioSummingJunctions(); 751 handleDirtyAudioSummingJunctions();
661 handleDirtyAudioNodeOutputs(); 752 handleDirtyAudioNodeOutputs();
662 753
663 updateAutomaticPullNodes(); 754 updateAutomaticPullNodes();
755 resolvePromisesForSuspend();
756
664 unlock(); 757 unlock();
665 } 758 }
666 } 759 }
667 760
668 void AudioContext::handleDeferredAudioNodeTasks() 761 void AudioContext::handleDeferredAudioNodeTasks()
669 { 762 {
670 ASSERT(isAudioThread() && isGraphOwner()); 763 ASSERT(isAudioThread() && isGraphOwner());
671 764
672 for (unsigned i = 0; i < m_deferredBreakConnectionList.size(); ++i) 765 for (unsigned i = 0; i < m_deferredBreakConnectionList.size(); ++i)
673 m_deferredBreakConnectionList[i]->breakConnectionWithLock(); 766 m_deferredBreakConnectionList[i]->breakConnectionWithLock();
(...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after
793 } 886 }
794 887
795 void AudioContext::processAutomaticPullNodes(size_t framesToProcess) 888 void AudioContext::processAutomaticPullNodes(size_t framesToProcess)
796 { 889 {
797 ASSERT(isAudioThread()); 890 ASSERT(isAudioThread());
798 891
799 for (unsigned i = 0; i < m_renderingAutomaticPullNodes.size(); ++i) 892 for (unsigned i = 0; i < m_renderingAutomaticPullNodes.size(); ++i)
800 m_renderingAutomaticPullNodes[i]->processIfNecessary(framesToProcess); 893 m_renderingAutomaticPullNodes[i]->processIfNecessary(framesToProcess);
801 } 894 }
802 895
896 void AudioContext::resolvePromisesForResumeOnMainThread()
897 {
898 ASSERT(isMainThread());
899 AutoLocker locker(this);
900
901 for (unsigned k = 0; k < m_resumePromises.size(); ++k) {
902 if (m_contextState == Closed) {
903 m_resumePromises[k]->reject(
904 DOMException::create(InvalidStateError, "Cannot resume a context that has been released"));
905 } else {
906 m_resumePromises[k]->resolve();
907 }
908 }
909
910 m_resumePromises.clear();
911 m_isResolvingResumePromises = false;
912 }
913
914 void AudioContext::resolvePromisesForResume()
915 {
916 // This runs inside the AudioContext's lock when handling pre-render tasks.
917 ASSERT(isAudioThread());
918 ASSERT(isGraphOwner());
919
920 // Resolve any pending promises created by resume(). Only do this we if have n't already started
921 // resolving these promises. This gets called very often and it takes some t ime to resolve the
922 // promises in the main thread.
923 if (!m_isResolvingResumePromises && m_resumePromises.size() > 0) {
924 m_isResolvingResumePromises = true;
925 callOnMainThread(bind(&AudioContext::resolvePromisesForResumeOnMainThrea d, this));
926 }
927 }
928
929 void AudioContext::resolvePromisesForSuspendOnMainThread()
930 {
931 ASSERT(isMainThread());
932 AutoLocker locker(this);
933
934 // We can stop rendering now.
935 if (m_destinationNode)
936 stopRendering();
937
938 for (unsigned k = 0; k < m_suspendPromises.size(); ++k) {
939 if (m_contextState == Closed) {
940 m_suspendPromises[k]->reject(
941 DOMException::create(InvalidStateError, "Cannot suspend a contex t that has been released"));
942 } else {
943 m_suspendPromises[k]->resolve();
944 }
945 }
946
947 m_suspendPromises.clear();
948 }
949
950 void AudioContext::resolvePromisesForSuspend()
951 {
952 // This runs inside the AudioContext's lock when handling pre-render tasks.
953 ASSERT(isAudioThread());
954 ASSERT(isGraphOwner());
955
956 // Resolve any pending promises created by suspend()
957 if (m_suspendPromises.size() > 0)
958 callOnMainThread(bind(&AudioContext::resolvePromisesForSuspendOnMainThre ad, this));
959
960 }
961
803 const AtomicString& AudioContext::interfaceName() const 962 const AtomicString& AudioContext::interfaceName() const
804 { 963 {
805 return EventTargetNames::AudioContext; 964 return EventTargetNames::AudioContext;
806 } 965 }
807 966
808 ExecutionContext* AudioContext::executionContext() const 967 ExecutionContext* AudioContext::executionContext() const
809 { 968 {
810 return m_isStopScheduled ? 0 : ActiveDOMObject::executionContext(); 969 return m_isStopScheduled ? 0 : ActiveDOMObject::executionContext();
811 } 970 }
812 971
813 void AudioContext::startRendering() 972 void AudioContext::startRendering()
814 { 973 {
815 destination()->startRendering(); 974 // This is called for both online and offline contexts.
975 ASSERT(isMainThread());
976 ASSERT(m_destinationNode);
977
978 if (m_contextState == Suspended) {
979 destination()->startRendering();
980 setContextState(Running);
981 }
982 }
983
984 void AudioContext::stopRendering()
985 {
986 ASSERT(isMainThread());
987 ASSERT(m_destinationNode);
988 ASSERT(!isOfflineContext());
989
990 if (m_contextState == Running) {
991 destination()->stopRendering();
992 setContextState(Suspended);
993 }
816 } 994 }
817 995
818 void AudioContext::fireCompletionEvent() 996 void AudioContext::fireCompletionEvent()
819 { 997 {
820 ASSERT(isMainThread()); 998 ASSERT(isMainThread());
821 if (!isMainThread()) 999 if (!isMainThread())
822 return; 1000 return;
823 1001
824 AudioBuffer* renderedBuffer = m_renderTarget.get(); 1002 AudioBuffer* renderedBuffer = m_renderTarget.get();
825 1003
1004 setContextState(Closed);
1005
826 ASSERT(renderedBuffer); 1006 ASSERT(renderedBuffer);
827 if (!renderedBuffer) 1007 if (!renderedBuffer)
828 return; 1008 return;
829 1009
830 // Avoid firing the event if the document has already gone away. 1010 // Avoid firing the event if the document has already gone away.
831 if (executionContext()) { 1011 if (executionContext()) {
832 // Call the offline rendering completion event listener. 1012 // Call the offline rendering completion event listener.
833 dispatchEvent(OfflineAudioCompletionEvent::create(renderedBuffer)); 1013 dispatchEvent(OfflineAudioCompletionEvent::create(renderedBuffer));
834 } 1014 }
835 } 1015 }
(...skipping 29 matching lines...) Expand all
865 1045
866 for (HashSet<AudioNode*>::iterator k = m_deferredCountModeChange.begin(); k != m_deferredCountModeChange.end(); ++k) 1046 for (HashSet<AudioNode*>::iterator k = m_deferredCountModeChange.begin(); k != m_deferredCountModeChange.end(); ++k)
867 (*k)->updateChannelCountMode(); 1047 (*k)->updateChannelCountMode();
868 1048
869 m_deferredCountModeChange.clear(); 1049 m_deferredCountModeChange.clear();
870 } 1050 }
871 1051
872 } // namespace blink 1052 } // namespace blink
873 1053
874 #endif // ENABLE(WEB_AUDIO) 1054 #endif // ENABLE(WEB_AUDIO)
OLDNEW
« no previous file with comments | « Source/modules/webaudio/AudioContext.h ('k') | Source/modules/webaudio/AudioContext.idl » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698