Bug 899135 - Drop buffers in ScriptProcessorNode instead of buffering them when the main thread is blocked. r=roc a=webaudio
authorPaul Adenot <paul@paul.cx>
Tue, 13 Aug 2013 19:34:22 +0200
changeset 153917 b9736f48819a3d815ea4e68509435d1e8c9abae8
parent 153916 1a1aeff8ab00ab695565c046bfa7c4a056d0fac5
child 153918 5ee1d298bc2d71701479f30ff4b79db53bdb003d
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-beta@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc, webaudio
bugs899135
milestone25.0a2
Bug 899135 - Drop buffers in ScriptProcessorNode instead of buffering them when the main thread is blocked. r=roc a=webaudio
content/media/webaudio/ScriptProcessorNode.cpp
--- a/content/media/webaudio/ScriptProcessorNode.cpp
+++ b/content/media/webaudio/ScriptProcessorNode.cpp
@@ -15,16 +15,20 @@
 #include "nsCxPusher.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/PodOperations.h"
 #include <deque>
 
 namespace mozilla {
 namespace dom {
 
+// The maximum latency, in seconds, that we can live with before dropping
+// buffers.
+static const float MAX_LATENCY_S = 0.5;
+
 NS_IMPL_CYCLE_COLLECTION_CLASS(ScriptProcessorNode)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ScriptProcessorNode)
   if (tmp->Context()) {
     tmp->Context()->UnregisterScriptProcessorNode(tmp);
   }
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(AudioNode)
 
@@ -85,28 +89,53 @@ private:
     // of the callers to perform the required locking, and we assert that every
     // time we access mBufferList.
     Mutex mMutex;
     // The list representing the queue.
     BufferList mBufferList;
   };
 
 public:
-  SharedBuffers()
+  SharedBuffers(float aSampleRate)
     : mOutputQueue("SharedBuffers::outputQueue")
     , mDelaySoFar(TRACK_TICKS_MAX)
+    , mSampleRate(aSampleRate)
+    , mDroppingBuffers(false)
   {
   }
 
   // main thread
   void FinishProducingOutputBuffer(ThreadSharedFloatArrayBufferList* aBuffer,
                                    uint32_t aBufferSize)
   {
     MOZ_ASSERT(NS_IsMainThread());
 
+    TimeStamp now = TimeStamp::Now();
+
+    if (mLastEventTime.IsNull()) {
+      mLastEventTime = now;
+    } else {
+      // When the main thread is blocked, and all the event are processed in a
+      // burst after the main thread unblocks, the |(now - mLastEventTime)|
+      // interval will be very short. |latency - bufferDuration| will be
+      // negative, effectively moving back mLatency to a smaller and smaller
+      // value, until it crosses zero, at which point we stop dropping buffers
+      // and resume normal operation.
+      float latency = (now - mLastEventTime).ToSeconds();
+      float bufferDuration = aBufferSize / mSampleRate;
+      mLatency += latency - bufferDuration;
+      mLastEventTime = now;
+      if (mLatency > MAX_LATENCY_S || (mDroppingBuffers && mLatency > 0.0)) {
+        mDroppingBuffers = true;
+        return;
+      } else {
+        mDroppingBuffers = false;
+      }
+    }
+
     MutexAutoLock lock(mOutputQueue.Lock());
     for (uint32_t offset = 0; offset < aBufferSize; offset += WEBAUDIO_BLOCK_SIZE) {
       AudioChunk& chunk = mOutputQueue.Produce();
       if (aBuffer) {
         chunk.mDuration = WEBAUDIO_BLOCK_SIZE;
         chunk.mBuffer = aBuffer;
         chunk.mChannelData.SetLength(aBuffer->GetChannels());
         for (uint32_t i = 0; i < aBuffer->GetChannels(); ++i) {
@@ -153,16 +182,26 @@ public:
   }
 
 private:
   OutputQueue mOutputQueue;
   // How much delay we've seen so far.  This measures the amount of delay
   // caused by the main thread lagging behind in producing output buffers.
   // TRACK_TICKS_MAX means that we have not received our first buffer yet.
   TrackTicks mDelaySoFar;
+  // The samplerate of the context.
+  float mSampleRate;
+  // This is the latency caused by the buffering. If this grows too high, we
+  // will drop buffers until it is acceptable.
+  float mLatency;
+  // This is the time at which we last produced a buffer, to detect if the main
+  // thread has been blocked.
+  TimeStamp mLastEventTime;
+  // True if we should be dropping buffers.
+  bool mDroppingBuffers;
 };
 
 class ScriptProcessorNodeEngine : public AudioNodeEngine
 {
 public:
   typedef nsAutoTArray<nsAutoArrayPtr<float>, 2> InputChannels;
 
   ScriptProcessorNodeEngine(ScriptProcessorNode* aNode,
@@ -381,17 +420,17 @@ private:
 ScriptProcessorNode::ScriptProcessorNode(AudioContext* aContext,
                                          uint32_t aBufferSize,
                                          uint32_t aNumberOfInputChannels,
                                          uint32_t aNumberOfOutputChannels)
   : AudioNode(aContext,
               aNumberOfInputChannels,
               mozilla::dom::ChannelCountMode::Explicit,
               mozilla::dom::ChannelInterpretation::Speakers)
-  , mSharedBuffers(new SharedBuffers())
+  , mSharedBuffers(new SharedBuffers(aContext->SampleRate()))
   , mBufferSize(aBufferSize ?
                   aBufferSize : // respect what the web developer requested
                   4096)         // choose our own buffer size -- 4KB for now
   , mNumberOfOutputChannels(aNumberOfOutputChannels)
 {
   MOZ_ASSERT(BufferSize() % WEBAUDIO_BLOCK_SIZE == 0, "Invalid buffer size");
   ScriptProcessorNodeEngine* engine =
     new ScriptProcessorNodeEngine(this,