LCOV - code coverage report
Current view: top level - dom/media/webaudio - AnalyserNode.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 197 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 31 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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 "mozilla/dom/AnalyserNode.h"
       8             : #include "mozilla/dom/AnalyserNodeBinding.h"
       9             : #include "AudioNodeEngine.h"
      10             : #include "AudioNodeStream.h"
      11             : #include "mozilla/Mutex.h"
      12             : #include "mozilla/PodOperations.h"
      13             : 
      14             : namespace mozilla {
      15             : 
      16             : static const uint32_t MAX_FFT_SIZE = 32768;
      17             : static const size_t CHUNK_COUNT = MAX_FFT_SIZE >> WEBAUDIO_BLOCK_SIZE_BITS;
      18             : static_assert(MAX_FFT_SIZE == CHUNK_COUNT * WEBAUDIO_BLOCK_SIZE,
      19             :               "MAX_FFT_SIZE must be a multiple of WEBAUDIO_BLOCK_SIZE");
      20             : static_assert((CHUNK_COUNT & (CHUNK_COUNT - 1)) == 0,
      21             :               "CHUNK_COUNT must be power of 2 for remainder behavior");
      22             : 
      23             : namespace dom {
      24             : 
      25           0 : NS_IMPL_ISUPPORTS_INHERITED0(AnalyserNode, AudioNode)
      26             : 
      27           0 : class AnalyserNodeEngine final : public AudioNodeEngine
      28             : {
      29           0 :   class TransferBuffer final : public Runnable
      30             :   {
      31             :   public:
      32           0 :     TransferBuffer(AudioNodeStream* aStream, const AudioChunk& aChunk)
      33           0 :       : Runnable("dom::AnalyserNodeEngine::TransferBuffer")
      34             :       , mStream(aStream)
      35           0 :       , mChunk(aChunk)
      36             :     {
      37           0 :     }
      38             : 
      39           0 :     NS_IMETHOD Run() override
      40             :     {
      41             :       RefPtr<AnalyserNode> node =
      42           0 :         static_cast<AnalyserNode*>(mStream->Engine()->NodeMainThread());
      43           0 :       if (node) {
      44           0 :         node->AppendChunk(mChunk);
      45             :       }
      46           0 :       return NS_OK;
      47             :     }
      48             : 
      49             :   private:
      50             :     RefPtr<AudioNodeStream> mStream;
      51             :     AudioChunk mChunk;
      52             :   };
      53             : 
      54             : public:
      55           0 :   explicit AnalyserNodeEngine(AnalyserNode* aNode)
      56           0 :     : AudioNodeEngine(aNode)
      57             :   {
      58           0 :     MOZ_ASSERT(NS_IsMainThread());
      59           0 :   }
      60             : 
      61           0 :   virtual void ProcessBlock(AudioNodeStream* aStream,
      62             :                             GraphTime aFrom,
      63             :                             const AudioBlock& aInput,
      64             :                             AudioBlock* aOutput,
      65             :                             bool* aFinished) override
      66             :   {
      67           0 :     *aOutput = aInput;
      68             : 
      69           0 :     if (aInput.IsNull()) {
      70             :       // If AnalyserNode::mChunks has only null chunks, then there is no need
      71             :       // to send further null chunks.
      72           0 :       if (mChunksToProcess == 0) {
      73           0 :         return;
      74             :       }
      75             : 
      76           0 :       --mChunksToProcess;
      77           0 :       if (mChunksToProcess == 0) {
      78           0 :         aStream->ScheduleCheckForInactive();
      79             :       }
      80             : 
      81             :     } else {
      82             :       // This many null chunks will be required to empty AnalyserNode::mChunks.
      83           0 :       mChunksToProcess = CHUNK_COUNT;
      84             :     }
      85             : 
      86             :     RefPtr<TransferBuffer> transfer =
      87           0 :       new TransferBuffer(aStream, aInput.AsAudioChunk());
      88           0 :     mAbstractMainThread->Dispatch(transfer.forget());
      89             :   }
      90             : 
      91           0 :   virtual bool IsActive() const override
      92             :   {
      93           0 :     return mChunksToProcess != 0;
      94             :   }
      95             : 
      96           0 :   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
      97             :   {
      98           0 :     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
      99             :   }
     100             : 
     101             :   uint32_t mChunksToProcess = 0;
     102             : };
     103             : 
     104             : /* static */ already_AddRefed<AnalyserNode>
     105           0 : AnalyserNode::Create(AudioContext& aAudioContext,
     106             :                      const AnalyserOptions& aOptions,
     107             :                      ErrorResult& aRv)
     108             : {
     109           0 :   if (aAudioContext.CheckClosed(aRv)) {
     110           0 :     return nullptr;
     111             :   }
     112             : 
     113           0 :   RefPtr<AnalyserNode> analyserNode = new AnalyserNode(&aAudioContext);
     114             : 
     115           0 :   analyserNode->Initialize(aOptions, aRv);
     116           0 :   if (NS_WARN_IF(aRv.Failed())) {
     117           0 :     return nullptr;
     118             :   }
     119             : 
     120           0 :   analyserNode->SetFftSize(aOptions.mFftSize, aRv);
     121           0 :   if (NS_WARN_IF(aRv.Failed())) {
     122           0 :     return nullptr;
     123             :   }
     124             : 
     125           0 :   analyserNode->SetMinDecibels(aOptions.mMinDecibels, aRv);
     126           0 :   if (NS_WARN_IF(aRv.Failed())) {
     127           0 :     return nullptr;
     128             :   }
     129             : 
     130           0 :   analyserNode->SetMaxDecibels(aOptions.mMaxDecibels, aRv);
     131           0 :   if (NS_WARN_IF(aRv.Failed())) {
     132           0 :     return nullptr;
     133             :   }
     134             : 
     135           0 :   analyserNode->SetSmoothingTimeConstant(aOptions.mSmoothingTimeConstant, aRv);
     136           0 :   if (NS_WARN_IF(aRv.Failed())) {
     137           0 :     return nullptr;
     138             :   }
     139             : 
     140           0 :   return analyserNode.forget();
     141             : }
     142             : 
     143           0 : AnalyserNode::AnalyserNode(AudioContext* aContext)
     144             :   : AudioNode(aContext,
     145             :               1,
     146             :               ChannelCountMode::Max,
     147             :               ChannelInterpretation::Speakers)
     148             :   , mAnalysisBlock(2048)
     149             :   , mMinDecibels(-100.)
     150             :   , mMaxDecibels(-30.)
     151           0 :   , mSmoothingTimeConstant(.8)
     152             : {
     153           0 :   mStream = AudioNodeStream::Create(aContext,
     154           0 :                                     new AnalyserNodeEngine(this),
     155             :                                     AudioNodeStream::NO_STREAM_FLAGS,
     156           0 :                                     aContext->Graph());
     157             : 
     158             :   // Enough chunks must be recorded to handle the case of fftSize being
     159             :   // increased to maximum immediately before getFloatTimeDomainData() is
     160             :   // called, for example.
     161           0 :   Unused << mChunks.SetLength(CHUNK_COUNT, fallible);
     162             : 
     163           0 :   AllocateBuffer();
     164           0 : }
     165             : 
     166             : size_t
     167           0 : AnalyserNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
     168             : {
     169           0 :   size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
     170           0 :   amount += mAnalysisBlock.SizeOfExcludingThis(aMallocSizeOf);
     171           0 :   amount += mChunks.ShallowSizeOfExcludingThis(aMallocSizeOf);
     172           0 :   amount += mOutputBuffer.ShallowSizeOfExcludingThis(aMallocSizeOf);
     173           0 :   return amount;
     174             : }
     175             : 
     176             : size_t
     177           0 : AnalyserNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
     178             : {
     179           0 :   return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
     180             : }
     181             : 
     182             : JSObject*
     183           0 : AnalyserNode::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
     184             : {
     185           0 :   return AnalyserNodeBinding::Wrap(aCx, this, aGivenProto);
     186             : }
     187             : 
     188             : void
     189           0 : AnalyserNode::SetFftSize(uint32_t aValue, ErrorResult& aRv)
     190             : {
     191             :   // Disallow values that are not a power of 2 and outside the [32,32768] range
     192           0 :   if (aValue < 32 ||
     193           0 :       aValue > MAX_FFT_SIZE ||
     194           0 :       (aValue & (aValue - 1)) != 0) {
     195           0 :     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     196           0 :     return;
     197             :   }
     198           0 :   if (FftSize() != aValue) {
     199           0 :     mAnalysisBlock.SetFFTSize(aValue);
     200           0 :     AllocateBuffer();
     201             :   }
     202             : }
     203             : 
     204             : void
     205           0 : AnalyserNode::SetMinDecibels(double aValue, ErrorResult& aRv)
     206             : {
     207           0 :   if (aValue >= mMaxDecibels) {
     208           0 :     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     209           0 :     return;
     210             :   }
     211           0 :   mMinDecibels = aValue;
     212             : }
     213             : 
     214             : void
     215           0 : AnalyserNode::SetMaxDecibels(double aValue, ErrorResult& aRv)
     216             : {
     217           0 :   if (aValue <= mMinDecibels) {
     218           0 :     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     219           0 :     return;
     220             :   }
     221           0 :   mMaxDecibels = aValue;
     222             : }
     223             : 
     224             : void
     225           0 : AnalyserNode::SetSmoothingTimeConstant(double aValue, ErrorResult& aRv)
     226             : {
     227           0 :   if (aValue < 0 || aValue > 1) {
     228           0 :     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     229           0 :     return;
     230             :   }
     231           0 :   mSmoothingTimeConstant = aValue;
     232             : }
     233             : 
     234             : void
     235           0 : AnalyserNode::GetFloatFrequencyData(const Float32Array& aArray)
     236             : {
     237           0 :   if (!FFTAnalysis()) {
     238             :     // Might fail to allocate memory
     239           0 :     return;
     240             :   }
     241             : 
     242           0 :   aArray.ComputeLengthAndData();
     243             : 
     244           0 :   float* buffer = aArray.Data();
     245           0 :   size_t length = std::min(size_t(aArray.Length()), mOutputBuffer.Length());
     246             : 
     247           0 :   for (size_t i = 0; i < length; ++i) {
     248           0 :     buffer[i] =
     249           0 :       WebAudioUtils::ConvertLinearToDecibels(mOutputBuffer[i],
     250           0 :                                              -std::numeric_limits<float>::infinity());
     251             :   }
     252             : }
     253             : 
     254             : void
     255           0 : AnalyserNode::GetByteFrequencyData(const Uint8Array& aArray)
     256             : {
     257           0 :   if (!FFTAnalysis()) {
     258             :     // Might fail to allocate memory
     259           0 :     return;
     260             :   }
     261             : 
     262           0 :   const double rangeScaleFactor = 1.0 / (mMaxDecibels - mMinDecibels);
     263             : 
     264           0 :   aArray.ComputeLengthAndData();
     265             : 
     266           0 :   unsigned char* buffer = aArray.Data();
     267           0 :   size_t length = std::min(size_t(aArray.Length()), mOutputBuffer.Length());
     268             : 
     269           0 :   for (size_t i = 0; i < length; ++i) {
     270           0 :     const double decibels = WebAudioUtils::ConvertLinearToDecibels(mOutputBuffer[i], mMinDecibels);
     271             :     // scale down the value to the range of [0, UCHAR_MAX]
     272           0 :     const double scaled = std::max(0.0, std::min(double(UCHAR_MAX),
     273           0 :                                                  UCHAR_MAX * (decibels - mMinDecibels) * rangeScaleFactor));
     274           0 :     buffer[i] = static_cast<unsigned char>(scaled);
     275             :   }
     276             : }
     277             : 
     278             : void
     279           0 : AnalyserNode::GetFloatTimeDomainData(const Float32Array& aArray)
     280             : {
     281           0 :   aArray.ComputeLengthAndData();
     282             : 
     283           0 :   float* buffer = aArray.Data();
     284           0 :   size_t length = std::min(aArray.Length(), FftSize());
     285             : 
     286           0 :   GetTimeDomainData(buffer, length);
     287           0 : }
     288             : 
     289             : void
     290           0 : AnalyserNode::GetByteTimeDomainData(const Uint8Array& aArray)
     291             : {
     292           0 :   aArray.ComputeLengthAndData();
     293             : 
     294           0 :   size_t length = std::min(aArray.Length(), FftSize());
     295             : 
     296           0 :   AlignedTArray<float> tmpBuffer;
     297           0 :   if (!tmpBuffer.SetLength(length, fallible)) {
     298           0 :     return;
     299             :   }
     300             : 
     301           0 :   GetTimeDomainData(tmpBuffer.Elements(), length);
     302             : 
     303           0 :   unsigned char* buffer = aArray.Data();
     304           0 :   for (size_t i = 0; i < length; ++i) {
     305           0 :     const float value = tmpBuffer[i];
     306             :     // scale the value to the range of [0, UCHAR_MAX]
     307           0 :     const float scaled = std::max(0.0f, std::min(float(UCHAR_MAX),
     308           0 :                                                  128.0f * (value + 1.0f)));
     309           0 :     buffer[i] = static_cast<unsigned char>(scaled);
     310             :   }
     311             : }
     312             : 
     313             : bool
     314           0 : AnalyserNode::FFTAnalysis()
     315             : {
     316           0 :   AlignedTArray<float> tmpBuffer;
     317           0 :   size_t fftSize = FftSize();
     318           0 :   if (!tmpBuffer.SetLength(fftSize, fallible)) {
     319           0 :     return false;
     320             :   }
     321             : 
     322           0 :   float* inputBuffer = tmpBuffer.Elements();
     323           0 :   GetTimeDomainData(inputBuffer, fftSize);
     324           0 :   ApplyBlackmanWindow(inputBuffer, fftSize);
     325           0 :   mAnalysisBlock.PerformFFT(inputBuffer);
     326             : 
     327             :   // Normalize so than an input sine wave at 0dBfs registers as 0dBfs (undo FFT scaling factor).
     328           0 :   const double magnitudeScale = 1.0 / fftSize;
     329             : 
     330           0 :   for (uint32_t i = 0; i < mOutputBuffer.Length(); ++i) {
     331           0 :     double scalarMagnitude = NS_hypot(mAnalysisBlock.RealData(i),
     332           0 :                                       mAnalysisBlock.ImagData(i)) *
     333           0 :                              magnitudeScale;
     334           0 :     mOutputBuffer[i] = mSmoothingTimeConstant * mOutputBuffer[i] +
     335           0 :                        (1.0 - mSmoothingTimeConstant) * scalarMagnitude;
     336             :   }
     337             : 
     338           0 :   return true;
     339             : }
     340             : 
     341             : void
     342           0 : AnalyserNode::ApplyBlackmanWindow(float* aBuffer, uint32_t aSize)
     343             : {
     344           0 :   double alpha = 0.16;
     345           0 :   double a0 = 0.5 * (1.0 - alpha);
     346           0 :   double a1 = 0.5;
     347           0 :   double a2 = 0.5 * alpha;
     348             : 
     349           0 :   for (uint32_t i = 0; i < aSize; ++i) {
     350           0 :     double x = double(i) / aSize;
     351           0 :     double window = a0 - a1 * cos(2 * M_PI * x) + a2 * cos(4 * M_PI * x);
     352           0 :     aBuffer[i] *= window;
     353             :   }
     354           0 : }
     355             : 
     356             : bool
     357           0 : AnalyserNode::AllocateBuffer()
     358             : {
     359           0 :   bool result = true;
     360           0 :   if (mOutputBuffer.Length() != FrequencyBinCount()) {
     361           0 :     if (!mOutputBuffer.SetLength(FrequencyBinCount(), fallible)) {
     362           0 :       return false;
     363             :     }
     364           0 :     memset(mOutputBuffer.Elements(), 0, sizeof(float) * FrequencyBinCount());
     365             :   }
     366           0 :   return result;
     367             : }
     368             : 
     369             : void
     370           0 : AnalyserNode::AppendChunk(const AudioChunk& aChunk)
     371             : {
     372           0 :   if (mChunks.Length() == 0) {
     373           0 :     return;
     374             :   }
     375             : 
     376           0 :   ++mCurrentChunk;
     377           0 :   mChunks[mCurrentChunk & (CHUNK_COUNT - 1)] = aChunk;
     378             : }
     379             : 
     380             : // Reads into aData the oldest aLength samples of the fftSize most recent
     381             : // samples.
     382             : void
     383           0 : AnalyserNode::GetTimeDomainData(float* aData, size_t aLength)
     384             : {
     385           0 :   size_t fftSize = FftSize();
     386           0 :   MOZ_ASSERT(aLength <= fftSize);
     387             : 
     388           0 :   if (mChunks.Length() == 0) {
     389           0 :     PodZero(aData, aLength);
     390           0 :     return;
     391             :   }
     392             : 
     393             :   size_t readChunk =
     394           0 :     mCurrentChunk - ((fftSize - 1) >> WEBAUDIO_BLOCK_SIZE_BITS);
     395           0 :   size_t readIndex = (0 - fftSize) & (WEBAUDIO_BLOCK_SIZE - 1);
     396           0 :   MOZ_ASSERT(readIndex == 0 || readIndex + fftSize == WEBAUDIO_BLOCK_SIZE);
     397             : 
     398           0 :   for (size_t writeIndex = 0; writeIndex < aLength; ) {
     399           0 :     const AudioChunk& chunk = mChunks[readChunk & (CHUNK_COUNT - 1)];
     400           0 :     const size_t channelCount = chunk.ChannelCount();
     401             :     size_t copyLength =
     402           0 :       std::min<size_t>(aLength - writeIndex, WEBAUDIO_BLOCK_SIZE);
     403           0 :     float* dataOut = &aData[writeIndex];
     404             : 
     405           0 :     if (channelCount == 0) {
     406           0 :       PodZero(dataOut, copyLength);
     407             :     } else {
     408           0 :       float scale = chunk.mVolume / channelCount;
     409             :       { // channel 0
     410             :         auto channelData =
     411           0 :           static_cast<const float*>(chunk.mChannelData[0]) + readIndex;
     412           0 :         AudioBufferCopyWithScale(channelData, scale, dataOut, copyLength);
     413             :       }
     414           0 :       for (uint32_t i = 1; i < channelCount; ++i) {
     415             :         auto channelData =
     416           0 :           static_cast<const float*>(chunk.mChannelData[i]) + readIndex;
     417           0 :         AudioBufferAddWithScale(channelData, scale, dataOut, copyLength);
     418             :       }
     419             :     }
     420             : 
     421           0 :     readChunk++;
     422           0 :     writeIndex += copyLength;
     423             :   }
     424             : }
     425             : 
     426             : } // namespace dom
     427             : } // namespace mozilla
     428             : 

Generated by: LCOV version 1.13