mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-07 04:05:49 +00:00
0ab5221feb
See bug 980506 for an extensive discussion about this. This patch adds three APIs to AudioNode in order for us to be able to build awesome devtools on top of it. * Weak reference API. This patch allows one to hold a weak reference to all AudioNode's using Components.utils.getWeakReference(). That way, the devtool's inspection code would not change the lifetime of AudioNodes. * AudioNode.id This is a chrome-only unique and monotonically incrementing ID for AudioNode objects. It is supposed to be used in order for the devtools to be able to identify a node without having to keep it alive. * webaudio-node-demise This is an observer notification that is called every time an AudioNode gets destroyed inside Gecko. The ID of the corresponding node is passed to this notification. --HG-- extra : rebase_source : 83246a990489daf44ddc97dd4ea372a8cebe8e00
421 lines
13 KiB
C++
421 lines
13 KiB
C++
/* -*- 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->UpdateNodeCount(-1);
|
|
}
|
|
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_IMETHODIMP_(MozExternalRefCountType)
|
|
AudioNode::Release()
|
|
{
|
|
if (mRefCnt.get() == 1) {
|
|
// We are about to be deleted, disconnect the object from the graph before
|
|
// the derived type is destroyed.
|
|
DisconnectFromGraph();
|
|
}
|
|
nsrefcnt r = DOMEventTargetHelper::Release();
|
|
NS_LOG_RELEASE(this, r, "AudioNode");
|
|
return r;
|
|
}
|
|
|
|
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++)
|
|
#ifdef DEBUG
|
|
, mDemiseNotified(false)
|
|
#endif
|
|
{
|
|
MOZ_ASSERT(aContext);
|
|
DOMEventTargetHelper::BindToOwner(aContext->GetParentObject());
|
|
SetIsDOMBinding();
|
|
aContext->UpdateNodeCount(1);
|
|
}
|
|
|
|
AudioNode::~AudioNode()
|
|
{
|
|
MOZ_ASSERT(mInputNodes.IsEmpty());
|
|
MOZ_ASSERT(mOutputNodes.IsEmpty());
|
|
MOZ_ASSERT(mOutputParams.IsEmpty());
|
|
#ifdef DEBUG
|
|
MOZ_ASSERT(mDemiseNotified,
|
|
"The webaudio-node-demise notification must have been sent");
|
|
#endif
|
|
if (mContext) {
|
|
mContext->UpdateNodeCount(-1);
|
|
}
|
|
}
|
|
|
|
size_t
|
|
AudioNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
|
|
{
|
|
// Not owned:
|
|
// - mContext
|
|
// - mStream
|
|
size_t amount = 0;
|
|
|
|
amount += mInputNodes.SizeOfExcludingThis(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.SizeOfExcludingThis(aMallocSizeOf);
|
|
|
|
amount += mOutputParams.SizeOfExcludingThis(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()
|
|
{
|
|
// Addref this temporarily so the refcount bumping below doesn't destroy us
|
|
// prematurely
|
|
nsRefPtr<AudioNode> kungFuDeathGrip = this;
|
|
|
|
// 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;
|
|
nsRefPtr<AudioNode> input = mInputNodes[i].mInputNode;
|
|
mInputNodes.RemoveElementAt(i);
|
|
input->mOutputNodes.RemoveElement(this);
|
|
}
|
|
|
|
while (!mOutputNodes.IsEmpty()) {
|
|
size_t i = mOutputNodes.Length() - 1;
|
|
nsRefPtr<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);
|
|
}
|
|
|
|
while (!mOutputParams.IsEmpty()) {
|
|
size_t i = mOutputParams.Length() - 1;
|
|
nsRefPtr<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();
|
|
}
|
|
|
|
void
|
|
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;
|
|
}
|
|
|
|
if (Context() != aDestination.Context()) {
|
|
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
|
|
return;
|
|
}
|
|
|
|
if (FindIndexOfNodeWithPorts(aDestination.mInputNodes, this, aInput, aOutput) !=
|
|
nsTArray<AudioNode::InputNode>::NoIndex) {
|
|
// connection already exists.
|
|
return;
|
|
}
|
|
|
|
// 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;
|
|
if (aDestination.mStream) {
|
|
// Connect streams in the MediaStreamGraph
|
|
MOZ_ASSERT(aDestination.mStream->AsProcessedStream());
|
|
ProcessedMediaStream* ps =
|
|
static_cast<ProcessedMediaStream*>(aDestination.mStream.get());
|
|
MOZ_ASSERT(aInput <= UINT16_MAX, "Unexpected large input port number");
|
|
MOZ_ASSERT(aOutput <= UINT16_MAX, "Unexpected large output port number");
|
|
input->mStreamPort =
|
|
ps->AllocateInputPort(mStream, MediaInputPort::FLAG_BLOCK_INPUT,
|
|
static_cast<uint16_t>(aInput),
|
|
static_cast<uint16_t>(aOutput));
|
|
}
|
|
|
|
// This connection may have connected a panner and a source.
|
|
Context()->UpdatePannerSource();
|
|
}
|
|
|
|
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);
|
|
|
|
// 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, MediaInputPort::FLAG_BLOCK_INPUT,
|
|
0, static_cast<uint16_t>(aOutput));
|
|
}
|
|
|
|
void
|
|
AudioNode::SendDoubleParameterToStream(uint32_t aIndex, double aValue)
|
|
{
|
|
AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get());
|
|
MOZ_ASSERT(ns, "How come we don't have a stream here?");
|
|
ns->SetDoubleParameter(aIndex, aValue);
|
|
}
|
|
|
|
void
|
|
AudioNode::SendInt32ParameterToStream(uint32_t aIndex, int32_t aValue)
|
|
{
|
|
AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get());
|
|
MOZ_ASSERT(ns, "How come we don't have a stream here?");
|
|
ns->SetInt32Parameter(aIndex, aValue);
|
|
}
|
|
|
|
void
|
|
AudioNode::SendThreeDPointParameterToStream(uint32_t aIndex, const ThreeDPoint& aValue)
|
|
{
|
|
AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get());
|
|
MOZ_ASSERT(ns, "How come we don't have a stream here?");
|
|
ns->SetThreeDPointParameter(aIndex, aValue);
|
|
}
|
|
|
|
void
|
|
AudioNode::SendChannelMixingParametersToStream()
|
|
{
|
|
AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get());
|
|
MOZ_ASSERT(ns, "How come we don't have a stream here?");
|
|
ns->SetChannelMixingParameters(mChannelCount, mChannelCountMode,
|
|
mChannelInterpretation);
|
|
}
|
|
|
|
void
|
|
AudioNode::SendTimelineParameterToStream(AudioNode* aNode, uint32_t aIndex,
|
|
const AudioParamTimeline& aValue)
|
|
{
|
|
AudioNodeStream* ns = static_cast<AudioNodeStream*>(aNode->mStream.get());
|
|
MOZ_ASSERT(ns, "How come we don't have a stream here?");
|
|
ns->SetTimelineParameter(aIndex, aValue);
|
|
}
|
|
|
|
void
|
|
AudioNode::Disconnect(uint32_t aOutput, ErrorResult& aRv)
|
|
{
|
|
if (aOutput >= NumberOfOutputs()) {
|
|
aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
|
|
return;
|
|
}
|
|
|
|
// 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 : public nsRunnable {
|
|
public:
|
|
explicit RunnableRelease(already_AddRefed<AudioNode> aNode)
|
|
: mNode(aNode) {}
|
|
|
|
NS_IMETHODIMP Run() MOZ_OVERRIDE
|
|
{
|
|
mNode = nullptr;
|
|
return NS_OK;
|
|
}
|
|
private:
|
|
nsRefPtr<AudioNode> mNode;
|
|
};
|
|
|
|
for (int32_t i = mOutputNodes.Length() - 1; i >= 0; --i) {
|
|
AudioNode* dest = mOutputNodes[i];
|
|
for (int32_t j = dest->mInputNodes.Length() - 1; j >= 0; --j) {
|
|
InputNode& input = dest->mInputNodes[j];
|
|
if (input.mInputNode == this && input.mOutputPort == aOutput) {
|
|
// Destroying the InputNode here sends a message to the graph thread
|
|
// to disconnect the streams, which should be sent before the
|
|
// RunAfterPendingUpdates() call below.
|
|
dest->mInputNodes.RemoveElementAt(j);
|
|
// 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.
|
|
nsRefPtr<nsIRunnable> runnable =
|
|
new RunnableRelease(mOutputNodes[i].forget());
|
|
mOutputNodes.RemoveElementAt(i);
|
|
mStream->RunAfterPendingUpdates(runnable.forget());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int32_t i = mOutputParams.Length() - 1; i >= 0; --i) {
|
|
AudioParam* dest = mOutputParams[i];
|
|
for (int32_t j = dest->InputNodes().Length() - 1; j >= 0; --j) {
|
|
const InputNode& input = dest->InputNodes()[j];
|
|
if (input.mInputNode == this && input.mOutputPort == aOutput) {
|
|
dest->RemoveInputNode(j);
|
|
// 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(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// This disconnection may have disconnected a panner and a source.
|
|
Context()->UpdatePannerSource();
|
|
}
|
|
|
|
void
|
|
AudioNode::DestroyMediaStream()
|
|
{
|
|
if (mStream) {
|
|
{
|
|
// Remove the node reference on the engine, and take care to not
|
|
// hold the lock when the stream gets destroyed, because that will
|
|
// cause the engine to be destroyed as well, and we don't want to
|
|
// be holding the lock as we're trying to destroy it!
|
|
AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get());
|
|
MutexAutoLock lock(ns->Engine()->NodeMutex());
|
|
MOZ_ASSERT(ns, "How come we don't have a stream here?");
|
|
MOZ_ASSERT(ns->Engine()->Node() == 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());
|
|
}
|
|
#ifdef DEBUG
|
|
mDemiseNotified = true;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void
|
|
AudioNode::RemoveOutputParam(AudioParam* aParam)
|
|
{
|
|
mOutputParams.RemoveElement(aParam);
|
|
}
|
|
|
|
}
|
|
}
|