Bug 881959 - Mute WebAudio nodes that are part of a cycle that contains no DelayNode, and make cycles work. r=ehsan, a=akeybl
authorPaul Adenot <paul@paul.cx>
Mon, 26 Aug 2013 19:19:36 +0200
changeset 154102 e0863bd262a9fb7d33599c4b704571419cdad15d
parent 154101 859717cd6229961e63567854f0d03113e6058917
child 154103 ffdc37756db8fe79c7c0d4a19b6e5bb86d71c52b
push id2876
push userryanvm@gmail.com
push dateMon, 23 Sep 2013 20:35:59 +0000
treeherdermozilla-beta@b09d09f59611 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan, akeybl
bugs881959
milestone25.0
Bug 881959 - Mute WebAudio nodes that are part of a cycle that contains no DelayNode, and make cycles work. r=ehsan, a=akeybl
content/media/AudioNodeEngine.h
content/media/AudioNodeStream.cpp
content/media/AudioNodeStream.h
content/media/MediaStreamGraph.cpp
content/media/webaudio/AudioNode.h
content/media/webaudio/DelayNode.cpp
content/media/webaudio/DelayNode.h
--- a/content/media/AudioNodeEngine.h
+++ b/content/media/AudioNodeEngine.h
@@ -10,16 +10,17 @@
 #include "mozilla/dom/AudioNode.h"
 #include "mozilla/dom/AudioParam.h"
 #include "mozilla/Mutex.h"
 
 namespace mozilla {
 
 namespace dom {
 struct ThreeDPoint;
+class DelayNodeEngine;
 }
 
 class AudioNodeStream;
 
 /**
  * This class holds onto a set of immutable channel buffers. The storage
  * for the buffers must be malloced, but the buffer pointers and the malloc
  * pointers can be different (e.g. if the buffers are contained inside
@@ -196,16 +197,18 @@ public:
     MOZ_COUNT_CTOR(AudioNodeEngine);
   }
   virtual ~AudioNodeEngine()
   {
     MOZ_ASSERT(!mNode, "The node reference must be already cleared");
     MOZ_COUNT_DTOR(AudioNodeEngine);
   }
 
+  virtual dom::DelayNodeEngine* AsDelayNodeEngine() { return nullptr; }
+
   virtual void SetStreamTimeParameter(uint32_t aIndex, TrackTicks aParam)
   {
     NS_ERROR("Invalid SetStreamTimeParameter index");
   }
   virtual void SetDoubleParameter(uint32_t aIndex, double aParam)
   {
     NS_ERROR("Invalid SetDoubleParameter index");
   }
--- a/content/media/AudioNodeStream.cpp
+++ b/content/media/AudioNodeStream.cpp
@@ -279,16 +279,30 @@ AudioNodeStream::ObtainInputBlock(AudioC
     }
     MediaStream* s = mInputs[i]->GetSource();
     AudioNodeStream* a = static_cast<AudioNodeStream*>(s);
     MOZ_ASSERT(a == s->AsAudioNodeStream());
     if (a->IsFinishedOnGraphThread() ||
         a->IsAudioParamStream()) {
       continue;
     }
+
+    // It is possible for mLastChunks to be empty here, because `a` might be a
+    // AudioNodeStream that has not been scheduled yet, because it is further
+    // down the graph _but_ as a connection to this node. Because we enforce the
+    // presence of at least one DelayNode, with at least one block of delay, and
+    // because the output of a DelayNode when it has been fed less that
+    // `delayTime` amount of audio is silence, we can simply continue here,
+    // because this input would not influence the output of this node. Next
+    // iteration, a->mLastChunks.IsEmpty() will be false, and everthing will
+    // work as usual.
+    if (a->mLastChunks.IsEmpty()) {
+      continue;
+    }
+
     AudioChunk* chunk = &a->mLastChunks[mInputs[i]->OutputNumber()];
     MOZ_ASSERT(chunk);
     if (chunk->IsNull() || chunk->mChannelData.IsEmpty()) {
       continue;
     }
 
     inputChunks.AppendElement(chunk);
     outputChannelCount =
@@ -405,18 +419,17 @@ AudioNodeStream::ProduceOutput(GraphTime
     FinishOutput();
   }
 
   EnsureTrack(AUDIO_NODE_STREAM_TRACK_ID, mSampleRate);
 
   uint16_t outputCount = std::max(uint16_t(1), mEngine->OutputCount());
   mLastChunks.SetLength(outputCount);
 
-  if (mInCycle) {
-    // XXX DelayNode not supported yet so just produce silence
+  if (mMuted) {
     for (uint16_t i = 0; i < outputCount; ++i) {
       mLastChunks[i].SetNull(WEBAUDIO_BLOCK_SIZE);
     }
   } else {
     for (uint16_t i = 0; i < outputCount; ++i) {
       mLastChunks[i].SetNull(0);
     }
 
--- a/content/media/AudioNodeStream.h
+++ b/content/media/AudioNodeStream.h
@@ -18,16 +18,17 @@
 #define LOG(type, msg)
 #endif
 
 namespace mozilla {
 
 namespace dom {
 struct ThreeDPoint;
 class AudioParamTimeline;
+class DelayNodeEngine;
 }
 
 class ThreadSharedFloatArrayBufferList;
 
 /**
  * An AudioNodeStream produces one audio track with ID AUDIO_TRACK.
  * The start time of the AudioTrack is aligned to the start time of the
  * AudioContext's destination node stream, plus some multiple of BLOCK_SIZE
@@ -50,17 +51,18 @@ public:
                   MediaStreamGraph::AudioNodeStreamKind aKind,
                   TrackRate aSampleRate)
     : ProcessedMediaStream(nullptr),
       mEngine(aEngine),
       mSampleRate(aSampleRate),
       mKind(aKind),
       mNumberOfInputChannels(2),
       mMarkAsFinishedAfterThisBlock(false),
-      mAudioParamStream(false)
+      mAudioParamStream(false),
+      mMuted(false)
   {
     MOZ_ASSERT(NS_IsMainThread());
     mChannelCountMode = dom::ChannelCountMode::Max;
     mChannelInterpretation = dom::ChannelInterpretation::Speakers;
     // AudioNodes are always producing data
     mHasCurrentData = true;
     MOZ_COUNT_CTOR(AudioNodeStream);
   }
@@ -99,16 +101,24 @@ public:
                                       dom::ChannelInterpretation aChannelInterpretation);
   virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo);
   TrackTicks GetCurrentPosition();
   bool AllInputsFinished() const;
   bool IsAudioParamStream() const
   {
     return mAudioParamStream;
   }
+  void Mute() {
+    mMuted = true;
+  }
+
+  void Unmute() {
+    mMuted = false;
+  }
+
   const OutputChunks& LastChunks() const
   {
     return mLastChunks;
   }
   virtual bool MainThreadNeedsUpdates() const MOZ_OVERRIDE
   {
     // Only source and external streams need updates on the main thread.
     return (mKind == MediaStreamGraph::SOURCE_STREAM && mFinished) ||
@@ -149,13 +159,15 @@ protected:
   // The mixing modes
   dom::ChannelCountMode mChannelCountMode;
   dom::ChannelInterpretation mChannelInterpretation;
   // Whether the stream should be marked as finished as soon
   // as the current time range has been computed block by block.
   bool mMarkAsFinishedAfterThisBlock;
   // Whether the stream is an AudioParamHelper stream.
   bool mAudioParamStream;
+  // Whether the stream is muted. Access only on the MediaStreamGraph thread.
+  bool mMuted;
 };
 
 }
 
 #endif /* MOZILLA_AUDIONODESTREAM_H_ */
--- a/content/media/MediaStreamGraph.cpp
+++ b/content/media/MediaStreamGraph.cpp
@@ -469,22 +469,42 @@ MediaStreamGraphImpl::MarkConsumed(Media
 void
 MediaStreamGraphImpl::UpdateStreamOrderForStream(mozilla::LinkedList<MediaStream>* aStack,
                                                  already_AddRefed<MediaStream> aStream)
 {
   nsRefPtr<MediaStream> stream = aStream;
   NS_ASSERTION(!stream->mHasBeenOrdered, "stream should not have already been ordered");
   if (stream->mIsOnOrderingStack) {
     MediaStream* iter = aStack->getLast();
+    AudioNodeStream* ns = stream->AsAudioNodeStream();
+    bool delayNodePresent = ns ? ns->Engine()->AsDelayNodeEngine() != nullptr : false;
+    bool cycleFound = false;
     if (iter) {
       do {
+        cycleFound = true;
         iter->AsProcessedStream()->mInCycle = true;
+        AudioNodeStream* ns = iter->AsAudioNodeStream();
+        if (ns && ns->Engine()->AsDelayNodeEngine()) {
+          delayNodePresent = true;
+        }
         iter = iter->getPrevious();
       } while (iter && iter != stream);
     }
+    if (cycleFound && !delayNodePresent) {
+      // If we have detected a cycle, the previous loop should exit with stream
+      // == iter. Go back in the cycle and mute all nodes we find.
+      MOZ_ASSERT(iter);
+      do {
+        // There can't be non-AudioNodeStream here, MediaStreamAudio{Source,
+        // Destination}Node are connected to regular MediaStreams, but they can't be
+        // in a cycle (there is no content API to do so).
+        MOZ_ASSERT(iter->AsAudioNodeStream());
+        iter->AsAudioNodeStream()->Mute();
+      } while((iter = iter->getNext()));
+    }
     return;
   }
   ProcessedMediaStream* ps = stream->AsProcessedStream();
   if (ps) {
     aStack->insertBack(stream);
     stream->mIsOnOrderingStack = true;
     for (uint32_t i = 0; i < ps->mInputs.Length(); ++i) {
       MediaStream* source = ps->mInputs[i]->mSource;
@@ -510,16 +530,20 @@ MediaStreamGraphImpl::UpdateStreamOrder(
     MediaStream* stream = mOldStreams[i];
     stream->mHasBeenOrdered = false;
     stream->mIsConsumed = false;
     stream->mIsOnOrderingStack = false;
     stream->mInBlockingSet = false;
     ProcessedMediaStream* ps = stream->AsProcessedStream();
     if (ps) {
       ps->mInCycle = false;
+      AudioNodeStream* ns = ps->AsAudioNodeStream();
+      if (ns) {
+        ns->Unmute();
+      }
     }
   }
 
   mozilla::LinkedList<MediaStream> stack;
   for (uint32_t i = 0; i < mOldStreams.Length(); ++i) {
     nsRefPtr<MediaStream>& s = mOldStreams[i];
     if (s->IsIntrinsicallyConsumed()) {
       MarkConsumed(s);
--- a/content/media/webaudio/AudioNode.h
+++ b/content/media/webaudio/AudioNode.h
@@ -128,16 +128,20 @@ public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioNode,
                                            nsDOMEventTargetHelper)
 
   virtual AudioBufferSourceNode* AsAudioBufferSourceNode() {
     return nullptr;
   }
 
+  virtual const DelayNode* AsDelayNode() const {
+    return nullptr;
+  }
+
   AudioContext* GetParentObject() const
   {
     return mContext;
   }
 
   AudioContext* Context() const
   {
     return mContext;
--- a/content/media/webaudio/DelayNode.cpp
+++ b/content/media/webaudio/DelayNode.cpp
@@ -38,16 +38,21 @@ public:
     // Use a smoothing range of 20ms
     , mProcessor(aMaxDelayFrames,
                  WebAudioUtils::ComputeSmoothingRate(0.02,
                                                      mDestination->SampleRate()))
     , mLeftOverData(INT32_MIN)
   {
   }
 
+  virtual DelayNodeEngine* AsDelayNodeEngine()
+  {
+    return this;
+  }
+
   void SetSourceStream(AudioNodeStream* aSource)
   {
     mSource = aSource;
   }
 
   enum Parameters {
     DELAY,
   };
--- a/content/media/webaudio/DelayNode.h
+++ b/content/media/webaudio/DelayNode.h
@@ -27,16 +27,21 @@ public:
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
 
   AudioParam* DelayTime() const
   {
     return mDelay;
   }
 
+  virtual const DelayNode* AsDelayNode() const MOZ_OVERRIDE
+  {
+    return this;
+  }
+
   virtual void NotifyInputConnected() MOZ_OVERRIDE
   {
     mMediaStreamGraphUpdateIndexAtLastInputConnection =
       mStream->Graph()->GetCurrentGraphUpdateIndex();
   }
   bool AcceptPlayingRefRelease(int64_t aLastGraphUpdateIndexProcessed) const
   {
     // Reject any requests to release the playing ref if the request was issued