1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/media/webaudio/ConvolverNode.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,279 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim:set ts=2 sw=2 sts=2 et cindent: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "ConvolverNode.h" 1.11 +#include "mozilla/dom/ConvolverNodeBinding.h" 1.12 +#include "AudioNodeEngine.h" 1.13 +#include "AudioNodeStream.h" 1.14 +#include "blink/Reverb.h" 1.15 +#include "PlayingRefChangeHandler.h" 1.16 + 1.17 +namespace mozilla { 1.18 +namespace dom { 1.19 + 1.20 +NS_IMPL_CYCLE_COLLECTION_INHERITED(ConvolverNode, AudioNode, mBuffer) 1.21 + 1.22 +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ConvolverNode) 1.23 +NS_INTERFACE_MAP_END_INHERITING(AudioNode) 1.24 + 1.25 +NS_IMPL_ADDREF_INHERITED(ConvolverNode, AudioNode) 1.26 +NS_IMPL_RELEASE_INHERITED(ConvolverNode, AudioNode) 1.27 + 1.28 +class ConvolverNodeEngine : public AudioNodeEngine 1.29 +{ 1.30 + typedef PlayingRefChangeHandler PlayingRefChanged; 1.31 +public: 1.32 + ConvolverNodeEngine(AudioNode* aNode, bool aNormalize) 1.33 + : AudioNodeEngine(aNode) 1.34 + , mBufferLength(0) 1.35 + , mLeftOverData(INT32_MIN) 1.36 + , mSampleRate(0.0f) 1.37 + , mUseBackgroundThreads(!aNode->Context()->IsOffline()) 1.38 + , mNormalize(aNormalize) 1.39 + { 1.40 + } 1.41 + 1.42 + enum Parameters { 1.43 + BUFFER_LENGTH, 1.44 + SAMPLE_RATE, 1.45 + NORMALIZE 1.46 + }; 1.47 + virtual void SetInt32Parameter(uint32_t aIndex, int32_t aParam) MOZ_OVERRIDE 1.48 + { 1.49 + switch (aIndex) { 1.50 + case BUFFER_LENGTH: 1.51 + // BUFFER_LENGTH is the first parameter that we set when setting a new buffer, 1.52 + // so we should be careful to invalidate the rest of our state here. 1.53 + mBuffer = nullptr; 1.54 + mSampleRate = 0.0f; 1.55 + mBufferLength = aParam; 1.56 + mLeftOverData = INT32_MIN; 1.57 + break; 1.58 + case SAMPLE_RATE: 1.59 + mSampleRate = aParam; 1.60 + break; 1.61 + case NORMALIZE: 1.62 + mNormalize = !!aParam; 1.63 + break; 1.64 + default: 1.65 + NS_ERROR("Bad ConvolverNodeEngine Int32Parameter"); 1.66 + } 1.67 + } 1.68 + virtual void SetDoubleParameter(uint32_t aIndex, double aParam) MOZ_OVERRIDE 1.69 + { 1.70 + switch (aIndex) { 1.71 + case SAMPLE_RATE: 1.72 + mSampleRate = aParam; 1.73 + AdjustReverb(); 1.74 + break; 1.75 + default: 1.76 + NS_ERROR("Bad ConvolverNodeEngine DoubleParameter"); 1.77 + } 1.78 + } 1.79 + virtual void SetBuffer(already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer) 1.80 + { 1.81 + mBuffer = aBuffer; 1.82 + AdjustReverb(); 1.83 + } 1.84 + 1.85 + void AdjustReverb() 1.86 + { 1.87 + // Note about empirical tuning (this is copied from Blink) 1.88 + // The maximum FFT size affects reverb performance and accuracy. 1.89 + // If the reverb is single-threaded and processes entirely in the real-time audio thread, 1.90 + // it's important not to make this too high. In this case 8192 is a good value. 1.91 + // But, the Reverb object is multi-threaded, so we want this as high as possible without losing too much accuracy. 1.92 + // Very large FFTs will have worse phase errors. Given these constraints 32768 is a good compromise. 1.93 + const size_t MaxFFTSize = 32768; 1.94 + 1.95 + if (!mBuffer || !mBufferLength || !mSampleRate) { 1.96 + mReverb = nullptr; 1.97 + mLeftOverData = INT32_MIN; 1.98 + return; 1.99 + } 1.100 + 1.101 + mReverb = new WebCore::Reverb(mBuffer, mBufferLength, 1.102 + WEBAUDIO_BLOCK_SIZE, 1.103 + MaxFFTSize, 2, mUseBackgroundThreads, 1.104 + mNormalize, mSampleRate); 1.105 + } 1.106 + 1.107 + virtual void ProcessBlock(AudioNodeStream* aStream, 1.108 + const AudioChunk& aInput, 1.109 + AudioChunk* aOutput, 1.110 + bool* aFinished) 1.111 + { 1.112 + if (!mReverb) { 1.113 + *aOutput = aInput; 1.114 + return; 1.115 + } 1.116 + 1.117 + AudioChunk input = aInput; 1.118 + if (aInput.IsNull()) { 1.119 + if (mLeftOverData > 0) { 1.120 + mLeftOverData -= WEBAUDIO_BLOCK_SIZE; 1.121 + AllocateAudioBlock(1, &input); 1.122 + WriteZeroesToAudioBlock(&input, 0, WEBAUDIO_BLOCK_SIZE); 1.123 + } else { 1.124 + if (mLeftOverData != INT32_MIN) { 1.125 + mLeftOverData = INT32_MIN; 1.126 + nsRefPtr<PlayingRefChanged> refchanged = 1.127 + new PlayingRefChanged(aStream, PlayingRefChanged::RELEASE); 1.128 + aStream->Graph()-> 1.129 + DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget()); 1.130 + } 1.131 + aOutput->SetNull(WEBAUDIO_BLOCK_SIZE); 1.132 + return; 1.133 + } 1.134 + } else { 1.135 + if (aInput.mVolume != 1.0f) { 1.136 + // Pre-multiply the input's volume 1.137 + uint32_t numChannels = aInput.mChannelData.Length(); 1.138 + AllocateAudioBlock(numChannels, &input); 1.139 + for (uint32_t i = 0; i < numChannels; ++i) { 1.140 + const float* src = static_cast<const float*>(aInput.mChannelData[i]); 1.141 + float* dest = static_cast<float*>(const_cast<void*>(input.mChannelData[i])); 1.142 + AudioBlockCopyChannelWithScale(src, aInput.mVolume, dest); 1.143 + } 1.144 + } 1.145 + 1.146 + if (mLeftOverData <= 0) { 1.147 + nsRefPtr<PlayingRefChanged> refchanged = 1.148 + new PlayingRefChanged(aStream, PlayingRefChanged::ADDREF); 1.149 + aStream->Graph()-> 1.150 + DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget()); 1.151 + } 1.152 + mLeftOverData = mBufferLength; 1.153 + MOZ_ASSERT(mLeftOverData > 0); 1.154 + } 1.155 + AllocateAudioBlock(2, aOutput); 1.156 + 1.157 + mReverb->process(&input, aOutput, WEBAUDIO_BLOCK_SIZE); 1.158 + } 1.159 + 1.160 + virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE 1.161 + { 1.162 + size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf); 1.163 + if (mBuffer && !mBuffer->IsShared()) { 1.164 + amount += mBuffer->SizeOfIncludingThis(aMallocSizeOf); 1.165 + } 1.166 + 1.167 + if (mReverb) { 1.168 + amount += mReverb->sizeOfIncludingThis(aMallocSizeOf); 1.169 + } 1.170 + 1.171 + return amount; 1.172 + } 1.173 + 1.174 + virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE 1.175 + { 1.176 + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); 1.177 + } 1.178 + 1.179 +private: 1.180 + nsRefPtr<ThreadSharedFloatArrayBufferList> mBuffer; 1.181 + nsAutoPtr<WebCore::Reverb> mReverb; 1.182 + int32_t mBufferLength; 1.183 + int32_t mLeftOverData; 1.184 + float mSampleRate; 1.185 + bool mUseBackgroundThreads; 1.186 + bool mNormalize; 1.187 +}; 1.188 + 1.189 +ConvolverNode::ConvolverNode(AudioContext* aContext) 1.190 + : AudioNode(aContext, 1.191 + 2, 1.192 + ChannelCountMode::Clamped_max, 1.193 + ChannelInterpretation::Speakers) 1.194 + , mNormalize(true) 1.195 +{ 1.196 + ConvolverNodeEngine* engine = new ConvolverNodeEngine(this, mNormalize); 1.197 + mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM); 1.198 +} 1.199 + 1.200 +size_t 1.201 +ConvolverNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const 1.202 +{ 1.203 + size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf); 1.204 + if (mBuffer) { 1.205 + // NB: mBuffer might be shared with the associated engine, by convention 1.206 + // the AudioNode will report. 1.207 + amount += mBuffer->SizeOfIncludingThis(aMallocSizeOf); 1.208 + } 1.209 + return amount; 1.210 +} 1.211 + 1.212 +size_t 1.213 +ConvolverNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const 1.214 +{ 1.215 + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); 1.216 +} 1.217 + 1.218 +JSObject* 1.219 +ConvolverNode::WrapObject(JSContext* aCx) 1.220 +{ 1.221 + return ConvolverNodeBinding::Wrap(aCx, this); 1.222 +} 1.223 + 1.224 +void 1.225 +ConvolverNode::SetBuffer(JSContext* aCx, AudioBuffer* aBuffer, ErrorResult& aRv) 1.226 +{ 1.227 + if (aBuffer) { 1.228 + switch (aBuffer->NumberOfChannels()) { 1.229 + case 1: 1.230 + case 2: 1.231 + case 4: 1.232 + // Supported number of channels 1.233 + break; 1.234 + default: 1.235 + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); 1.236 + return; 1.237 + } 1.238 + } 1.239 + 1.240 + mBuffer = aBuffer; 1.241 + 1.242 + // Send the buffer to the stream 1.243 + AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get()); 1.244 + MOZ_ASSERT(ns, "Why don't we have a stream here?"); 1.245 + if (mBuffer) { 1.246 + uint32_t length = mBuffer->Length(); 1.247 + nsRefPtr<ThreadSharedFloatArrayBufferList> data = 1.248 + mBuffer->GetThreadSharedChannelsForRate(aCx); 1.249 + if (data && length < WEBAUDIO_BLOCK_SIZE) { 1.250 + // For very small impulse response buffers, we need to pad the 1.251 + // buffer with 0 to make sure that the Reverb implementation 1.252 + // has enough data to compute FFTs from. 1.253 + length = WEBAUDIO_BLOCK_SIZE; 1.254 + nsRefPtr<ThreadSharedFloatArrayBufferList> paddedBuffer = 1.255 + new ThreadSharedFloatArrayBufferList(data->GetChannels()); 1.256 + float* channelData = (float*) malloc(sizeof(float) * length * data->GetChannels()); 1.257 + for (uint32_t i = 0; i < data->GetChannels(); ++i) { 1.258 + PodCopy(channelData + length * i, data->GetData(i), mBuffer->Length()); 1.259 + PodZero(channelData + length * i + mBuffer->Length(), WEBAUDIO_BLOCK_SIZE - mBuffer->Length()); 1.260 + paddedBuffer->SetData(i, (i == 0) ? channelData : nullptr, channelData); 1.261 + } 1.262 + data = paddedBuffer; 1.263 + } 1.264 + SendInt32ParameterToStream(ConvolverNodeEngine::BUFFER_LENGTH, length); 1.265 + SendDoubleParameterToStream(ConvolverNodeEngine::SAMPLE_RATE, 1.266 + mBuffer->SampleRate()); 1.267 + ns->SetBuffer(data.forget()); 1.268 + } else { 1.269 + ns->SetBuffer(nullptr); 1.270 + } 1.271 +} 1.272 + 1.273 +void 1.274 +ConvolverNode::SetNormalize(bool aNormalize) 1.275 +{ 1.276 + mNormalize = aNormalize; 1.277 + SendInt32ParameterToStream(ConvolverNodeEngine::NORMALIZE, aNormalize); 1.278 +} 1.279 + 1.280 +} 1.281 +} 1.282 +