Bug 899135 - Drop buffers in ScriptProcessorNode instead of buffering them when the main thread is blocked. r=roc
authorPaul Adenot <paul@paul.cx>
Tue, 13 Aug 2013 19:34:22 +0200
changeset 142650 21e600809600fccb0f09faa71e0b7bb2720f97df
parent 142649 2241fd070f12e227e608efed3230719d0b3f822a
child 142651 455da612ee5f9d68b0336fc26aae693554bdd1bf
push id25104
push useremorley@mozilla.com
push dateThu, 15 Aug 2013 10:56:09 +0000
treeherdermozilla-central@31c08ca022b3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs899135
milestone26.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 899135 - Drop buffers in ScriptProcessorNode instead of buffering them when the main thread is blocked. r=roc
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,