1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/media/webaudio/AnalyserNode.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,343 @@ 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 "mozilla/dom/AnalyserNode.h" 1.11 +#include "mozilla/dom/AnalyserNodeBinding.h" 1.12 +#include "AudioNodeEngine.h" 1.13 +#include "AudioNodeStream.h" 1.14 +#include "mozilla/Mutex.h" 1.15 +#include "mozilla/PodOperations.h" 1.16 + 1.17 +namespace mozilla { 1.18 +namespace dom { 1.19 + 1.20 +NS_IMPL_ISUPPORTS_INHERITED0(AnalyserNode, AudioNode) 1.21 + 1.22 +class AnalyserNodeEngine : public AudioNodeEngine 1.23 +{ 1.24 + class TransferBuffer : public nsRunnable 1.25 + { 1.26 + public: 1.27 + TransferBuffer(AudioNodeStream* aStream, 1.28 + const AudioChunk& aChunk) 1.29 + : mStream(aStream) 1.30 + , mChunk(aChunk) 1.31 + { 1.32 + } 1.33 + 1.34 + NS_IMETHOD Run() 1.35 + { 1.36 + nsRefPtr<AnalyserNode> node; 1.37 + { 1.38 + // No need to keep holding the lock for the whole duration of this 1.39 + // function, since we're holding a strong reference to it, so if 1.40 + // we can obtain the reference, we will hold the node alive in 1.41 + // this function. 1.42 + MutexAutoLock lock(mStream->Engine()->NodeMutex()); 1.43 + node = static_cast<AnalyserNode*>(mStream->Engine()->Node()); 1.44 + } 1.45 + if (node) { 1.46 + node->AppendChunk(mChunk); 1.47 + } 1.48 + return NS_OK; 1.49 + } 1.50 + 1.51 + private: 1.52 + nsRefPtr<AudioNodeStream> mStream; 1.53 + AudioChunk mChunk; 1.54 + }; 1.55 + 1.56 +public: 1.57 + explicit AnalyserNodeEngine(AnalyserNode* aNode) 1.58 + : AudioNodeEngine(aNode) 1.59 + { 1.60 + MOZ_ASSERT(NS_IsMainThread()); 1.61 + } 1.62 + 1.63 + virtual void ProcessBlock(AudioNodeStream* aStream, 1.64 + const AudioChunk& aInput, 1.65 + AudioChunk* aOutput, 1.66 + bool* aFinished) MOZ_OVERRIDE 1.67 + { 1.68 + *aOutput = aInput; 1.69 + 1.70 + MutexAutoLock lock(NodeMutex()); 1.71 + 1.72 + if (Node() && 1.73 + aInput.mChannelData.Length() > 0) { 1.74 + nsRefPtr<TransferBuffer> transfer = new TransferBuffer(aStream, aInput); 1.75 + NS_DispatchToMainThread(transfer); 1.76 + } 1.77 + } 1.78 + 1.79 + virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE 1.80 + { 1.81 + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); 1.82 + } 1.83 +}; 1.84 + 1.85 +AnalyserNode::AnalyserNode(AudioContext* aContext) 1.86 + : AudioNode(aContext, 1.87 + 1, 1.88 + ChannelCountMode::Explicit, 1.89 + ChannelInterpretation::Speakers) 1.90 + , mAnalysisBlock(2048) 1.91 + , mMinDecibels(-100.) 1.92 + , mMaxDecibels(-30.) 1.93 + , mSmoothingTimeConstant(.8) 1.94 + , mWriteIndex(0) 1.95 +{ 1.96 + mStream = aContext->Graph()->CreateAudioNodeStream(new AnalyserNodeEngine(this), 1.97 + MediaStreamGraph::INTERNAL_STREAM); 1.98 + AllocateBuffer(); 1.99 +} 1.100 + 1.101 +size_t 1.102 +AnalyserNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const 1.103 +{ 1.104 + size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf); 1.105 + amount += mAnalysisBlock.SizeOfExcludingThis(aMallocSizeOf); 1.106 + amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf); 1.107 + amount += mOutputBuffer.SizeOfExcludingThis(aMallocSizeOf); 1.108 + return amount; 1.109 +} 1.110 + 1.111 +size_t 1.112 +AnalyserNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const 1.113 +{ 1.114 + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); 1.115 +} 1.116 + 1.117 +JSObject* 1.118 +AnalyserNode::WrapObject(JSContext* aCx) 1.119 +{ 1.120 + return AnalyserNodeBinding::Wrap(aCx, this); 1.121 +} 1.122 + 1.123 +void 1.124 +AnalyserNode::SetFftSize(uint32_t aValue, ErrorResult& aRv) 1.125 +{ 1.126 + // Disallow values that are not a power of 2 and outside the [32,2048] range 1.127 + if (aValue < 32 || 1.128 + aValue > 2048 || 1.129 + (aValue & (aValue - 1)) != 0) { 1.130 + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); 1.131 + return; 1.132 + } 1.133 + if (FftSize() != aValue) { 1.134 + mAnalysisBlock.SetFFTSize(aValue); 1.135 + AllocateBuffer(); 1.136 + } 1.137 +} 1.138 + 1.139 +void 1.140 +AnalyserNode::SetMinDecibels(double aValue, ErrorResult& aRv) 1.141 +{ 1.142 + if (aValue >= mMaxDecibels) { 1.143 + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); 1.144 + return; 1.145 + } 1.146 + mMinDecibels = aValue; 1.147 +} 1.148 + 1.149 +void 1.150 +AnalyserNode::SetMaxDecibels(double aValue, ErrorResult& aRv) 1.151 +{ 1.152 + if (aValue <= mMinDecibels) { 1.153 + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); 1.154 + return; 1.155 + } 1.156 + mMaxDecibels = aValue; 1.157 +} 1.158 + 1.159 +void 1.160 +AnalyserNode::SetSmoothingTimeConstant(double aValue, ErrorResult& aRv) 1.161 +{ 1.162 + if (aValue < 0 || aValue > 1) { 1.163 + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); 1.164 + return; 1.165 + } 1.166 + mSmoothingTimeConstant = aValue; 1.167 +} 1.168 + 1.169 +void 1.170 +AnalyserNode::GetFloatFrequencyData(const Float32Array& aArray) 1.171 +{ 1.172 + if (!FFTAnalysis()) { 1.173 + // Might fail to allocate memory 1.174 + return; 1.175 + } 1.176 + 1.177 + aArray.ComputeLengthAndData(); 1.178 + 1.179 + float* buffer = aArray.Data(); 1.180 + uint32_t length = std::min(aArray.Length(), mOutputBuffer.Length()); 1.181 + 1.182 + for (uint32_t i = 0; i < length; ++i) { 1.183 + buffer[i] = WebAudioUtils::ConvertLinearToDecibels(mOutputBuffer[i], mMinDecibels); 1.184 + } 1.185 +} 1.186 + 1.187 +void 1.188 +AnalyserNode::GetByteFrequencyData(const Uint8Array& aArray) 1.189 +{ 1.190 + if (!FFTAnalysis()) { 1.191 + // Might fail to allocate memory 1.192 + return; 1.193 + } 1.194 + 1.195 + const double rangeScaleFactor = 1.0 / (mMaxDecibels - mMinDecibels); 1.196 + 1.197 + aArray.ComputeLengthAndData(); 1.198 + 1.199 + unsigned char* buffer = aArray.Data(); 1.200 + uint32_t length = std::min(aArray.Length(), mOutputBuffer.Length()); 1.201 + 1.202 + for (uint32_t i = 0; i < length; ++i) { 1.203 + const double decibels = WebAudioUtils::ConvertLinearToDecibels(mOutputBuffer[i], mMinDecibels); 1.204 + // scale down the value to the range of [0, UCHAR_MAX] 1.205 + const double scaled = std::max(0.0, std::min(double(UCHAR_MAX), 1.206 + UCHAR_MAX * (decibels - mMinDecibels) * rangeScaleFactor)); 1.207 + buffer[i] = static_cast<unsigned char>(scaled); 1.208 + } 1.209 +} 1.210 + 1.211 +void 1.212 +AnalyserNode::GetFloatTimeDomainData(const Float32Array& aArray) 1.213 +{ 1.214 + aArray.ComputeLengthAndData(); 1.215 + 1.216 + float* buffer = aArray.Data(); 1.217 + uint32_t length = std::min(aArray.Length(), mBuffer.Length()); 1.218 + 1.219 + for (uint32_t i = 0; i < length; ++i) { 1.220 + buffer[i] = mBuffer[(i + mWriteIndex) % mBuffer.Length()];; 1.221 + } 1.222 +} 1.223 + 1.224 +void 1.225 +AnalyserNode::GetByteTimeDomainData(const Uint8Array& aArray) 1.226 +{ 1.227 + aArray.ComputeLengthAndData(); 1.228 + 1.229 + unsigned char* buffer = aArray.Data(); 1.230 + uint32_t length = std::min(aArray.Length(), mBuffer.Length()); 1.231 + 1.232 + for (uint32_t i = 0; i < length; ++i) { 1.233 + const float value = mBuffer[(i + mWriteIndex) % mBuffer.Length()]; 1.234 + // scale the value to the range of [0, UCHAR_MAX] 1.235 + const float scaled = std::max(0.0f, std::min(float(UCHAR_MAX), 1.236 + 128.0f * (value + 1.0f))); 1.237 + buffer[i] = static_cast<unsigned char>(scaled); 1.238 + } 1.239 +} 1.240 + 1.241 +bool 1.242 +AnalyserNode::FFTAnalysis() 1.243 +{ 1.244 + float* inputBuffer; 1.245 + bool allocated = false; 1.246 + if (mWriteIndex == 0) { 1.247 + inputBuffer = mBuffer.Elements(); 1.248 + } else { 1.249 + inputBuffer = static_cast<float*>(moz_malloc(FftSize() * sizeof(float))); 1.250 + if (!inputBuffer) { 1.251 + return false; 1.252 + } 1.253 + memcpy(inputBuffer, mBuffer.Elements() + mWriteIndex, sizeof(float) * (FftSize() - mWriteIndex)); 1.254 + memcpy(inputBuffer + FftSize() - mWriteIndex, mBuffer.Elements(), sizeof(float) * mWriteIndex); 1.255 + allocated = true; 1.256 + } 1.257 + 1.258 + ApplyBlackmanWindow(inputBuffer, FftSize()); 1.259 + 1.260 + mAnalysisBlock.PerformFFT(inputBuffer); 1.261 + 1.262 + // Normalize so than an input sine wave at 0dBfs registers as 0dBfs (undo FFT scaling factor). 1.263 + const double magnitudeScale = 1.0 / FftSize(); 1.264 + 1.265 + for (uint32_t i = 0; i < mOutputBuffer.Length(); ++i) { 1.266 + double scalarMagnitude = NS_hypot(mAnalysisBlock.RealData(i), 1.267 + mAnalysisBlock.ImagData(i)) * 1.268 + magnitudeScale; 1.269 + mOutputBuffer[i] = mSmoothingTimeConstant * mOutputBuffer[i] + 1.270 + (1.0 - mSmoothingTimeConstant) * scalarMagnitude; 1.271 + } 1.272 + 1.273 + if (allocated) { 1.274 + moz_free(inputBuffer); 1.275 + } 1.276 + return true; 1.277 +} 1.278 + 1.279 +void 1.280 +AnalyserNode::ApplyBlackmanWindow(float* aBuffer, uint32_t aSize) 1.281 +{ 1.282 + double alpha = 0.16; 1.283 + double a0 = 0.5 * (1.0 - alpha); 1.284 + double a1 = 0.5; 1.285 + double a2 = 0.5 * alpha; 1.286 + 1.287 + for (uint32_t i = 0; i < aSize; ++i) { 1.288 + double x = double(i) / aSize; 1.289 + double window = a0 - a1 * cos(2 * M_PI * x) + a2 * cos(4 * M_PI * x); 1.290 + aBuffer[i] *= window; 1.291 + } 1.292 +} 1.293 + 1.294 +bool 1.295 +AnalyserNode::AllocateBuffer() 1.296 +{ 1.297 + bool result = true; 1.298 + if (mBuffer.Length() != FftSize()) { 1.299 + result = mBuffer.SetLength(FftSize()); 1.300 + if (result) { 1.301 + memset(mBuffer.Elements(), 0, sizeof(float) * FftSize()); 1.302 + mWriteIndex = 0; 1.303 + 1.304 + result = mOutputBuffer.SetLength(FrequencyBinCount()); 1.305 + if (result) { 1.306 + memset(mOutputBuffer.Elements(), 0, sizeof(float) * FrequencyBinCount()); 1.307 + } 1.308 + } 1.309 + } 1.310 + return result; 1.311 +} 1.312 + 1.313 +void 1.314 +AnalyserNode::AppendChunk(const AudioChunk& aChunk) 1.315 +{ 1.316 + const uint32_t bufferSize = mBuffer.Length(); 1.317 + const uint32_t channelCount = aChunk.mChannelData.Length(); 1.318 + uint32_t chunkDuration = aChunk.mDuration; 1.319 + MOZ_ASSERT((bufferSize & (bufferSize - 1)) == 0); // Must be a power of two! 1.320 + MOZ_ASSERT(channelCount > 0); 1.321 + MOZ_ASSERT(chunkDuration == WEBAUDIO_BLOCK_SIZE); 1.322 + 1.323 + if (chunkDuration > bufferSize) { 1.324 + // Copy a maximum bufferSize samples. 1.325 + chunkDuration = bufferSize; 1.326 + } 1.327 + 1.328 + PodCopy(mBuffer.Elements() + mWriteIndex, static_cast<const float*>(aChunk.mChannelData[0]), chunkDuration); 1.329 + for (uint32_t i = 1; i < channelCount; ++i) { 1.330 + AudioBlockAddChannelWithScale(static_cast<const float*>(aChunk.mChannelData[i]), 1.0f, 1.331 + mBuffer.Elements() + mWriteIndex); 1.332 + } 1.333 + if (channelCount > 1) { 1.334 + AudioBlockInPlaceScale(mBuffer.Elements() + mWriteIndex, 1.335 + 1.0f / aChunk.mChannelData.Length()); 1.336 + } 1.337 + mWriteIndex += chunkDuration; 1.338 + MOZ_ASSERT(mWriteIndex <= bufferSize); 1.339 + if (mWriteIndex >= bufferSize) { 1.340 + mWriteIndex = 0; 1.341 + } 1.342 +} 1.343 + 1.344 +} 1.345 +} 1.346 +