content/media/webaudio/ScriptProcessorNode.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     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/. */
     7 #include "ScriptProcessorNode.h"
     8 #include "mozilla/dom/ScriptProcessorNodeBinding.h"
     9 #include "AudioBuffer.h"
    10 #include "AudioDestinationNode.h"
    11 #include "AudioNodeEngine.h"
    12 #include "AudioNodeStream.h"
    13 #include "AudioProcessingEvent.h"
    14 #include "WebAudioUtils.h"
    15 #include "nsCxPusher.h"
    16 #include "mozilla/Mutex.h"
    17 #include "mozilla/PodOperations.h"
    18 #include <deque>
    20 namespace mozilla {
    21 namespace dom {
    23 // The maximum latency, in seconds, that we can live with before dropping
    24 // buffers.
    25 static const float MAX_LATENCY_S = 0.5;
    27 NS_IMPL_ISUPPORTS_INHERITED0(ScriptProcessorNode, AudioNode)
    29 // This class manages a queue of output buffers shared between
    30 // the main thread and the Media Stream Graph thread.
    31 class SharedBuffers
    32 {
    33 private:
    34   class OutputQueue
    35   {
    36   public:
    37     explicit OutputQueue(const char* aName)
    38       : mMutex(aName)
    39     {}
    41     size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
    42     {
    43       mMutex.AssertCurrentThreadOwns();
    45       size_t amount = 0;
    46       for (size_t i = 0; i < mBufferList.size(); i++) {
    47         amount += mBufferList[i].SizeOfExcludingThis(aMallocSizeOf, false);
    48       }
    50       return amount;
    51     }
    53     Mutex& Lock() const { return const_cast<OutputQueue*>(this)->mMutex; }
    55     size_t ReadyToConsume() const
    56     {
    57       mMutex.AssertCurrentThreadOwns();
    58       MOZ_ASSERT(!NS_IsMainThread());
    59       return mBufferList.size();
    60     }
    62     // Produce one buffer
    63     AudioChunk& Produce()
    64     {
    65       mMutex.AssertCurrentThreadOwns();
    66       MOZ_ASSERT(NS_IsMainThread());
    67       mBufferList.push_back(AudioChunk());
    68       return mBufferList.back();
    69     }
    71     // Consumes one buffer.
    72     AudioChunk Consume()
    73     {
    74       mMutex.AssertCurrentThreadOwns();
    75       MOZ_ASSERT(!NS_IsMainThread());
    76       MOZ_ASSERT(ReadyToConsume() > 0);
    77       AudioChunk front = mBufferList.front();
    78       mBufferList.pop_front();
    79       return front;
    80     }
    82     // Empties the buffer queue.
    83     void Clear()
    84     {
    85       mMutex.AssertCurrentThreadOwns();
    86       mBufferList.clear();
    87     }
    89   private:
    90     typedef std::deque<AudioChunk> BufferList;
    92     // Synchronizes access to mBufferList.  Note that it's the responsibility
    93     // of the callers to perform the required locking, and we assert that every
    94     // time we access mBufferList.
    95     Mutex mMutex;
    96     // The list representing the queue.
    97     BufferList mBufferList;
    98   };
   100 public:
   101   SharedBuffers(float aSampleRate)
   102     : mOutputQueue("SharedBuffers::outputQueue")
   103     , mDelaySoFar(TRACK_TICKS_MAX)
   104     , mSampleRate(aSampleRate)
   105     , mLatency(0.0)
   106     , mDroppingBuffers(false)
   107   {
   108   }
   110   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
   111   {
   112     size_t amount = aMallocSizeOf(this);
   114     {
   115       MutexAutoLock lock(mOutputQueue.Lock());
   116       amount += mOutputQueue.SizeOfExcludingThis(aMallocSizeOf);
   117     }
   119     return amount;
   120   }
   122   // main thread
   123   void FinishProducingOutputBuffer(ThreadSharedFloatArrayBufferList* aBuffer,
   124                                    uint32_t aBufferSize)
   125   {
   126     MOZ_ASSERT(NS_IsMainThread());
   128     TimeStamp now = TimeStamp::Now();
   130     if (mLastEventTime.IsNull()) {
   131       mLastEventTime = now;
   132     } else {
   133       // When the main thread is blocked, and all the event are processed in a
   134       // burst after the main thread unblocks, the |(now - mLastEventTime)|
   135       // interval will be very short. |latency - bufferDuration| will be
   136       // negative, effectively moving back mLatency to a smaller and smaller
   137       // value, until it crosses zero, at which point we stop dropping buffers
   138       // and resume normal operation. This does not work if at the same time,
   139       // the MSG thread was also slowed down, so if the latency on the MSG
   140       // thread is normal, and we are still dropping buffers, and mLatency is
   141       // still more than twice the duration of a buffer, we reset it and stop
   142       // dropping buffers.
   143       float latency = (now - mLastEventTime).ToSeconds();
   144       float bufferDuration = aBufferSize / mSampleRate;
   145       mLatency += latency - bufferDuration;
   146       mLastEventTime = now;
   147       if (mLatency > MAX_LATENCY_S ||
   148           (mDroppingBuffers && mLatency > 0.0 &&
   149            fabs(latency - bufferDuration) < bufferDuration)) {
   150         mDroppingBuffers = true;
   151         return;
   152       } else {
   153         if (mDroppingBuffers) {
   154           mLatency = 0;
   155         }
   156         mDroppingBuffers = false;
   157       }
   158     }
   160     MutexAutoLock lock(mOutputQueue.Lock());
   161     for (uint32_t offset = 0; offset < aBufferSize; offset += WEBAUDIO_BLOCK_SIZE) {
   162       AudioChunk& chunk = mOutputQueue.Produce();
   163       if (aBuffer) {
   164         chunk.mDuration = WEBAUDIO_BLOCK_SIZE;
   165         chunk.mBuffer = aBuffer;
   166         chunk.mChannelData.SetLength(aBuffer->GetChannels());
   167         for (uint32_t i = 0; i < aBuffer->GetChannels(); ++i) {
   168           chunk.mChannelData[i] = aBuffer->GetData(i) + offset;
   169         }
   170         chunk.mVolume = 1.0f;
   171         chunk.mBufferFormat = AUDIO_FORMAT_FLOAT32;
   172       } else {
   173         chunk.SetNull(WEBAUDIO_BLOCK_SIZE);
   174       }
   175     }
   176   }
   178   // graph thread
   179   AudioChunk GetOutputBuffer()
   180   {
   181     MOZ_ASSERT(!NS_IsMainThread());
   182     AudioChunk buffer;
   184     {
   185       MutexAutoLock lock(mOutputQueue.Lock());
   186       if (mOutputQueue.ReadyToConsume() > 0) {
   187         if (mDelaySoFar == TRACK_TICKS_MAX) {
   188           mDelaySoFar = 0;
   189         }
   190         buffer = mOutputQueue.Consume();
   191       } else {
   192         // If we're out of buffers to consume, just output silence
   193         buffer.SetNull(WEBAUDIO_BLOCK_SIZE);
   194         if (mDelaySoFar != TRACK_TICKS_MAX) {
   195           // Remember the delay that we just hit
   196           mDelaySoFar += WEBAUDIO_BLOCK_SIZE;
   197         }
   198       }
   199     }
   201     return buffer;
   202   }
   204   TrackTicks DelaySoFar() const
   205   {
   206     MOZ_ASSERT(!NS_IsMainThread());
   207     return mDelaySoFar == TRACK_TICKS_MAX ? 0 : mDelaySoFar;
   208   }
   210   void Reset()
   211   {
   212     MOZ_ASSERT(!NS_IsMainThread());
   213     mDelaySoFar = TRACK_TICKS_MAX;
   214     mLatency = 0.0f;
   215     {
   216       MutexAutoLock lock(mOutputQueue.Lock());
   217       mOutputQueue.Clear();
   218     }
   219     mLastEventTime = TimeStamp();
   220   }
   222 private:
   223   OutputQueue mOutputQueue;
   224   // How much delay we've seen so far.  This measures the amount of delay
   225   // caused by the main thread lagging behind in producing output buffers.
   226   // TRACK_TICKS_MAX means that we have not received our first buffer yet.
   227   TrackTicks mDelaySoFar;
   228   // The samplerate of the context.
   229   float mSampleRate;
   230   // This is the latency caused by the buffering. If this grows too high, we
   231   // will drop buffers until it is acceptable.
   232   float mLatency;
   233   // This is the time at which we last produced a buffer, to detect if the main
   234   // thread has been blocked.
   235   TimeStamp mLastEventTime;
   236   // True if we should be dropping buffers.
   237   bool mDroppingBuffers;
   238 };
   240 class ScriptProcessorNodeEngine : public AudioNodeEngine
   241 {
   242 public:
   243   typedef nsAutoTArray<nsAutoArrayPtr<float>, 2> InputChannels;
   245   ScriptProcessorNodeEngine(ScriptProcessorNode* aNode,
   246                             AudioDestinationNode* aDestination,
   247                             uint32_t aBufferSize,
   248                             uint32_t aNumberOfInputChannels)
   249     : AudioNodeEngine(aNode)
   250     , mSharedBuffers(aNode->GetSharedBuffers())
   251     , mSource(nullptr)
   252     , mDestination(static_cast<AudioNodeStream*> (aDestination->Stream()))
   253     , mBufferSize(aBufferSize)
   254     , mInputWriteIndex(0)
   255     , mSeenNonSilenceInput(false)
   256   {
   257     mInputChannels.SetLength(aNumberOfInputChannels);
   258     AllocateInputBlock();
   259   }
   261   void SetSourceStream(AudioNodeStream* aSource)
   262   {
   263     mSource = aSource;
   264   }
   266   virtual void ProcessBlock(AudioNodeStream* aStream,
   267                             const AudioChunk& aInput,
   268                             AudioChunk* aOutput,
   269                             bool* aFinished) MOZ_OVERRIDE
   270   {
   271     MutexAutoLock lock(NodeMutex());
   273     // If our node is dead, just output silence.
   274     if (!Node()) {
   275       aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
   276       return;
   277     }
   279     // This node is not connected to anything. Per spec, we don't fire the
   280     // onaudioprocess event. We also want to clear out the input and output
   281     // buffer queue, and output a null buffer.
   282     if (!(aStream->ConsumerCount() ||
   283           aStream->AsProcessedStream()->InputPortCount())) {
   284       aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
   285       mSharedBuffers->Reset();
   286       mSeenNonSilenceInput = false;
   287       mInputWriteIndex = 0;
   288       return;
   289     }
   291     // First, record our input buffer
   292     for (uint32_t i = 0; i < mInputChannels.Length(); ++i) {
   293       if (aInput.IsNull()) {
   294         PodZero(mInputChannels[i] + mInputWriteIndex,
   295                 aInput.GetDuration());
   296       } else {
   297         mSeenNonSilenceInput = true;
   298         MOZ_ASSERT(aInput.GetDuration() == WEBAUDIO_BLOCK_SIZE, "sanity check");
   299         MOZ_ASSERT(aInput.mChannelData.Length() == mInputChannels.Length());
   300         AudioBlockCopyChannelWithScale(static_cast<const float*>(aInput.mChannelData[i]),
   301                                        aInput.mVolume,
   302                                        mInputChannels[i] + mInputWriteIndex);
   303       }
   304     }
   305     mInputWriteIndex += aInput.GetDuration();
   307     // Now, see if we have data to output
   308     // Note that we need to do this before sending the buffer to the main
   309     // thread so that our delay time is updated.
   310     *aOutput = mSharedBuffers->GetOutputBuffer();
   312     if (mInputWriteIndex >= mBufferSize) {
   313       SendBuffersToMainThread(aStream);
   314       mInputWriteIndex -= mBufferSize;
   315       mSeenNonSilenceInput = false;
   316       AllocateInputBlock();
   317     }
   318   }
   320   virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
   321   {
   322     // Not owned:
   323     // - mSharedBuffers
   324     // - mSource (probably)
   325     // - mDestination (probably)
   326     size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
   327     amount += mInputChannels.SizeOfExcludingThis(aMallocSizeOf);
   328     for (size_t i = 0; i < mInputChannels.Length(); i++) {
   329       amount += mInputChannels[i].SizeOfExcludingThis(aMallocSizeOf);
   330     }
   332     return amount;
   333   }
   335   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
   336   {
   337     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   338   }
   340 private:
   341   void AllocateInputBlock()
   342   {
   343     for (unsigned i = 0; i < mInputChannels.Length(); ++i) {
   344       if (!mInputChannels[i]) {
   345         mInputChannels[i] = new float[mBufferSize];
   346       }
   347     }
   348   }
   350   void SendBuffersToMainThread(AudioNodeStream* aStream)
   351   {
   352     MOZ_ASSERT(!NS_IsMainThread());
   354     // we now have a full input buffer ready to be sent to the main thread.
   355     TrackTicks playbackTick = mSource->GetCurrentPosition();
   356     // Add the duration of the current sample
   357     playbackTick += WEBAUDIO_BLOCK_SIZE;
   358     // Add the delay caused by the main thread
   359     playbackTick += mSharedBuffers->DelaySoFar();
   360     // Compute the playback time in the coordinate system of the destination
   361     // FIXME: bug 970773
   362     double playbackTime =
   363       mSource->DestinationTimeFromTicks(mDestination, playbackTick);
   365     class Command : public nsRunnable
   366     {
   367     public:
   368       Command(AudioNodeStream* aStream,
   369               InputChannels& aInputChannels,
   370               double aPlaybackTime,
   371               bool aNullInput)
   372         : mStream(aStream)
   373         , mPlaybackTime(aPlaybackTime)
   374         , mNullInput(aNullInput)
   375       {
   376         mInputChannels.SetLength(aInputChannels.Length());
   377         if (!aNullInput) {
   378           for (uint32_t i = 0; i < mInputChannels.Length(); ++i) {
   379             mInputChannels[i] = aInputChannels[i].forget();
   380           }
   381         }
   382       }
   384       NS_IMETHODIMP Run()
   385       {
   386         // If it's not safe to run scripts right now, schedule this to run later
   387         if (!nsContentUtils::IsSafeToRunScript()) {
   388           nsContentUtils::AddScriptRunner(this);
   389           return NS_OK;
   390         }
   392         nsRefPtr<ScriptProcessorNode> node;
   393         {
   394           // No need to keep holding the lock for the whole duration of this
   395           // function, since we're holding a strong reference to it, so if
   396           // we can obtain the reference, we will hold the node alive in
   397           // this function.
   398           MutexAutoLock lock(mStream->Engine()->NodeMutex());
   399           node = static_cast<ScriptProcessorNode*>(mStream->Engine()->Node());
   400         }
   401         if (!node || !node->Context()) {
   402           return NS_OK;
   403         }
   405         AutoPushJSContext cx(node->Context()->GetJSContext());
   406         if (cx) {
   409           // Create the input buffer
   410           nsRefPtr<AudioBuffer> inputBuffer;
   411           if (!mNullInput) {
   412             ErrorResult rv;
   413             inputBuffer =
   414               AudioBuffer::Create(node->Context(), mInputChannels.Length(),
   415                                   node->BufferSize(),
   416                                   node->Context()->SampleRate(), cx, rv);
   417             if (rv.Failed()) {
   418               return NS_OK;
   419             }
   420             // Put the channel data inside it
   421             for (uint32_t i = 0; i < mInputChannels.Length(); ++i) {
   422               inputBuffer->SetRawChannelContents(cx, i, mInputChannels[i]);
   423             }
   424           }
   426           // Ask content to produce data in the output buffer
   427           // Note that we always avoid creating the output buffer here, and we try to
   428           // avoid creating the input buffer as well.  The AudioProcessingEvent class
   429           // knows how to lazily create them if needed once the script tries to access
   430           // them.  Otherwise, we may be able to get away without creating them!
   431           nsRefPtr<AudioProcessingEvent> event = new AudioProcessingEvent(node, nullptr, nullptr);
   432           event->InitEvent(inputBuffer,
   433                            mInputChannels.Length(),
   434                            mPlaybackTime);
   435           node->DispatchTrustedEvent(event);
   437           // Steal the output buffers if they have been set.  Don't create a
   438           // buffer if it hasn't been used to return output;
   439           // FinishProducingOutputBuffer() will optimize output = null.
   440           // GetThreadSharedChannelsForRate() may also return null after OOM.
   441           nsRefPtr<ThreadSharedFloatArrayBufferList> output;
   442           if (event->HasOutputBuffer()) {
   443             ErrorResult rv;
   444             AudioBuffer* buffer = event->GetOutputBuffer(rv);
   445             // HasOutputBuffer() returning true means that GetOutputBuffer()
   446             // will not fail.
   447             MOZ_ASSERT(!rv.Failed());
   448             output = buffer->GetThreadSharedChannelsForRate(cx);
   449           }
   451           // Append it to our output buffer queue
   452           node->GetSharedBuffers()->FinishProducingOutputBuffer(output, node->BufferSize());
   453         }
   454         return NS_OK;
   455       }
   456     private:
   457       nsRefPtr<AudioNodeStream> mStream;
   458       InputChannels mInputChannels;
   459       double mPlaybackTime;
   460       bool mNullInput;
   461     };
   463     NS_DispatchToMainThread(new Command(aStream, mInputChannels,
   464                                         playbackTime,
   465                                         !mSeenNonSilenceInput));
   466   }
   468   friend class ScriptProcessorNode;
   470   SharedBuffers* mSharedBuffers;
   471   AudioNodeStream* mSource;
   472   AudioNodeStream* mDestination;
   473   InputChannels mInputChannels;
   474   const uint32_t mBufferSize;
   475   // The write index into the current input buffer
   476   uint32_t mInputWriteIndex;
   477   bool mSeenNonSilenceInput;
   478 };
   480 ScriptProcessorNode::ScriptProcessorNode(AudioContext* aContext,
   481                                          uint32_t aBufferSize,
   482                                          uint32_t aNumberOfInputChannels,
   483                                          uint32_t aNumberOfOutputChannels)
   484   : AudioNode(aContext,
   485               aNumberOfInputChannels,
   486               mozilla::dom::ChannelCountMode::Explicit,
   487               mozilla::dom::ChannelInterpretation::Speakers)
   488   , mSharedBuffers(new SharedBuffers(aContext->SampleRate()))
   489   , mBufferSize(aBufferSize ?
   490                   aBufferSize : // respect what the web developer requested
   491                   4096)         // choose our own buffer size -- 4KB for now
   492   , mNumberOfOutputChannels(aNumberOfOutputChannels)
   493 {
   494   MOZ_ASSERT(BufferSize() % WEBAUDIO_BLOCK_SIZE == 0, "Invalid buffer size");
   495   ScriptProcessorNodeEngine* engine =
   496     new ScriptProcessorNodeEngine(this,
   497                                   aContext->Destination(),
   498                                   BufferSize(),
   499                                   aNumberOfInputChannels);
   500   mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM);
   501   engine->SetSourceStream(static_cast<AudioNodeStream*> (mStream.get()));
   502 }
   504 ScriptProcessorNode::~ScriptProcessorNode()
   505 {
   506 }
   508 size_t
   509 ScriptProcessorNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
   510 {
   511   size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
   512   amount += mSharedBuffers->SizeOfIncludingThis(aMallocSizeOf);
   513   return amount;
   514 }
   516 size_t
   517 ScriptProcessorNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
   518 {
   519   return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   520 }
   522 JSObject*
   523 ScriptProcessorNode::WrapObject(JSContext* aCx)
   524 {
   525   return ScriptProcessorNodeBinding::Wrap(aCx, this);
   526 }
   528 }
   529 }

mercurial