tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

AnalyserNode.cpp (12345B)


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