blob: b3b4fcf7dc874839228b89bf9a2474604b7a6332 [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 "third_party/blink/renderer/modules/webaudio/audio_node.h"
#include <inttypes.h>
#include "third_party/blink/public/platform/modules/webrtc/webrtc_logging.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_audio_node_options.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_channel_count_mode.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_channel_interpretation.h"
#include "third_party/blink/renderer/modules/webaudio/audio_graph_tracer.h"
#include "third_party/blink/renderer/modules/webaudio/audio_handler.h"
#include "third_party/blink/renderer/modules/webaudio/audio_node_output.h"
#include "third_party/blink/renderer/modules/webaudio/audio_node_wiring.h"
#include "third_party/blink/renderer/modules/webaudio/audio_param.h"
#include "third_party/blink/renderer/modules/webaudio/base_audio_context.h"
#include "third_party/blink/renderer/modules/webaudio/script_processor_handler.h"
#include "third_party/blink/renderer/platform/bindings/exception_messages.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/wtf/text/strcat.h"
#if DEBUG_AUDIONODE_REFERENCES
#include <stdio.h>
#endif
namespace blink {
AudioNode::AudioNode(BaseAudioContext& context)
: InspectorHelperMixin(context.GraphTracer(), context.Uuid()),
context_(context),
deferred_task_handler_(&context.GetDeferredTaskHandler()),
handler_(nullptr) {}
AudioNode::~AudioNode() {
// The graph lock is required to destroy the handler. And we can't use
// `context_` to touch it, since that object may also be a dead heap object.
{
DeferredTaskHandler::GraphAutoLocker locker(*deferred_task_handler_);
handler_ = nullptr;
}
}
void AudioNode::Dispose() {
DCHECK(IsMainThread());
#if DEBUG_AUDIONODE_REFERENCES
fprintf(stderr, "[%16p]: %16p: %2d: AudioNode::dispose %16p @%g\n", context(),
this, Handler().GetNodeType(), handler_.get(),
context()->currentTime());
#endif
DeferredTaskHandler::GraphAutoLocker locker(context());
Handler().Dispose();
// Add the handler to the orphan list. This keeps the handler alive until it
// can be deleted at a safe point (in pre/post handler task). If the graph is
// being processed, the handler must be added. If the context is suspended,
// the handler still needs to be added in case the context is resumed.
DCHECK(context());
if (context()->IsPullingAudioGraph() ||
context()->ContextState() == V8AudioContextState::Enum::kSuspended) {
context()->GetDeferredTaskHandler().AddRenderingOrphanHandler(
std::move(handler_));
}
// Notify the inspector that this node is going away. The actual clean up
// will be done in the subclass implementation.
ReportWillBeDestroyed();
}
void AudioNode::SetHandler(scoped_refptr<AudioHandler> handler) {
DCHECK(handler);
handler_ = std::move(handler);
// Unless the node is an AudioDestinationNode, notify the inspector that the
// construction is completed. The actual report will be done in the subclass
// implementation. (A destination node is owned by the context and will be
// reported by it.)
if (handler_->GetNodeType() != AudioHandler::NodeType::kNodeTypeDestination) {
ReportDidCreate();
}
#if DEBUG_AUDIONODE_REFERENCES
fprintf(stderr, "[%16p]: %16p: %2d: AudioNode::AudioNode %16p\n", context(),
this, handler_->GetNodeType(), handler_.get());
#endif
}
bool AudioNode::ContainsHandler() const {
return handler_.get();
}
AudioHandler& AudioNode::Handler() const {
return *handler_;
}
void AudioNode::Trace(Visitor* visitor) const {
visitor->Trace(context_);
visitor->Trace(connected_nodes_);
visitor->Trace(connected_params_);
InspectorHelperMixin::Trace(visitor);
EventTarget::Trace(visitor);
}
void AudioNode::HandleChannelOptions(const AudioNodeOptions* options,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
if (options->hasChannelCount()) {
setChannelCount(options->channelCount(), exception_state);
}
if (options->hasChannelCountMode()) {
setChannelCountMode(options->channelCountMode(), exception_state);
}
if (options->hasChannelInterpretation()) {
setChannelInterpretation(options->channelInterpretation(), exception_state);
}
}
String AudioNode::GetNodeName() const {
return Handler().NodeTypeName();
}
BaseAudioContext* AudioNode::context() const {
return context_.Get();
}
AudioNode* AudioNode::connect(AudioNode* destination,
unsigned output_index,
unsigned input_index,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
DeferredTaskHandler::GraphAutoLocker locker(context());
context()->WarnForConnectionIfContextClosed();
if (!destination) {
exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError,
"invalid destination node.");
return nullptr;
}
// Sanity check input and output indices.
if (output_index >= numberOfOutputs()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
StrCat({"output index (", String::Number(output_index),
") exceeds number of outputs (",
String::Number(numberOfOutputs()), ")."}));
return nullptr;
}
if (destination && input_index >= destination->numberOfInputs()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
StrCat({"input index (", String::Number(input_index),
") exceeds number of inputs (",
String::Number(destination->numberOfInputs()), ")."}));
return nullptr;
}
if (context() != destination->context()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidAccessError,
"cannot connect to an AudioNode "
"belonging to a different audio context.");
return nullptr;
}
// ScriptProcessorNodes with 0 output channels can't be connected to any
// destination. If there are no output channels, what would the destination
// receive? Just disallow this.
if (Handler().GetNodeType() ==
AudioHandler::NodeType::kNodeTypeScriptProcessor &&
static_cast<ScriptProcessorHandler&>(Handler())
.NumberOfOutputChannels() == 0) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidAccessError,
"cannot connect a ScriptProcessorNode "
"with 0 output channels to any "
"destination node.");
return nullptr;
}
SendLogMessage(
__func__, String::Format(
"({output=[index:%u, type:%s, handler:0x%" PRIXPTR "]} --> "
"{input=[index:%u, type:%s, handler:0x%" PRIXPTR "]})",
output_index, Handler().NodeTypeName().Utf8().c_str(),
reinterpret_cast<uintptr_t>(&Handler()), input_index,
destination->Handler().NodeTypeName().Utf8().c_str(),
reinterpret_cast<uintptr_t>(&destination->Handler())));
// Once the destination node is connected, the source node (e.g.,
// MediaElementAudioSourceNode) can eventually disable the MediaElement's
// audio output to the device.
ConnectToDestinationReady();
AudioNodeWiring::Connect(Handler().Output(output_index),
destination->Handler().Input(input_index));
if (!connected_nodes_[output_index]) {
connected_nodes_[output_index] =
MakeGarbageCollected<GCedHeapHashSet<Member<AudioNode>>>();
}
connected_nodes_[output_index]->insert(destination);
Handler().UpdatePullStatusIfNeeded();
GraphTracer().DidConnectNodes(this, destination, output_index, input_index);
return destination;
}
void AudioNode::connect(AudioParam* param,
unsigned output_index,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
DeferredTaskHandler::GraphAutoLocker locker(context());
context()->WarnForConnectionIfContextClosed();
if (!param) {
exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError,
"invalid AudioParam.");
return;
}
if (output_index >= numberOfOutputs()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
StrCat({"output index (", String::Number(output_index),
") exceeds number of outputs (",
String::Number(numberOfOutputs()), ")."}));
return;
}
if (context() != param->Context()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidAccessError,
"cannot connect to an AudioParam "
"belonging to a different audio context.");
return;
}
// Once the destination node is connected, the source node (e.g.,
// MediaElementAudioSourceNode) can eventually disable the MediaElement's
// audio output to the device.
ConnectToDestinationReady();
AudioNodeWiring::Connect(Handler().Output(output_index), param->Handler());
if (!connected_params_[output_index]) {
connected_params_[output_index] =
MakeGarbageCollected<GCedHeapHashSet<Member<AudioParam>>>();
}
connected_params_[output_index]->insert(param);
Handler().UpdatePullStatusIfNeeded();
GraphTracer().DidConnectNodeParam(this, param, output_index);
}
void AudioNode::DisconnectAllFromOutput(unsigned output_index) {
Handler().Output(output_index).DisconnectAll();
connected_nodes_[output_index] = nullptr;
connected_params_[output_index] = nullptr;
}
bool AudioNode::DisconnectFromOutputIfConnected(
unsigned output_index,
AudioNode& destination,
unsigned input_index_of_destination) {
AudioNodeOutput& output = Handler().Output(output_index);
AudioNodeInput& input =
destination.Handler().Input(input_index_of_destination);
if (!AudioNodeWiring::IsConnected(output, input)) {
return false;
}
AudioNodeWiring::Disconnect(output, input);
connected_nodes_[output_index]->erase(&destination);
return true;
}
bool AudioNode::DisconnectFromOutputIfConnected(unsigned output_index,
AudioParam& param) {
AudioNodeOutput& output = Handler().Output(output_index);
if (!AudioNodeWiring::IsConnected(output, param.Handler())) {
return false;
}
AudioNodeWiring::Disconnect(output, param.Handler());
connected_params_[output_index]->erase(&param);
return true;
}
void AudioNode::disconnect() {
DCHECK(IsMainThread());
DeferredTaskHandler::GraphAutoLocker locker(context());
// Disconnect all outgoing connections.
for (unsigned i = 0; i < numberOfOutputs(); ++i) {
DisconnectAllFromOutput(i);
}
Handler().UpdatePullStatusIfNeeded();
GraphTracer().DidDisconnectNodes(this);
}
void AudioNode::disconnect(unsigned output_index,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
DeferredTaskHandler::GraphAutoLocker locker(context());
// Sanity check on the output index.
if (output_index >= numberOfOutputs()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
ExceptionMessages::IndexOutsideRange(
"output index", output_index, 0u,
ExceptionMessages::kInclusiveBound, numberOfOutputs() - 1,
ExceptionMessages::kInclusiveBound));
return;
}
// Disconnect all outgoing connections from the given output.
DisconnectAllFromOutput(output_index);
Handler().UpdatePullStatusIfNeeded();
GraphTracer().DidDisconnectNodes(this, nullptr, output_index);
}
void AudioNode::disconnect(AudioNode* destination,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
if (context() != destination->context()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidAccessError,
"cannot disconnect from an AudioNode "
"belonging to a different audio context.");
return;
}
DeferredTaskHandler::GraphAutoLocker locker(context());
unsigned number_of_disconnections = 0;
// FIXME: Can this be optimized? ChannelSplitter and ChannelMerger can have
// 32 ports and that requires 1024 iterations to validate entire connections.
for (unsigned output_index = 0; output_index < numberOfOutputs();
++output_index) {
for (unsigned input_index = 0;
input_index < destination->Handler().NumberOfInputs(); ++input_index) {
if (DisconnectFromOutputIfConnected(output_index, *destination,
input_index)) {
number_of_disconnections++;
}
}
}
// If there is no connection to the destination, throw an exception.
if (number_of_disconnections == 0) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidAccessError,
"the given destination is not connected.");
return;
}
Handler().UpdatePullStatusIfNeeded();
GraphTracer().DidDisconnectNodes(this, destination);
}
void AudioNode::disconnect(AudioNode* destination,
unsigned output_index,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
if (context() != destination->context()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidAccessError,
"cannot disconnect from an AudioNode "
"belonging to a different audio context.");
return;
}
DeferredTaskHandler::GraphAutoLocker locker(context());
if (output_index >= numberOfOutputs()) {
// The output index is out of range. Throw an exception.
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
ExceptionMessages::IndexOutsideRange(
"output index", output_index, 0u,
ExceptionMessages::kInclusiveBound, numberOfOutputs() - 1,
ExceptionMessages::kInclusiveBound));
return;
}
// If the output index is valid, proceed to disconnect.
unsigned number_of_disconnections = 0;
// Sanity check on destination inputs and disconnect when possible.
for (unsigned input_index = 0; input_index < destination->numberOfInputs();
++input_index) {
if (DisconnectFromOutputIfConnected(output_index, *destination,
input_index)) {
number_of_disconnections++;
}
}
// If there is no connection to the destination, throw an exception.
if (number_of_disconnections == 0) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidAccessError,
StrCat({"output (", String::Number(output_index),
") is not connected to the given destination."}));
}
Handler().UpdatePullStatusIfNeeded();
GraphTracer().DidDisconnectNodes(this, destination, output_index);
}
void AudioNode::disconnect(AudioNode* destination,
unsigned output_index,
unsigned input_index,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
if (context() != destination->context()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidAccessError,
"cannot disconnect from an AudioNode "
"belonging to a different audio context.");
return;
}
DeferredTaskHandler::GraphAutoLocker locker(context());
if (output_index >= numberOfOutputs()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
ExceptionMessages::IndexOutsideRange(
"output index", output_index, 0u,
ExceptionMessages::kInclusiveBound, numberOfOutputs() - 1,
ExceptionMessages::kInclusiveBound));
return;
}
if (input_index >= destination->Handler().NumberOfInputs()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
ExceptionMessages::IndexOutsideRange(
"input index", input_index, 0u, ExceptionMessages::kInclusiveBound,
destination->numberOfInputs() - 1,
ExceptionMessages::kInclusiveBound));
return;
}
// If both indices are valid, proceed to disconnect.
if (!DisconnectFromOutputIfConnected(output_index, *destination,
input_index)) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidAccessError,
StrCat({"output (", String::Number(output_index),
") is not connected to the input (",
String::Number(input_index), ") of the destination."}));
return;
}
Handler().UpdatePullStatusIfNeeded();
GraphTracer().DidDisconnectNodes(
this, destination, output_index, input_index);
}
void AudioNode::disconnect(AudioParam* destination_param,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
if (context() != destination_param->Context()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidAccessError,
"cannot disconnect from an AudioParam "
"belonging to a different audio context.");
return;
}
DeferredTaskHandler::GraphAutoLocker locker(context());
// The number of disconnection made.
unsigned number_of_disconnections = 0;
// Check if the node output is connected the destination AudioParam.
// Disconnect if connected and increase `number_of_disconnections` by 1.
for (unsigned output_index = 0; output_index < Handler().NumberOfOutputs();
++output_index) {
if (DisconnectFromOutputIfConnected(output_index, *destination_param)) {
number_of_disconnections++;
}
}
// Throw an exception when there is no valid connection to the destination.
if (number_of_disconnections == 0) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidAccessError,
"the given AudioParam is not connected.");
return;
}
Handler().UpdatePullStatusIfNeeded();
GraphTracer().DidDisconnectNodeParam(this, destination_param);
}
void AudioNode::disconnect(AudioParam* destination_param,
unsigned output_index,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
DeferredTaskHandler::GraphAutoLocker locker(context());
if (context() != destination_param->Context()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidAccessError,
"cannot disconnect from an AudioParam belonging to a different "
"BaseAudioContext.");
return;
}
if (output_index >= Handler().NumberOfOutputs()) {
// The output index is out of range. Throw an exception.
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
ExceptionMessages::IndexOutsideRange(
"output index", output_index, 0u,
ExceptionMessages::kInclusiveBound, numberOfOutputs() - 1,
ExceptionMessages::kInclusiveBound));
return;
}
// If the output index is valid, proceed to disconnect.
if (!DisconnectFromOutputIfConnected(output_index, *destination_param)) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidAccessError,
StrCat({"specified destination AudioParam and node output (",
String::Number(output_index), ") are not connected."}));
return;
}
Handler().UpdatePullStatusIfNeeded();
GraphTracer().DidDisconnectNodeParam(this, destination_param, output_index);
}
unsigned AudioNode::numberOfInputs() const {
return Handler().NumberOfInputs();
}
unsigned AudioNode::numberOfOutputs() const {
return Handler().NumberOfOutputs();
}
unsigned AudioNode::channelCount() const {
return Handler().ChannelCount();
}
void AudioNode::setChannelCount(unsigned count,
ExceptionState& exception_state) {
Handler().SetChannelCount(count, exception_state);
}
V8ChannelCountMode AudioNode::channelCountMode() const {
return V8ChannelCountMode(Handler().GetChannelCountMode());
}
void AudioNode::setChannelCountMode(const V8ChannelCountMode& mode,
ExceptionState& exception_state) {
Handler().SetChannelCountMode(mode.AsEnum(), exception_state);
}
V8ChannelInterpretation AudioNode::channelInterpretation() const {
return V8ChannelInterpretation(Handler().ChannelInterpretation());
}
void AudioNode::setChannelInterpretation(
const V8ChannelInterpretation& interpretation,
ExceptionState& exception_state) {
Handler().SetChannelInterpretation(interpretation.AsEnum(), exception_state);
}
const AtomicString& AudioNode::InterfaceName() const {
return event_target_names::kAudioNode;
}
ExecutionContext* AudioNode::GetExecutionContext() const {
return context()->GetExecutionContext();
}
void AudioNode::DidAddOutput(unsigned number_of_outputs) {
connected_nodes_.push_back(nullptr);
DCHECK_EQ(number_of_outputs, connected_nodes_.size());
connected_params_.push_back(nullptr);
DCHECK_EQ(number_of_outputs, connected_params_.size());
}
void AudioNode::SendLogMessage(const char* const function_name,
const String& message) {
WebRtcLogMessage(
String::Format("[WA]AN::%s %s", function_name, message.Utf8().c_str())
.Utf8());
}
} // namespace blink