|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "AudioNode.h" |
|
8 #include "mozilla/ErrorResult.h" |
|
9 #include "AudioNodeStream.h" |
|
10 #include "AudioNodeEngine.h" |
|
11 #include "mozilla/dom/AudioParam.h" |
|
12 |
|
13 namespace mozilla { |
|
14 namespace dom { |
|
15 |
|
16 static const uint32_t INVALID_PORT = 0xffffffff; |
|
17 |
|
18 NS_IMPL_CYCLE_COLLECTION_CLASS(AudioNode) |
|
19 |
|
20 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(AudioNode, DOMEventTargetHelper) |
|
21 tmp->DisconnectFromGraph(); |
|
22 if (tmp->mContext) { |
|
23 tmp->mContext->UpdateNodeCount(-1); |
|
24 } |
|
25 NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext) |
|
26 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputNodes) |
|
27 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputParams) |
|
28 NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
|
29 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AudioNode, |
|
30 DOMEventTargetHelper) |
|
31 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext) |
|
32 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputNodes) |
|
33 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputParams) |
|
34 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
|
35 |
|
36 NS_IMPL_ADDREF_INHERITED(AudioNode, DOMEventTargetHelper) |
|
37 |
|
38 NS_IMETHODIMP_(MozExternalRefCountType) |
|
39 AudioNode::Release() |
|
40 { |
|
41 if (mRefCnt.get() == 1) { |
|
42 // We are about to be deleted, disconnect the object from the graph before |
|
43 // the derived type is destroyed. |
|
44 DisconnectFromGraph(); |
|
45 } |
|
46 nsrefcnt r = DOMEventTargetHelper::Release(); |
|
47 NS_LOG_RELEASE(this, r, "AudioNode"); |
|
48 return r; |
|
49 } |
|
50 |
|
51 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AudioNode) |
|
52 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) |
|
53 |
|
54 AudioNode::AudioNode(AudioContext* aContext, |
|
55 uint32_t aChannelCount, |
|
56 ChannelCountMode aChannelCountMode, |
|
57 ChannelInterpretation aChannelInterpretation) |
|
58 : DOMEventTargetHelper(aContext->GetParentObject()) |
|
59 , mContext(aContext) |
|
60 , mChannelCount(aChannelCount) |
|
61 , mChannelCountMode(aChannelCountMode) |
|
62 , mChannelInterpretation(aChannelInterpretation) |
|
63 { |
|
64 MOZ_ASSERT(aContext); |
|
65 DOMEventTargetHelper::BindToOwner(aContext->GetParentObject()); |
|
66 SetIsDOMBinding(); |
|
67 aContext->UpdateNodeCount(1); |
|
68 } |
|
69 |
|
70 AudioNode::~AudioNode() |
|
71 { |
|
72 MOZ_ASSERT(mInputNodes.IsEmpty()); |
|
73 MOZ_ASSERT(mOutputNodes.IsEmpty()); |
|
74 MOZ_ASSERT(mOutputParams.IsEmpty()); |
|
75 if (mContext) { |
|
76 mContext->UpdateNodeCount(-1); |
|
77 } |
|
78 } |
|
79 |
|
80 size_t |
|
81 AudioNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const |
|
82 { |
|
83 // Not owned: |
|
84 // - mContext |
|
85 // - mStream |
|
86 size_t amount = 0; |
|
87 |
|
88 amount += mInputNodes.SizeOfExcludingThis(aMallocSizeOf); |
|
89 for (size_t i = 0; i < mInputNodes.Length(); i++) { |
|
90 amount += mInputNodes[i].SizeOfExcludingThis(aMallocSizeOf); |
|
91 } |
|
92 |
|
93 // Just measure the array. The entire audio node graph is measured via the |
|
94 // MediaStreamGraph's streams, so we don't want to double-count the elements. |
|
95 amount += mOutputNodes.SizeOfExcludingThis(aMallocSizeOf); |
|
96 |
|
97 amount += mOutputParams.SizeOfExcludingThis(aMallocSizeOf); |
|
98 for (size_t i = 0; i < mOutputParams.Length(); i++) { |
|
99 amount += mOutputParams[i]->SizeOfIncludingThis(aMallocSizeOf); |
|
100 } |
|
101 |
|
102 return amount; |
|
103 } |
|
104 |
|
105 size_t |
|
106 AudioNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const |
|
107 { |
|
108 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); |
|
109 } |
|
110 |
|
111 template <class InputNode> |
|
112 static uint32_t |
|
113 FindIndexOfNode(const nsTArray<InputNode>& aInputNodes, const AudioNode* aNode) |
|
114 { |
|
115 for (uint32_t i = 0; i < aInputNodes.Length(); ++i) { |
|
116 if (aInputNodes[i].mInputNode == aNode) { |
|
117 return i; |
|
118 } |
|
119 } |
|
120 return nsTArray<InputNode>::NoIndex; |
|
121 } |
|
122 |
|
123 template <class InputNode> |
|
124 static uint32_t |
|
125 FindIndexOfNodeWithPorts(const nsTArray<InputNode>& aInputNodes, const AudioNode* aNode, |
|
126 uint32_t aInputPort, uint32_t aOutputPort) |
|
127 { |
|
128 for (uint32_t i = 0; i < aInputNodes.Length(); ++i) { |
|
129 if (aInputNodes[i].mInputNode == aNode && |
|
130 aInputNodes[i].mInputPort == aInputPort && |
|
131 aInputNodes[i].mOutputPort == aOutputPort) { |
|
132 return i; |
|
133 } |
|
134 } |
|
135 return nsTArray<InputNode>::NoIndex; |
|
136 } |
|
137 |
|
138 void |
|
139 AudioNode::DisconnectFromGraph() |
|
140 { |
|
141 // Addref this temporarily so the refcount bumping below doesn't destroy us |
|
142 // prematurely |
|
143 nsRefPtr<AudioNode> kungFuDeathGrip = this; |
|
144 |
|
145 // The idea here is that we remove connections one by one, and at each step |
|
146 // the graph is in a valid state. |
|
147 |
|
148 // Disconnect inputs. We don't need them anymore. |
|
149 while (!mInputNodes.IsEmpty()) { |
|
150 uint32_t i = mInputNodes.Length() - 1; |
|
151 nsRefPtr<AudioNode> input = mInputNodes[i].mInputNode; |
|
152 mInputNodes.RemoveElementAt(i); |
|
153 input->mOutputNodes.RemoveElement(this); |
|
154 } |
|
155 |
|
156 while (!mOutputNodes.IsEmpty()) { |
|
157 uint32_t i = mOutputNodes.Length() - 1; |
|
158 nsRefPtr<AudioNode> output = mOutputNodes[i].forget(); |
|
159 mOutputNodes.RemoveElementAt(i); |
|
160 uint32_t inputIndex = FindIndexOfNode(output->mInputNodes, this); |
|
161 // It doesn't matter which one we remove, since we're going to remove all |
|
162 // entries for this node anyway. |
|
163 output->mInputNodes.RemoveElementAt(inputIndex); |
|
164 } |
|
165 |
|
166 while (!mOutputParams.IsEmpty()) { |
|
167 uint32_t i = mOutputParams.Length() - 1; |
|
168 nsRefPtr<AudioParam> output = mOutputParams[i].forget(); |
|
169 mOutputParams.RemoveElementAt(i); |
|
170 uint32_t inputIndex = FindIndexOfNode(output->InputNodes(), this); |
|
171 // It doesn't matter which one we remove, since we're going to remove all |
|
172 // entries for this node anyway. |
|
173 output->RemoveInputNode(inputIndex); |
|
174 } |
|
175 |
|
176 DestroyMediaStream(); |
|
177 } |
|
178 |
|
179 void |
|
180 AudioNode::Connect(AudioNode& aDestination, uint32_t aOutput, |
|
181 uint32_t aInput, ErrorResult& aRv) |
|
182 { |
|
183 if (aOutput >= NumberOfOutputs() || |
|
184 aInput >= aDestination.NumberOfInputs()) { |
|
185 aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
|
186 return; |
|
187 } |
|
188 |
|
189 if (Context() != aDestination.Context()) { |
|
190 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); |
|
191 return; |
|
192 } |
|
193 |
|
194 if (FindIndexOfNodeWithPorts(aDestination.mInputNodes, this, aInput, aOutput) != |
|
195 nsTArray<AudioNode::InputNode>::NoIndex) { |
|
196 // connection already exists. |
|
197 return; |
|
198 } |
|
199 |
|
200 // The MediaStreamGraph will handle cycle detection. We don't need to do it |
|
201 // here. |
|
202 |
|
203 mOutputNodes.AppendElement(&aDestination); |
|
204 InputNode* input = aDestination.mInputNodes.AppendElement(); |
|
205 input->mInputNode = this; |
|
206 input->mInputPort = aInput; |
|
207 input->mOutputPort = aOutput; |
|
208 if (aDestination.mStream) { |
|
209 // Connect streams in the MediaStreamGraph |
|
210 MOZ_ASSERT(aDestination.mStream->AsProcessedStream()); |
|
211 ProcessedMediaStream* ps = |
|
212 static_cast<ProcessedMediaStream*>(aDestination.mStream.get()); |
|
213 MOZ_ASSERT(aInput <= UINT16_MAX, "Unexpected large input port number"); |
|
214 MOZ_ASSERT(aOutput <= UINT16_MAX, "Unexpected large output port number"); |
|
215 input->mStreamPort = |
|
216 ps->AllocateInputPort(mStream, MediaInputPort::FLAG_BLOCK_INPUT, |
|
217 static_cast<uint16_t>(aInput), |
|
218 static_cast<uint16_t>(aOutput)); |
|
219 } |
|
220 |
|
221 // This connection may have connected a panner and a source. |
|
222 Context()->UpdatePannerSource(); |
|
223 } |
|
224 |
|
225 void |
|
226 AudioNode::Connect(AudioParam& aDestination, uint32_t aOutput, |
|
227 ErrorResult& aRv) |
|
228 { |
|
229 if (aOutput >= NumberOfOutputs()) { |
|
230 aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
|
231 return; |
|
232 } |
|
233 |
|
234 if (Context() != aDestination.GetParentObject()) { |
|
235 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); |
|
236 return; |
|
237 } |
|
238 |
|
239 if (FindIndexOfNodeWithPorts(aDestination.InputNodes(), this, INVALID_PORT, aOutput) != |
|
240 nsTArray<AudioNode::InputNode>::NoIndex) { |
|
241 // connection already exists. |
|
242 return; |
|
243 } |
|
244 |
|
245 mOutputParams.AppendElement(&aDestination); |
|
246 InputNode* input = aDestination.AppendInputNode(); |
|
247 input->mInputNode = this; |
|
248 input->mInputPort = INVALID_PORT; |
|
249 input->mOutputPort = aOutput; |
|
250 |
|
251 MediaStream* stream = aDestination.Stream(); |
|
252 MOZ_ASSERT(stream->AsProcessedStream()); |
|
253 ProcessedMediaStream* ps = static_cast<ProcessedMediaStream*>(stream); |
|
254 |
|
255 // Setup our stream as an input to the AudioParam's stream |
|
256 MOZ_ASSERT(aOutput <= UINT16_MAX, "Unexpected large output port number"); |
|
257 input->mStreamPort = ps->AllocateInputPort(mStream, MediaInputPort::FLAG_BLOCK_INPUT, |
|
258 0, static_cast<uint16_t>(aOutput)); |
|
259 } |
|
260 |
|
261 void |
|
262 AudioNode::SendDoubleParameterToStream(uint32_t aIndex, double aValue) |
|
263 { |
|
264 AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get()); |
|
265 MOZ_ASSERT(ns, "How come we don't have a stream here?"); |
|
266 ns->SetDoubleParameter(aIndex, aValue); |
|
267 } |
|
268 |
|
269 void |
|
270 AudioNode::SendInt32ParameterToStream(uint32_t aIndex, int32_t aValue) |
|
271 { |
|
272 AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get()); |
|
273 MOZ_ASSERT(ns, "How come we don't have a stream here?"); |
|
274 ns->SetInt32Parameter(aIndex, aValue); |
|
275 } |
|
276 |
|
277 void |
|
278 AudioNode::SendThreeDPointParameterToStream(uint32_t aIndex, const ThreeDPoint& aValue) |
|
279 { |
|
280 AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get()); |
|
281 MOZ_ASSERT(ns, "How come we don't have a stream here?"); |
|
282 ns->SetThreeDPointParameter(aIndex, aValue); |
|
283 } |
|
284 |
|
285 void |
|
286 AudioNode::SendChannelMixingParametersToStream() |
|
287 { |
|
288 AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get()); |
|
289 MOZ_ASSERT(ns, "How come we don't have a stream here?"); |
|
290 ns->SetChannelMixingParameters(mChannelCount, mChannelCountMode, |
|
291 mChannelInterpretation); |
|
292 } |
|
293 |
|
294 void |
|
295 AudioNode::SendTimelineParameterToStream(AudioNode* aNode, uint32_t aIndex, |
|
296 const AudioParamTimeline& aValue) |
|
297 { |
|
298 AudioNodeStream* ns = static_cast<AudioNodeStream*>(aNode->mStream.get()); |
|
299 MOZ_ASSERT(ns, "How come we don't have a stream here?"); |
|
300 ns->SetTimelineParameter(aIndex, aValue); |
|
301 } |
|
302 |
|
303 void |
|
304 AudioNode::Disconnect(uint32_t aOutput, ErrorResult& aRv) |
|
305 { |
|
306 if (aOutput >= NumberOfOutputs()) { |
|
307 aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
|
308 return; |
|
309 } |
|
310 |
|
311 // An upstream node may be starting to play on the graph thread, and the |
|
312 // engine for a downstream node may be sending a PlayingRefChangeHandler |
|
313 // ADDREF message to this (main) thread. Wait for a round trip before |
|
314 // releasing nodes, to give engines receiving sound now time to keep their |
|
315 // nodes alive. |
|
316 class RunnableRelease : public nsRunnable { |
|
317 public: |
|
318 explicit RunnableRelease(already_AddRefed<AudioNode> aNode) |
|
319 : mNode(aNode) {} |
|
320 |
|
321 NS_IMETHODIMP Run() MOZ_OVERRIDE |
|
322 { |
|
323 mNode = nullptr; |
|
324 return NS_OK; |
|
325 } |
|
326 private: |
|
327 nsRefPtr<AudioNode> mNode; |
|
328 }; |
|
329 |
|
330 for (int32_t i = mOutputNodes.Length() - 1; i >= 0; --i) { |
|
331 AudioNode* dest = mOutputNodes[i]; |
|
332 for (int32_t j = dest->mInputNodes.Length() - 1; j >= 0; --j) { |
|
333 InputNode& input = dest->mInputNodes[j]; |
|
334 if (input.mInputNode == this && input.mOutputPort == aOutput) { |
|
335 // Destroying the InputNode here sends a message to the graph thread |
|
336 // to disconnect the streams, which should be sent before the |
|
337 // RunAfterPendingUpdates() call below. |
|
338 dest->mInputNodes.RemoveElementAt(j); |
|
339 // Remove one instance of 'dest' from mOutputNodes. There could be |
|
340 // others, and it's not correct to remove them all since some of them |
|
341 // could be for different output ports. |
|
342 nsRefPtr<nsIRunnable> runnable = |
|
343 new RunnableRelease(mOutputNodes[i].forget()); |
|
344 mOutputNodes.RemoveElementAt(i); |
|
345 mStream->RunAfterPendingUpdates(runnable.forget()); |
|
346 break; |
|
347 } |
|
348 } |
|
349 } |
|
350 |
|
351 for (int32_t i = mOutputParams.Length() - 1; i >= 0; --i) { |
|
352 AudioParam* dest = mOutputParams[i]; |
|
353 for (int32_t j = dest->InputNodes().Length() - 1; j >= 0; --j) { |
|
354 const InputNode& input = dest->InputNodes()[j]; |
|
355 if (input.mInputNode == this && input.mOutputPort == aOutput) { |
|
356 dest->RemoveInputNode(j); |
|
357 // Remove one instance of 'dest' from mOutputParams. There could be |
|
358 // others, and it's not correct to remove them all since some of them |
|
359 // could be for different output ports. |
|
360 mOutputParams.RemoveElementAt(i); |
|
361 break; |
|
362 } |
|
363 } |
|
364 } |
|
365 |
|
366 // This disconnection may have disconnected a panner and a source. |
|
367 Context()->UpdatePannerSource(); |
|
368 } |
|
369 |
|
370 void |
|
371 AudioNode::DestroyMediaStream() |
|
372 { |
|
373 if (mStream) { |
|
374 { |
|
375 // Remove the node reference on the engine, and take care to not |
|
376 // hold the lock when the stream gets destroyed, because that will |
|
377 // cause the engine to be destroyed as well, and we don't want to |
|
378 // be holding the lock as we're trying to destroy it! |
|
379 AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get()); |
|
380 MutexAutoLock lock(ns->Engine()->NodeMutex()); |
|
381 MOZ_ASSERT(ns, "How come we don't have a stream here?"); |
|
382 MOZ_ASSERT(ns->Engine()->Node() == this, "Invalid node reference"); |
|
383 ns->Engine()->ClearNode(); |
|
384 } |
|
385 |
|
386 mStream->Destroy(); |
|
387 mStream = nullptr; |
|
388 } |
|
389 } |
|
390 |
|
391 void |
|
392 AudioNode::RemoveOutputParam(AudioParam* aParam) |
|
393 { |
|
394 mOutputParams.RemoveElement(aParam); |
|
395 } |
|
396 |
|
397 } |
|
398 } |