diff -r 000000000000 -r 6474c204b198 content/media/webaudio/AudioContext.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/content/media/webaudio/AudioContext.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,693 @@ +/* -*- 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 "AudioContext.h" + +#include "nsPIDOMWindow.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/AnalyserNode.h" +#include "mozilla/dom/AudioContextBinding.h" +#include "mozilla/dom/HTMLMediaElement.h" +#include "mozilla/dom/OfflineAudioContextBinding.h" +#include "mozilla/dom/OwningNonNull.h" +#include "MediaStreamGraph.h" +#include "AudioDestinationNode.h" +#include "AudioBufferSourceNode.h" +#include "AudioBuffer.h" +#include "GainNode.h" +#include "MediaElementAudioSourceNode.h" +#include "MediaStreamAudioSourceNode.h" +#include "DelayNode.h" +#include "PannerNode.h" +#include "AudioListener.h" +#include "DynamicsCompressorNode.h" +#include "BiquadFilterNode.h" +#include "ScriptProcessorNode.h" +#include "ChannelMergerNode.h" +#include "ChannelSplitterNode.h" +#include "MediaStreamAudioDestinationNode.h" +#include "WaveShaperNode.h" +#include "PeriodicWave.h" +#include "ConvolverNode.h" +#include "OscillatorNode.h" +#include "nsNetUtil.h" +#include "AudioStream.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_CLASS(AudioContext) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AudioContext) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDestination) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mListener) + if (!tmp->mIsStarted) { + NS_IMPL_CYCLE_COLLECTION_UNLINK(mActiveNodes) + } +NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(DOMEventTargetHelper) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AudioContext, + DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDestination) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListener) + if (!tmp->mIsStarted) { + MOZ_ASSERT(tmp->mIsOffline, + "Online AudioContexts should always be started"); + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActiveNodes) + } +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_ADDREF_INHERITED(AudioContext, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(AudioContext, DOMEventTargetHelper) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AudioContext) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +static float GetSampleRateForAudioContext(bool aIsOffline, float aSampleRate) +{ + if (aIsOffline) { + return aSampleRate; + } else { + AudioStream::InitPreferredSampleRate(); + return static_cast(AudioStream::PreferredSampleRate()); + } +} + +AudioContext::AudioContext(nsPIDOMWindow* aWindow, + bool aIsOffline, + AudioChannel aChannel, + uint32_t aNumberOfChannels, + uint32_t aLength, + float aSampleRate) + : DOMEventTargetHelper(aWindow) + , mSampleRate(GetSampleRateForAudioContext(aIsOffline, aSampleRate)) + , mNumberOfChannels(aNumberOfChannels) + , mNodeCount(0) + , mIsOffline(aIsOffline) + , mIsStarted(!aIsOffline) + , mIsShutDown(false) +{ + aWindow->AddAudioContext(this); + + // Note: AudioDestinationNode needs an AudioContext that must already be + // bound to the window. + mDestination = new AudioDestinationNode(this, aIsOffline, aChannel, + aNumberOfChannels, aLength, aSampleRate); + // We skip calling SetIsOnlyNodeForContext during mDestination's constructor, + // because we can only call SetIsOnlyNodeForContext after mDestination has + // been set up. + mDestination->SetIsOnlyNodeForContext(true); +} + +AudioContext::~AudioContext() +{ + nsPIDOMWindow* window = GetOwner(); + if (window) { + window->RemoveAudioContext(this); + } + + UnregisterWeakMemoryReporter(this); +} + +JSObject* +AudioContext::WrapObject(JSContext* aCx) +{ + if (mIsOffline) { + return OfflineAudioContextBinding::Wrap(aCx, this); + } else { + return AudioContextBinding::Wrap(aCx, this); + } +} + +/* static */ already_AddRefed +AudioContext::Constructor(const GlobalObject& aGlobal, + ErrorResult& aRv) +{ + nsCOMPtr window = do_QueryInterface(aGlobal.GetAsSupports()); + if (!window) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + nsRefPtr object = new AudioContext(window, false); + + RegisterWeakMemoryReporter(object); + + return object.forget(); +} + +/* static */ already_AddRefed +AudioContext::Constructor(const GlobalObject& aGlobal, + AudioChannel aChannel, + ErrorResult& aRv) +{ + nsCOMPtr window = do_QueryInterface(aGlobal.GetAsSupports()); + if (!window) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + nsRefPtr object = new AudioContext(window, false, aChannel); + + RegisterWeakMemoryReporter(object); + + return object.forget(); +} + +/* static */ already_AddRefed +AudioContext::Constructor(const GlobalObject& aGlobal, + uint32_t aNumberOfChannels, + uint32_t aLength, + float aSampleRate, + ErrorResult& aRv) +{ + nsCOMPtr window = do_QueryInterface(aGlobal.GetAsSupports()); + if (!window) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + if (aNumberOfChannels == 0 || + aNumberOfChannels > WebAudioUtils::MaxChannelCount || + aLength == 0 || + aSampleRate < WebAudioUtils::MinSampleRate || + aSampleRate > WebAudioUtils::MaxSampleRate) { + // The DOM binding protects us against infinity and NaN + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return nullptr; + } + + nsRefPtr object = new AudioContext(window, + true, + AudioChannel::Normal, + aNumberOfChannels, + aLength, + aSampleRate); + + RegisterWeakMemoryReporter(object); + + return object.forget(); +} + +already_AddRefed +AudioContext::CreateBufferSource() +{ + nsRefPtr bufferNode = + new AudioBufferSourceNode(this); + return bufferNode.forget(); +} + +already_AddRefed +AudioContext::CreateBuffer(JSContext* aJSContext, uint32_t aNumberOfChannels, + uint32_t aLength, float aSampleRate, + ErrorResult& aRv) +{ + if (!aNumberOfChannels) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; + } + + return AudioBuffer::Create(this, aNumberOfChannels, aLength, + aSampleRate, aJSContext, aRv); +} + +namespace { + +bool IsValidBufferSize(uint32_t aBufferSize) { + switch (aBufferSize) { + case 0: // let the implementation choose the buffer size + case 256: + case 512: + case 1024: + case 2048: + case 4096: + case 8192: + case 16384: + return true; + default: + return false; + } +} + +} + +already_AddRefed +AudioContext::CreateMediaStreamDestination(ErrorResult& aRv) +{ + if (mIsOffline) { + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return nullptr; + } + + nsRefPtr node = + new MediaStreamAudioDestinationNode(this); + return node.forget(); +} + +already_AddRefed +AudioContext::CreateScriptProcessor(uint32_t aBufferSize, + uint32_t aNumberOfInputChannels, + uint32_t aNumberOfOutputChannels, + ErrorResult& aRv) +{ + if ((aNumberOfInputChannels == 0 && aNumberOfOutputChannels == 0) || + aNumberOfInputChannels > WebAudioUtils::MaxChannelCount || + aNumberOfOutputChannels > WebAudioUtils::MaxChannelCount || + !IsValidBufferSize(aBufferSize)) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; + } + + nsRefPtr scriptProcessor = + new ScriptProcessorNode(this, aBufferSize, aNumberOfInputChannels, + aNumberOfOutputChannels); + return scriptProcessor.forget(); +} + +already_AddRefed +AudioContext::CreateAnalyser() +{ + nsRefPtr analyserNode = new AnalyserNode(this); + return analyserNode.forget(); +} + +already_AddRefed +AudioContext::CreateMediaElementSource(HTMLMediaElement& aMediaElement, + ErrorResult& aRv) +{ + if (mIsOffline) { + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return nullptr; + } + nsRefPtr stream = aMediaElement.MozCaptureStream(aRv); + if (aRv.Failed()) { + return nullptr; + } + nsRefPtr mediaElementAudioSourceNode = + new MediaElementAudioSourceNode(this, stream); + return mediaElementAudioSourceNode.forget(); +} + +already_AddRefed +AudioContext::CreateMediaStreamSource(DOMMediaStream& aMediaStream, + ErrorResult& aRv) +{ + if (mIsOffline) { + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return nullptr; + } + nsRefPtr mediaStreamAudioSourceNode = + new MediaStreamAudioSourceNode(this, &aMediaStream); + return mediaStreamAudioSourceNode.forget(); +} + +already_AddRefed +AudioContext::CreateGain() +{ + nsRefPtr gainNode = new GainNode(this); + return gainNode.forget(); +} + +already_AddRefed +AudioContext::CreateWaveShaper() +{ + nsRefPtr waveShaperNode = new WaveShaperNode(this); + return waveShaperNode.forget(); +} + +already_AddRefed +AudioContext::CreateDelay(double aMaxDelayTime, ErrorResult& aRv) +{ + if (aMaxDelayTime > 0. && aMaxDelayTime < 180.) { + nsRefPtr delayNode = new DelayNode(this, aMaxDelayTime); + return delayNode.forget(); + } + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return nullptr; +} + +already_AddRefed +AudioContext::CreatePanner() +{ + nsRefPtr pannerNode = new PannerNode(this); + mPannerNodes.PutEntry(pannerNode); + return pannerNode.forget(); +} + +already_AddRefed +AudioContext::CreateConvolver() +{ + nsRefPtr convolverNode = new ConvolverNode(this); + return convolverNode.forget(); +} + +already_AddRefed +AudioContext::CreateChannelSplitter(uint32_t aNumberOfOutputs, ErrorResult& aRv) +{ + if (aNumberOfOutputs == 0 || + aNumberOfOutputs > WebAudioUtils::MaxChannelCount) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; + } + + nsRefPtr splitterNode = + new ChannelSplitterNode(this, aNumberOfOutputs); + return splitterNode.forget(); +} + +already_AddRefed +AudioContext::CreateChannelMerger(uint32_t aNumberOfInputs, ErrorResult& aRv) +{ + if (aNumberOfInputs == 0 || + aNumberOfInputs > WebAudioUtils::MaxChannelCount) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; + } + + nsRefPtr mergerNode = + new ChannelMergerNode(this, aNumberOfInputs); + return mergerNode.forget(); +} + +already_AddRefed +AudioContext::CreateDynamicsCompressor() +{ + nsRefPtr compressorNode = + new DynamicsCompressorNode(this); + return compressorNode.forget(); +} + +already_AddRefed +AudioContext::CreateBiquadFilter() +{ + nsRefPtr filterNode = + new BiquadFilterNode(this); + return filterNode.forget(); +} + +already_AddRefed +AudioContext::CreateOscillator() +{ + nsRefPtr oscillatorNode = + new OscillatorNode(this); + return oscillatorNode.forget(); +} + +already_AddRefed +AudioContext::CreatePeriodicWave(const Float32Array& aRealData, + const Float32Array& aImagData, + ErrorResult& aRv) +{ + aRealData.ComputeLengthAndData(); + aImagData.ComputeLengthAndData(); + + if (aRealData.Length() != aImagData.Length() || + aRealData.Length() == 0 || + aRealData.Length() > 4096) { + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return nullptr; + } + + nsRefPtr periodicWave = + new PeriodicWave(this, aRealData.Data(), aImagData.Data(), + aImagData.Length(), aRv); + if (aRv.Failed()) { + return nullptr; + } + return periodicWave.forget(); +} + +AudioListener* +AudioContext::Listener() +{ + if (!mListener) { + mListener = new AudioListener(this); + } + return mListener; +} + +void +AudioContext::DecodeAudioData(const ArrayBuffer& aBuffer, + DecodeSuccessCallback& aSuccessCallback, + const Optional >& aFailureCallback) +{ + AutoJSAPI jsapi; + JSContext* cx = jsapi.cx(); + JSAutoCompartment ac(cx, aBuffer.Obj()); + + aBuffer.ComputeLengthAndData(); + + // Neuter the array buffer + size_t length = aBuffer.Length(); + JS::RootedObject obj(cx, aBuffer.Obj()); + + uint8_t* data = static_cast(JS_StealArrayBufferContents(cx, obj)); + + // Sniff the content of the media. + // Failed type sniffing will be handled by AsyncDecodeMedia. + nsAutoCString contentType; + NS_SniffContent(NS_DATA_SNIFFER_CATEGORY, nullptr, data, length, contentType); + + nsRefPtr failureCallback; + if (aFailureCallback.WasPassed()) { + failureCallback = &aFailureCallback.Value(); + } + nsRefPtr job( + new WebAudioDecodeJob(contentType, this, + &aSuccessCallback, failureCallback)); + mDecoder.AsyncDecodeMedia(contentType.get(), data, length, *job); + // Transfer the ownership to mDecodeJobs + mDecodeJobs.AppendElement(job); +} + +void +AudioContext::RemoveFromDecodeQueue(WebAudioDecodeJob* aDecodeJob) +{ + mDecodeJobs.RemoveElement(aDecodeJob); +} + +void +AudioContext::RegisterActiveNode(AudioNode* aNode) +{ + if (!mIsShutDown) { + mActiveNodes.PutEntry(aNode); + } +} + +void +AudioContext::UnregisterActiveNode(AudioNode* aNode) +{ + mActiveNodes.RemoveEntry(aNode); +} + +void +AudioContext::UnregisterAudioBufferSourceNode(AudioBufferSourceNode* aNode) +{ + UpdatePannerSource(); +} + +void +AudioContext::UnregisterPannerNode(PannerNode* aNode) +{ + mPannerNodes.RemoveEntry(aNode); + if (mListener) { + mListener->UnregisterPannerNode(aNode); + } +} + +static PLDHashOperator +FindConnectedSourcesOn(nsPtrHashKey* aEntry, void* aData) +{ + aEntry->GetKey()->FindConnectedSources(); + return PL_DHASH_NEXT; +} + +void +AudioContext::UpdatePannerSource() +{ + mPannerNodes.EnumerateEntries(FindConnectedSourcesOn, nullptr); +} + +uint32_t +AudioContext::MaxChannelCount() const +{ + return mIsOffline ? mNumberOfChannels : AudioStream::MaxNumberOfChannels(); +} + +MediaStreamGraph* +AudioContext::Graph() const +{ + return Destination()->Stream()->Graph(); +} + +MediaStream* +AudioContext::DestinationStream() const +{ + if (Destination()) { + return Destination()->Stream(); + } + return nullptr; +} + +double +AudioContext::CurrentTime() const +{ + return MediaTimeToSeconds(Destination()->Stream()->GetCurrentTime()) + + ExtraCurrentTime(); +} + +void +AudioContext::Shutdown() +{ + mIsShutDown = true; + + // We mute rather than suspending, because the delay between the ::Shutdown + // call and the CC would make us overbuffer in the MediaStreamGraph. + // See bug 936784 for details. + if (!mIsOffline) { + Mute(); + } + + mDecoder.Shutdown(); + + // Release references to active nodes. + // Active AudioNodes don't unregister in destructors, at which point the + // Node is already unregistered. + mActiveNodes.Clear(); + + // For offline contexts, we can destroy the MediaStreamGraph at this point. + if (mIsOffline && mDestination) { + mDestination->OfflineShutdown(); + } +} + +void +AudioContext::Suspend() +{ + MediaStream* ds = DestinationStream(); + if (ds) { + ds->ChangeExplicitBlockerCount(1); + } +} + +void +AudioContext::Resume() +{ + MediaStream* ds = DestinationStream(); + if (ds) { + ds->ChangeExplicitBlockerCount(-1); + } +} + +void +AudioContext::UpdateNodeCount(int32_t aDelta) +{ + bool firstNode = mNodeCount == 0; + mNodeCount += aDelta; + MOZ_ASSERT(mNodeCount >= 0); + // mDestinationNode may be null when we're destroying nodes unlinked by CC + if (!firstNode && mDestination) { + mDestination->SetIsOnlyNodeForContext(mNodeCount == 1); + } +} + +JSContext* +AudioContext::GetJSContext() const +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr scriptGlobal = + do_QueryInterface(GetParentObject()); + if (!scriptGlobal) { + return nullptr; + } + nsIScriptContext* scriptContext = scriptGlobal->GetContext(); + if (!scriptContext) { + return nullptr; + } + return scriptContext->GetNativeContext(); +} + +void +AudioContext::StartRendering(ErrorResult& aRv) +{ + MOZ_ASSERT(mIsOffline, "This should only be called on OfflineAudioContext"); + if (mIsStarted) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + mIsStarted = true; + mDestination->StartRendering(); +} + +void +AudioContext::Mute() const +{ + MOZ_ASSERT(!mIsOffline); + if (mDestination) { + mDestination->Mute(); + } +} + +void +AudioContext::Unmute() const +{ + MOZ_ASSERT(!mIsOffline); + if (mDestination) { + mDestination->Unmute(); + } +} + +AudioChannel +AudioContext::MozAudioChannelType() const +{ + return mDestination->MozAudioChannelType(); +} + +void +AudioContext::SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv) +{ + mDestination->SetMozAudioChannelType(aValue, aRv); +} + +size_t +AudioContext::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const +{ + // AudioNodes are tracked separately because we do not want the AudioContext + // to track all of the AudioNodes it creates, so we wouldn't be able to + // traverse them from here. + + size_t amount = aMallocSizeOf(this); + if (mListener) { + amount += mListener->SizeOfIncludingThis(aMallocSizeOf); + } + amount += mDecoder.SizeOfExcludingThis(aMallocSizeOf); + amount += mDecodeJobs.SizeOfExcludingThis(aMallocSizeOf); + for (uint32_t i = 0; i < mDecodeJobs.Length(); ++i) { + amount += mDecodeJobs[i]->SizeOfExcludingThis(aMallocSizeOf); + } + amount += mActiveNodes.SizeOfExcludingThis(nullptr, aMallocSizeOf); + amount += mPannerNodes.SizeOfExcludingThis(nullptr, aMallocSizeOf); + return amount; +} + +NS_IMETHODIMP +AudioContext::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData) +{ + int64_t amount = SizeOfIncludingThis(MallocSizeOf); + return MOZ_COLLECT_REPORT("explicit/webaudio/audiocontext", KIND_HEAP, UNITS_BYTES, + amount, "Memory used by AudioContext objects (Web Audio)."); +} + +double +AudioContext::ExtraCurrentTime() const +{ + return mDestination->ExtraCurrentTime(); +} + +} +}