content/media/webaudio/ConvolverNode.cpp

changeset 0
6474c204b198
     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 +

mercurial