| /* |
| * 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 "modules/webaudio/ScriptProcessorNode.h" |
| #include "bindings/core/v8/ExceptionState.h" |
| #include "core/dom/ExceptionCode.h" |
| #include "core/dom/ExecutionContext.h" |
| #include "core/dom/TaskRunnerHelper.h" |
| #include "modules/webaudio/AudioBuffer.h" |
| #include "modules/webaudio/AudioNodeInput.h" |
| #include "modules/webaudio/AudioNodeOutput.h" |
| #include "modules/webaudio/AudioProcessingEvent.h" |
| #include "modules/webaudio/BaseAudioContext.h" |
| #include "platform/CrossThreadFunctional.h" |
| #include "platform/WaitableEvent.h" |
| #include "public/platform/Platform.h" |
| |
| namespace blink { |
| |
| ScriptProcessorHandler::ScriptProcessorHandler( |
| AudioNode& node, |
| float sample_rate, |
| size_t buffer_size, |
| unsigned number_of_input_channels, |
| unsigned number_of_output_channels) |
| : AudioHandler(kNodeTypeScriptProcessor, node, sample_rate), |
| double_buffer_index_(0), |
| buffer_size_(buffer_size), |
| buffer_read_write_index_(0), |
| number_of_input_channels_(number_of_input_channels), |
| number_of_output_channels_(number_of_output_channels), |
| internal_input_bus_(AudioBus::Create(number_of_input_channels, |
| AudioUtilities::kRenderQuantumFrames, |
| false)) { |
| // Regardless of the allowed buffer sizes, we still need to process at the |
| // granularity of the AudioNode. |
| if (buffer_size_ < AudioUtilities::kRenderQuantumFrames) |
| buffer_size_ = AudioUtilities::kRenderQuantumFrames; |
| |
| DCHECK_LE(number_of_input_channels, BaseAudioContext::MaxNumberOfChannels()); |
| |
| AddInput(); |
| AddOutput(number_of_output_channels); |
| |
| channel_count_ = number_of_input_channels; |
| SetInternalChannelCountMode(kExplicit); |
| |
| Initialize(); |
| } |
| |
| PassRefPtr<ScriptProcessorHandler> ScriptProcessorHandler::Create( |
| AudioNode& node, |
| float sample_rate, |
| size_t buffer_size, |
| unsigned number_of_input_channels, |
| unsigned number_of_output_channels) { |
| return AdoptRef(new ScriptProcessorHandler(node, sample_rate, buffer_size, |
| number_of_input_channels, |
| number_of_output_channels)); |
| } |
| |
| ScriptProcessorHandler::~ScriptProcessorHandler() { |
| Uninitialize(); |
| } |
| |
| void ScriptProcessorHandler::Initialize() { |
| if (IsInitialized()) |
| return; |
| |
| float sample_rate = 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 < 2; ++i) { |
| AudioBuffer* input_buffer = |
| number_of_input_channels_ |
| ? AudioBuffer::Create(number_of_input_channels_, BufferSize(), |
| sample_rate) |
| : 0; |
| AudioBuffer* output_buffer = |
| number_of_output_channels_ |
| ? AudioBuffer::Create(number_of_output_channels_, BufferSize(), |
| sample_rate) |
| : 0; |
| |
| input_buffers_.push_back(input_buffer); |
| output_buffers_.push_back(output_buffer); |
| } |
| |
| AudioHandler::Initialize(); |
| } |
| |
| void ScriptProcessorHandler::Process(size_t frames_to_process) { |
| // 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 which is exposed directly to |
| // JavaScript (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. |
| |
| // Get input and output busses. |
| AudioBus* input_bus = Input(0).Bus(); |
| AudioBus* output_bus = Output(0).Bus(); |
| |
| // Get input and output buffers. We double-buffer both the input and output |
| // sides. |
| unsigned double_buffer_index = this->DoubleBufferIndex(); |
| bool is_double_buffer_index_good = |
| double_buffer_index < 2 && double_buffer_index < input_buffers_.size() && |
| double_buffer_index < output_buffers_.size(); |
| DCHECK(is_double_buffer_index_good); |
| if (!is_double_buffer_index_good) |
| return; |
| |
| AudioBuffer* input_buffer = input_buffers_[double_buffer_index].Get(); |
| AudioBuffer* output_buffer = output_buffers_[double_buffer_index].Get(); |
| |
| // Check the consistency of input and output buffers. |
| unsigned number_of_input_channels = internal_input_bus_->NumberOfChannels(); |
| bool buffers_are_good = |
| output_buffer && BufferSize() == output_buffer->length() && |
| buffer_read_write_index_ + frames_to_process <= BufferSize(); |
| |
| // If the number of input channels is zero, it's ok to have inputBuffer = 0. |
| if (internal_input_bus_->NumberOfChannels()) |
| buffers_are_good = buffers_are_good && input_buffer && |
| BufferSize() == input_buffer->length(); |
| |
| DCHECK(buffers_are_good); |
| if (!buffers_are_good) |
| return; |
| |
| // We assume that bufferSize() is evenly divisible by framesToProcess - should |
| // always be true, but we should still check. |
| bool is_frames_to_process_good = frames_to_process && |
| BufferSize() >= frames_to_process && |
| !(BufferSize() % frames_to_process); |
| DCHECK(is_frames_to_process_good); |
| if (!is_frames_to_process_good) |
| return; |
| |
| unsigned number_of_output_channels = output_bus->NumberOfChannels(); |
| |
| bool channels_are_good = |
| (number_of_input_channels == number_of_input_channels_) && |
| (number_of_output_channels == number_of_output_channels_); |
| DCHECK(channels_are_good); |
| if (!channels_are_good) |
| return; |
| |
| for (unsigned i = 0; i < number_of_input_channels; ++i) |
| internal_input_bus_->SetChannelMemory( |
| i, |
| input_buffer->getChannelData(i).View()->Data() + |
| buffer_read_write_index_, |
| frames_to_process); |
| |
| if (number_of_input_channels) |
| internal_input_bus_->CopyFrom(*input_bus); |
| |
| // Copy from the output buffer to the output. |
| for (unsigned i = 0; i < number_of_output_channels; ++i) { |
| memcpy(output_bus->Channel(i)->MutableData(), |
| output_buffer->getChannelData(i).View()->Data() + |
| buffer_read_write_index_, |
| sizeof(float) * frames_to_process); |
| } |
| |
| // Update the buffering index. |
| buffer_read_write_index_ = |
| (buffer_read_write_index_ + frames_to_process) % 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 (!buffer_read_write_index_) { |
| // Avoid building up requests on the main thread to fire process events when |
| // they're not being handled. This could be a problem if the main thread is |
| // very busy doing other things and is being held up handling previous |
| // requests. The audio thread can't block on this lock, so we call |
| // tryLock() instead. |
| MutexTryLocker try_locker(process_event_lock_); |
| if (!try_locker.Locked()) { |
| // 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. |
| output_buffer->Zero(); |
| } else if (Context()->GetExecutionContext()) { |
| // With the realtime context, execute the script code asynchronously |
| // and do not wait. |
| if (Context()->HasRealtimeConstraint()) { |
| // Fire the event on the main thread with the appropriate buffer |
| // index. |
| TaskRunnerHelper::Get(TaskType::kMediaElementEvent, |
| Context()->GetExecutionContext()) |
| ->PostTask( |
| BLINK_FROM_HERE, |
| CrossThreadBind(&ScriptProcessorHandler::FireProcessEvent, |
| RefPtr<ScriptProcessorHandler>(this), |
| double_buffer_index_)); |
| } else { |
| // If this node is in the offline audio context, use the |
| // waitable event to synchronize to the offline rendering thread. |
| std::unique_ptr<WaitableEvent> waitable_event = |
| WTF::MakeUnique<WaitableEvent>(); |
| |
| TaskRunnerHelper::Get(TaskType::kMediaElementEvent, |
| Context()->GetExecutionContext()) |
| ->PostTask( |
| BLINK_FROM_HERE, |
| CrossThreadBind(&ScriptProcessorHandler:: |
| FireProcessEventForOfflineAudioContext, |
| RefPtr<ScriptProcessorHandler>(this), |
| double_buffer_index_, |
| CrossThreadUnretained(waitable_event.get()))); |
| |
| // Okay to block the offline audio rendering thread since it is |
| // not the actual audio device thread. |
| waitable_event->Wait(); |
| } |
| } |
| |
| SwapBuffers(); |
| } |
| } |
| |
| void ScriptProcessorHandler::FireProcessEvent(unsigned double_buffer_index) { |
| DCHECK(IsMainThread()); |
| |
| DCHECK_LT(double_buffer_index, 2u); |
| if (double_buffer_index > 1) |
| return; |
| |
| AudioBuffer* input_buffer = input_buffers_[double_buffer_index].Get(); |
| AudioBuffer* output_buffer = output_buffers_[double_buffer_index].Get(); |
| DCHECK(output_buffer); |
| if (!output_buffer) |
| return; |
| |
| // Avoid firing the event if the document has already gone away. |
| if (GetNode() && Context() && Context()->GetExecutionContext()) { |
| // This synchronizes with process(). |
| MutexLocker process_locker(process_event_lock_); |
| |
| // Calculate a playbackTime with the buffersize which needs to be processed |
| // each time onaudioprocess is called. The outputBuffer being passed to JS |
| // will be played after exhuasting previous outputBuffer by |
| // double-buffering. |
| double playback_time = (Context()->CurrentSampleFrame() + buffer_size_) / |
| static_cast<double>(Context()->sampleRate()); |
| |
| // Call the JavaScript event handler which will do the audio processing. |
| GetNode()->DispatchEvent(AudioProcessingEvent::Create( |
| input_buffer, output_buffer, playback_time)); |
| } |
| } |
| |
| void ScriptProcessorHandler::FireProcessEventForOfflineAudioContext( |
| unsigned double_buffer_index, |
| WaitableEvent* waitable_event) { |
| DCHECK(IsMainThread()); |
| |
| DCHECK_LT(double_buffer_index, 2u); |
| if (double_buffer_index > 1) { |
| waitable_event->Signal(); |
| return; |
| } |
| |
| AudioBuffer* input_buffer = input_buffers_[double_buffer_index].Get(); |
| AudioBuffer* output_buffer = output_buffers_[double_buffer_index].Get(); |
| DCHECK(output_buffer); |
| if (!output_buffer) { |
| waitable_event->Signal(); |
| return; |
| } |
| |
| if (GetNode() && Context() && Context()->GetExecutionContext()) { |
| // We do not need a process lock here because the offline render thread |
| // is locked by the waitable event. |
| double playback_time = (Context()->CurrentSampleFrame() + buffer_size_) / |
| static_cast<double>(Context()->sampleRate()); |
| GetNode()->DispatchEvent(AudioProcessingEvent::Create( |
| input_buffer, output_buffer, playback_time)); |
| } |
| |
| waitable_event->Signal(); |
| } |
| |
| double ScriptProcessorHandler::TailTime() const { |
| return std::numeric_limits<double>::infinity(); |
| } |
| |
| double ScriptProcessorHandler::LatencyTime() const { |
| return std::numeric_limits<double>::infinity(); |
| } |
| |
| void ScriptProcessorHandler::SetChannelCount(unsigned long channel_count, |
| ExceptionState& exception_state) { |
| DCHECK(IsMainThread()); |
| BaseAudioContext::AutoLocker locker(Context()); |
| |
| if (channel_count != channel_count_) { |
| exception_state.ThrowDOMException( |
| kNotSupportedError, "channelCount cannot be changed from " + |
| String::Number(channel_count_) + " to " + |
| String::Number(channel_count)); |
| } |
| } |
| |
| void ScriptProcessorHandler::SetChannelCountMode( |
| const String& mode, |
| ExceptionState& exception_state) { |
| DCHECK(IsMainThread()); |
| BaseAudioContext::AutoLocker locker(Context()); |
| |
| if ((mode == "max") || (mode == "clamped-max")) { |
| exception_state.ThrowDOMException( |
| kNotSupportedError, |
| "channelCountMode cannot be changed from 'explicit' to '" + mode + "'"); |
| } |
| } |
| |
| // ---------------------------------------------------------------- |
| |
| ScriptProcessorNode::ScriptProcessorNode(BaseAudioContext& context, |
| float sample_rate, |
| size_t buffer_size, |
| unsigned number_of_input_channels, |
| unsigned number_of_output_channels) |
| : AudioNode(context) { |
| SetHandler(ScriptProcessorHandler::Create(*this, sample_rate, buffer_size, |
| number_of_input_channels, |
| number_of_output_channels)); |
| } |
| |
| static size_t ChooseBufferSize(size_t callback_buffer_size) { |
| // Choose a buffer size based on the audio hardware buffer size. Arbitarily |
| // make it a power of two that is 4 times greater than the hardware buffer |
| // size. |
| // FIXME: What is the best way to choose this? |
| size_t buffer_size = |
| 1 << static_cast<unsigned>(log2(4 * callback_buffer_size) + 0.5); |
| |
| if (buffer_size < 256) |
| return 256; |
| if (buffer_size > 16384) |
| return 16384; |
| |
| return buffer_size; |
| } |
| |
| ScriptProcessorNode* ScriptProcessorNode::Create( |
| BaseAudioContext& context, |
| ExceptionState& exception_state) { |
| DCHECK(IsMainThread()); |
| |
| // Default buffer size is 0 (let WebAudio choose) with 2 inputs and 2 |
| // outputs. |
| return Create(context, 0, 2, 2, exception_state); |
| } |
| |
| ScriptProcessorNode* ScriptProcessorNode::Create( |
| BaseAudioContext& context, |
| size_t buffer_size, |
| ExceptionState& exception_state) { |
| DCHECK(IsMainThread()); |
| |
| // Default is 2 inputs and 2 outputs. |
| return Create(context, buffer_size, 2, 2, exception_state); |
| } |
| |
| ScriptProcessorNode* ScriptProcessorNode::Create( |
| BaseAudioContext& context, |
| size_t buffer_size, |
| unsigned number_of_input_channels, |
| ExceptionState& exception_state) { |
| DCHECK(IsMainThread()); |
| |
| // Default is 2 outputs. |
| return Create(context, buffer_size, number_of_input_channels, 2, |
| exception_state); |
| } |
| |
| ScriptProcessorNode* ScriptProcessorNode::Create( |
| BaseAudioContext& context, |
| size_t buffer_size, |
| unsigned number_of_input_channels, |
| unsigned number_of_output_channels, |
| ExceptionState& exception_state) { |
| DCHECK(IsMainThread()); |
| |
| if (context.IsContextClosed()) { |
| context.ThrowExceptionForClosedState(exception_state); |
| return nullptr; |
| } |
| |
| if (number_of_input_channels == 0 && number_of_output_channels == 0) { |
| exception_state.ThrowDOMException( |
| kIndexSizeError, |
| "number of input channels and output channels cannot both be zero."); |
| return nullptr; |
| } |
| |
| if (number_of_input_channels > BaseAudioContext::MaxNumberOfChannels()) { |
| exception_state.ThrowDOMException( |
| kIndexSizeError, |
| "number of input channels (" + |
| String::Number(number_of_input_channels) + ") exceeds maximum (" + |
| String::Number(BaseAudioContext::MaxNumberOfChannels()) + ")."); |
| return nullptr; |
| } |
| |
| if (number_of_output_channels > BaseAudioContext::MaxNumberOfChannels()) { |
| exception_state.ThrowDOMException( |
| kIndexSizeError, |
| "number of output channels (" + |
| String::Number(number_of_output_channels) + ") exceeds maximum (" + |
| String::Number(BaseAudioContext::MaxNumberOfChannels()) + ")."); |
| return nullptr; |
| } |
| |
| // Check for valid buffer size. |
| switch (buffer_size) { |
| case 0: |
| // Choose an appropriate size. For an AudioContext, we need to |
| // choose an appropriate size based on the callback buffer size. |
| // For OfflineAudioContext, there's no callback buffer size, so |
| // just use the minimum valid buffer size. |
| buffer_size = |
| context.HasRealtimeConstraint() |
| ? ChooseBufferSize(context.destination()->CallbackBufferSize()) |
| : 256; |
| break; |
| case 256: |
| case 512: |
| case 1024: |
| case 2048: |
| case 4096: |
| case 8192: |
| case 16384: |
| break; |
| default: |
| exception_state.ThrowDOMException( |
| kIndexSizeError, |
| "buffer size (" + String::Number(buffer_size) + |
| ") must be 0 or a power of two between 256 and 16384."); |
| return nullptr; |
| } |
| |
| ScriptProcessorNode* node = new ScriptProcessorNode( |
| context, context.sampleRate(), buffer_size, number_of_input_channels, |
| number_of_output_channels); |
| |
| if (!node) |
| return nullptr; |
| |
| // context keeps reference until we stop making javascript rendering callbacks |
| context.NotifySourceNodeStartedProcessing(node); |
| |
| return node; |
| } |
| |
| size_t ScriptProcessorNode::bufferSize() const { |
| return static_cast<ScriptProcessorHandler&>(Handler()).BufferSize(); |
| } |
| |
| bool ScriptProcessorNode::HasPendingActivity() const { |
| // To prevent the node from leaking after the context is closed. |
| if (context()->IsContextClosed()) |
| return false; |
| |
| // If |onaudioprocess| event handler is defined, the node should not be |
| // GCed even if it is out of scope. |
| if (HasEventListeners(EventTypeNames::audioprocess)) |
| return true; |
| |
| return false; |
| } |
| |
| } // namespace blink |