Chromium Code Reviews| 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 * 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 Loading... | |
| 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 "platform/audio/FFTFrame.h" | 39 #include "platform/audio/FFTFrame.h" |
| 38 #include "platform/audio/HRTFPanner.h" | 40 #include "platform/audio/HRTFPanner.h" |
| 39 #include "modules/mediastream/MediaStream.h" | 41 #include "modules/mediastream/MediaStream.h" |
| 40 #include "modules/webaudio/AnalyserNode.h" | 42 #include "modules/webaudio/AnalyserNode.h" |
| 41 #include "modules/webaudio/AudioBuffer.h" | 43 #include "modules/webaudio/AudioBuffer.h" |
| 42 #include "modules/webaudio/AudioBufferCallback.h" | 44 #include "modules/webaudio/AudioBufferCallback.h" |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 94 return audioContext; | 96 return audioContext; |
| 95 } | 97 } |
| 96 | 98 |
| 97 // Constructor for rendering to the audio hardware. | 99 // Constructor for rendering to the audio hardware. |
| 98 AudioContext::AudioContext(Document* document) | 100 AudioContext::AudioContext(Document* document) |
| 99 : ActiveDOMObject(document) | 101 : ActiveDOMObject(document) |
| 100 , m_isStopScheduled(false) | 102 , m_isStopScheduled(false) |
| 101 , m_isCleared(false) | 103 , m_isCleared(false) |
| 102 , m_isInitialized(false) | 104 , m_isInitialized(false) |
| 103 , m_destinationNode(nullptr) | 105 , m_destinationNode(nullptr) |
| 106 , m_isResolvingResumePromises(false) | |
| 104 , m_automaticPullNodesNeedUpdating(false) | 107 , m_automaticPullNodesNeedUpdating(false) |
| 105 , m_connectionCount(0) | 108 , m_connectionCount(0) |
| 106 , m_audioThread(0) | 109 , m_audioThread(0) |
| 107 , m_isOfflineContext(false) | 110 , m_isOfflineContext(false) |
| 111 , m_contextState(Paused) | |
| 108 { | 112 { |
| 109 m_destinationNode = DefaultAudioDestinationNode::create(this); | 113 m_destinationNode = DefaultAudioDestinationNode::create(this); |
| 110 | 114 |
| 111 initialize(); | 115 initialize(); |
| 112 #if DEBUG_AUDIONODE_REFERENCES | 116 #if DEBUG_AUDIONODE_REFERENCES |
| 113 fprintf(stderr, "%p: AudioContext::AudioContext() #%u\n", this, AudioContext ::s_hardwareContextCount); | 117 fprintf(stderr, "%p: AudioContext::AudioContext() #%u\n", this, AudioContext ::s_hardwareContextCount); |
| 114 #endif | 118 #endif |
| 115 } | 119 } |
| 116 | 120 |
| 117 // Constructor for offline (non-realtime) rendering. | 121 // Constructor for offline (non-realtime) rendering. |
| 118 AudioContext::AudioContext(Document* document, unsigned numberOfChannels, size_t numberOfFrames, float sampleRate) | 122 AudioContext::AudioContext(Document* document, unsigned numberOfChannels, size_t numberOfFrames, float sampleRate) |
| 119 : ActiveDOMObject(document) | 123 : ActiveDOMObject(document) |
| 120 , m_isStopScheduled(false) | 124 , m_isStopScheduled(false) |
| 121 , m_isCleared(false) | 125 , m_isCleared(false) |
| 122 , m_isInitialized(false) | 126 , m_isInitialized(false) |
| 123 , m_destinationNode(nullptr) | 127 , m_destinationNode(nullptr) |
| 128 , m_isResolvingResumePromises(false) | |
| 124 , m_automaticPullNodesNeedUpdating(false) | 129 , m_automaticPullNodesNeedUpdating(false) |
| 125 , m_connectionCount(0) | 130 , m_connectionCount(0) |
| 126 , m_audioThread(0) | 131 , m_audioThread(0) |
| 127 , m_isOfflineContext(true) | 132 , m_isOfflineContext(true) |
| 133 , m_contextState(Paused) | |
| 128 { | 134 { |
| 129 // Create a new destination for offline rendering. | 135 // Create a new destination for offline rendering. |
| 130 m_renderTarget = AudioBuffer::create(numberOfChannels, numberOfFrames, sampl eRate); | 136 m_renderTarget = AudioBuffer::create(numberOfChannels, numberOfFrames, sampl eRate); |
| 131 if (m_renderTarget.get()) | 137 if (m_renderTarget.get()) |
| 132 m_destinationNode = OfflineAudioDestinationNode::create(this, m_renderTa rget.get()); | 138 m_destinationNode = OfflineAudioDestinationNode::create(this, m_renderTa rget.get()); |
| 133 | 139 |
| 134 initialize(); | 140 initialize(); |
| 135 } | 141 } |
| 136 | 142 |
| 137 AudioContext::~AudioContext() | 143 AudioContext::~AudioContext() |
|
haraken
2014/10/16 01:13:43
Can we add ASSERT(!m_resumePromises.size())?
Raymond Toy
2014/10/16 17:56:31
Done.
| |
| 138 { | 144 { |
| 139 #if DEBUG_AUDIONODE_REFERENCES | 145 #if DEBUG_AUDIONODE_REFERENCES |
| 140 fprintf(stderr, "%p: AudioContext::~AudioContext()\n", this); | 146 fprintf(stderr, "%p: AudioContext::~AudioContext()\n", this); |
| 141 #endif | 147 #endif |
| 142 // 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. | 148 // 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. |
| 143 ASSERT(!m_isInitialized); | 149 ASSERT(!m_isInitialized); |
| 144 ASSERT(!m_referencedNodes.size()); | 150 ASSERT(!m_referencedNodes.size()); |
| 145 ASSERT(!m_finishedNodes.size()); | 151 ASSERT(!m_finishedNodes.size()); |
| 146 ASSERT(!m_automaticPullNodes.size()); | 152 ASSERT(!m_automaticPullNodes.size()); |
| 147 if (m_automaticPullNodesNeedUpdating) | 153 if (m_automaticPullNodesNeedUpdating) |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 158 m_listener = AudioListener::create(); | 164 m_listener = AudioListener::create(); |
| 159 | 165 |
| 160 if (m_destinationNode.get()) { | 166 if (m_destinationNode.get()) { |
| 161 m_destinationNode->initialize(); | 167 m_destinationNode->initialize(); |
| 162 | 168 |
| 163 if (!isOfflineContext()) { | 169 if (!isOfflineContext()) { |
| 164 // This starts the audio thread. The destination node's provideInput () method will now be called repeatedly to render audio. | 170 // This starts the audio thread. The destination node's provideInput () method will now be called repeatedly to render audio. |
| 165 // Each time provideInput() is called, a portion of the audio stream is rendered. Let's call this time period a "render quantum". | 171 // Each time provideInput() is called, a portion of the audio stream is rendered. Let's call this time period a "render quantum". |
| 166 // NOTE: for now default AudioContext does not need an explicit star tRendering() call from JavaScript. | 172 // NOTE: for now default AudioContext does not need an explicit star tRendering() call from JavaScript. |
| 167 // We may want to consider requiring it for symmetry with OfflineAud ioContext. | 173 // We may want to consider requiring it for symmetry with OfflineAud ioContext. |
| 168 m_destinationNode->startRendering(); | 174 startRendering(); |
| 169 ++s_hardwareContextCount; | 175 ++s_hardwareContextCount; |
| 170 } | 176 } |
| 171 | 177 |
| 172 m_isInitialized = true; | 178 m_isInitialized = true; |
| 173 } | 179 } |
| 174 } | 180 } |
| 175 | 181 |
| 176 void AudioContext::clear() | 182 void AudioContext::clear() |
| 177 { | 183 { |
| 178 // We need to run disposers before destructing m_contextGraphMutex. | 184 // We need to run disposers before destructing m_contextGraphMutex. |
| (...skipping 345 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 524 exceptionState.throwDOMException( | 530 exceptionState.throwDOMException( |
| 525 IndexSizeError, | 531 IndexSizeError, |
| 526 "length of imaginary array (" + String::number(imag->length()) | 532 "length of imaginary array (" + String::number(imag->length()) |
| 527 + ") exceeds allowed maximum of 4096"); | 533 + ") exceeds allowed maximum of 4096"); |
| 528 return 0; | 534 return 0; |
| 529 } | 535 } |
| 530 | 536 |
| 531 return PeriodicWave::create(sampleRate(), real, imag); | 537 return PeriodicWave::create(sampleRate(), real, imag); |
| 532 } | 538 } |
| 533 | 539 |
| 540 String AudioContext::state() const | |
| 541 { | |
| 542 switch (m_contextState) { | |
| 543 case Paused: | |
| 544 return "paused"; | |
| 545 case Running: | |
| 546 return "running"; | |
| 547 case Released: | |
| 548 return "released"; | |
| 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 Paused: | |
| 559 ASSERT(m_contextState == Running); | |
| 560 break; | |
| 561 case Running: | |
| 562 ASSERT(m_contextState == Paused); | |
| 563 break; | |
| 564 case Released: | |
| 565 ASSERT(m_contextState != Released); | |
| 566 break; | |
| 567 } | |
| 568 | |
| 569 m_contextState = newState; | |
| 570 } | |
| 571 | |
| 572 void AudioContext::suspendContext(ExceptionState& exceptionState) | |
| 573 { | |
| 574 ASSERT(isMainThread()); | |
| 575 AutoLocker locker(this); | |
| 576 | |
| 577 if (m_contextState == Released) { | |
| 578 exceptionState.throwDOMException( | |
| 579 InvalidStateError, | |
| 580 "cannot suspend an AudioContext that has been released"); | |
| 581 return; | |
| 582 } | |
| 583 | |
| 584 if (m_destinationNode && !isOfflineContext()) { | |
| 585 stopRendering(); | |
| 586 } | |
| 587 } | |
| 588 | |
| 589 ScriptPromise AudioContext::resumeContext(ScriptState* scriptState) | |
| 590 { | |
| 591 ASSERT(isMainThread()); | |
| 592 AutoLocker locker(this); | |
| 593 | |
| 594 RefPtr<ScriptPromiseResolver> resolver = ScriptPromiseResolver::create(scrip tState); | |
| 595 | |
| 596 ScriptPromise promise = resolver->promise(); | |
| 597 | |
| 598 if (isOfflineContext()) { | |
| 599 // For offline context, resolve now, but reject if the context has been released. | |
| 600 if (m_contextState == Released) { | |
| 601 resolver->reject( | |
| 602 DOMException::create(InvalidStateError, "Cannot resume a context that has been released")); | |
| 603 } else { | |
| 604 resolver->resolve(); | |
| 605 } | |
| 606 } else { | |
| 607 // Restart the destination node to pull on the audio graph. | |
| 608 if (m_destinationNode) { | |
| 609 startRendering(); | |
| 610 } | |
| 611 | |
| 612 // Save the promise which will get resolved when the destination node st arts pulling on the | |
| 613 // graph again. | |
| 614 m_resumePromises.append(resolver); | |
| 615 } | |
| 616 | |
| 617 return promise; | |
| 618 } | |
| 619 | |
| 534 void AudioContext::notifyNodeFinishedProcessing(AudioNode* node) | 620 void AudioContext::notifyNodeFinishedProcessing(AudioNode* node) |
| 535 { | 621 { |
| 536 ASSERT(isAudioThread()); | 622 ASSERT(isAudioThread()); |
| 537 m_finishedNodes.append(node); | 623 m_finishedNodes.append(node); |
| 538 } | 624 } |
| 539 | 625 |
| 540 void AudioContext::derefFinishedSourceNodes() | 626 void AudioContext::derefFinishedSourceNodes() |
| 541 { | 627 { |
| 542 ASSERT(isGraphOwner()); | 628 ASSERT(isGraphOwner()); |
| 543 ASSERT(isAudioThread()); | 629 ASSERT(isAudioThread()); |
| (...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 630 // It's OK if the tryLock() fails, we'll just take slightly longer to pick u p the changes. | 716 // It's OK if the tryLock() fails, we'll just take slightly longer to pick u p the changes. |
| 631 if (tryLock()) { | 717 if (tryLock()) { |
| 632 // Update the channel count mode. | 718 // Update the channel count mode. |
| 633 updateChangedChannelCountMode(); | 719 updateChangedChannelCountMode(); |
| 634 | 720 |
| 635 // Fixup the state of any dirty AudioSummingJunctions and AudioNodeOutpu ts. | 721 // Fixup the state of any dirty AudioSummingJunctions and AudioNodeOutpu ts. |
| 636 handleDirtyAudioSummingJunctions(); | 722 handleDirtyAudioSummingJunctions(); |
| 637 handleDirtyAudioNodeOutputs(); | 723 handleDirtyAudioNodeOutputs(); |
| 638 | 724 |
| 639 updateAutomaticPullNodes(); | 725 updateAutomaticPullNodes(); |
| 726 resolvePromisesForResume(); | |
| 727 | |
| 640 unlock(); | 728 unlock(); |
| 641 } | 729 } |
| 642 } | 730 } |
| 643 | 731 |
| 644 void AudioContext::handlePostRenderTasks() | 732 void AudioContext::handlePostRenderTasks() |
| 645 { | 733 { |
| 646 ASSERT(isAudioThread()); | 734 ASSERT(isAudioThread()); |
| 647 | 735 |
| 648 // Must use a tryLock() here too. Don't worry, the lock will very rarely be contended and this method is called frequently. | 736 // Must use a tryLock() here too. Don't worry, the lock will very rarely be contended and this method is called frequently. |
| 649 // 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 | 737 // 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 |
| (...skipping 145 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 795 } | 883 } |
| 796 | 884 |
| 797 void AudioContext::processAutomaticPullNodes(size_t framesToProcess) | 885 void AudioContext::processAutomaticPullNodes(size_t framesToProcess) |
| 798 { | 886 { |
| 799 ASSERT(isAudioThread()); | 887 ASSERT(isAudioThread()); |
| 800 | 888 |
| 801 for (unsigned i = 0; i < m_renderingAutomaticPullNodes.size(); ++i) | 889 for (unsigned i = 0; i < m_renderingAutomaticPullNodes.size(); ++i) |
| 802 m_renderingAutomaticPullNodes[i]->processIfNecessary(framesToProcess); | 890 m_renderingAutomaticPullNodes[i]->processIfNecessary(framesToProcess); |
| 803 } | 891 } |
| 804 | 892 |
| 893 void AudioContext::resolvePromisesForResumeOnMainThread() | |
| 894 { | |
| 895 ASSERT(isMainThread()); | |
| 896 AutoLocker locker(this); | |
| 897 | |
| 898 for (unsigned k = 0; k < m_resumePromises.size(); ++k) { | |
| 899 if (m_contextState == Released) { | |
| 900 m_resumePromises[k]->reject( | |
| 901 DOMException::create(InvalidStateError, "Cannot resume a context that has been released")); | |
| 902 } else { | |
| 903 m_resumePromises[k]->resolve(); | |
| 904 } | |
| 905 } | |
| 906 | |
| 907 m_resumePromises.clear(); | |
| 908 m_isResolvingResumePromises = false; | |
| 909 } | |
| 910 | |
| 911 void AudioContext::resolvePromisesForResume() | |
| 912 { | |
| 913 // This runs inside the AudioContext's lock when handling pre-render tasks. | |
| 914 ASSERT(isAudioThread()); | |
| 915 ASSERT(isGraphOwner()); | |
| 916 | |
| 917 // Resolve any pending promises created by resume(). Only do this we if have n't already started | |
| 918 // resolving these promises. This gets called very often and it takes some t ime to resolve the | |
| 919 // promises in the main thread. | |
| 920 if (!m_isResolvingResumePromises && m_resumePromises.size() > 0) { | |
| 921 m_isResolvingResumePromises = true; | |
| 922 callOnMainThread(bind(&AudioContext::resolvePromisesForResumeOnMainThrea d, this)); | |
| 923 } | |
| 924 } | |
| 925 | |
| 805 const AtomicString& AudioContext::interfaceName() const | 926 const AtomicString& AudioContext::interfaceName() const |
| 806 { | 927 { |
| 807 return EventTargetNames::AudioContext; | 928 return EventTargetNames::AudioContext; |
| 808 } | 929 } |
| 809 | 930 |
| 810 ExecutionContext* AudioContext::executionContext() const | 931 ExecutionContext* AudioContext::executionContext() const |
| 811 { | 932 { |
| 812 return m_isStopScheduled ? 0 : ActiveDOMObject::executionContext(); | 933 return m_isStopScheduled ? 0 : ActiveDOMObject::executionContext(); |
| 813 } | 934 } |
| 814 | 935 |
| 815 void AudioContext::startRendering() | 936 void AudioContext::startRendering() |
| 816 { | 937 { |
| 817 destination()->startRendering(); | 938 ASSERT(isMainThread()); |
|
haraken
2014/10/16 01:13:43
Shall we add ASSERT(!isOfflineContext())?
haraken
2014/10/16 01:13:43
Shall we add ASSERT(m_destinationNode)?
Raymond Toy
2014/10/16 17:56:31
Done.
Raymond Toy
2014/10/16 17:56:31
Actually no. startRendering is called for both on
| |
| 939 | |
| 940 if (m_contextState == Paused) { | |
| 941 destination()->startRendering(); | |
| 942 setContextState(Running); | |
| 943 } | |
| 944 } | |
| 945 | |
| 946 void AudioContext::stopRendering() | |
| 947 { | |
| 948 ASSERT(isMainThread()); | |
| 949 ASSERT(m_destinationNode); | |
| 950 ASSERT(!isOfflineContext()); | |
| 951 | |
| 952 if (m_contextState == Running) { | |
| 953 destination()->stopRendering(); | |
| 954 setContextState(Paused); | |
| 955 } | |
| 818 } | 956 } |
| 819 | 957 |
| 820 void AudioContext::fireCompletionEvent() | 958 void AudioContext::fireCompletionEvent() |
| 821 { | 959 { |
| 822 ASSERT(isMainThread()); | 960 ASSERT(isMainThread()); |
| 823 if (!isMainThread()) | 961 if (!isMainThread()) |
| 824 return; | 962 return; |
| 825 | 963 |
| 826 AudioBuffer* renderedBuffer = m_renderTarget.get(); | 964 AudioBuffer* renderedBuffer = m_renderTarget.get(); |
| 827 | 965 |
| 966 setContextState(Released); | |
| 967 | |
| 828 ASSERT(renderedBuffer); | 968 ASSERT(renderedBuffer); |
| 829 if (!renderedBuffer) | 969 if (!renderedBuffer) |
| 830 return; | 970 return; |
| 831 | 971 |
| 832 // Avoid firing the event if the document has already gone away. | 972 // Avoid firing the event if the document has already gone away. |
| 833 if (executionContext()) { | 973 if (executionContext()) { |
| 834 // Call the offline rendering completion event listener. | 974 // Call the offline rendering completion event listener. |
| 835 dispatchEvent(OfflineAudioCompletionEvent::create(renderedBuffer)); | 975 dispatchEvent(OfflineAudioCompletionEvent::create(renderedBuffer)); |
| 836 } | 976 } |
| 837 } | 977 } |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 867 | 1007 |
| 868 for (HashSet<AudioNode*>::iterator k = m_deferredCountModeChange.begin(); k != m_deferredCountModeChange.end(); ++k) | 1008 for (HashSet<AudioNode*>::iterator k = m_deferredCountModeChange.begin(); k != m_deferredCountModeChange.end(); ++k) |
| 869 (*k)->updateChannelCountMode(); | 1009 (*k)->updateChannelCountMode(); |
| 870 | 1010 |
| 871 m_deferredCountModeChange.clear(); | 1011 m_deferredCountModeChange.clear(); |
| 872 } | 1012 } |
| 873 | 1013 |
| 874 } // namespace blink | 1014 } // namespace blink |
| 875 | 1015 |
| 876 #endif // ENABLE(WEB_AUDIO) | 1016 #endif // ENABLE(WEB_AUDIO) |
| OLD | NEW |