Bug 856361. Part 3: Refactor AudioNodeStream to create ComputeFinalOuputChannelCount, AccumulateInputChunk and AdvanceOutputSegment, and make AudioNodeStreams be treated as always consumed by the MediaStreamGraph. r=ehsan, a=webaudio
authorRobert O'Callahan <robert@ocallahan.org>
Wed, 24 Jul 2013 22:11:35 +1200
changeset 153843 d5464c63d3e78ad02191eac5a22248997f0de3b7
parent 153842 e7c56a36e60e351aa3b4fa23885aeacde0730f6b
child 153844 98c2f778021053dc7eea9bf593af8490565de84c
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)
reviewersehsan, webaudio
bugs856361
milestone25.0a2
Bug 856361. Part 3: Refactor AudioNodeStream to create ComputeFinalOuputChannelCount, AccumulateInputChunk and AdvanceOutputSegment, and make AudioNodeStreams be treated as always consumed by the MediaStreamGraph. r=ehsan, a=webaudio
content/media/AudioNodeStream.cpp
content/media/AudioNodeStream.h
content/media/DOMMediaStream.h
content/media/MediaStreamGraph.cpp
content/media/MediaStreamGraph.h
--- a/content/media/AudioNodeStream.cpp
+++ b/content/media/AudioNodeStream.cpp
@@ -243,16 +243,34 @@ AudioNodeStream::AllInputsFinished() con
   for (uint32_t i = 0; i < inputCount; ++i) {
     if (!mInputs[i]->GetSource()->IsFinishedOnGraphThread()) {
       return false;
     }
   }
   return !!inputCount;
 }
 
+uint32_t
+AudioNodeStream::ComputeFinalOuputChannelCount(uint32_t aInputChannelCount)
+{
+  switch (mChannelCountMode) {
+  case ChannelCountMode::Explicit:
+    // Disregard the channel count we've calculated from inputs, and just use
+    // mNumberOfInputChannels.
+    return mNumberOfInputChannels;
+  case ChannelCountMode::Clamped_max:
+    // Clamp the computed output channel count to mNumberOfInputChannels.
+    return std::min(aInputChannelCount, mNumberOfInputChannels);
+  default:
+  case ChannelCountMode::Max:
+    // Nothing to do here, just shut up the compiler warning.
+    return aInputChannelCount;
+  }
+}
+
 void
 AudioNodeStream::ObtainInputBlock(AudioChunk& aTmpChunk, uint32_t aPortIndex)
 {
   uint32_t inputCount = mInputs.Length();
   uint32_t outputChannelCount = 1;
   nsAutoTArray<AudioChunk*,250> inputChunks;
   for (uint32_t i = 0; i < inputCount; ++i) {
     if (aPortIndex != mInputs[i]->InputNumber()) {
@@ -272,30 +290,17 @@ AudioNodeStream::ObtainInputBlock(AudioC
       continue;
     }
 
     inputChunks.AppendElement(chunk);
     outputChannelCount =
       GetAudioChannelsSuperset(outputChannelCount, chunk->mChannelData.Length());
   }
 
-  switch (mChannelCountMode) {
-  case ChannelCountMode::Explicit:
-    // Disregard the output channel count that we've calculated, and just use
-    // mNumberOfInputChannels.
-    outputChannelCount = mNumberOfInputChannels;
-    break;
-  case ChannelCountMode::Clamped_max:
-    // Clamp the computed output channel count to mNumberOfInputChannels.
-    outputChannelCount = std::min(outputChannelCount, mNumberOfInputChannels);
-    break;
-  case ChannelCountMode::Max:
-    // Nothing to do here, just shut up the compiler warning.
-    break;
-  }
+  outputChannelCount = ComputeFinalOuputChannelCount(outputChannelCount);
 
   uint32_t inputChunkCount = inputChunks.Length();
   if (inputChunkCount == 0 ||
       (inputChunkCount == 1 && inputChunks[0]->mChannelData.Length() == 0)) {
     aTmpChunk.SetNull(WEBAUDIO_BLOCK_SIZE);
     return;
   }
 
@@ -306,91 +311,106 @@ AudioNodeStream::ObtainInputBlock(AudioC
   }
 
   if (outputChannelCount == 0) {
     aTmpChunk.SetNull(WEBAUDIO_BLOCK_SIZE);
     return;
   }
 
   AllocateAudioBlock(outputChannelCount, &aTmpChunk);
-  float silenceChannel[WEBAUDIO_BLOCK_SIZE] = {0.f};
   // The static storage here should be 1KB, so it's fine
   nsAutoTArray<float, GUESS_AUDIO_CHANNELS*WEBAUDIO_BLOCK_SIZE> downmixBuffer;
 
   for (uint32_t i = 0; i < inputChunkCount; ++i) {
-    AudioChunk* chunk = inputChunks[i];
-    nsAutoTArray<const void*,GUESS_AUDIO_CHANNELS> channels;
-    channels.AppendElements(chunk->mChannelData);
-    if (channels.Length() < outputChannelCount) {
-      if (mChannelInterpretation == ChannelInterpretation::Speakers) {
-        AudioChannelsUpMix(&channels, outputChannelCount, nullptr);
-        NS_ASSERTION(outputChannelCount == channels.Length(),
-                     "We called GetAudioChannelsSuperset to avoid this");
+    AccumulateInputChunk(i, *inputChunks[i], &aTmpChunk, &downmixBuffer);
+  }
+}
+
+void
+AudioNodeStream::AccumulateInputChunk(uint32_t aInputIndex, const AudioChunk& aChunk,
+                                      AudioChunk* aBlock,
+                                      nsTArray<float>* aDownmixBuffer)
+{
+  nsAutoTArray<const void*,GUESS_AUDIO_CHANNELS> channels;
+  UpMixDownMixChunk(&aChunk, aBlock->mChannelData.Length(), channels, *aDownmixBuffer);
+
+  for (uint32_t c = 0; c < channels.Length(); ++c) {
+    const float* inputData = static_cast<const float*>(channels[c]);
+    float* outputData = static_cast<float*>(const_cast<void*>(aBlock->mChannelData[c]));
+    if (inputData) {
+      if (aInputIndex == 0) {
+        AudioBlockCopyChannelWithScale(inputData, aChunk.mVolume, outputData);
       } else {
-        // Fill up the remaining channels by zeros
-        for (uint32_t j = channels.Length(); j < outputChannelCount; ++j) {
-          channels.AppendElement(silenceChannel);
-        }
+        AudioBlockAddChannelWithScale(inputData, aChunk.mVolume, outputData);
       }
-    } else if (channels.Length() > outputChannelCount) {
-      if (mChannelInterpretation == ChannelInterpretation::Speakers) {
-        nsAutoTArray<float*,GUESS_AUDIO_CHANNELS> outputChannels;
-        outputChannels.SetLength(outputChannelCount);
-        downmixBuffer.SetLength(outputChannelCount * WEBAUDIO_BLOCK_SIZE);
-        for (uint32_t j = 0; j < outputChannelCount; ++j) {
-          outputChannels[j] = &downmixBuffer[j * WEBAUDIO_BLOCK_SIZE];
-        }
-
-        AudioChannelsDownMix(channels, outputChannels.Elements(),
-                             outputChannelCount, WEBAUDIO_BLOCK_SIZE);
-
-        channels.SetLength(outputChannelCount);
-        for (uint32_t j = 0; j < channels.Length(); ++j) {
-          channels[j] = outputChannels[j];
-        }
-      } else {
-        // Drop the remaining channels
-        channels.RemoveElementsAt(outputChannelCount,
-                                  channels.Length() - outputChannelCount);
+    } else {
+      if (aInputIndex == 0) {
+        PodZero(outputData, WEBAUDIO_BLOCK_SIZE);
       }
     }
+  }
+}
 
-    for (uint32_t c = 0; c < channels.Length(); ++c) {
-      const float* inputData = static_cast<const float*>(channels[c]);
-      float* outputData = static_cast<float*>(const_cast<void*>(aTmpChunk.mChannelData[c]));
-      if (inputData) {
-        if (i == 0) {
-          AudioBlockCopyChannelWithScale(inputData, chunk->mVolume, outputData);
-        } else {
-          AudioBlockAddChannelWithScale(inputData, chunk->mVolume, outputData);
-        }
-      } else {
-        if (i == 0) {
-          memset(outputData, 0, WEBAUDIO_BLOCK_SIZE*sizeof(float));
-        }
+void
+AudioNodeStream::UpMixDownMixChunk(const AudioChunk* aChunk,
+                                   uint32_t aOutputChannelCount,
+                                   nsTArray<const void*>& aOutputChannels,
+                                   nsTArray<float>& aDownmixBuffer)
+{
+  static const float silenceChannel[WEBAUDIO_BLOCK_SIZE] = {0.f};
+
+  aOutputChannels.AppendElements(aChunk->mChannelData);
+  if (aOutputChannels.Length() < aOutputChannelCount) {
+    if (mChannelInterpretation == ChannelInterpretation::Speakers) {
+      AudioChannelsUpMix(&aOutputChannels, aOutputChannelCount, nullptr);
+      NS_ASSERTION(aOutputChannelCount == aOutputChannels.Length(),
+                   "We called GetAudioChannelsSuperset to avoid this");
+    } else {
+      // Fill up the remaining aOutputChannels by zeros
+      for (uint32_t j = aOutputChannels.Length(); j < aOutputChannelCount; ++j) {
+        aOutputChannels.AppendElement(silenceChannel);
       }
     }
+  } else if (aOutputChannels.Length() > aOutputChannelCount) {
+    if (mChannelInterpretation == ChannelInterpretation::Speakers) {
+      nsAutoTArray<float*,GUESS_AUDIO_CHANNELS> outputChannels;
+      outputChannels.SetLength(aOutputChannelCount);
+      aDownmixBuffer.SetLength(aOutputChannelCount * WEBAUDIO_BLOCK_SIZE);
+      for (uint32_t j = 0; j < aOutputChannelCount; ++j) {
+        outputChannels[j] = &aDownmixBuffer[j * WEBAUDIO_BLOCK_SIZE];
+      }
+
+      AudioChannelsDownMix(aOutputChannels, outputChannels.Elements(),
+                           aOutputChannelCount, WEBAUDIO_BLOCK_SIZE);
+
+      aOutputChannels.SetLength(aOutputChannelCount);
+      for (uint32_t j = 0; j < aOutputChannels.Length(); ++j) {
+        aOutputChannels[j] = outputChannels[j];
+      }
+    } else {
+      // Drop the remaining aOutputChannels
+      aOutputChannels.RemoveElementsAt(aOutputChannelCount,
+        aOutputChannels.Length() - aOutputChannelCount);
+    }
   }
 }
 
 // The MediaStreamGraph guarantees that this is actually one block, for
 // AudioNodeStreams.
 void
 AudioNodeStream::ProduceOutput(GraphTime aFrom, GraphTime aTo)
 {
   if (mMarkAsFinishedAfterThisBlock) {
     // This stream was finished the last time that we looked at it, and all
     // of the depending streams have finished their output as well, so now
     // it's time to mark this stream as finished.
     FinishOutput();
   }
 
-  StreamBuffer::Track* track = EnsureTrack(AUDIO_NODE_STREAM_TRACK_ID, mSampleRate);
-
-  AudioSegment* segment = track->Get<AudioSegment>();
+  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
     for (uint16_t i = 0; i < outputCount; ++i) {
       mLastChunks[i].SetNull(WEBAUDIO_BLOCK_SIZE);
@@ -419,16 +439,25 @@ AudioNodeStream::ProduceOutput(GraphTime
   }
 
   if (mDisabledTrackIDs.Contains(AUDIO_NODE_STREAM_TRACK_ID)) {
     for (uint32_t i = 0; i < mLastChunks.Length(); ++i) {
       mLastChunks[i].SetNull(WEBAUDIO_BLOCK_SIZE);
     }
   }
 
+  AdvanceOutputSegment();
+}
+
+void
+AudioNodeStream::AdvanceOutputSegment()
+{
+  StreamBuffer::Track* track = EnsureTrack(AUDIO_NODE_STREAM_TRACK_ID, mSampleRate);
+  AudioSegment* segment = track->Get<AudioSegment>();
+
   if (mKind == MediaStreamGraph::EXTERNAL_STREAM) {
     segment->AppendAndConsumeChunk(&mLastChunks[0]);
   } else {
     segment->AppendNullData(mLastChunks[0].GetDuration());
   }
 
   for (uint32_t j = 0; j < mListeners.Length(); ++j) {
     MediaStreamListener* l = mListeners[j];
--- a/content/media/AudioNodeStream.h
+++ b/content/media/AudioNodeStream.h
@@ -109,24 +109,36 @@ public:
     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) ||
            mKind == MediaStreamGraph::EXTERNAL_STREAM;
   }
+  virtual bool IsIntrinsicallyConsumed() const MOZ_OVERRIDE
+  {
+    return true;
+  }
 
   // Any thread
   AudioNodeEngine* Engine() { return mEngine; }
   TrackRate SampleRate() const { return mSampleRate; }
 
 protected:
+  void AdvanceOutputSegment();
   void FinishOutput();
+  void AccumulateInputChunk(uint32_t aInputIndex, const AudioChunk& aChunk,
+                            AudioChunk* aBlock,
+                            nsTArray<float>* aDownmixBuffer);
+  void UpMixDownMixChunk(const AudioChunk* aChunk, uint32_t aOutputChannelCount,
+                         nsTArray<const void*>& aOutputChannels,
+                         nsTArray<float>& aDownmixBuffer);
 
+  uint32_t ComputeFinalOuputChannelCount(uint32_t aInputChannelCount);
   void ObtainInputBlock(AudioChunk& aTmpChunk, uint32_t aPortIndex);
 
   // The engine that will generate output for this node.
   nsAutoPtr<AudioNodeEngine> mEngine;
   // The last block produced by this node.
   OutputChunks mLastChunks;
   // The stream's sampling rate
   const TrackRate mSampleRate;
--- a/content/media/DOMMediaStream.h
+++ b/content/media/DOMMediaStream.h
@@ -63,20 +63,22 @@ public:
   {
     return mWindow;
   }
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
 
   // WebIDL
   double CurrentTime();
+
   void GetAudioTracks(nsTArray<nsRefPtr<AudioStreamTrack> >& aTracks);
   void GetVideoTracks(nsTArray<nsRefPtr<VideoStreamTrack> >& aTracks);
 
-  MediaStream* GetStream() { return mStream; }
+  MediaStream* GetStream() const { return mStream; }
+
   bool IsFinished();
   /**
    * Returns a principal indicating who may access this stream. The stream contents
    * can only be accessed by principals subsuming this principal.
    */
   nsIPrincipal* GetPrincipal() { return mPrincipal; }
 
   /**
--- a/content/media/MediaStreamGraph.cpp
+++ b/content/media/MediaStreamGraph.cpp
@@ -515,17 +515,17 @@ MediaStreamGraphImpl::UpdateStreamOrder(
     if (ps) {
       ps->mInCycle = false;
     }
   }
 
   mozilla::LinkedList<MediaStream> stack;
   for (uint32_t i = 0; i < mOldStreams.Length(); ++i) {
     nsRefPtr<MediaStream>& s = mOldStreams[i];
-    if (!s->mAudioOutputs.IsEmpty() || !s->mVideoOutputs.IsEmpty()) {
+    if (s->IsIntrinsicallyConsumed()) {
       MarkConsumed(s);
     }
     if (!s->mHasBeenOrdered) {
       UpdateStreamOrderForStream(&stack, s.forget());
     }
   }
 }
 
--- a/content/media/MediaStreamGraph.h
+++ b/content/media/MediaStreamGraph.h
@@ -395,16 +395,26 @@ public:
   void ChangeExplicitBlockerCountImpl(GraphTime aTime, int32_t aDelta)
   {
     mExplicitBlockerCount.SetAtAndAfter(aTime, mExplicitBlockerCount.GetAt(aTime) + aDelta);
   }
   void AddListenerImpl(already_AddRefed<MediaStreamListener> aListener);
   void RemoveListenerImpl(MediaStreamListener* aListener);
   void RemoveAllListenersImpl();
   void SetTrackEnabledImpl(TrackID aTrackID, bool aEnabled);
+  /**
+   * Returns true when this stream requires the contents of its inputs even if
+   * its own outputs are not being consumed. This is used to signal inputs to
+   * this stream that they are being consumed; when they're not being consumed,
+   * we make some optimizations.
+   */
+  virtual bool IsIntrinsicallyConsumed() const
+  {
+    return !mAudioOutputs.IsEmpty() || !mVideoOutputs.IsEmpty();
+  }
 
   void AddConsumer(MediaInputPort* aPort)
   {
     mConsumers.AppendElement(aPort);
   }
   void RemoveConsumer(MediaInputPort* aPort)
   {
     mConsumers.RemoveElement(aPort);