/* * 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. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE 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 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 "AudioContext.h" #include "AudioBuffer.h" #include "AudioBufferSourceNode.h" #include "AudioChannelMerger.h" #include "AudioChannelSplitter.h" #include "AudioGainNode.h" #include "AudioListener.h" #include "AudioPannerNode.h" #include "CachedAudio.h" #include "ConvolverNode.h" #include "DelayNode.h" #include "Document.h" #include "HRTFDatabaseLoader.h" #include "HRTFPanner.h" #include "HTMLNames.h" #include "HighPass2FilterNode.h" #include "JavaScriptAudioNode.h" #include "LowPass2FilterNode.h" #include "PlatformString.h" #include "RealtimeAnalyserNode.h" #include #include #include // FIXME: check the proper way to reference an undefined thread ID const int UndefinedThreadIdentifier = 0xffffffff; namespace WebCore { PassRefPtr AudioContext::createAudioRequest(const String &url, bool mixToMono) { lazyInitialize(); // Convert relative URL to absolute KURL completedURL = document()->completeURL(url); String completedURLString = completedURL.string(); RefPtr cachedAudio = CachedAudio::create(completedURLString, this, document(), sampleRate(), mixToMono); CachedAudio* c = cachedAudio.get(); m_cachedAudioReferences.append(c); return cachedAudio; } PassRefPtr AudioContext::create(Document* document) { return adoptRef(new AudioContext(document)); } AudioContext::AudioContext(Document* document) : ActiveDOMObject(document, this) , m_isInitialized(false) , m_isAudioThreadFinished(false) , m_document(document) , m_destinationNode(0) , m_connectionCount(0) , m_audioThread(0) , m_graphOwnerThread(UndefinedThreadIdentifier) { // Note: because adoptRef() won't be called until we leave this constructor, but code in this constructor needs to reference this context, // relax the check. relaxAdoptionRequirement(); m_destinationNode = AudioDestinationNode::create(this); m_listener = AudioListener::create(); m_temporaryMonoBus = adoptPtr(new AudioBus(1, AudioNode::ProcessingSizeInFrames)); m_temporaryStereoBus = adoptPtr(new AudioBus(2, AudioNode::ProcessingSizeInFrames)); // This sets in motion an asynchronous loading mechanism on another thread. // We can check 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). hrtfDatabaseLoader()->loadAsynchronously(sampleRate()); } AudioContext::~AudioContext() { #if DEBUG_AUDIONODE_REFERENCES printf("%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_nodesToDelete.size()); ASSERT(!m_referencedNodes.size()); ASSERT(!m_finishedNodes.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()) { // 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". m_destinationNode->initialize(); } m_isInitialized = true; } } } void AudioContext::uninitialize() { if (m_isInitialized) { // 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; // 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. m_destinationNode.clear(); // Get rid of the sources which may still be playing. derefUnfinishedSourceNodes(); // Because the AudioBuffers are garbage collected, we can't delete them here. // Instead, at least release the potentially large amount of allocated memory for the audio data. // Note that we do this *after* the context is uninitialized and stops processing audio. for (unsigned i = 0; i < m_allocatedBuffers.size(); ++i) m_allocatedBuffers[i]->releaseMemory(); m_allocatedBuffers.clear(); 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 hrtfDatabaseLoader()->isLoaded(); } void AudioContext::stop() { m_document = 0; // document is going away uninitialize(); } Document* AudioContext::document() { ASSERT(m_document); return m_document; } bool AudioContext::hasDocument() { return m_document; } void AudioContext::refBuffer(PassRefPtr buffer) { m_allocatedBuffers.append(buffer); } PassRefPtr AudioContext::createBuffer(unsigned numberOfChannels, size_t numberOfFrames, double sampleRate) { return AudioBuffer::create(numberOfChannels, numberOfFrames, sampleRate); } PassRefPtr AudioContext::createBufferSource() { ASSERT(isMainThread()); lazyInitialize(); RefPtr node = AudioBufferSourceNode::create(this, m_destinationNode->sampleRate()); refNode(node.get()); // context keeps reference until source has finished playing return node; } PassRefPtr AudioContext::createJavaScriptNode(size_t bufferSize) { ASSERT(isMainThread()); lazyInitialize(); RefPtr node = JavaScriptAudioNode::create(this, m_destinationNode->sampleRate(), bufferSize); refNode(node.get()); // context keeps reference until we stop making javascript rendering callbacks return node; } PassRefPtr AudioContext::createLowPass2Filter() { ASSERT(isMainThread()); lazyInitialize(); return LowPass2FilterNode::create(this, m_destinationNode->sampleRate()); } PassRefPtr AudioContext::createHighPass2Filter() { ASSERT(isMainThread()); lazyInitialize(); return HighPass2FilterNode::create(this, m_destinationNode->sampleRate()); } PassRefPtr AudioContext::createPanner() { ASSERT(isMainThread()); lazyInitialize(); return AudioPannerNode::create(this, m_destinationNode->sampleRate()); } PassRefPtr AudioContext::createConvolver() { ASSERT(isMainThread()); lazyInitialize(); return ConvolverNode::create(this, m_destinationNode->sampleRate()); } PassRefPtr AudioContext::createAnalyser() { ASSERT(isMainThread()); lazyInitialize(); return RealtimeAnalyserNode::create(this, m_destinationNode->sampleRate()); } PassRefPtr AudioContext::createGainNode() { ASSERT(isMainThread()); lazyInitialize(); return AudioGainNode::create(this, m_destinationNode->sampleRate()); } PassRefPtr AudioContext::createDelayNode() { ASSERT(isMainThread()); lazyInitialize(); return DelayNode::create(this, m_destinationNode->sampleRate()); } PassRefPtr AudioContext::createChannelSplitter() { ASSERT(isMainThread()); lazyInitialize(); return AudioChannelSplitter::create(this, m_destinationNode->sampleRate()); } PassRefPtr AudioContext::createChannelMerger() { ASSERT(isMainThread()); lazyInitialize(); return AudioChannelMerger::create(this, m_destinationNode->sampleRate()); } 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(); mustReleaseLock = true; } m_graphOwnerThread = thisThread; } 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() { return currentThread() == m_audioThread; } bool AudioContext::isGraphOwner() { return currentThread() == m_graphOwnerThread; } void AudioContext::addDeferredFinishDeref(AudioNode* node, AudioNode::RefType refType) { ASSERT(isAudioThread()); m_deferredFinishDerefList.append(AudioContext::RefInfo(node, refType)); } 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(); // Finally actually delete. deleteMarkedNodes(); if (mustReleaseLock) unlock(); } } void AudioContext::handleDeferredFinishDerefs() { ASSERT(isAudioThread()); for (unsigned i = 0; i < m_deferredFinishDerefList.size(); ++i) { AudioNode* node = m_deferredFinishDerefList[i].m_node; AudioNode::RefType refType = m_deferredFinishDerefList[i].m_refType; node->finishDeref(refType); } m_deferredFinishDerefList.clear(); } void AudioContext::markForDeletion(AudioNode* node) { ASSERT(isGraphOwner()); m_nodesToDelete.append(node); } void AudioContext::deleteMarkedNodes() { ASSERT(isGraphOwner() || isAudioThreadFinished()); // Note: deleting an AudioNode can cause m_nodesToDelete to grow. while (size_t n = m_nodesToDelete.size()) { AudioNode* node = m_nodesToDelete[n - 1]; m_nodesToDelete.removeLast(); delete node; } } } // namespace WebCore #endif // ENABLE(WEB_AUDIO)