/* * Copyright (C) 2010, Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #if ENABLE(WEB_AUDIO) #include "modules/webaudio/AudioContext.h" #include "bindings/v8/ExceptionMessages.h" #include "bindings/v8/ExceptionState.h" #include "core/dom/Document.h" #include "core/dom/ExceptionCode.h" #include "core/html/HTMLMediaElement.h" #include "core/inspector/ScriptCallStack.h" #include "platform/audio/FFTFrame.h" #include "platform/audio/HRTFPanner.h" #include "modules/mediastream/MediaStream.h" #include "modules/webaudio/AnalyserNode.h" #include "modules/webaudio/AudioBuffer.h" #include "modules/webaudio/AudioBufferCallback.h" #include "modules/webaudio/AudioBufferSourceNode.h" #include "modules/webaudio/AudioListener.h" #include "modules/webaudio/AudioNodeInput.h" #include "modules/webaudio/AudioNodeOutput.h" #include "modules/webaudio/BiquadFilterNode.h" #include "modules/webaudio/ChannelMergerNode.h" #include "modules/webaudio/ChannelSplitterNode.h" #include "modules/webaudio/ConvolverNode.h" #include "modules/webaudio/DefaultAudioDestinationNode.h" #include "modules/webaudio/DelayNode.h" #include "modules/webaudio/DynamicsCompressorNode.h" #include "modules/webaudio/GainNode.h" #include "modules/webaudio/MediaElementAudioSourceNode.h" #include "modules/webaudio/MediaStreamAudioDestinationNode.h" #include "modules/webaudio/MediaStreamAudioSourceNode.h" #include "modules/webaudio/OfflineAudioCompletionEvent.h" #include "modules/webaudio/OfflineAudioContext.h" #include "modules/webaudio/OfflineAudioDestinationNode.h" #include "modules/webaudio/OscillatorNode.h" #include "modules/webaudio/PannerNode.h" #include "modules/webaudio/PeriodicWave.h" #include "modules/webaudio/ScriptProcessorNode.h" #include "modules/webaudio/WaveShaperNode.h" #if DEBUG_AUDIONODE_REFERENCES #include #endif #include "wtf/ArrayBuffer.h" #include "wtf/Atomics.h" #include "wtf/PassOwnPtr.h" #include "wtf/text/WTFString.h" // FIXME: check the proper way to reference an undefined thread ID const int UndefinedThreadIdentifier = 0xffffffff; namespace WebCore { bool AudioContext::isSampleRateRangeGood(float sampleRate) { // FIXME: It would be nice if the minimum sample-rate could be less than 44.1KHz, // but that will require some fixes in HRTFPanner::fftSizeForSampleRate(), and some testing there. return sampleRate >= 44100 && sampleRate <= 96000; } // Don't allow more than this number of simultaneous AudioContexts talking to hardware. const unsigned MaxHardwareContexts = 4; unsigned AudioContext::s_hardwareContextCount = 0; PassRefPtr AudioContext::create(Document& document, ExceptionState& exceptionState) { ASSERT(isMainThread()); if (s_hardwareContextCount >= MaxHardwareContexts) { exceptionState.throwDOMException( SyntaxError, "number of hardware contexts reached maximum (" + String::number(MaxHardwareContexts) + ")."); return 0; } RefPtr audioContext(adoptRef(new AudioContext(&document))); audioContext->suspendIfNeeded(); return audioContext.release(); } PassRefPtr AudioContext::create(Document& document, unsigned numberOfChannels, size_t numberOfFrames, float sampleRate, ExceptionState& exceptionState) { document.addConsoleMessage(JSMessageSource, WarningMessageLevel, "Deprecated AudioContext constructor: use OfflineAudioContext instead"); return OfflineAudioContext::create(&document, numberOfChannels, numberOfFrames, sampleRate, exceptionState); } // Constructor for rendering to the audio hardware. AudioContext::AudioContext(Document* document) : ActiveDOMObject(document) , m_isStopScheduled(false) , m_isInitialized(false) , m_isAudioThreadFinished(false) , m_destinationNode(0) , m_isDeletionScheduled(false) , m_automaticPullNodesNeedUpdating(false) , m_connectionCount(0) , m_audioThread(0) , m_graphOwnerThread(UndefinedThreadIdentifier) , m_isOfflineContext(false) , m_activeSourceCount(0) { constructCommon(); m_destinationNode = DefaultAudioDestinationNode::create(this); // This sets in motion an asynchronous loading mechanism on another thread. // We can check m_hrtfDatabaseLoader->isLoaded() to find out whether or not it has been fully loaded. // It's not that useful to have a callback function for this since the audio thread automatically starts rendering on the graph // when this has finished (see AudioDestinationNode). m_hrtfDatabaseLoader = HRTFDatabaseLoader::createAndLoadAsynchronouslyIfNecessary(sampleRate()); } // Constructor for offline (non-realtime) rendering. AudioContext::AudioContext(Document* document, unsigned numberOfChannels, size_t numberOfFrames, float sampleRate) : ActiveDOMObject(document) , m_isStopScheduled(false) , m_isInitialized(false) , m_isAudioThreadFinished(false) , m_destinationNode(0) , m_automaticPullNodesNeedUpdating(false) , m_connectionCount(0) , m_audioThread(0) , m_graphOwnerThread(UndefinedThreadIdentifier) , m_isOfflineContext(true) , m_activeSourceCount(0) { constructCommon(); m_hrtfDatabaseLoader = HRTFDatabaseLoader::createAndLoadAsynchronouslyIfNecessary(sampleRate); // Create a new destination for offline rendering. m_renderTarget = AudioBuffer::create(numberOfChannels, numberOfFrames, sampleRate); ASSERT(m_renderTarget); m_destinationNode = OfflineAudioDestinationNode::create(this, m_renderTarget.get()); ASSERT(m_destinationNode); } void AudioContext::constructCommon() { ScriptWrappable::init(this); // According to spec AudioContext must die only after page navigate. // Lets mark it as ActiveDOMObject with pending activity and unmark it in clear method. setPendingActivity(this); FFTFrame::initialize(); m_listener = AudioListener::create(); } AudioContext::~AudioContext() { #if DEBUG_AUDIONODE_REFERENCES fprintf(stderr, "%p: AudioContext::~AudioContext()\n", this); #endif // AudioNodes keep a reference to their context, so there should be no way to be in the destructor if there are still AudioNodes around. ASSERT(!m_isInitialized); ASSERT(m_isStopScheduled); ASSERT(!m_nodesToDelete.size()); ASSERT(!m_referencedNodes.size()); ASSERT(!m_finishedNodes.size()); ASSERT(!m_automaticPullNodes.size()); if (m_automaticPullNodesNeedUpdating) m_renderingAutomaticPullNodes.resize(m_automaticPullNodes.size()); ASSERT(!m_renderingAutomaticPullNodes.size()); } void AudioContext::lazyInitialize() { if (!m_isInitialized) { // Don't allow the context to initialize a second time after it's already been explicitly uninitialized. ASSERT(!m_isAudioThreadFinished); if (!m_isAudioThreadFinished) { if (m_destinationNode.get()) { m_destinationNode->initialize(); if (!isOfflineContext()) { // This starts the audio thread. The destination node's provideInput() method will now be called repeatedly to render audio. // Each time provideInput() is called, a portion of the audio stream is rendered. Let's call this time period a "render quantum". // NOTE: for now default AudioContext does not need an explicit startRendering() call from JavaScript. // We may want to consider requiring it for symmetry with OfflineAudioContext. m_destinationNode->startRendering(); ++s_hardwareContextCount; } } m_isInitialized = true; } } } void AudioContext::clear() { // We have to release our reference to the destination node before the context will ever be deleted since the destination node holds a reference to the context. if (m_destinationNode) m_destinationNode.clear(); // Audio thread is dead. Nobody will schedule node deletion action. Let's do it ourselves. do { deleteMarkedNodes(); m_nodesToDelete.append(m_nodesMarkedForDeletion); m_nodesMarkedForDeletion.clear(); } while (m_nodesToDelete.size()); // It was set in constructCommon. unsetPendingActivity(this); } void AudioContext::uninitialize() { ASSERT(isMainThread()); if (!m_isInitialized) return; // This stops the audio thread and all audio rendering. m_destinationNode->uninitialize(); // Don't allow the context to initialize a second time after it's already been explicitly uninitialized. m_isAudioThreadFinished = true; if (!isOfflineContext()) { ASSERT(s_hardwareContextCount); --s_hardwareContextCount; } // Get rid of the sources which may still be playing. derefUnfinishedSourceNodes(); m_isInitialized = false; } bool AudioContext::isInitialized() const { return m_isInitialized; } bool AudioContext::isRunnable() const { if (!isInitialized()) return false; // Check with the HRTF spatialization system to see if it's finished loading. return m_hrtfDatabaseLoader->isLoaded(); } void AudioContext::stopDispatch(void* userData) { AudioContext* context = reinterpret_cast(userData); ASSERT(context); if (!context) return; context->uninitialize(); context->clear(); } void AudioContext::stop() { // Usually ExecutionContext calls stop twice. if (m_isStopScheduled) return; m_isStopScheduled = true; // Don't call uninitialize() immediately here because the ExecutionContext is in the middle // of dealing with all of its ActiveDOMObjects at this point. uninitialize() can de-reference other // ActiveDOMObjects so let's schedule uninitialize() to be called later. // FIXME: see if there's a more direct way to handle this issue. callOnMainThread(stopDispatch, this); } PassRefPtr AudioContext::createBuffer(unsigned numberOfChannels, size_t numberOfFrames, float sampleRate, ExceptionState& exceptionState) { RefPtr audioBuffer = AudioBuffer::create(numberOfChannels, numberOfFrames, sampleRate); if (!audioBuffer.get()) { if (numberOfChannels > AudioContext::maxNumberOfChannels()) { exceptionState.throwDOMException( NotSupportedError, "requested number of channels (" + String::number(numberOfChannels) + ") exceeds maximum (" + String::number(AudioContext::maxNumberOfChannels()) + ")"); } else if (sampleRate < AudioBuffer::minAllowedSampleRate() || sampleRate > AudioBuffer::maxAllowedSampleRate()) { exceptionState.throwDOMException( NotSupportedError, "requested sample rate (" + String::number(sampleRate) + ") does not lie in the allowed range of " + String::number(AudioBuffer::minAllowedSampleRate()) + "-" + String::number(AudioBuffer::maxAllowedSampleRate()) + " Hz"); } else if (!numberOfFrames) { exceptionState.throwDOMException( NotSupportedError, "number of frames must be greater than 0."); } else { exceptionState.throwDOMException( NotSupportedError, "unable to create buffer of " + String::number(numberOfChannels) + " channel(s) of " + String::number(numberOfFrames) + " frames each."); } return 0; } return audioBuffer; } PassRefPtr AudioContext::createBuffer(ArrayBuffer* arrayBuffer, bool mixToMono, ExceptionState& exceptionState) { ASSERT(arrayBuffer); if (!arrayBuffer) { exceptionState.throwDOMException( SyntaxError, "invalid ArrayBuffer."); return 0; } RefPtr audioBuffer = AudioBuffer::createFromAudioFileData(arrayBuffer->data(), arrayBuffer->byteLength(), mixToMono, sampleRate()); if (!audioBuffer.get()) { exceptionState.throwDOMException( SyntaxError, "invalid audio data in ArrayBuffer."); return 0; } return audioBuffer; } void AudioContext::decodeAudioData(ArrayBuffer* audioData, PassOwnPtr successCallback, PassOwnPtr errorCallback, ExceptionState& exceptionState) { if (!audioData) { exceptionState.throwDOMException( SyntaxError, "invalid ArrayBuffer for audioData."); return; } m_audioDecoder.decodeAsync(audioData, sampleRate(), successCallback, errorCallback); } PassRefPtr AudioContext::createBufferSource() { ASSERT(isMainThread()); lazyInitialize(); RefPtr node = AudioBufferSourceNode::create(this, m_destinationNode->sampleRate()); // Because this is an AudioScheduledSourceNode, the context keeps a reference until it has finished playing. // When this happens, AudioScheduledSourceNode::finish() calls AudioContext::notifyNodeFinishedProcessing(). refNode(node.get()); return node; } PassRefPtr AudioContext::createMediaElementSource(HTMLMediaElement* mediaElement, ExceptionState& exceptionState) { if (!mediaElement) { exceptionState.throwDOMException( InvalidStateError, "invalid HTMLMedialElement."); return 0; } ASSERT(isMainThread()); lazyInitialize(); // First check if this media element already has a source node. if (mediaElement->audioSourceNode()) { exceptionState.throwDOMException( InvalidStateError, "invalid HTMLMediaElement."); return 0; } RefPtr node = MediaElementAudioSourceNode::create(this, mediaElement); mediaElement->setAudioSourceNode(node.get()); refNode(node.get()); // context keeps reference until node is disconnected return node; } PassRefPtr AudioContext::createMediaStreamSource(MediaStream* mediaStream, ExceptionState& exceptionState) { if (!mediaStream) { exceptionState.throwDOMException( InvalidStateError, "invalid MediaStream source"); return 0; } ASSERT(isMainThread()); lazyInitialize(); AudioSourceProvider* provider = 0; MediaStreamTrackVector audioTracks = mediaStream->getAudioTracks(); RefPtr audioTrack; // FIXME: get a provider for non-local MediaStreams (like from a remote peer). for (size_t i = 0; i < audioTracks.size(); ++i) { audioTrack = audioTracks[i]; if (audioTrack->component()->audioSourceProvider()) { provider = audioTrack->component()->audioSourceProvider(); break; } } RefPtr node = MediaStreamAudioSourceNode::create(this, mediaStream, audioTrack.get(), provider); // FIXME: Only stereo streams are supported right now. We should be able to accept multi-channel streams. node->setFormat(2, sampleRate()); refNode(node.get()); // context keeps reference until node is disconnected return node; } PassRefPtr AudioContext::createMediaStreamDestination() { // FIXME: Add support for an optional argument which specifies the number of channels. // FIXME: The default should probably be stereo instead of mono. return MediaStreamAudioDestinationNode::create(this, 1); } PassRefPtr AudioContext::createScriptProcessor(ExceptionState& exceptionState) { // Set number of input/output channels to stereo by default. return createScriptProcessor(0, 2, 2, exceptionState); } PassRefPtr AudioContext::createScriptProcessor(size_t bufferSize, ExceptionState& exceptionState) { // Set number of input/output channels to stereo by default. return createScriptProcessor(bufferSize, 2, 2, exceptionState); } PassRefPtr AudioContext::createScriptProcessor(size_t bufferSize, size_t numberOfInputChannels, ExceptionState& exceptionState) { // Set number of output channels to stereo by default. return createScriptProcessor(bufferSize, numberOfInputChannels, 2, exceptionState); } PassRefPtr AudioContext::createScriptProcessor(size_t bufferSize, size_t numberOfInputChannels, size_t numberOfOutputChannels, ExceptionState& exceptionState) { ASSERT(isMainThread()); lazyInitialize(); RefPtr node = ScriptProcessorNode::create(this, m_destinationNode->sampleRate(), bufferSize, numberOfInputChannels, numberOfOutputChannels); if (!node.get()) { if (!numberOfInputChannels && !numberOfOutputChannels) { exceptionState.throwDOMException( IndexSizeError, "number of input channels and output channels cannot both be zero."); } else if (numberOfInputChannels > AudioContext::maxNumberOfChannels()) { exceptionState.throwDOMException( IndexSizeError, "number of input channels (" + String::number(numberOfInputChannels) + ") exceeds maximum (" + String::number(AudioContext::maxNumberOfChannels()) + ")."); } else if (numberOfOutputChannels > AudioContext::maxNumberOfChannels()) { exceptionState.throwDOMException( IndexSizeError, "number of output channels (" + String::number(numberOfInputChannels) + ") exceeds maximum (" + String::number(AudioContext::maxNumberOfChannels()) + ")."); } else { exceptionState.throwDOMException( IndexSizeError, "buffer size (" + String::number(bufferSize) + ") must be a power of two between 256 and 16384."); } return 0; } refNode(node.get()); // context keeps reference until we stop making javascript rendering callbacks return node; } PassRefPtr AudioContext::createBiquadFilter() { ASSERT(isMainThread()); lazyInitialize(); return BiquadFilterNode::create(this, m_destinationNode->sampleRate()); } PassRefPtr AudioContext::createWaveShaper() { ASSERT(isMainThread()); lazyInitialize(); return WaveShaperNode::create(this); } PassRefPtr AudioContext::createPanner() { ASSERT(isMainThread()); lazyInitialize(); return PannerNode::create(this, m_destinationNode->sampleRate()); } PassRefPtr AudioContext::createConvolver() { ASSERT(isMainThread()); lazyInitialize(); return ConvolverNode::create(this, m_destinationNode->sampleRate()); } PassRefPtr AudioContext::createDynamicsCompressor() { ASSERT(isMainThread()); lazyInitialize(); return DynamicsCompressorNode::create(this, m_destinationNode->sampleRate()); } PassRefPtr AudioContext::createAnalyser() { ASSERT(isMainThread()); lazyInitialize(); return AnalyserNode::create(this, m_destinationNode->sampleRate()); } PassRefPtr AudioContext::createGain() { ASSERT(isMainThread()); lazyInitialize(); return GainNode::create(this, m_destinationNode->sampleRate()); } PassRefPtr AudioContext::createDelay(ExceptionState& exceptionState) { const double defaultMaxDelayTime = 1; return createDelay(defaultMaxDelayTime, exceptionState); } PassRefPtr AudioContext::createDelay(double maxDelayTime, ExceptionState& exceptionState) { ASSERT(isMainThread()); lazyInitialize(); RefPtr node = DelayNode::create(this, m_destinationNode->sampleRate(), maxDelayTime, exceptionState); if (exceptionState.hadException()) return 0; return node; } PassRefPtr AudioContext::createChannelSplitter(ExceptionState& exceptionState) { const unsigned ChannelSplitterDefaultNumberOfOutputs = 6; return createChannelSplitter(ChannelSplitterDefaultNumberOfOutputs, exceptionState); } PassRefPtr AudioContext::createChannelSplitter(size_t numberOfOutputs, ExceptionState& exceptionState) { ASSERT(isMainThread()); lazyInitialize(); RefPtr node = ChannelSplitterNode::create(this, m_destinationNode->sampleRate(), numberOfOutputs); if (!node.get()) { exceptionState.throwDOMException( IndexSizeError, "number of outputs (" + String::number(numberOfOutputs) + ") must be between 1 and " + String::number(AudioContext::maxNumberOfChannels()) + "."); return 0; } return node; } PassRefPtr AudioContext::createChannelMerger(ExceptionState& exceptionState) { const unsigned ChannelMergerDefaultNumberOfInputs = 6; return createChannelMerger(ChannelMergerDefaultNumberOfInputs, exceptionState); } PassRefPtr AudioContext::createChannelMerger(size_t numberOfInputs, ExceptionState& exceptionState) { ASSERT(isMainThread()); lazyInitialize(); RefPtr node = ChannelMergerNode::create(this, m_destinationNode->sampleRate(), numberOfInputs); if (!node.get()) { exceptionState.throwDOMException( IndexSizeError, "number of inputs (" + String::number(numberOfInputs) + ") must be between 1 and " + String::number(AudioContext::maxNumberOfChannels()) + "."); return 0; } return node; } PassRefPtr AudioContext::createOscillator() { ASSERT(isMainThread()); lazyInitialize(); RefPtr node = OscillatorNode::create(this, m_destinationNode->sampleRate()); // Because this is an AudioScheduledSourceNode, the context keeps a reference until it has finished playing. // When this happens, AudioScheduledSourceNode::finish() calls AudioContext::notifyNodeFinishedProcessing(). refNode(node.get()); return node; } PassRefPtr AudioContext::createPeriodicWave(Float32Array* real, Float32Array* imag, ExceptionState& exceptionState) { ASSERT(isMainThread()); if (!real) { exceptionState.throwDOMException( SyntaxError, "invalid real array"); return 0; } if (!imag) { exceptionState.throwDOMException( SyntaxError, "invalid imaginary array"); return 0; } if (real->length() != imag->length()) { exceptionState.throwDOMException( IndexSizeError, "length of real array (" + String::number(real->length()) + ") and length of imaginary array (" + String::number(imag->length()) + ") must match."); return 0; } if (real->length() > 4096) { exceptionState.throwDOMException( IndexSizeError, "length of real array (" + String::number(real->length()) + ") exceeds allowed maximum of 4096"); return 0; } if (imag->length() > 4096) { exceptionState.throwDOMException( IndexSizeError, "length of imaginary array (" + String::number(imag->length()) + ") exceeds allowed maximum of 4096"); return 0; } lazyInitialize(); return PeriodicWave::create(sampleRate(), real, imag); } void AudioContext::notifyNodeFinishedProcessing(AudioNode* node) { ASSERT(isAudioThread()); m_finishedNodes.append(node); } void AudioContext::derefFinishedSourceNodes() { ASSERT(isGraphOwner()); ASSERT(isAudioThread() || isAudioThreadFinished()); for (unsigned i = 0; i < m_finishedNodes.size(); i++) derefNode(m_finishedNodes[i]); m_finishedNodes.clear(); } void AudioContext::refNode(AudioNode* node) { ASSERT(isMainThread()); AutoLocker locker(this); node->ref(AudioNode::RefTypeConnection); m_referencedNodes.append(node); } void AudioContext::derefNode(AudioNode* node) { ASSERT(isGraphOwner()); node->deref(AudioNode::RefTypeConnection); for (unsigned i = 0; i < m_referencedNodes.size(); ++i) { if (node == m_referencedNodes[i]) { m_referencedNodes.remove(i); break; } } } void AudioContext::derefUnfinishedSourceNodes() { ASSERT(isMainThread() && isAudioThreadFinished()); for (unsigned i = 0; i < m_referencedNodes.size(); ++i) m_referencedNodes[i]->deref(AudioNode::RefTypeConnection); m_referencedNodes.clear(); } void AudioContext::lock(bool& mustReleaseLock) { // Don't allow regular lock in real-time audio thread. ASSERT(isMainThread()); ThreadIdentifier thisThread = currentThread(); if (thisThread == m_graphOwnerThread) { // We already have the lock. mustReleaseLock = false; } else { // Acquire the lock. m_contextGraphMutex.lock(); m_graphOwnerThread = thisThread; mustReleaseLock = true; } } bool AudioContext::tryLock(bool& mustReleaseLock) { ThreadIdentifier thisThread = currentThread(); bool isAudioThread = thisThread == audioThread(); // Try to catch cases of using try lock on main thread - it should use regular lock. ASSERT(isAudioThread || isAudioThreadFinished()); if (!isAudioThread) { // In release build treat tryLock() as lock() (since above ASSERT(isAudioThread) never fires) - this is the best we can do. lock(mustReleaseLock); return true; } bool hasLock; if (thisThread == m_graphOwnerThread) { // Thread already has the lock. hasLock = true; mustReleaseLock = false; } else { // Don't already have the lock - try to acquire it. hasLock = m_contextGraphMutex.tryLock(); if (hasLock) m_graphOwnerThread = thisThread; mustReleaseLock = hasLock; } return hasLock; } void AudioContext::unlock() { ASSERT(currentThread() == m_graphOwnerThread); m_graphOwnerThread = UndefinedThreadIdentifier; m_contextGraphMutex.unlock(); } bool AudioContext::isAudioThread() const { return currentThread() == m_audioThread; } bool AudioContext::isGraphOwner() const { return currentThread() == m_graphOwnerThread; } void AudioContext::addDeferredFinishDeref(AudioNode* node) { ASSERT(isAudioThread()); m_deferredFinishDerefList.append(node); } void AudioContext::handlePreRenderTasks() { ASSERT(isAudioThread()); // At the beginning of every render quantum, try to update the internal rendering graph state (from main thread changes). // It's OK if the tryLock() fails, we'll just take slightly longer to pick up the changes. bool mustReleaseLock; if (tryLock(mustReleaseLock)) { // Fixup the state of any dirty AudioSummingJunctions and AudioNodeOutputs. handleDirtyAudioSummingJunctions(); handleDirtyAudioNodeOutputs(); updateAutomaticPullNodes(); if (mustReleaseLock) unlock(); } } void AudioContext::handlePostRenderTasks() { ASSERT(isAudioThread()); // Must use a tryLock() here too. Don't worry, the lock will very rarely be contended and this method is called frequently. // The worst that can happen is that there will be some nodes which will take slightly longer than usual to be deleted or removed // from the render graph (in which case they'll render silence). bool mustReleaseLock; if (tryLock(mustReleaseLock)) { // Take care of finishing any derefs where the tryLock() failed previously. handleDeferredFinishDerefs(); // Dynamically clean up nodes which are no longer needed. derefFinishedSourceNodes(); // Don't delete in the real-time thread. Let the main thread do it. // Ref-counted objects held by certain AudioNodes may not be thread-safe. scheduleNodeDeletion(); // Fixup the state of any dirty AudioSummingJunctions and AudioNodeOutputs. handleDirtyAudioSummingJunctions(); handleDirtyAudioNodeOutputs(); updateAutomaticPullNodes(); if (mustReleaseLock) unlock(); } } void AudioContext::handleDeferredFinishDerefs() { ASSERT(isAudioThread() && isGraphOwner()); for (unsigned i = 0; i < m_deferredFinishDerefList.size(); ++i) { AudioNode* node = m_deferredFinishDerefList[i]; node->finishDeref(AudioNode::RefTypeConnection); } m_deferredFinishDerefList.clear(); } void AudioContext::markForDeletion(AudioNode* node) { ASSERT(isGraphOwner()); if (isAudioThreadFinished()) m_nodesToDelete.append(node); else m_nodesMarkedForDeletion.append(node); // This is probably the best time for us to remove the node from automatic pull list, // since all connections are gone and we hold the graph lock. Then when handlePostRenderTasks() // gets a chance to schedule the deletion work, updateAutomaticPullNodes() also gets a chance to // modify m_renderingAutomaticPullNodes. removeAutomaticPullNode(node); } void AudioContext::scheduleNodeDeletion() { bool isGood = m_isInitialized && isGraphOwner(); ASSERT(isGood); if (!isGood) return; // Make sure to call deleteMarkedNodes() on main thread. if (m_nodesMarkedForDeletion.size() && !m_isDeletionScheduled) { m_nodesToDelete.append(m_nodesMarkedForDeletion); m_nodesMarkedForDeletion.clear(); m_isDeletionScheduled = true; // Don't let ourself get deleted before the callback. // See matching deref() in deleteMarkedNodesDispatch(). ref(); callOnMainThread(deleteMarkedNodesDispatch, this); } } void AudioContext::deleteMarkedNodesDispatch(void* userData) { AudioContext* context = reinterpret_cast(userData); ASSERT(context); if (!context) return; context->deleteMarkedNodes(); context->deref(); } void AudioContext::deleteMarkedNodes() { ASSERT(isMainThread()); // Protect this object from being deleted before we release the mutex locked by AutoLocker. RefPtr protect(this); { AutoLocker locker(this); while (size_t n = m_nodesToDelete.size()) { AudioNode* node = m_nodesToDelete[n - 1]; m_nodesToDelete.removeLast(); // Before deleting the node, clear out any AudioNodeInputs from m_dirtySummingJunctions. unsigned numberOfInputs = node->numberOfInputs(); for (unsigned i = 0; i < numberOfInputs; ++i) m_dirtySummingJunctions.remove(node->input(i)); // Before deleting the node, clear out any AudioNodeOutputs from m_dirtyAudioNodeOutputs. unsigned numberOfOutputs = node->numberOfOutputs(); for (unsigned i = 0; i < numberOfOutputs; ++i) m_dirtyAudioNodeOutputs.remove(node->output(i)); // Finally, delete it. delete node; } m_isDeletionScheduled = false; } } void AudioContext::markSummingJunctionDirty(AudioSummingJunction* summingJunction) { ASSERT(isGraphOwner()); m_dirtySummingJunctions.add(summingJunction); } void AudioContext::removeMarkedSummingJunction(AudioSummingJunction* summingJunction) { ASSERT(isMainThread()); AutoLocker locker(this); m_dirtySummingJunctions.remove(summingJunction); } void AudioContext::markAudioNodeOutputDirty(AudioNodeOutput* output) { ASSERT(isGraphOwner()); m_dirtyAudioNodeOutputs.add(output); } void AudioContext::handleDirtyAudioSummingJunctions() { ASSERT(isGraphOwner()); for (HashSet::iterator i = m_dirtySummingJunctions.begin(); i != m_dirtySummingJunctions.end(); ++i) (*i)->updateRenderingState(); m_dirtySummingJunctions.clear(); } void AudioContext::handleDirtyAudioNodeOutputs() { ASSERT(isGraphOwner()); for (HashSet::iterator i = m_dirtyAudioNodeOutputs.begin(); i != m_dirtyAudioNodeOutputs.end(); ++i) (*i)->updateRenderingState(); m_dirtyAudioNodeOutputs.clear(); } void AudioContext::addAutomaticPullNode(AudioNode* node) { ASSERT(isGraphOwner()); if (!m_automaticPullNodes.contains(node)) { m_automaticPullNodes.add(node); m_automaticPullNodesNeedUpdating = true; } } void AudioContext::removeAutomaticPullNode(AudioNode* node) { ASSERT(isGraphOwner()); if (m_automaticPullNodes.contains(node)) { m_automaticPullNodes.remove(node); m_automaticPullNodesNeedUpdating = true; } } void AudioContext::updateAutomaticPullNodes() { ASSERT(isGraphOwner()); if (m_automaticPullNodesNeedUpdating) { // Copy from m_automaticPullNodes to m_renderingAutomaticPullNodes. m_renderingAutomaticPullNodes.resize(m_automaticPullNodes.size()); unsigned j = 0; for (HashSet::iterator i = m_automaticPullNodes.begin(); i != m_automaticPullNodes.end(); ++i, ++j) { AudioNode* output = *i; m_renderingAutomaticPullNodes[j] = output; } m_automaticPullNodesNeedUpdating = false; } } void AudioContext::processAutomaticPullNodes(size_t framesToProcess) { ASSERT(isAudioThread()); for (unsigned i = 0; i < m_renderingAutomaticPullNodes.size(); ++i) m_renderingAutomaticPullNodes[i]->processIfNecessary(framesToProcess); } const AtomicString& AudioContext::interfaceName() const { return EventTargetNames::AudioContext; } ExecutionContext* AudioContext::executionContext() const { return m_isStopScheduled ? 0 : ActiveDOMObject::executionContext(); } void AudioContext::startRendering() { destination()->startRendering(); } void AudioContext::fireCompletionEvent() { ASSERT(isMainThread()); if (!isMainThread()) return; AudioBuffer* renderedBuffer = m_renderTarget.get(); ASSERT(renderedBuffer); if (!renderedBuffer) return; // Avoid firing the event if the document has already gone away. if (executionContext()) { // Call the offline rendering completion event listener. dispatchEvent(OfflineAudioCompletionEvent::create(renderedBuffer)); } } void AudioContext::incrementActiveSourceCount() { atomicIncrement(&m_activeSourceCount); } void AudioContext::decrementActiveSourceCount() { atomicDecrement(&m_activeSourceCount); } } // namespace WebCore #endif // ENABLE(WEB_AUDIO)