Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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/. */
7 #include "AudioDestinationNode.h"
8 #include "mozilla/dom/AudioDestinationNodeBinding.h"
9 #include "mozilla/Preferences.h"
10 #include "AudioChannelAgent.h"
11 #include "AudioChannelService.h"
12 #include "AudioNodeEngine.h"
13 #include "AudioNodeStream.h"
14 #include "MediaStreamGraph.h"
15 #include "OfflineAudioCompletionEvent.h"
16 #include "nsIInterfaceRequestorUtils.h"
17 #include "nsIDocShell.h"
18 #include "nsIPermissionManager.h"
19 #include "nsIScriptObjectPrincipal.h"
20 #include "nsServiceManagerUtils.h"
21 #include "nsIAppShell.h"
22 #include "nsWidgetsCID.h"
24 namespace mozilla {
25 namespace dom {
27 static uint8_t gWebAudioOutputKey;
29 class OfflineDestinationNodeEngine : public AudioNodeEngine
30 {
31 public:
32 typedef AutoFallibleTArray<nsAutoArrayPtr<float>, 2> InputChannels;
34 OfflineDestinationNodeEngine(AudioDestinationNode* aNode,
35 uint32_t aNumberOfChannels,
36 uint32_t aLength,
37 float aSampleRate)
38 : AudioNodeEngine(aNode)
39 , mWriteIndex(0)
40 , mLength(aLength)
41 , mSampleRate(aSampleRate)
42 {
43 // These allocations might fail if content provides a huge number of
44 // channels or size, but it's OK since we'll deal with the failure
45 // gracefully.
46 if (mInputChannels.SetLength(aNumberOfChannels)) {
47 static const fallible_t fallible = fallible_t();
48 for (uint32_t i = 0; i < aNumberOfChannels; ++i) {
49 mInputChannels[i] = new(fallible) float[aLength];
50 if (!mInputChannels[i]) {
51 mInputChannels.Clear();
52 break;
53 }
54 }
55 }
56 }
58 virtual void ProcessBlock(AudioNodeStream* aStream,
59 const AudioChunk& aInput,
60 AudioChunk* aOutput,
61 bool* aFinished) MOZ_OVERRIDE
62 {
63 // Do this just for the sake of political correctness; this output
64 // will not go anywhere.
65 *aOutput = aInput;
67 // Handle the case of allocation failure in the input buffer
68 if (mInputChannels.IsEmpty()) {
69 return;
70 }
72 if (mWriteIndex >= mLength) {
73 NS_ASSERTION(mWriteIndex == mLength, "Overshot length");
74 // Don't record any more.
75 return;
76 }
78 // Record our input buffer
79 MOZ_ASSERT(mWriteIndex < mLength, "How did this happen?");
80 const uint32_t duration = std::min(WEBAUDIO_BLOCK_SIZE, mLength - mWriteIndex);
81 const uint32_t commonChannelCount = std::min(mInputChannels.Length(),
82 aInput.mChannelData.Length());
83 // First, copy as many channels in the input as we have
84 for (uint32_t i = 0; i < commonChannelCount; ++i) {
85 if (aInput.IsNull()) {
86 PodZero(mInputChannels[i] + mWriteIndex, duration);
87 } else {
88 const float* inputBuffer = static_cast<const float*>(aInput.mChannelData[i]);
89 if (duration == WEBAUDIO_BLOCK_SIZE) {
90 // Use the optimized version of the copy with scale operation
91 AudioBlockCopyChannelWithScale(inputBuffer, aInput.mVolume,
92 mInputChannels[i] + mWriteIndex);
93 } else {
94 if (aInput.mVolume == 1.0f) {
95 PodCopy(mInputChannels[i] + mWriteIndex, inputBuffer, duration);
96 } else {
97 for (uint32_t j = 0; j < duration; ++j) {
98 mInputChannels[i][mWriteIndex + j] = aInput.mVolume * inputBuffer[j];
99 }
100 }
101 }
102 }
103 }
104 // Then, silence all of the remaining channels
105 for (uint32_t i = commonChannelCount; i < mInputChannels.Length(); ++i) {
106 PodZero(mInputChannels[i] + mWriteIndex, duration);
107 }
108 mWriteIndex += duration;
110 if (mWriteIndex >= mLength) {
111 NS_ASSERTION(mWriteIndex == mLength, "Overshot length");
112 // Go to finished state. When the graph's current time eventually reaches
113 // the end of the stream, then the main thread will be notified and we'll
114 // shut down the AudioContext.
115 *aFinished = true;
116 }
117 }
119 void FireOfflineCompletionEvent(AudioDestinationNode* aNode)
120 {
121 AudioContext* context = aNode->Context();
122 context->Shutdown();
123 // Shutdown drops self reference, but the context is still referenced by aNode,
124 // which is strongly referenced by the runnable that called
125 // AudioDestinationNode::FireOfflineCompletionEvent.
127 AutoPushJSContext cx(context->GetJSContext());
128 if (!cx) {
130 return;
131 }
132 JSAutoRequest ar(cx);
134 // Create the input buffer
135 ErrorResult rv;
136 nsRefPtr<AudioBuffer> renderedBuffer =
137 AudioBuffer::Create(context, mInputChannels.Length(),
138 mLength, mSampleRate, cx, rv);
139 if (rv.Failed()) {
140 return;
141 }
142 for (uint32_t i = 0; i < mInputChannels.Length(); ++i) {
143 renderedBuffer->SetRawChannelContents(cx, i, mInputChannels[i]);
144 }
146 nsRefPtr<OfflineAudioCompletionEvent> event =
147 new OfflineAudioCompletionEvent(context, nullptr, nullptr);
148 event->InitEvent(renderedBuffer);
149 context->DispatchTrustedEvent(event);
150 }
152 virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
153 {
154 size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
155 amount += mInputChannels.SizeOfExcludingThis(aMallocSizeOf);
156 return amount;
157 }
159 virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
160 {
161 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
162 }
164 private:
165 // The input to the destination node is recorded in the mInputChannels buffer.
166 // When this buffer fills up with mLength frames, the buffered input is sent
167 // to the main thread in order to dispatch OfflineAudioCompletionEvent.
168 InputChannels mInputChannels;
169 // An index representing the next offset in mInputChannels to be written to.
170 uint32_t mWriteIndex;
171 // How many frames the OfflineAudioContext intends to produce.
172 uint32_t mLength;
173 float mSampleRate;
174 };
176 class DestinationNodeEngine : public AudioNodeEngine
177 {
178 public:
179 explicit DestinationNodeEngine(AudioDestinationNode* aNode)
180 : AudioNodeEngine(aNode)
181 , mVolume(1.0f)
182 {
183 }
185 virtual void ProcessBlock(AudioNodeStream* aStream,
186 const AudioChunk& aInput,
187 AudioChunk* aOutput,
188 bool* aFinished) MOZ_OVERRIDE
189 {
190 *aOutput = aInput;
191 aOutput->mVolume *= mVolume;
192 }
194 virtual void SetDoubleParameter(uint32_t aIndex, double aParam) MOZ_OVERRIDE
195 {
196 if (aIndex == VOLUME) {
197 mVolume = aParam;
198 }
199 }
201 enum Parameters {
202 VOLUME,
203 };
205 virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
206 {
207 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
208 }
210 private:
211 float mVolume;
212 };
214 static bool UseAudioChannelService()
215 {
216 return Preferences::GetBool("media.useAudioChannelService");
217 }
219 NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioDestinationNode, AudioNode,
220 mAudioChannelAgent)
222 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AudioDestinationNode)
223 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
224 NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
225 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
226 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
228 NS_IMPL_ADDREF_INHERITED(AudioDestinationNode, AudioNode)
229 NS_IMPL_RELEASE_INHERITED(AudioDestinationNode, AudioNode)
231 AudioDestinationNode::AudioDestinationNode(AudioContext* aContext,
232 bool aIsOffline,
233 AudioChannel aChannel,
234 uint32_t aNumberOfChannels,
235 uint32_t aLength,
236 float aSampleRate)
237 : AudioNode(aContext,
238 aIsOffline ? aNumberOfChannels : 2,
239 ChannelCountMode::Explicit,
240 ChannelInterpretation::Speakers)
241 , mFramesToProduce(aLength)
242 , mAudioChannel(AudioChannel::Normal)
243 , mIsOffline(aIsOffline)
244 , mHasFinished(false)
245 , mExtraCurrentTime(0)
246 , mExtraCurrentTimeSinceLastStartedBlocking(0)
247 , mExtraCurrentTimeUpdatedSinceLastStableState(false)
248 {
249 MediaStreamGraph* graph = aIsOffline ?
250 MediaStreamGraph::CreateNonRealtimeInstance(aSampleRate) :
251 MediaStreamGraph::GetInstance();
252 AudioNodeEngine* engine = aIsOffline ?
253 new OfflineDestinationNodeEngine(this, aNumberOfChannels,
254 aLength, aSampleRate) :
255 static_cast<AudioNodeEngine*>(new DestinationNodeEngine(this));
257 mStream = graph->CreateAudioNodeStream(engine, MediaStreamGraph::EXTERNAL_STREAM);
258 mStream->SetAudioChannelType(aChannel);
259 mStream->AddMainThreadListener(this);
260 mStream->AddAudioOutput(&gWebAudioOutputKey);
262 if (aChannel != AudioChannel::Normal) {
263 ErrorResult rv;
264 SetMozAudioChannelType(aChannel, rv);
265 }
267 if (!aIsOffline && UseAudioChannelService()) {
268 nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner());
269 if (target) {
270 target->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"), this,
271 /* useCapture = */ true,
272 /* wantsUntrusted = */ false);
273 }
275 CreateAudioChannelAgent();
276 }
277 }
279 size_t
280 AudioDestinationNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
281 {
282 size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
283 // Might be useful in the future:
284 // - mAudioChannelAgent
285 return amount;
286 }
288 size_t
289 AudioDestinationNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
290 {
291 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
292 }
294 void
295 AudioDestinationNode::DestroyMediaStream()
296 {
297 if (mAudioChannelAgent && !Context()->IsOffline()) {
298 mAudioChannelAgent->StopPlaying();
299 mAudioChannelAgent = nullptr;
301 nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner());
302 NS_ENSURE_TRUE_VOID(target);
304 target->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"), this,
305 /* useCapture = */ true);
306 }
308 if (!mStream)
309 return;
311 mStream->RemoveMainThreadListener(this);
312 MediaStreamGraph* graph = mStream->Graph();
313 if (graph->IsNonRealtime()) {
314 MediaStreamGraph::DestroyNonRealtimeInstance(graph);
315 }
316 AudioNode::DestroyMediaStream();
317 }
319 void
320 AudioDestinationNode::NotifyMainThreadStateChanged()
321 {
322 if (mStream->IsFinished() && !mHasFinished) {
323 mHasFinished = true;
324 if (mIsOffline) {
325 nsCOMPtr<nsIRunnable> runnable =
326 NS_NewRunnableMethod(this, &AudioDestinationNode::FireOfflineCompletionEvent);
327 NS_DispatchToCurrentThread(runnable);
328 }
329 }
330 }
332 void
333 AudioDestinationNode::FireOfflineCompletionEvent()
334 {
335 AudioNodeStream* stream = static_cast<AudioNodeStream*>(Stream());
336 OfflineDestinationNodeEngine* engine =
337 static_cast<OfflineDestinationNodeEngine*>(stream->Engine());
338 engine->FireOfflineCompletionEvent(this);
339 }
341 uint32_t
342 AudioDestinationNode::MaxChannelCount() const
343 {
344 return Context()->MaxChannelCount();
345 }
347 void
348 AudioDestinationNode::SetChannelCount(uint32_t aChannelCount, ErrorResult& aRv)
349 {
350 if (aChannelCount > MaxChannelCount()) {
351 aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
352 return;
353 }
355 AudioNode::SetChannelCount(aChannelCount, aRv);
356 }
358 void
359 AudioDestinationNode::Mute()
360 {
361 MOZ_ASSERT(Context() && !Context()->IsOffline());
362 SendDoubleParameterToStream(DestinationNodeEngine::VOLUME, 0.0f);
363 }
365 void
366 AudioDestinationNode::Unmute()
367 {
368 MOZ_ASSERT(Context() && !Context()->IsOffline());
369 SendDoubleParameterToStream(DestinationNodeEngine::VOLUME, 1.0f);
370 }
372 void
373 AudioDestinationNode::OfflineShutdown()
374 {
375 MOZ_ASSERT(Context() && Context()->IsOffline(),
376 "Should only be called on a valid OfflineAudioContext");
378 MediaStreamGraph::DestroyNonRealtimeInstance(mStream->Graph());
379 mOfflineRenderingRef.Drop(this);
380 }
382 JSObject*
383 AudioDestinationNode::WrapObject(JSContext* aCx)
384 {
385 return AudioDestinationNodeBinding::Wrap(aCx, this);
386 }
388 void
389 AudioDestinationNode::StartRendering()
390 {
391 mOfflineRenderingRef.Take(this);
392 mStream->Graph()->StartNonRealtimeProcessing(TrackRate(Context()->SampleRate()), mFramesToProduce);
393 }
395 void
396 AudioDestinationNode::SetCanPlay(bool aCanPlay)
397 {
398 mStream->SetTrackEnabled(AudioNodeStream::AUDIO_TRACK, aCanPlay);
399 }
401 NS_IMETHODIMP
402 AudioDestinationNode::HandleEvent(nsIDOMEvent* aEvent)
403 {
404 nsAutoString type;
405 aEvent->GetType(type);
407 if (!type.EqualsLiteral("visibilitychange")) {
408 return NS_ERROR_FAILURE;
409 }
411 nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner());
412 NS_ENSURE_TRUE(docshell, NS_ERROR_FAILURE);
414 bool isActive = false;
415 docshell->GetIsActive(&isActive);
417 mAudioChannelAgent->SetVisibilityState(isActive);
418 return NS_OK;
419 }
421 NS_IMETHODIMP
422 AudioDestinationNode::CanPlayChanged(int32_t aCanPlay)
423 {
424 SetCanPlay(aCanPlay == AudioChannelState::AUDIO_CHANNEL_STATE_NORMAL);
425 return NS_OK;
426 }
428 NS_IMETHODIMP
429 AudioDestinationNode::WindowVolumeChanged()
430 {
431 MOZ_ASSERT(mAudioChannelAgent);
433 if (!mStream) {
434 return NS_OK;
435 }
437 float volume;
438 nsresult rv = mAudioChannelAgent->GetWindowVolume(&volume);
439 NS_ENSURE_SUCCESS(rv, rv);
441 mStream->SetAudioOutputVolume(&gWebAudioOutputKey, volume);
442 return NS_OK;
443 }
445 AudioChannel
446 AudioDestinationNode::MozAudioChannelType() const
447 {
448 return mAudioChannel;
449 }
451 void
452 AudioDestinationNode::SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv)
453 {
454 if (Context()->IsOffline()) {
455 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
456 return;
457 }
459 if (aValue != mAudioChannel &&
460 CheckAudioChannelPermissions(aValue)) {
461 mAudioChannel = aValue;
463 if (mAudioChannelAgent) {
464 CreateAudioChannelAgent();
465 }
466 }
467 }
469 bool
470 AudioDestinationNode::CheckAudioChannelPermissions(AudioChannel aValue)
471 {
472 if (!Preferences::GetBool("media.useAudioChannelService")) {
473 return true;
474 }
476 // Only normal channel doesn't need permission.
477 if (aValue == AudioChannel::Normal) {
478 return true;
479 }
481 // Maybe this audio channel is equal to the default one.
482 if (aValue == AudioChannelService::GetDefaultAudioChannel()) {
483 return true;
484 }
486 nsCOMPtr<nsIPermissionManager> permissionManager =
487 do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
488 if (!permissionManager) {
489 return false;
490 }
492 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetOwner());
493 NS_ASSERTION(sop, "Window didn't QI to nsIScriptObjectPrincipal!");
494 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
496 uint32_t perm = nsIPermissionManager::UNKNOWN_ACTION;
498 nsCString channel;
499 channel.AssignASCII(AudioChannelValues::strings[uint32_t(aValue)].value,
500 AudioChannelValues::strings[uint32_t(aValue)].length);
501 permissionManager->TestExactPermissionFromPrincipal(principal,
502 nsCString(NS_LITERAL_CSTRING("audio-channel-") + channel).get(),
503 &perm);
505 return perm == nsIPermissionManager::ALLOW_ACTION;
506 }
508 void
509 AudioDestinationNode::CreateAudioChannelAgent()
510 {
511 if (mAudioChannelAgent) {
512 mAudioChannelAgent->StopPlaying();
513 }
515 mAudioChannelAgent = new AudioChannelAgent();
516 mAudioChannelAgent->InitWithWeakCallback(GetOwner(),
517 static_cast<int32_t>(mAudioChannel),
518 this);
520 nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner());
521 if (docshell) {
522 bool isActive = false;
523 docshell->GetIsActive(&isActive);
524 mAudioChannelAgent->SetVisibilityState(isActive);
525 }
527 int32_t state = 0;
528 mAudioChannelAgent->StartPlaying(&state);
529 SetCanPlay(state == AudioChannelState::AUDIO_CHANNEL_STATE_NORMAL);
530 }
532 void
533 AudioDestinationNode::NotifyStableState()
534 {
535 mExtraCurrentTimeUpdatedSinceLastStableState = false;
536 }
538 static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
540 void
541 AudioDestinationNode::ScheduleStableStateNotification()
542 {
543 nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
544 if (appShell) {
545 nsCOMPtr<nsIRunnable> event =
546 NS_NewRunnableMethod(this, &AudioDestinationNode::NotifyStableState);
547 appShell->RunInStableState(event);
548 }
549 }
551 double
552 AudioDestinationNode::ExtraCurrentTime()
553 {
554 if (!mStartedBlockingDueToBeingOnlyNode.IsNull() &&
555 !mExtraCurrentTimeUpdatedSinceLastStableState) {
556 mExtraCurrentTimeUpdatedSinceLastStableState = true;
557 mExtraCurrentTimeSinceLastStartedBlocking =
558 (TimeStamp::Now() - mStartedBlockingDueToBeingOnlyNode).ToSeconds();
559 ScheduleStableStateNotification();
560 }
561 return mExtraCurrentTime + mExtraCurrentTimeSinceLastStartedBlocking;
562 }
564 void
565 AudioDestinationNode::SetIsOnlyNodeForContext(bool aIsOnlyNode)
566 {
567 if (!mStartedBlockingDueToBeingOnlyNode.IsNull() == aIsOnlyNode) {
568 // Nothing changed.
569 return;
570 }
572 if (!mStream) {
573 // DestroyMediaStream has been called, presumably during CC Unlink().
574 return;
575 }
577 if (mIsOffline) {
578 // Don't block the destination stream for offline AudioContexts, since
579 // we expect the zero data produced when there are no other nodes to
580 // show up in its result buffer. Also, we would get confused by adding
581 // ExtraCurrentTime before StartRendering has even been called.
582 return;
583 }
585 if (aIsOnlyNode) {
586 mStream->ChangeExplicitBlockerCount(1);
587 mStartedBlockingDueToBeingOnlyNode = TimeStamp::Now();
588 // Don't do an update of mExtraCurrentTimeSinceLastStartedBlocking until the next stable state.
589 mExtraCurrentTimeUpdatedSinceLastStableState = true;
590 ScheduleStableStateNotification();
591 } else {
592 // Force update of mExtraCurrentTimeSinceLastStartedBlocking if necessary
593 ExtraCurrentTime();
594 mExtraCurrentTime += mExtraCurrentTimeSinceLastStartedBlocking;
595 mExtraCurrentTimeSinceLastStartedBlocking = 0;
596 mStream->ChangeExplicitBlockerCount(-1);
597 mStartedBlockingDueToBeingOnlyNode = TimeStamp();
598 }
599 }
601 }
603 }