| /* |
| * 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 "ScriptProcessorNode.h" |
| |
| #include "AudioBuffer.h" |
| #include "AudioBus.h" |
| #include "AudioContext.h" |
| #include "AudioNodeInput.h" |
| #include "AudioNodeOutput.h" |
| #include "AudioProcessingEvent.h" |
| #include "AudioUtilities.h" |
| #include "Document.h" |
| #include "EventNames.h" |
| #include <JavaScriptCore/Float32Array.h> |
| #include <wtf/IsoMallocInlines.h> |
| #include <wtf/MainThread.h> |
| |
| namespace WebCore { |
| |
| WTF_MAKE_ISO_ALLOCATED_IMPL(ScriptProcessorNode); |
| |
| Ref<ScriptProcessorNode> ScriptProcessorNode::create(BaseAudioContext& context, size_t bufferSize, unsigned numberOfInputChannels, unsigned numberOfOutputChannels) |
| { |
| auto node = adoptRef(*new ScriptProcessorNode(context, bufferSize, numberOfInputChannels, numberOfOutputChannels)); |
| node->suspendIfNeeded(); |
| return node; |
| } |
| |
| ScriptProcessorNode::ScriptProcessorNode(BaseAudioContext& context, size_t bufferSize, unsigned numberOfInputChannels, unsigned numberOfOutputChannels) |
| : AudioNode(context, NodeTypeJavaScript) |
| , ActiveDOMObject(context.scriptExecutionContext()) |
| , m_bufferSize(bufferSize) |
| , m_numberOfInputChannels(numberOfInputChannels) |
| , m_numberOfOutputChannels(numberOfOutputChannels) |
| , m_internalInputBus(AudioBus::create(numberOfInputChannels, AudioUtilities::renderQuantumSize, false)) |
| { |
| // Regardless of the allowed buffer sizes, we still need to process at the granularity of the AudioNode. |
| if (m_bufferSize < AudioUtilities::renderQuantumSize) |
| m_bufferSize = AudioUtilities::renderQuantumSize; |
| |
| ASSERT(numberOfInputChannels <= AudioContext::maxNumberOfChannels); |
| |
| initializeDefaultNodeOptions(numberOfInputChannels, ChannelCountMode::Explicit, ChannelInterpretation::Speakers); |
| addInput(); |
| addOutput(numberOfOutputChannels); |
| |
| initialize(); |
| } |
| |
| ScriptProcessorNode::~ScriptProcessorNode() |
| { |
| ASSERT(!hasPendingActivity()); |
| uninitialize(); |
| } |
| |
| void ScriptProcessorNode::initialize() |
| { |
| if (isInitialized()) |
| return; |
| |
| float sampleRate = context().sampleRate(); |
| |
| // Create double buffers on both the input and output sides. |
| // These AudioBuffers will be directly accessed in the main thread by JavaScript. |
| for (unsigned i = 0; i < bufferCount; ++i) { |
| // We prevent detaching the AudioBuffers here since we pass those to JS and reuse them. |
| m_inputBuffers[i] = m_numberOfInputChannels ? AudioBuffer::create(m_numberOfInputChannels, bufferSize(), sampleRate, AudioBuffer::LegacyPreventDetaching::Yes) : nullptr; |
| m_outputBuffers[i] = m_numberOfOutputChannels ? AudioBuffer::create(m_numberOfOutputChannels, bufferSize(), sampleRate, AudioBuffer::LegacyPreventDetaching::Yes) : nullptr; |
| } |
| |
| AudioNode::initialize(); |
| } |
| |
| RefPtr<AudioBuffer> ScriptProcessorNode::createInputBufferForJS(AudioBuffer* inputBuffer) const |
| { |
| if (!inputBuffer) |
| return nullptr; |
| |
| // As an optimization, we reuse the same buffer as last time when possible. |
| if (!m_cachedInputBufferForJS || !inputBuffer->copyTo(*m_cachedInputBufferForJS)) |
| m_cachedInputBufferForJS = inputBuffer->clone(); |
| |
| return m_cachedInputBufferForJS; |
| } |
| |
| RefPtr<AudioBuffer> ScriptProcessorNode::createOutputBufferForJS(AudioBuffer& outputBuffer) const |
| { |
| // As an optimization, we reuse the same buffer as last time when possible. |
| if (!m_cachedOutputBufferForJS || !m_cachedOutputBufferForJS->topologyMatches(outputBuffer)) |
| m_cachedOutputBufferForJS = outputBuffer.clone(AudioBuffer::ShouldCopyChannelData::No); |
| else |
| m_cachedOutputBufferForJS->zero(); |
| |
| return m_cachedOutputBufferForJS; |
| } |
| |
| void ScriptProcessorNode::uninitialize() |
| { |
| if (!isInitialized()) |
| return; |
| |
| for (unsigned i = 0; i < bufferCount; ++i) { |
| Locker locker { m_bufferLocks[i] }; |
| m_inputBuffers[i] = nullptr; |
| m_outputBuffers[i] = nullptr; |
| } |
| |
| AudioNode::uninitialize(); |
| } |
| |
| void ScriptProcessorNode::process(size_t framesToProcess) |
| { |
| // Discussion about inputs and outputs: |
| // As in other AudioNodes, ScriptProcessorNode uses an AudioBus for its input and output (see inputBus and outputBus below). |
| // Additionally, there is a double-buffering for input and output (see inputBuffer and outputBuffer below). |
| // This node is the producer for inputBuffer and the consumer for outputBuffer. |
| // The JavaScript code is the consumer of inputBuffer and the producer for outputBuffer. The JavaScript gets its own copy |
| // of the buffers for safety reasons. |
| |
| // Get input and output busses. |
| AudioBus* inputBus = this->input(0)->bus(); |
| AudioBus* outputBus = this->output(0)->bus(); |
| |
| // Get input and output buffers. We double-buffer both the input and output sides. |
| unsigned bufferIndex = this->bufferIndex(); |
| ASSERT(bufferIndex < bufferCount); |
| |
| if (!m_bufferLocks[bufferIndex].tryLock()) { |
| // We're late in handling the previous request. The main thread must be |
| // very busy. The best we can do is clear out the buffer ourself here. |
| outputBus->zero(); |
| return; |
| } |
| Locker locker { AdoptLock, m_bufferLocks[bufferIndex] }; |
| |
| AudioBuffer* inputBuffer = m_inputBuffers[bufferIndex].get(); |
| AudioBuffer* outputBuffer = m_outputBuffers[bufferIndex].get(); |
| |
| // Check the consistency of input and output buffers. |
| unsigned numberOfInputChannels = m_internalInputBus->numberOfChannels(); |
| bool buffersAreGood = outputBuffer && bufferSize() == outputBuffer->length() && m_bufferReadWriteIndex + framesToProcess <= bufferSize(); |
| |
| // If the number of input channels is zero, it's ok to have inputBuffer = 0. |
| if (m_internalInputBus->numberOfChannels()) |
| buffersAreGood = buffersAreGood && inputBuffer && bufferSize() == inputBuffer->length(); |
| |
| ASSERT(buffersAreGood); |
| if (!buffersAreGood) |
| return; |
| |
| // We assume that bufferSize() is evenly divisible by framesToProcess - should always be true, but we should still check. |
| bool isFramesToProcessGood = framesToProcess && bufferSize() >= framesToProcess && !(bufferSize() % framesToProcess); |
| ASSERT(isFramesToProcessGood); |
| if (!isFramesToProcessGood) |
| return; |
| |
| unsigned numberOfOutputChannels = outputBus->numberOfChannels(); |
| |
| bool channelsAreGood = (numberOfInputChannels == m_numberOfInputChannels) && (numberOfOutputChannels == m_numberOfOutputChannels); |
| ASSERT(channelsAreGood); |
| if (!channelsAreGood) |
| return; |
| |
| for (unsigned i = 0; i < numberOfInputChannels; i++) |
| m_internalInputBus->setChannelMemory(i, inputBuffer->rawChannelData(i) + m_bufferReadWriteIndex, framesToProcess); |
| |
| if (numberOfInputChannels) |
| m_internalInputBus->copyFrom(*inputBus); |
| |
| // Copy from the output buffer to the output. |
| for (unsigned i = 0; i < numberOfOutputChannels; ++i) |
| memcpy(outputBus->channel(i)->mutableData(), outputBuffer->rawChannelData(i) + m_bufferReadWriteIndex, sizeof(float) * framesToProcess); |
| |
| // Update the buffering index. |
| m_bufferReadWriteIndex = (m_bufferReadWriteIndex + framesToProcess) % bufferSize(); |
| |
| // m_bufferReadWriteIndex will wrap back around to 0 when the current input and output buffers are full. |
| // When this happens, fire an event and swap buffers. |
| if (!m_bufferReadWriteIndex) { |
| // Heap allocations are forbidden on the audio thread for performance reasons so we need to |
| // explicitly allow the following allocation(s). |
| DisableMallocRestrictionsForCurrentThreadScope disableMallocRestrictions; |
| |
| // Reference ourself so we don't accidentally get deleted before fireProcessEvent() gets called. |
| // We only wait for script code execution when the context is an offline one for performance reasons. |
| if (context().isOfflineContext()) { |
| callOnMainThreadAndWait([this, bufferIndex, protector = Ref { *this }] { |
| fireProcessEvent(bufferIndex); |
| }); |
| } else { |
| callOnMainThread([this, bufferIndex, protector = Ref { *this }] { |
| Locker locker { m_bufferLocks[bufferIndex] }; |
| fireProcessEvent(bufferIndex); |
| }); |
| } |
| |
| swapBuffers(); |
| } |
| } |
| |
| void ScriptProcessorNode::fireProcessEvent(unsigned bufferIndex) |
| { |
| ASSERT(isMainThread()); |
| |
| AudioBuffer* inputBuffer = m_inputBuffers[bufferIndex].get(); |
| AudioBuffer* outputBuffer = m_outputBuffers[bufferIndex].get(); |
| ASSERT(outputBuffer); |
| if (!outputBuffer) |
| return; |
| |
| // Avoid firing the event if the document has already gone away. |
| if (context().isStopped()) |
| return; |
| |
| // Calculate playbackTime with the buffersize which needs to be processed each time when onaudioprocess is called. |
| // The outputBuffer being passed to JS will be played after exhausting previous outputBuffer by double-buffering. |
| double playbackTime = (context().currentSampleFrame() + m_bufferSize) / static_cast<double>(context().sampleRate()); |
| |
| auto inputBufferForJS = createInputBufferForJS(inputBuffer); |
| auto outputBufferForJS = createOutputBufferForJS(*outputBuffer); |
| |
| // Call the JavaScript event handler which will do the audio processing. |
| dispatchEvent(AudioProcessingEvent::create(inputBufferForJS.get(), outputBufferForJS.get(), playbackTime)); |
| |
| if (!outputBufferForJS->copyTo(*outputBuffer)) |
| outputBuffer->zero(); |
| } |
| |
| ExceptionOr<void> ScriptProcessorNode::setChannelCount(unsigned channelCount) |
| { |
| ASSERT(isMainThread()); |
| |
| if (channelCount != this->channelCount()) |
| return Exception { IndexSizeError, "ScriptProcessorNode's channelCount cannot be changed"_s }; |
| return { }; |
| } |
| |
| ExceptionOr<void> ScriptProcessorNode::setChannelCountMode(ChannelCountMode mode) |
| { |
| ASSERT(isMainThread()); |
| |
| if (mode != this->channelCountMode()) |
| return Exception { NotSupportedError, "ScriptProcessorNode's channelCountMode cannot be changed from 'explicit'"_s }; |
| |
| return { }; |
| } |
| |
| double ScriptProcessorNode::tailTime() const |
| { |
| return std::numeric_limits<double>::infinity(); |
| } |
| |
| double ScriptProcessorNode::latencyTime() const |
| { |
| return std::numeric_limits<double>::infinity(); |
| } |
| |
| bool ScriptProcessorNode::requiresTailProcessing() const |
| { |
| // Always return true since the tail and latency are never zero. |
| return true; |
| } |
| |
| void ScriptProcessorNode::eventListenersDidChange() |
| { |
| m_hasAudioProcessEventListener = hasEventListeners(eventNames().audioprocessEvent); |
| } |
| |
| bool ScriptProcessorNode::virtualHasPendingActivity() const |
| { |
| if (context().isClosed()) |
| return false; |
| |
| return m_hasAudioProcessEventListener; |
| } |
| |
| } // namespace WebCore |
| |
| #endif // ENABLE(WEB_AUDIO) |