Bug 865257 - Implement MediaStreamAudioDestinationNode. r=ehsan,roc
authorJosh Matthews <josh@joshmatthews.net>
Tue, 21 May 2013 15:17:47 -0400
changeset 146689 fbcd6734691f17391337ac766af7dcd6925786f4
parent 146688 6c897b8852ab011772c3f87363e8f3fc1be539fd
child 146690 14e11638efcbb4071241c512097e7aee937a1ed5
push id2697
push userbbajaj@mozilla.com
push dateMon, 05 Aug 2013 18:49:53 +0000
treeherdermozilla-beta@dfec938c7b63 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan, roc
bugs865257
milestone24.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 865257 - Implement MediaStreamAudioDestinationNode. r=ehsan,roc
content/media/AudioNodeStream.cpp
content/media/AudioNodeStream.h
content/media/DOMMediaStream.cpp
content/media/DOMMediaStream.h
content/media/MediaStreamGraph.cpp
content/media/MediaStreamGraph.h
content/media/TrackUnionStream.h
content/media/moz.build
content/media/webaudio/AudioContext.cpp
content/media/webaudio/AudioContext.h
content/media/webaudio/MediaStreamAudioDestinationNode.cpp
content/media/webaudio/MediaStreamAudioDestinationNode.h
content/media/webaudio/moz.build
content/media/webaudio/test/Makefile.in
content/media/webaudio/test/test_mediaStreamAudioDestinationNode.html
dom/bindings/Bindings.conf
dom/webidl/AudioContext.webidl
dom/webidl/MediaStreamAudioDestinationNode.webidl
dom/webidl/WebIDL.mk
--- a/content/media/AudioNodeStream.cpp
+++ b/content/media/AudioNodeStream.cpp
@@ -14,16 +14,17 @@ using namespace mozilla::dom;
 namespace mozilla {
 
 /**
  * An AudioNodeStream produces a single audio track with ID
  * AUDIO_NODE_STREAM_TRACK_ID. This track has rate AudioContext::sIdealAudioRate
  * for regular audio contexts, and the rate requested by the web content
  * for offline audio contexts.
  * Each chunk in the track is a single block of WEBAUDIO_BLOCK_SIZE samples.
+ * Note: This must be a different value than MEDIA_STREAM_DEST_TRACK_ID
  */
 static const int AUDIO_NODE_STREAM_TRACK_ID = 1;
 
 AudioNodeStream::~AudioNodeStream()
 {
   MOZ_COUNT_DTOR(AudioNodeStream);
 }
 
@@ -230,33 +231,16 @@ AudioNodeStream::SetChannelMixingParamet
   MOZ_ASSERT(int(aChannelCountMode) < INT16_MAX);
   MOZ_ASSERT(int(aChannelInterpretation) < INT16_MAX);
 
   mNumberOfInputChannels = aNumberOfChannels;
   mChannelCountMode = aChannelCountMode;
   mChannelInterpretation = aChannelInterpretation;
 }
 
-StreamBuffer::Track*
-AudioNodeStream::EnsureTrack()
-{
-  StreamBuffer::Track* track = mBuffer.FindTrack(AUDIO_NODE_STREAM_TRACK_ID);
-  if (!track) {
-    nsAutoPtr<MediaSegment> segment(new AudioSegment());
-    for (uint32_t j = 0; j < mListeners.Length(); ++j) {
-      MediaStreamListener* l = mListeners[j];
-      l->NotifyQueuedTrackChanges(Graph(), AUDIO_NODE_STREAM_TRACK_ID, mSampleRate, 0,
-                                  MediaStreamListener::TRACK_EVENT_CREATED,
-                                  *segment);
-    }
-    track = &mBuffer.AddTrack(AUDIO_NODE_STREAM_TRACK_ID, mSampleRate, 0, segment.forget());
-  }
-  return track;
-}
-
 bool
 AudioNodeStream::AllInputsFinished() const
 {
   uint32_t inputCount = mInputs.Length();
   for (uint32_t i = 0; i < inputCount; ++i) {
     if (!mInputs[i]->GetSource()->IsFinishedOnGraphThread()) {
       return false;
     }
@@ -394,17 +378,17 @@ AudioNodeStream::ProduceOutput(GraphTime
 {
   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();
+  StreamBuffer::Track* track = EnsureTrack(AUDIO_NODE_STREAM_TRACK_ID, mSampleRate);
 
   AudioSegment* segment = track->Get<AudioSegment>();
 
   uint16_t outputCount = std::max(uint16_t(1), mEngine->OutputCount());
   mLastChunks.SetLength(outputCount);
 
   if (mInCycle) {
     // XXX DelayNode not supported yet so just produce silence
@@ -455,27 +439,27 @@ AudioNodeStream::ProduceOutput(GraphTime
                                 mSampleRate, segment->GetDuration(), 0,
                                 tmpSegment);
   }
 }
 
 TrackTicks
 AudioNodeStream::GetCurrentPosition()
 {
-  return EnsureTrack()->Get<AudioSegment>()->GetDuration();
+  return EnsureTrack(AUDIO_NODE_STREAM_TRACK_ID, mSampleRate)->Get<AudioSegment>()->GetDuration();
 }
 
 void
 AudioNodeStream::FinishOutput()
 {
   if (IsFinishedOnGraphThread()) {
     return;
   }
 
-  StreamBuffer::Track* track = EnsureTrack();
+  StreamBuffer::Track* track = EnsureTrack(AUDIO_NODE_STREAM_TRACK_ID, mSampleRate);
   track->SetEnded();
   FinishOnGraphThread();
 
   for (uint32_t j = 0; j < mListeners.Length(); ++j) {
     MediaStreamListener* l = mListeners[j];
     AudioSegment emptySegment;
     l->NotifyQueuedTrackChanges(Graph(), AUDIO_NODE_STREAM_TRACK_ID,
                                 mSampleRate,
--- a/content/media/AudioNodeStream.h
+++ b/content/media/AudioNodeStream.h
@@ -111,17 +111,16 @@ public:
 
   // Any thread
   AudioNodeEngine* Engine() { return mEngine; }
   TrackRate SampleRate() const { return mSampleRate; }
 
 protected:
   void FinishOutput();
 
-  StreamBuffer::Track* EnsureTrack();
   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.cpp
+++ b/content/media/DOMMediaStream.cpp
@@ -3,16 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "DOMMediaStream.h"
 #include "nsDOMClassInfoID.h"
 #include "nsContentUtils.h"
 #include "mozilla/dom/MediaStreamBinding.h"
 #include "mozilla/dom/LocalMediaStreamBinding.h"
+#include "mozilla/dom/AudioNode.h"
 #include "MediaStreamGraph.h"
 #include "AudioStreamTrack.h"
 #include "VideoStreamTrack.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMMediaStream)
@@ -34,16 +35,25 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTracks)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(DOMMediaStream)
 
 NS_IMPL_ISUPPORTS_INHERITED1(DOMLocalMediaStream, DOMMediaStream,
                              nsIDOMLocalMediaStream)
 
+NS_IMPL_CYCLE_COLLECTION_INHERITED_1(DOMAudioNodeMediaStream, DOMMediaStream,
+                                     mStreamNode)
+
+NS_IMPL_ADDREF_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream)
+NS_IMPL_RELEASE_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMAudioNodeMediaStream)
+NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream)
+
 class DOMMediaStream::StreamListener : public MediaStreamListener {
 public:
   StreamListener(DOMMediaStream* aStream)
     : mStream(aStream)
   {}
 
   // Main thread only
   void Forget() { mStream = nullptr; }
@@ -341,8 +351,23 @@ DOMLocalMediaStream::CreateSourceStream(
 already_AddRefed<DOMLocalMediaStream>
 DOMLocalMediaStream::CreateTrackUnionStream(nsIDOMWindow* aWindow,
                                             TrackTypeHints aHintContents)
 {
   nsRefPtr<DOMLocalMediaStream> stream = new DOMLocalMediaStream();
   stream->InitTrackUnionStream(aWindow, aHintContents);
   return stream.forget();
 }
+
+DOMAudioNodeMediaStream::DOMAudioNodeMediaStream(AudioNode* aNode)
+: mStreamNode(aNode)
+{
+}
+
+already_AddRefed<DOMAudioNodeMediaStream>
+DOMAudioNodeMediaStream::CreateTrackUnionStream(nsIDOMWindow* aWindow,
+                                                AudioNode* aNode,
+                                                TrackTypeHints aHintContents)
+{
+  nsRefPtr<DOMAudioNodeMediaStream> stream = new DOMAudioNodeMediaStream(aNode);
+  stream->InitTrackUnionStream(aWindow, aHintContents);
+  return stream.forget();
+}
--- a/content/media/DOMMediaStream.h
+++ b/content/media/DOMMediaStream.h
@@ -28,16 +28,17 @@ class nsXPCClassInfo;
 #undef CurrentTime
 #endif
 
 namespace mozilla {
 
 class MediaStream;
 
 namespace dom {
+class AudioNode;
 class MediaStreamTrack;
 class AudioStreamTrack;
 class VideoStreamTrack;
 }
 
 /**
  * DOM wrapper for MediaStreams.
  */
@@ -201,11 +202,34 @@ public:
 
   /**
    * Create an nsDOMLocalMediaStream whose underlying stream is a TrackUnionStream.
    */
   static already_AddRefed<DOMLocalMediaStream>
   CreateTrackUnionStream(nsIDOMWindow* aWindow, TrackTypeHints aHintContents = 0);
 };
 
+class DOMAudioNodeMediaStream : public DOMMediaStream
+{
+  typedef dom::AudioNode AudioNode;
+public:
+  DOMAudioNodeMediaStream(AudioNode* aNode);
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream)
+
+  /**
+   * Create a DOMAudioNodeMediaStream whose underlying stream is a TrackUnionStream.
+   */
+  static already_AddRefed<DOMAudioNodeMediaStream>
+  CreateTrackUnionStream(nsIDOMWindow* aWindow,
+                         AudioNode* aNode,
+                         TrackTypeHints aHintContents = 0);
+
+private:
+  // If this object wraps a stream owned by an AudioNode, we need to ensure that
+  // the node isn't cycle-collected too early.
+  nsRefPtr<AudioNode> mStreamNode;
+};
+
 }
 
 #endif /* NSDOMMEDIASTREAM_H_ */
--- a/content/media/MediaStreamGraph.cpp
+++ b/content/media/MediaStreamGraph.cpp
@@ -1520,16 +1520,33 @@ MediaStream::StreamTimeToGraphTime(Strea
 }
 
 void
 MediaStream::FinishOnGraphThread()
 {
   GraphImpl()->FinishStream(this);
 }
 
+StreamBuffer::Track*
+MediaStream::EnsureTrack(TrackID aTrackId, TrackRate aSampleRate)
+{
+  StreamBuffer::Track* track = mBuffer.FindTrack(aTrackId);
+  if (!track) {
+    nsAutoPtr<MediaSegment> segment(new AudioSegment());
+    for (uint32_t j = 0; j < mListeners.Length(); ++j) {
+      MediaStreamListener* l = mListeners[j];
+      l->NotifyQueuedTrackChanges(Graph(), aTrackId, aSampleRate, 0,
+                                  MediaStreamListener::TRACK_EVENT_CREATED,
+                                  *segment);
+    }
+    track = &mBuffer.AddTrack(aTrackId, aSampleRate, 0, segment.forget());
+  }
+  return track;
+}
+
 void
 MediaStream::RemoveAllListenersImpl()
 {
   for (int32_t i = mListeners.Length() - 1; i >= 0; --i) {
     nsRefPtr<MediaStreamListener> listener = mListeners[i].forget();
     listener->NotifyRemoved(GraphImpl());
   }
   mListeners.Clear();
--- a/content/media/MediaStreamGraph.h
+++ b/content/media/MediaStreamGraph.h
@@ -272,19 +272,21 @@ public:
     , mHasCurrentData(false)
     , mNotifiedHasCurrentData(false)
     , mWrapper(aWrapper)
     , mMainThreadCurrentTime(0)
     , mMainThreadFinished(false)
     , mMainThreadDestroyed(false)
     , mGraph(nullptr)
   {
+    MOZ_COUNT_CTOR(MediaStream);
   }
   virtual ~MediaStream()
   {
+    MOZ_COUNT_DTOR(MediaStream);
     NS_ASSERTION(mMainThreadDestroyed, "Should have been destroyed already");
     NS_ASSERTION(mMainThreadListeners.IsEmpty(),
                  "All main thread listeners should have been removed");
   }
 
   /**
    * Returns the graph that owns this stream.
    */
@@ -426,16 +428,18 @@ public:
    * will not be blocked after mStateComputedTime.
    */
   GraphTime StreamTimeToGraphTime(StreamTime aTime);
   bool IsFinishedOnGraphThread() { return mFinished; }
   void FinishOnGraphThread();
 
   bool HasCurrentData() { return mHasCurrentData; }
 
+  StreamBuffer::Track* EnsureTrack(TrackID aTrack, TrackRate aSampleRate);
+
   void ApplyTrackDisabling(TrackID aTrackID, MediaSegment* aSegment);
 
   DOMMediaStream* GetWrapper()
   {
     NS_ASSERTION(NS_IsMainThread(), "Only use DOMMediaStream on main thread");
     return mWrapper;
   }
 
--- a/content/media/TrackUnionStream.h
+++ b/content/media/TrackUnionStream.h
@@ -21,16 +21,17 @@ namespace mozilla {
  * See MediaStreamGraph::CreateTrackUnionStream.
  * This file is only included by MediaStreamGraph.cpp so it's OK to put the
  * entire implementation in this header file.
  */
 class TrackUnionStream : public ProcessedMediaStream {
 public:
   TrackUnionStream(DOMMediaStream* aWrapper) :
     ProcessedMediaStream(aWrapper),
+    mFilterCallback(nullptr),
     mMaxTrackID(0) {}
 
   virtual void RemoveInput(MediaInputPort* aPort)
   {
     for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) {
       if (mTrackMap[i].mInputPort == aPort) {
         EndTrack(i);
         mTrackMap.RemoveElementAt(i);
@@ -70,17 +71,17 @@ public:
               CopyTrackData(tracks.get(), j, aFrom, aTo, &trackFinished);
             }
             mappedTracksFinished[j] = trackFinished;
             mappedTracksWithMatchingInputTracks[j] = true;
             found = true;
             break;
           }
         }
-        if (!found) {
+        if (!found && (!mFilterCallback || mFilterCallback(tracks.get()))) {
           bool trackFinished = false;
           uint32_t mapIndex = AddTrack(mInputs[i], tracks.get(), aFrom);
           CopyTrackData(tracks.get(), mapIndex, aFrom, aTo, &trackFinished);
           mappedTracksFinished.AppendElement(trackFinished);
           mappedTracksWithMatchingInputTracks.AppendElement(true);
         }
       }
     }
@@ -102,17 +103,26 @@ public:
     }
     mBuffer.AdvanceKnownTracksTime(GraphTimeToStreamTime(aTo));
     if (allHaveCurrentData) {
       // We can make progress if we're not blocked
       mHasCurrentData = true;
     }
   }
 
+  // Consumers may specify a filtering callback to apply to every input track.
+  // Returns true to allow the track to act as an input; false to reject it entirely.
+  typedef bool (*TrackIDFilterCallback)(StreamBuffer::Track*);
+  void SetTrackIDFilter(TrackIDFilterCallback aCallback) {
+    mFilterCallback = aCallback;
+  }
+
 protected:
+  TrackIDFilterCallback mFilterCallback;
+
   // Only non-ended tracks are allowed to persist in this map.
   struct TrackMapEntry {
     MediaInputPort* mInputPort;
     // We keep track IDs instead of track pointers because
     // tracks can be removed without us being notified (e.g.
     // when a finished track is forgotten.) When we need a Track*,
     // we call StreamBuffer::FindTrack, which will return null if
     // the track has been deleted.
--- a/content/media/moz.build
+++ b/content/media/moz.build
@@ -66,16 +66,17 @@ EXPORTS += [
     'MediaDecoderStateMachine.h',
     'MediaMetadataManager.h',
     'MediaResource.h',
     'MediaSegment.h',
     'MediaStreamGraph.h',
     'SharedBuffer.h',
     'StreamBuffer.h',
     'TimeVarying.h',
+    'TrackUnionStream.h',
     'VideoFrameContainer.h',
     'VideoSegment.h',
     'VideoUtils.h',
     'VorbisUtils.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'AudioStreamTrack.h',
--- a/content/media/webaudio/AudioContext.cpp
+++ b/content/media/webaudio/AudioContext.cpp
@@ -19,16 +19,17 @@
 #include "DelayNode.h"
 #include "PannerNode.h"
 #include "AudioListener.h"
 #include "DynamicsCompressorNode.h"
 #include "BiquadFilterNode.h"
 #include "ScriptProcessorNode.h"
 #include "ChannelMergerNode.h"
 #include "ChannelSplitterNode.h"
+#include "MediaStreamAudioDestinationNode.h"
 #include "WaveShaperNode.h"
 #include "WaveTable.h"
 #include "ConvolverNode.h"
 #include "nsNetUtil.h"
 
 namespace mozilla {
 namespace dom {
 
@@ -199,16 +200,24 @@ bool IsValidBufferSize(uint32_t aBufferS
     return true;
   default:
     return false;
   }
 }
 
 }
 
+already_AddRefed<MediaStreamAudioDestinationNode>
+AudioContext::CreateMediaStreamDestination()
+{
+  nsRefPtr<MediaStreamAudioDestinationNode> node =
+      new MediaStreamAudioDestinationNode(this);
+  return node.forget();
+}
+
 already_AddRefed<ScriptProcessorNode>
 AudioContext::CreateScriptProcessor(uint32_t aBufferSize,
                                     uint32_t aNumberOfInputChannels,
                                     uint32_t aNumberOfOutputChannels,
                                     ErrorResult& aRv)
 {
   if ((aNumberOfInputChannels == 0 && aNumberOfOutputChannels == 0) ||
       aNumberOfInputChannels > WebAudioUtils::MaxChannelCount ||
--- a/content/media/webaudio/AudioContext.h
+++ b/content/media/webaudio/AudioContext.h
@@ -46,16 +46,17 @@ class AudioListener;
 class BiquadFilterNode;
 class ChannelMergerNode;
 class ChannelSplitterNode;
 class ConvolverNode;
 class DelayNode;
 class DynamicsCompressorNode;
 class GainNode;
 class GlobalObject;
+class MediaStreamAudioDestinationNode;
 class OfflineRenderSuccessCallback;
 class PannerNode;
 class ScriptProcessorNode;
 class WaveShaperNode;
 class WaveTable;
 
 class AudioContext MOZ_FINAL : public nsDOMEventTargetHelper,
                                public EnableWebAudioCheck
@@ -120,16 +121,19 @@ public:
   CreateBuffer(JSContext* aJSContext, uint32_t aNumberOfChannels,
                uint32_t aLength, float aSampleRate,
                ErrorResult& aRv);
 
   already_AddRefed<AudioBuffer>
   CreateBuffer(JSContext* aJSContext, ArrayBuffer& aBuffer,
                bool aMixToMono, ErrorResult& aRv);
 
+  already_AddRefed<MediaStreamAudioDestinationNode>
+  CreateMediaStreamDestination();
+
   already_AddRefed<ScriptProcessorNode>
   CreateScriptProcessor(uint32_t aBufferSize,
                         uint32_t aNumberOfInputChannels,
                         uint32_t aNumberOfOutputChannels,
                         ErrorResult& aRv);
 
   already_AddRefed<ScriptProcessorNode>
   CreateJavaScriptNode(uint32_t aBufferSize,
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/MediaStreamAudioDestinationNode.cpp
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaStreamAudioDestinationNode.h"
+#include "mozilla/dom/AudioStreamTrack.h"
+#include "mozilla/dom/MediaStreamAudioDestinationNodeBinding.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeStream.h"
+#include "DOMMediaStream.h"
+#include "TrackUnionStream.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED_1(MediaStreamAudioDestinationNode, AudioNode, mDOMStream)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaStreamAudioDestinationNode)
+NS_INTERFACE_MAP_END_INHERITING(AudioNode)
+
+NS_IMPL_ADDREF_INHERITED(MediaStreamAudioDestinationNode, AudioNode)
+NS_IMPL_RELEASE_INHERITED(MediaStreamAudioDestinationNode, AudioNode)
+
+// This must be a different value than AUDIO_NODE_STREAM_TRACK_ID
+static const int MEDIA_STREAM_DEST_TRACK_ID = 2;
+
+class MediaStreamDestinationEngine : public AudioNodeEngine {
+public:
+  MediaStreamDestinationEngine(AudioNode* aNode, ProcessedMediaStream* aOutputStream)
+    : AudioNodeEngine(aNode)
+    , mOutputStream(aOutputStream)
+  {
+    MOZ_ASSERT(mOutputStream);
+  }
+
+  virtual void ProduceAudioBlock(AudioNodeStream* aStream,
+                                 const AudioChunk& aInput,
+                                 AudioChunk* aOutput,
+                                 bool* aFinished) MOZ_OVERRIDE
+  {
+    *aOutput = aInput;
+    StreamBuffer::Track* track = mOutputStream->EnsureTrack(MEDIA_STREAM_DEST_TRACK_ID,
+                                                            aStream->SampleRate());
+    AudioSegment* segment = track->Get<AudioSegment>();
+    segment->AppendAndConsumeChunk(aOutput);
+  }
+
+private:
+  ProcessedMediaStream* mOutputStream;
+};
+
+// This callback is used to ensure that only the audio data for this track is audible
+static bool FilterAudioNodeStreamTrack(StreamBuffer::Track* aTrack)
+{
+  return aTrack->GetID() == MEDIA_STREAM_DEST_TRACK_ID;
+}
+
+MediaStreamAudioDestinationNode::MediaStreamAudioDestinationNode(AudioContext* aContext)
+  : AudioNode(aContext,
+              2,
+              ChannelCountMode::Explicit,
+              ChannelInterpretation::Speakers)
+  , mDOMStream(DOMAudioNodeMediaStream::CreateTrackUnionStream(GetOwner(),
+                                                               this,
+                                                               DOMMediaStream::HINT_CONTENTS_AUDIO))
+{
+  TrackUnionStream* tus = static_cast<TrackUnionStream*>(mDOMStream->GetStream());
+  MOZ_ASSERT(tus == mDOMStream->GetStream()->AsProcessedStream());
+  tus->SetTrackIDFilter(FilterAudioNodeStreamTrack);
+
+  MediaStreamDestinationEngine* engine = new MediaStreamDestinationEngine(this, tus);
+  mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM);
+  mPort = tus->AllocateInputPort(mStream, 0);
+}
+
+void
+MediaStreamAudioDestinationNode::DestroyMediaStream()
+{
+  AudioNode::DestroyMediaStream();
+  if (mPort) {
+    mPort->Destroy();
+    mPort = nullptr;
+  }
+}
+
+JSObject*
+MediaStreamAudioDestinationNode::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
+{
+  return MediaStreamAudioDestinationNodeBinding::Wrap(aCx, aScope, this);
+}
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/MediaStreamAudioDestinationNode.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MediaStreamAudioDestinationNode_h_
+#define MediaStreamAudioDestinationNode_h_
+
+#include "AudioNode.h"
+
+namespace mozilla {
+class DOMMediaStream;
+
+namespace dom {
+
+class MediaStreamAudioDestinationNode : public AudioNode
+{
+public:
+  explicit MediaStreamAudioDestinationNode(AudioContext* aContext);
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaStreamAudioDestinationNode, AudioNode)
+
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+
+  virtual uint16_t NumberOfOutputs() const MOZ_FINAL MOZ_OVERRIDE
+  {
+    return 0;
+  }
+
+  virtual void DestroyMediaStream() MOZ_OVERRIDE;
+
+  DOMMediaStream* DOMStream() const
+  {
+    return mDOMStream;
+  }
+
+private:
+  nsRefPtr<DOMMediaStream> mDOMStream;
+  nsRefPtr<MediaInputPort> mPort;
+};
+
+}
+}
+
+#endif
--- a/content/media/webaudio/moz.build
+++ b/content/media/webaudio/moz.build
@@ -34,16 +34,17 @@ EXPORTS.mozilla.dom += [
     'BiquadFilterNode.h',
     'ChannelMergerNode.h',
     'ChannelSplitterNode.h',
     'ConvolverNode.h',
     'DelayNode.h',
     'DynamicsCompressorNode.h',
     'EnableWebAudioCheck.h',
     'GainNode.h',
+    'MediaStreamAudioDestinationNode.h',
     'OfflineAudioCompletionEvent.h',
     'PannerNode.h',
     'ScriptProcessorNode.h',
     'WaveShaperNode.h',
     'WaveTable.h',
 ]
 
 CPP_SOURCES += [
@@ -60,16 +61,17 @@ CPP_SOURCES += [
     'ChannelMergerNode.cpp',
     'ChannelSplitterNode.cpp',
     'ConvolverNode.cpp',
     'DelayNode.cpp',
     'DynamicsCompressorNode.cpp',
     'EnableWebAudioCheck.cpp',
     'GainNode.cpp',
     'MediaBufferDecoder.cpp',
+    'MediaStreamAudioDestinationNode.cpp',
     'OfflineAudioCompletionEvent.cpp',
     'PannerNode.cpp',
     'ScriptProcessorNode.cpp',
     'ThreeDPoint.cpp',
     'WaveShaperNode.cpp',
     'WaveTable.cpp',
     'WebAudioUtils.cpp',
 ]
--- a/content/media/webaudio/test/Makefile.in
+++ b/content/media/webaudio/test/Makefile.in
@@ -57,16 +57,17 @@ MOCHITEST_FILES := \
   test_delayNode.html \
   test_delayNodeSmallMaxDelay.html \
   test_delayNodeWithGain.html \
   test_dynamicsCompressorNode.html \
   test_gainNode.html \
   test_gainNodeInLoop.html \
   test_maxChannelCount.html \
   test_mediaDecoding.html \
+  test_mediaStreamAudioDestinationNode.html \
   test_mixingRules.html \
   test_nodeToParamConnection.html \
   test_OfflineAudioContext.html \
   test_offlineDestinationChannelCountLess.html \
   test_offlineDestinationChannelCountMore.html \
   test_pannerNode.html \
   test_pannerNode_equalPower.html \
   test_scriptProcessorNode.html \
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/test_mediaStreamAudioDestinationNode.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test MediaStreamAudioDestinationNode</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="webaudio.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<audio id="audioelem"></audio>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+  SpecialPowers.setBoolPref("media.webaudio.enabled", true);
+
+  var context = new AudioContext();
+  var buffer = context.createBuffer(1, 2048, context.sampleRate);
+  for (var i = 0; i < 2048; ++i) {
+    buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+  }
+
+  var source = context.createBufferSource();
+  source.buffer = buffer;
+
+  var dest = context.createMediaStreamDestination();
+  source.connect(dest);
+
+  var elem = document.getElementById('audioelem');
+  elem.mozSrcObject = dest.stream;
+  elem.onloadedmetadata = function() {
+    ok(true, "got metadata event");
+    setTimeout(function() {
+      is(elem.played.length, 1, "should have a played interval");
+      is(elem.played.start(0), 0, "should have played immediately");
+      isnot(elem.played.end(0), 0, "should have played for a non-zero interval");
+      SpecialPowers.clearUserPref("media.webaudio.enabled");
+      SimpleTest.finish();
+    }, 2000);
+  };
+
+  source.start(0);
+  elem.play();
+});
+</script>
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -619,16 +619,21 @@ DOMInterfaces = {
     'nativeType': 'mozilla::DOMMediaStream'
 },
 {
     'nativeType': 'JSObject',
     'workers': True,
     'skipGen': True
 }],
 
+'MediaStreamAudioDestinationNode': {
+    'resultNotAddRefed': [ 'stream' ],
+    'binaryNames': { 'stream': 'DOMStream' }
+},
+
 'MediaStreamList': {
     'headerFile': 'MediaStreamList.h',
     'resultNotAddRefed': [ '__indexedGetter' ],
     'binaryNames': { '__indexedGetter': 'IndexedGetter' }
 },
 
 'MediaStreamTrack': {
     'concrete': False
--- a/dom/webidl/AudioContext.webidl
+++ b/dom/webidl/AudioContext.webidl
@@ -30,16 +30,19 @@ interface AudioContext : EventTarget {
     void decodeAudioData(ArrayBuffer audioData,
                          DecodeSuccessCallback successCallback,
                          optional DecodeErrorCallback errorCallback);
 
     // AudioNode creation 
     [Creator]
     AudioBufferSourceNode createBufferSource();
 
+    [Creator]
+    MediaStreamAudioDestinationNode createMediaStreamDestination();
+
     [Creator, Throws]
     ScriptProcessorNode createScriptProcessor(optional unsigned long bufferSize = 0,
                                               optional unsigned long numberOfInputChannels = 2,
                                               optional unsigned long numberOfOutputChannels = 2);
 
     [Creator]
     AnalyserNode createAnalyser();
     [Creator]
new file mode 100644
--- /dev/null
+++ b/dom/webidl/MediaStreamAudioDestinationNode.webidl
@@ -0,0 +1,18 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html
+ *
+ * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
+ * liability, trademark and document use rules apply.
+ */
+
+[PrefControlled]
+interface MediaStreamAudioDestinationNode : AudioNode {
+
+    readonly attribute MediaStream stream;
+
+};
\ No newline at end of file
--- a/dom/webidl/WebIDL.mk
+++ b/dom/webidl/WebIDL.mk
@@ -168,16 +168,17 @@ webidl_files = \
   InspectorUtils.webidl \
   KeyboardEvent.webidl \
   KeyEvent.webidl \
   LinkStyle.webidl \
   LocalMediaStream.webidl \
   Location.webidl \
   MediaError.webidl \
   MediaStream.webidl \
+  MediaStreamAudioDestinationNode.webidl \
   MediaStreamEvent.webidl \
   MediaStreamTrack.webidl \
   MessageEvent.webidl \
   MobileMessageManager.webidl \
   MouseEvent.webidl \
   MouseScrollEvent.webidl \
   MozActivity.webidl \
   MozMmsMessage.webidl \