diff options
Diffstat (limited to 'dom/media/webaudio/AudioNode.cpp')
-rw-r--r-- | dom/media/webaudio/AudioNode.cpp | 666 |
1 files changed, 666 insertions, 0 deletions
diff --git a/dom/media/webaudio/AudioNode.cpp b/dom/media/webaudio/AudioNode.cpp new file mode 100644 index 000000000..2b64fcf88 --- /dev/null +++ b/dom/media/webaudio/AudioNode.cpp @@ -0,0 +1,666 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AudioNode.h" +#include "mozilla/ErrorResult.h" +#include "AudioNodeStream.h" +#include "AudioNodeEngine.h" +#include "mozilla/dom/AudioParam.h" +#include "mozilla/Services.h" +#include "nsIObserverService.h" + +namespace mozilla { +namespace dom { + +static const uint32_t INVALID_PORT = 0xffffffff; +static uint32_t gId = 0; + +NS_IMPL_CYCLE_COLLECTION_CLASS(AudioNode) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(AudioNode, DOMEventTargetHelper) + tmp->DisconnectFromGraph(); + if (tmp->mContext) { + tmp->mContext->UnregisterNode(tmp); + } + NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputNodes) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputParams) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AudioNode, + DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputNodes) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputParams) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_ADDREF_INHERITED(AudioNode, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(AudioNode, DOMEventTargetHelper) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AudioNode) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +AudioNode::AudioNode(AudioContext* aContext, + uint32_t aChannelCount, + ChannelCountMode aChannelCountMode, + ChannelInterpretation aChannelInterpretation) + : DOMEventTargetHelper(aContext->GetParentObject()) + , mContext(aContext) + , mChannelCount(aChannelCount) + , mChannelCountMode(aChannelCountMode) + , mChannelInterpretation(aChannelInterpretation) + , mId(gId++) + , mPassThrough(false) +{ + MOZ_ASSERT(aContext); + DOMEventTargetHelper::BindToOwner(aContext->GetParentObject()); + aContext->RegisterNode(this); +} + +AudioNode::~AudioNode() +{ + MOZ_ASSERT(mInputNodes.IsEmpty()); + MOZ_ASSERT(mOutputNodes.IsEmpty()); + MOZ_ASSERT(mOutputParams.IsEmpty()); + MOZ_ASSERT(!mStream, + "The webaudio-node-demise notification must have been sent"); + if (mContext) { + mContext->UnregisterNode(this); + } +} + +size_t +AudioNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const +{ + // Not owned: + // - mContext + // - mStream + size_t amount = 0; + + amount += mInputNodes.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (size_t i = 0; i < mInputNodes.Length(); i++) { + amount += mInputNodes[i].SizeOfExcludingThis(aMallocSizeOf); + } + + // Just measure the array. The entire audio node graph is measured via the + // MediaStreamGraph's streams, so we don't want to double-count the elements. + amount += mOutputNodes.ShallowSizeOfExcludingThis(aMallocSizeOf); + + amount += mOutputParams.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (size_t i = 0; i < mOutputParams.Length(); i++) { + amount += mOutputParams[i]->SizeOfIncludingThis(aMallocSizeOf); + } + + return amount; +} + +size_t +AudioNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const +{ + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + +template <class InputNode> +static size_t +FindIndexOfNode(const nsTArray<InputNode>& aInputNodes, const AudioNode* aNode) +{ + for (size_t i = 0; i < aInputNodes.Length(); ++i) { + if (aInputNodes[i].mInputNode == aNode) { + return i; + } + } + return nsTArray<InputNode>::NoIndex; +} + +template <class InputNode> +static size_t +FindIndexOfNodeWithPorts(const nsTArray<InputNode>& aInputNodes, + const AudioNode* aNode, + uint32_t aInputPort, uint32_t aOutputPort) +{ + for (size_t i = 0; i < aInputNodes.Length(); ++i) { + if (aInputNodes[i].mInputNode == aNode && + aInputNodes[i].mInputPort == aInputPort && + aInputNodes[i].mOutputPort == aOutputPort) { + return i; + } + } + return nsTArray<InputNode>::NoIndex; +} + +void +AudioNode::DisconnectFromGraph() +{ + MOZ_ASSERT(mRefCnt.get() > mInputNodes.Length(), + "Caller should be holding a reference"); + + // The idea here is that we remove connections one by one, and at each step + // the graph is in a valid state. + + // Disconnect inputs. We don't need them anymore. + while (!mInputNodes.IsEmpty()) { + size_t i = mInputNodes.Length() - 1; + RefPtr<AudioNode> input = mInputNodes[i].mInputNode; + mInputNodes.RemoveElementAt(i); + input->mOutputNodes.RemoveElement(this); + } + + while (!mOutputNodes.IsEmpty()) { + size_t i = mOutputNodes.Length() - 1; + RefPtr<AudioNode> output = mOutputNodes[i].forget(); + mOutputNodes.RemoveElementAt(i); + size_t inputIndex = FindIndexOfNode(output->mInputNodes, this); + // It doesn't matter which one we remove, since we're going to remove all + // entries for this node anyway. + output->mInputNodes.RemoveElementAt(inputIndex); + // This effects of this connection will remain. + output->NotifyHasPhantomInput(); + } + + while (!mOutputParams.IsEmpty()) { + size_t i = mOutputParams.Length() - 1; + RefPtr<AudioParam> output = mOutputParams[i].forget(); + mOutputParams.RemoveElementAt(i); + size_t inputIndex = FindIndexOfNode(output->InputNodes(), this); + // It doesn't matter which one we remove, since we're going to remove all + // entries for this node anyway. + output->RemoveInputNode(inputIndex); + } + + DestroyMediaStream(); +} + +AudioNode* +AudioNode::Connect(AudioNode& aDestination, uint32_t aOutput, + uint32_t aInput, ErrorResult& aRv) +{ + if (aOutput >= NumberOfOutputs() || + aInput >= aDestination.NumberOfInputs()) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; + } + + if (Context() != aDestination.Context()) { + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return nullptr; + } + + if (FindIndexOfNodeWithPorts(aDestination.mInputNodes, + this, aInput, aOutput) != + nsTArray<AudioNode::InputNode>::NoIndex) { + // connection already exists. + return &aDestination; + } + + WEB_AUDIO_API_LOG("%f: %s %u Connect() to %s %u", + Context()->CurrentTime(), NodeType(), Id(), + aDestination.NodeType(), aDestination.Id()); + + // The MediaStreamGraph will handle cycle detection. We don't need to do it + // here. + + mOutputNodes.AppendElement(&aDestination); + InputNode* input = aDestination.mInputNodes.AppendElement(); + input->mInputNode = this; + input->mInputPort = aInput; + input->mOutputPort = aOutput; + AudioNodeStream* destinationStream = aDestination.mStream; + if (mStream && destinationStream) { + // Connect streams in the MediaStreamGraph + MOZ_ASSERT(aInput <= UINT16_MAX, "Unexpected large input port number"); + MOZ_ASSERT(aOutput <= UINT16_MAX, "Unexpected large output port number"); + input->mStreamPort = destinationStream-> + AllocateInputPort(mStream, AudioNodeStream::AUDIO_TRACK, TRACK_ANY, + static_cast<uint16_t>(aInput), + static_cast<uint16_t>(aOutput)); + } + aDestination.NotifyInputsChanged(); + + // This connection may have connected a panner and a source. + Context()->UpdatePannerSource(); + + return &aDestination; +} + +void +AudioNode::Connect(AudioParam& aDestination, uint32_t aOutput, + ErrorResult& aRv) +{ + if (aOutput >= NumberOfOutputs()) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + if (Context() != aDestination.GetParentObject()) { + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return; + } + + if (FindIndexOfNodeWithPorts(aDestination.InputNodes(), + this, INVALID_PORT, aOutput) != + nsTArray<AudioNode::InputNode>::NoIndex) { + // connection already exists. + return; + } + + mOutputParams.AppendElement(&aDestination); + InputNode* input = aDestination.AppendInputNode(); + input->mInputNode = this; + input->mInputPort = INVALID_PORT; + input->mOutputPort = aOutput; + + MediaStream* stream = aDestination.Stream(); + MOZ_ASSERT(stream->AsProcessedStream()); + ProcessedMediaStream* ps = static_cast<ProcessedMediaStream*>(stream); + if (mStream) { + // Setup our stream as an input to the AudioParam's stream + MOZ_ASSERT(aOutput <= UINT16_MAX, "Unexpected large output port number"); + input->mStreamPort = + ps->AllocateInputPort(mStream, AudioNodeStream::AUDIO_TRACK, TRACK_ANY, + 0, static_cast<uint16_t>(aOutput)); + } +} + +void +AudioNode::SendDoubleParameterToStream(uint32_t aIndex, double aValue) +{ + MOZ_ASSERT(mStream, "How come we don't have a stream here?"); + mStream->SetDoubleParameter(aIndex, aValue); +} + +void +AudioNode::SendInt32ParameterToStream(uint32_t aIndex, int32_t aValue) +{ + MOZ_ASSERT(mStream, "How come we don't have a stream here?"); + mStream->SetInt32Parameter(aIndex, aValue); +} + +void +AudioNode::SendThreeDPointParameterToStream(uint32_t aIndex, + const ThreeDPoint& aValue) +{ + MOZ_ASSERT(mStream, "How come we don't have a stream here?"); + mStream->SetThreeDPointParameter(aIndex, aValue); +} + +void +AudioNode::SendChannelMixingParametersToStream() +{ + if (mStream) { + mStream->SetChannelMixingParameters(mChannelCount, mChannelCountMode, + mChannelInterpretation); + } +} + +template<> +bool +AudioNode::DisconnectFromOutputIfConnected<AudioNode>(uint32_t aOutputNodeIndex, + uint32_t aInputIndex) +{ + WEB_AUDIO_API_LOG("%f: %s %u Disconnect()", Context()->CurrentTime(), + NodeType(), Id()); + + AudioNode* destination = mOutputNodes[aOutputNodeIndex]; + + MOZ_ASSERT(aOutputNodeIndex < mOutputNodes.Length()); + MOZ_ASSERT(aInputIndex < destination->InputNodes().Length()); + + // An upstream node may be starting to play on the graph thread, and the + // engine for a downstream node may be sending a PlayingRefChangeHandler + // ADDREF message to this (main) thread. Wait for a round trip before + // releasing nodes, to give engines receiving sound now time to keep their + // nodes alive. + class RunnableRelease final : public Runnable + { + public: + explicit RunnableRelease(already_AddRefed<AudioNode> aNode) + : mNode(aNode) {} + + NS_IMETHOD Run() override + { + mNode = nullptr; + return NS_OK; + } + private: + RefPtr<AudioNode> mNode; + }; + + InputNode& input = destination->mInputNodes[aInputIndex]; + if (input.mInputNode != this) { + return false; + } + + // Remove one instance of 'dest' from mOutputNodes. There could be + // others, and it's not correct to remove them all since some of them + // could be for different output ports. + RefPtr<AudioNode> output = mOutputNodes[aOutputNodeIndex].forget(); + mOutputNodes.RemoveElementAt(aOutputNodeIndex); + // Destroying the InputNode here sends a message to the graph thread + // to disconnect the streams, which should be sent before the + // RunAfterPendingUpdates() call below. + destination->mInputNodes.RemoveElementAt(aInputIndex); + output->NotifyInputsChanged(); + if (mStream) { + nsCOMPtr<nsIRunnable> runnable = new RunnableRelease(output.forget()); + mStream->RunAfterPendingUpdates(runnable.forget()); + } + return true; +} + +template<> +bool +AudioNode::DisconnectFromOutputIfConnected<AudioParam>(uint32_t aOutputParamIndex, + uint32_t aInputIndex) +{ + MOZ_ASSERT(aOutputParamIndex < mOutputParams.Length()); + + AudioParam* destination = mOutputParams[aOutputParamIndex]; + + MOZ_ASSERT(aInputIndex < destination->InputNodes().Length()); + + const InputNode& input = destination->InputNodes()[aInputIndex]; + if (input.mInputNode != this) { + return false; + } + destination->RemoveInputNode(aInputIndex); + // Remove one instance of 'dest' from mOutputParams. There could be + // others, and it's not correct to remove them all since some of them + // could be for different output ports. + mOutputParams.RemoveElementAt(aOutputParamIndex); + return true; +} + +template<> +const nsTArray<AudioNode::InputNode>& +AudioNode::InputsForDestination<AudioNode>(uint32_t aOutputNodeIndex) const { + return mOutputNodes[aOutputNodeIndex]->InputNodes(); +} + +template<> +const nsTArray<AudioNode::InputNode>& +AudioNode::InputsForDestination<AudioParam>(uint32_t aOutputNodeIndex) const { + return mOutputParams[aOutputNodeIndex]->InputNodes(); +} + +template<typename DestinationType, typename Predicate> +bool +AudioNode::DisconnectMatchingDestinationInputs(uint32_t aDestinationIndex, + Predicate aPredicate) +{ + bool wasConnected = false; + uint32_t inputCount = + InputsForDestination<DestinationType>(aDestinationIndex).Length(); + + for (int32_t inputIndex = inputCount - 1; inputIndex >= 0; --inputIndex) { + const InputNode& input = + InputsForDestination<DestinationType>(aDestinationIndex)[inputIndex]; + if (aPredicate(input)) { + if (DisconnectFromOutputIfConnected<DestinationType>(aDestinationIndex, + inputIndex)) { + wasConnected = true; + break; + } + } + } + return wasConnected; +} + +void +AudioNode::Disconnect(ErrorResult& aRv) +{ + for (int32_t outputIndex = mOutputNodes.Length() - 1; + outputIndex >= 0; --outputIndex) { + DisconnectMatchingDestinationInputs<AudioNode>(outputIndex, + [](const InputNode&) { + return true; + }); + } + + for (int32_t outputIndex = mOutputParams.Length() - 1; + outputIndex >= 0; --outputIndex) { + DisconnectMatchingDestinationInputs<AudioParam>(outputIndex, + [](const InputNode&) { + return true; + }); + } + + // This disconnection may have disconnected a panner and a source. + Context()->UpdatePannerSource(); +} + +void +AudioNode::Disconnect(uint32_t aOutput, ErrorResult& aRv) +{ + if (aOutput >= NumberOfOutputs()) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + for (int32_t outputIndex = mOutputNodes.Length() - 1; + outputIndex >= 0; --outputIndex) { + DisconnectMatchingDestinationInputs<AudioNode>( + outputIndex, + [aOutput](const InputNode& aInputNode) { + return aInputNode.mOutputPort == aOutput; + }); + } + + for (int32_t outputIndex = mOutputParams.Length() - 1; + outputIndex >= 0; --outputIndex) { + DisconnectMatchingDestinationInputs<AudioParam>( + outputIndex, + [aOutput](const InputNode& aInputNode) { + return aInputNode.mOutputPort == aOutput; + }); + } + + // This disconnection may have disconnected a panner and a source. + Context()->UpdatePannerSource(); +} + +void +AudioNode::Disconnect(AudioNode& aDestination, ErrorResult& aRv) +{ + bool wasConnected = false; + + for (int32_t outputIndex = mOutputNodes.Length() - 1; + outputIndex >= 0; --outputIndex) { + if (mOutputNodes[outputIndex] != &aDestination) { + continue; + } + wasConnected |= + DisconnectMatchingDestinationInputs<AudioNode>(outputIndex, + [](const InputNode&) { + return true; + }); + } + + if (!wasConnected) { + aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); + return; + } + + // This disconnection may have disconnected a panner and a source. + Context()->UpdatePannerSource(); +} + +void +AudioNode::Disconnect(AudioNode& aDestination, + uint32_t aOutput, + ErrorResult& aRv) +{ + if (aOutput >= NumberOfOutputs()) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + bool wasConnected = false; + + for (int32_t outputIndex = mOutputNodes.Length() - 1; + outputIndex >= 0; --outputIndex) { + if (mOutputNodes[outputIndex] != &aDestination) { + continue; + } + wasConnected |= + DisconnectMatchingDestinationInputs<AudioNode>( + outputIndex, + [aOutput](const InputNode& aInputNode) { + return aInputNode.mOutputPort == aOutput; + }); + } + + if (!wasConnected) { + aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); + return; + } + + // This disconnection may have disconnected a panner and a source. + Context()->UpdatePannerSource(); +} + +void +AudioNode::Disconnect(AudioNode& aDestination, + uint32_t aOutput, + uint32_t aInput, + ErrorResult& aRv) +{ + if (aOutput >= NumberOfOutputs()) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + if (aInput >= aDestination.NumberOfInputs()) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + bool wasConnected = false; + + for (int32_t outputIndex = mOutputNodes.Length() - 1; + outputIndex >= 0; --outputIndex) { + if (mOutputNodes[outputIndex] != &aDestination) { + continue; + } + wasConnected |= + DisconnectMatchingDestinationInputs<AudioNode>( + outputIndex, + [aOutput, aInput](const InputNode& aInputNode) { + return aInputNode.mOutputPort == aOutput && + aInputNode.mInputPort == aInput; + }); + } + + if (!wasConnected) { + aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); + return; + } + + // This disconnection may have disconnected a panner and a source. + Context()->UpdatePannerSource(); +} + +void +AudioNode::Disconnect(AudioParam& aDestination, ErrorResult& aRv) +{ + bool wasConnected = false; + + for (int32_t outputIndex = mOutputParams.Length() - 1; + outputIndex >= 0; --outputIndex) { + if (mOutputParams[outputIndex] != &aDestination) { + continue; + } + wasConnected |= + DisconnectMatchingDestinationInputs<AudioParam>(outputIndex, + [](const InputNode&) { + return true; + }); + } + + if (!wasConnected) { + aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); + return; + } +} + +void +AudioNode::Disconnect(AudioParam& aDestination, + uint32_t aOutput, + ErrorResult& aRv) +{ + if (aOutput >= NumberOfOutputs()) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + bool wasConnected = false; + + for (int32_t outputIndex = mOutputParams.Length() - 1; + outputIndex >= 0; --outputIndex) { + if (mOutputParams[outputIndex] != &aDestination) { + continue; + } + wasConnected |= + DisconnectMatchingDestinationInputs<AudioParam>( + outputIndex, + [aOutput](const InputNode& aInputNode) { + return aInputNode.mOutputPort == aOutput; + }); + } + + if (!wasConnected) { + aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); + return; + } +} + +void +AudioNode::DestroyMediaStream() +{ + if (mStream) { + // Remove the node pointer on the engine. + AudioNodeStream* ns = mStream; + MOZ_ASSERT(ns, "How come we don't have a stream here?"); + MOZ_ASSERT(ns->Engine()->NodeMainThread() == this, + "Invalid node reference"); + ns->Engine()->ClearNode(); + + mStream->Destroy(); + mStream = nullptr; + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + nsAutoString id; + id.AppendPrintf("%u", mId); + obs->NotifyObservers(nullptr, "webaudio-node-demise", id.get()); + } + } +} + +void +AudioNode::RemoveOutputParam(AudioParam* aParam) +{ + mOutputParams.RemoveElement(aParam); +} + +bool +AudioNode::PassThrough() const +{ + MOZ_ASSERT(NumberOfInputs() <= 1 && NumberOfOutputs() == 1); + return mPassThrough; +} + +void +AudioNode::SetPassThrough(bool aPassThrough) +{ + MOZ_ASSERT(NumberOfInputs() <= 1 && NumberOfOutputs() == 1); + mPassThrough = aPassThrough; + if (mStream) { + mStream->SetPassThrough(mPassThrough); + } +} + +} // namespace dom +} // namespace mozilla |