blob: 0d23cf584e7737e0b6cb74480557b0331513323b [file] [log] [blame]
/*
* 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 "AudioContext.h"
#include "ArrayBuffer.h"
#include "AudioBuffer.h"
#include "AudioBufferSourceNode.h"
#include "AudioChannelMerger.h"
#include "AudioChannelSplitter.h"
#include "AudioGainNode.h"
#include "AudioListener.h"
#include "AudioNodeInput.h"
#include "AudioNodeOutput.h"
#include "AudioPannerNode.h"
#include "ConvolverNode.h"
#include "DefaultAudioDestinationNode.h"
#include "DelayNode.h"
#include "Document.h"
#include "FFTFrame.h"
#include "HRTFDatabaseLoader.h"
#include "HRTFPanner.h"
#include "HighPass2FilterNode.h"
#include "JavaScriptAudioNode.h"
#include "LowPass2FilterNode.h"
#include "OfflineAudioCompletionEvent.h"
#include "OfflineAudioDestinationNode.h"
#include "PlatformString.h"
#include "RealtimeAnalyserNode.h"
#if DEBUG_AUDIONODE_REFERENCES
#include <stdio.h>
#endif
#include <wtf/OwnPtr.h>
#include <wtf/PassOwnPtr.h>
#include <wtf/RefCounted.h>
// FIXME: check the proper way to reference an undefined thread ID
const int UndefinedThreadIdentifier = 0xffffffff;
const unsigned MaxNodesToDeletePerQuantum = 10;
namespace WebCore {
PassRefPtr<AudioContext> AudioContext::create(Document* document)
{
return adoptRef(new AudioContext(document));
}
PassRefPtr<AudioContext> AudioContext::createOfflineContext(Document* document, unsigned numberOfChannels, size_t numberOfFrames, double sampleRate)
{
return adoptRef(new AudioContext(document, numberOfChannels, numberOfFrames, sampleRate));
}
// Constructor for rendering to the audio hardware.
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)
, m_isOfflineContext(false)
{
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());
// FIXME: for now default AudioContext does not need an explicit startRendering() call.
// We may want to consider requiring it for symmetry with OfflineAudioContext
m_destinationNode->startRendering();
}
// Constructor for offline (non-realtime) rendering.
AudioContext::AudioContext(Document* document, unsigned numberOfChannels, size_t numberOfFrames, double sampleRate)
: ActiveDOMObject(document, this)
, m_isInitialized(false)
, m_isAudioThreadFinished(false)
, m_document(document)
, m_destinationNode(0)
, m_connectionCount(0)
, m_audioThread(0)
, m_graphOwnerThread(UndefinedThreadIdentifier)
, m_isOfflineContext(true)
{
constructCommon();
// FIXME: the passed in sampleRate MUST match the hardware sample-rate since HRTFDatabaseLoader is a singleton.
m_hrtfDatabaseLoader = HRTFDatabaseLoader::createAndLoadAsynchronouslyIfNecessary(sampleRate);
// Create a new destination for offline rendering.
m_renderTarget = AudioBuffer::create(numberOfChannels, numberOfFrames, sampleRate);
m_destinationNode = OfflineAudioDestinationNode::create(this, m_renderTarget.get());
}
void AudioContext::constructCommon()
{
// 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();
FFTFrame::initialize();
m_listener = AudioListener::create();
m_temporaryMonoBus = adoptPtr(new AudioBus(1, AudioNode::ProcessingSizeInFrames));
m_temporaryStereoBus = adoptPtr(new AudioBus(2, AudioNode::ProcessingSizeInFrames));
}
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 m_hrtfDatabaseLoader->isLoaded();
}
void AudioContext::stop()
{
m_document = 0; // document is going away
uninitialize();
}
Document* AudioContext::document() const
{
ASSERT(m_document);
return m_document;
}
bool AudioContext::hasDocument()
{
return m_document;
}
void AudioContext::refBuffer(PassRefPtr<AudioBuffer> buffer)
{
m_allocatedBuffers.append(buffer);
}
PassRefPtr<AudioBuffer> AudioContext::createBuffer(unsigned numberOfChannels, size_t numberOfFrames, double sampleRate)
{
return AudioBuffer::create(numberOfChannels, numberOfFrames, sampleRate);
}
PassRefPtr<AudioBuffer> AudioContext::createBuffer(ArrayBuffer* arrayBuffer, bool mixToMono)
{
ASSERT(arrayBuffer);
if (!arrayBuffer)
return 0;
return AudioBuffer::createFromAudioFileData(arrayBuffer->data(), arrayBuffer->byteLength(), mixToMono, sampleRate());
}
PassRefPtr<AudioBufferSourceNode> AudioContext::createBufferSource()
{
ASSERT(isMainThread());
lazyInitialize();
RefPtr<AudioBufferSourceNode> node = AudioBufferSourceNode::create(this, m_destinationNode->sampleRate());
refNode(node.get()); // context keeps reference until source has finished playing
return node;
}
PassRefPtr<JavaScriptAudioNode> AudioContext::createJavaScriptNode(size_t bufferSize)
{
ASSERT(isMainThread());
lazyInitialize();
RefPtr<JavaScriptAudioNode> node = JavaScriptAudioNode::create(this, m_destinationNode->sampleRate(), bufferSize);
refNode(node.get()); // context keeps reference until we stop making javascript rendering callbacks
return node;
}
PassRefPtr<LowPass2FilterNode> AudioContext::createLowPass2Filter()
{
ASSERT(isMainThread());
lazyInitialize();
return LowPass2FilterNode::create(this, m_destinationNode->sampleRate());
}
PassRefPtr<HighPass2FilterNode> AudioContext::createHighPass2Filter()
{
ASSERT(isMainThread());
lazyInitialize();
return HighPass2FilterNode::create(this, m_destinationNode->sampleRate());
}
PassRefPtr<AudioPannerNode> AudioContext::createPanner()
{
ASSERT(isMainThread());
lazyInitialize();
return AudioPannerNode::create(this, m_destinationNode->sampleRate());
}
PassRefPtr<ConvolverNode> AudioContext::createConvolver()
{
ASSERT(isMainThread());
lazyInitialize();
return ConvolverNode::create(this, m_destinationNode->sampleRate());
}
PassRefPtr<RealtimeAnalyserNode> AudioContext::createAnalyser()
{
ASSERT(isMainThread());
lazyInitialize();
return RealtimeAnalyserNode::create(this, m_destinationNode->sampleRate());
}
PassRefPtr<AudioGainNode> AudioContext::createGainNode()
{
ASSERT(isMainThread());
lazyInitialize();
return AudioGainNode::create(this, m_destinationNode->sampleRate());
}
PassRefPtr<DelayNode> AudioContext::createDelayNode()
{
ASSERT(isMainThread());
lazyInitialize();
return DelayNode::create(this, m_destinationNode->sampleRate());
}
PassRefPtr<AudioChannelSplitter> AudioContext::createChannelSplitter()
{
ASSERT(isMainThread());
lazyInitialize();
return AudioChannelSplitter::create(this, m_destinationNode->sampleRate());
}
PassRefPtr<AudioChannelMerger> 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();
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, AudioNode::RefType refType)
{
ASSERT(isAudioThread());
m_deferredFinishDerefList.append(AudioContext::RefInfo(node, refType));
}
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 AudioNodeInputs and AudioNodeOutputs.
handleDirtyAudioNodeInputs();
handleDirtyAudioNodeOutputs();
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();
// Finally actually delete.
deleteMarkedNodes();
// Fixup the state of any dirty AudioNodeInputs and AudioNodeOutputs.
handleDirtyAudioNodeInputs();
handleDirtyAudioNodeOutputs();
if (mustReleaseLock)
unlock();
}
}
void AudioContext::handleDeferredFinishDerefs()
{
ASSERT(isAudioThread() && isGraphOwner());
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.
size_t nodesDeleted = 0;
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_dirtyAudioNodeInputs.
unsigned numberOfInputs = node->numberOfInputs();
for (unsigned i = 0; i < numberOfInputs; ++i)
m_dirtyAudioNodeInputs.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;
// Don't delete too many nodes per render quantum since we don't want to do too much work in the realtime audio thread.
if (++nodesDeleted > MaxNodesToDeletePerQuantum)
break;
}
}
void AudioContext::markAudioNodeInputDirty(AudioNodeInput* input)
{
ASSERT(isGraphOwner());
m_dirtyAudioNodeInputs.add(input);
}
void AudioContext::markAudioNodeOutputDirty(AudioNodeOutput* output)
{
ASSERT(isGraphOwner());
m_dirtyAudioNodeOutputs.add(output);
}
void AudioContext::handleDirtyAudioNodeInputs()
{
ASSERT(isGraphOwner());
for (HashSet<AudioNodeInput*>::iterator i = m_dirtyAudioNodeInputs.begin(); i != m_dirtyAudioNodeInputs.end(); ++i)
(*i)->updateRenderingState();
m_dirtyAudioNodeInputs.clear();
}
void AudioContext::handleDirtyAudioNodeOutputs()
{
ASSERT(isGraphOwner());
for (HashSet<AudioNodeOutput*>::iterator i = m_dirtyAudioNodeOutputs.begin(); i != m_dirtyAudioNodeOutputs.end(); ++i)
(*i)->updateRenderingState();
m_dirtyAudioNodeOutputs.clear();
}
ScriptExecutionContext* AudioContext::scriptExecutionContext() const
{
return document();
}
AudioContext* AudioContext::toAudioContext()
{
return this;
}
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 (hasDocument()) {
// Call the offline rendering completion event listener.
dispatchEvent(OfflineAudioCompletionEvent::create(renderedBuffer));
}
}
} // namespace WebCore
#endif // ENABLE(WEB_AUDIO)