Backed out changeset 2ac8fe9a90c5 (bug 948269) for timeouts in gaia-integration tests; CLOSED TREE
authorEd Morley <emorley@mozilla.com>
Fri, 04 Apr 2014 16:32:19 +0100
changeset 189138 fcd79d6f4a7e4b4c0004952c692cbdee0fef0b2a
parent 189137 b327711444ed47ae1a96947d604d68d073dd4b1b
child 189139 2a069087bed1f1d39aa041f3946419d45d14bb42
child 189177 50e3d7132fa9ebd99ace0aef3179ad032333b5af
push idunknown
push userunknown
push dateunknown
bugs948269
milestone31.0a1
backs out2ac8fe9a90c51994b83e4a7fddbb9489b5b6c6bb
Backed out changeset 2ac8fe9a90c5 (bug 948269) for timeouts in gaia-integration tests; CLOSED TREE
content/media/AudioSink.cpp
content/media/AudioSink.h
content/media/AudioStream.cpp
content/media/AudioStream.h
content/media/MediaDecoderStateMachine.cpp
content/media/MediaDecoderStateMachine.h
content/media/moz.build
deleted file mode 100644
--- a/content/media/AudioSink.cpp
+++ /dev/null
@@ -1,400 +0,0 @@
-/* -*- 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 "AudioSink.h"
-#include "MediaDecoderStateMachine.h"
-#include "AudioStream.h"
-#include "prenv.h"
-
-namespace mozilla {
-
-#ifdef PR_LOGGING
-extern PRLogModuleInfo* gMediaDecoderLog;
-#define SINK_LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg)
-#else
-#define SINK_LOG(type, msg)
-#endif
-
-AudioSink::AudioSink(MediaDecoderStateMachine* aStateMachine,
-                     int64_t aStartTime, AudioInfo aInfo, dom::AudioChannelType aChannelType)
-  : mStateMachine(aStateMachine)
-  , mStartTime(aStartTime)
-  , mWritten(0)
-  , mInfo(aInfo)
-  , mChannelType(aChannelType)
-  , mVolume(1.0)
-  , mPlaybackRate(1.0)
-  , mPreservesPitch(false)
-  , mStopAudioThread(false)
-  , mSetVolume(false)
-  , mSetPlaybackRate(false)
-  , mSetPreservesPitch(false)
-  , mPlaying(true)
-{
-  NS_ASSERTION(mStartTime != -1, "Should have audio start time by now");
-}
-
-nsresult
-AudioSink::Init()
-{
-  nsresult rv = NS_NewNamedThread("Media Audio",
-                                  getter_AddRefs(mThread),
-                                  nullptr,
-                                  MEDIA_THREAD_STACK_SIZE);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-  nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(this, &AudioSink::AudioLoop);
-  return mThread->Dispatch(event, NS_DISPATCH_NORMAL);
-}
-
-int64_t
-AudioSink::GetPosition()
-{
-  if (!mAudioStream) {
-    return 0;
-  }
-  return mAudioStream->GetPosition();
-}
-
-void
-AudioSink::PrepareToShutdown()
-{
-  AssertCurrentThreadInMonitor();
-  mStopAudioThread = true;
-  if (mAudioStream) {
-    mAudioStream->Cancel();
-  }
-  GetReentrantMonitor().NotifyAll();
-}
-
-void
-AudioSink::Shutdown()
-{
-  mThread->Shutdown();
-  mThread = nullptr;
-  MOZ_ASSERT(!mAudioStream);
-}
-
-void
-AudioSink::SetVolume(double aVolume)
-{
-  AssertCurrentThreadInMonitor();
-  mVolume = aVolume;
-  mSetVolume = true;
-}
-
-void
-AudioSink::SetPlaybackRate(double aPlaybackRate)
-{
-  AssertCurrentThreadInMonitor();
-  NS_ASSERTION(mPlaybackRate != 0, "Don't set the playbackRate to 0 on AudioStream");
-  mPlaybackRate = aPlaybackRate;
-  mSetPlaybackRate = true;
-}
-
-void
-AudioSink::SetPreservesPitch(bool aPreservesPitch)
-{
-  AssertCurrentThreadInMonitor();
-  mPreservesPitch = aPreservesPitch;
-  mSetPreservesPitch = true;
-}
-
-void
-AudioSink::StartPlayback()
-{
-  AssertCurrentThreadInMonitor();
-  mPlaying = true;
-}
-
-void
-AudioSink::StopPlayback()
-{
-  AssertCurrentThreadInMonitor();
-  mPlaying = false;
-}
-
-void
-AudioSink::AudioLoop()
-{
-  AssertOnAudioThread();
-  SINK_LOG(PR_LOG_DEBUG, ("%p AudioSink: AudioLoop started", this));
-
-  if (NS_FAILED(InitializeAudioStream())) {
-    NS_WARNING("Initializing AudioStream failed.");
-    return;
-  }
-
-  while (1) {
-    WaitForAudioToPlay();
-    if (!IsPlaybackContinuing()) {
-      break;
-    }
-
-    // See if there's a gap in the audio. If there is, push silence into the
-    // audio hardware, so we can play across the gap.
-    // Calculate the timestamp of the next chunk of audio in numbers of
-    // samples.
-    NS_ASSERTION(AudioQueue().GetSize() > 0, "Should have data to play");
-    CheckedInt64 sampleTime = UsecsToFrames(AudioQueue().PeekFront()->mTime, mInfo.mRate);
-
-    // Calculate the number of frames that have been pushed onto the audio hardware.
-    CheckedInt64 playedFrames = UsecsToFrames(mStartTime, mInfo.mRate) + mWritten;
-
-    CheckedInt64 missingFrames = sampleTime - playedFrames;
-    if (!missingFrames.isValid() || !sampleTime.isValid()) {
-      NS_WARNING("Int overflow adding in AudioLoop");
-      break;
-    }
-
-    if (missingFrames.value() > 0) {
-      // The next audio chunk begins some time after the end of the last chunk
-      // we pushed to the audio hardware. We must push silence into the audio
-      // hardware so that the next audio chunk begins playback at the correct
-      // time.
-      missingFrames = std::min<int64_t>(UINT32_MAX, missingFrames.value());
-      mWritten += PlaySilence(static_cast<uint32_t>(missingFrames.value()));
-    } else {
-      mWritten += PlayFromAudioQueue();
-    }
-    int64_t endTime = GetEndTime();
-    if (endTime != -1) {
-      mStateMachine->OnAudioEndTimeUpdate(endTime);
-    }
-  }
-  ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-  if (AudioQueue().AtEndOfStream() && !mStopAudioThread) {
-    Drain();
-  }
-  SINK_LOG(PR_LOG_DEBUG, ("%p AudioSink: AudioLoop complete", this));
-  Cleanup();
-  SINK_LOG(PR_LOG_DEBUG, ("%p AudioSink: AudioLoop exit", this));
-}
-
-nsresult
-AudioSink::InitializeAudioStream()
-{
-  // AudioStream initialization can block for extended periods in unusual
-  // circumstances, so we take care to drop the decoder monitor while
-  // initializing.
-  nsAutoPtr<AudioStream> audioStream(new AudioStream());
-  audioStream->Init(mInfo.mChannels, mInfo.mRate,
-                    mChannelType, AudioStream::HighLatency);
-  // TODO: Check Init's return value and bail on error.  Unfortunately this
-  // causes some tests to fail due to playback failing.
-  ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-  mAudioStream = audioStream;
-  UpdateStreamSettings();
-
-  return NS_OK;
-}
-
-void
-AudioSink::Drain()
-{
-  MOZ_ASSERT(!mAudioStream->IsPaused());
-  AssertCurrentThreadInMonitor();
-  // If the media was too short to trigger the start of the audio stream,
-  // start it now.
-  mAudioStream->Start();
-  {
-    ReentrantMonitorAutoExit exit(GetReentrantMonitor());
-    mAudioStream->Drain();
-  }
-}
-
-void
-AudioSink::Cleanup()
-{
-  // Must hold lock while shutting down and anulling the audio stream to prevent
-  // state machine thread trying to use it while we're destroying it.
-  AssertCurrentThreadInMonitor();
-  mAudioStream->Shutdown();
-  mAudioStream = nullptr;
-  mStateMachine->OnAudioSinkComplete();
-}
-
-bool
-AudioSink::ExpectMoreAudioData()
-{
-  return AudioQueue().GetSize() == 0 && !AudioQueue().IsFinished();
-}
-
-void
-AudioSink::WaitForAudioToPlay()
-{
-  // Wait while we're not playing, and we're not shutting down, or we're
-  // playing and we've got no audio to play.
-  ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-  while (!mStopAudioThread && (!mPlaying || ExpectMoreAudioData())) {
-    if (!mPlaying && !mAudioStream->IsPaused()) {
-      mAudioStream->Pause();
-    }
-    mon.Wait();
-  }
-}
-
-bool
-AudioSink::IsPlaybackContinuing()
-{
-  ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-  // If we're shutting down, break out and exit the audio thread.
-  // Also break out if audio is being captured.
-  if (mStopAudioThread || AudioQueue().AtEndOfStream()) {
-    return false;
-  }
-
-  if (mPlaying && mAudioStream->IsPaused()) {
-    mAudioStream->Resume();
-  }
-
-  UpdateStreamSettings();
-  return true;
-}
-
-uint32_t
-AudioSink::PlaySilence(uint32_t aFrames)
-{
-  // Maximum number of bytes we'll allocate and write at once to the audio
-  // hardware when the audio stream contains missing frames and we're
-  // writing silence in order to fill the gap. We limit our silence-writes
-  // to 32KB in order to avoid allocating an impossibly large chunk of
-  // memory if we encounter a large chunk of silence.
-  const uint32_t SILENCE_BYTES_CHUNK = 32 * 1024;
-
-  AssertOnAudioThread();
-  NS_ASSERTION(!mAudioStream->IsPaused(), "Don't play when paused");
-  uint32_t maxFrames = SILENCE_BYTES_CHUNK / mInfo.mChannels / sizeof(AudioDataValue);
-  uint32_t frames = std::min(aFrames, maxFrames);
-  if (!PR_GetEnv("MOZ_QUIET")) {
-    SINK_LOG(PR_LOG_DEBUG, ("%p AudioSink: playing %u frames of silence", this, aFrames));
-  }
-  WriteSilence(frames);
-  return frames;
-}
-
-uint32_t
-AudioSink::PlayFromAudioQueue()
-{
-  AssertOnAudioThread();
-  NS_ASSERTION(!mAudioStream->IsPaused(), "Don't play when paused");
-  nsAutoPtr<AudioData> audio(AudioQueue().PopFront());
-  {
-    ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-    NS_WARN_IF_FALSE(mPlaying, "Should be playing");
-    // Awaken the decode loop if it's waiting for space to free up in the
-    // audio queue.
-    GetReentrantMonitor().NotifyAll();
-  }
-  if (!PR_GetEnv("MOZ_QUIET")) {
-    SINK_LOG(PR_LOG_DEBUG, ("%p AudioSink: playing %u frames of audio at time %lld",
-                            this, audio->mFrames, audio->mTime));
-  }
-  mAudioStream->Write(audio->mAudioData, audio->mFrames);
-
-  StartAudioStreamPlaybackIfNeeded();
-
-  if (audio->mOffset != -1) {
-    mStateMachine->OnPlaybackOffsetUpdate(audio->mOffset);
-  }
-  return audio->mFrames;
-}
-
-void
-AudioSink::UpdateStreamSettings()
-{
-  AssertCurrentThreadInMonitor();
-
-  bool setVolume = mSetVolume;
-  bool setPlaybackRate = mSetPlaybackRate;
-  bool setPreservesPitch = mSetPreservesPitch;
-  double volume = mVolume;
-  double playbackRate = mPlaybackRate;
-  bool preservesPitch = mPreservesPitch;
-
-  mSetVolume = false;
-  mSetPlaybackRate = false;
-  mSetPreservesPitch = false;
-
-  {
-    ReentrantMonitorAutoExit exit(GetReentrantMonitor());
-    if (setVolume) {
-      mAudioStream->SetVolume(volume);
-    }
-
-    if (setPlaybackRate &&
-        NS_FAILED(mAudioStream->SetPlaybackRate(playbackRate))) {
-      NS_WARNING("Setting the playback rate failed in AudioSink.");
-    }
-
-    if (setPreservesPitch &&
-      NS_FAILED(mAudioStream->SetPreservesPitch(preservesPitch))) {
-      NS_WARNING("Setting the pitch preservation failed in AudioSink.");
-    }
-  }
-}
-
-void
-AudioSink::StartAudioStreamPlaybackIfNeeded()
-{
-  // This value has been chosen empirically.
-  const uint32_t MIN_WRITE_BEFORE_START_USECS = 200000;
-
-  // We want to have enough data in the buffer to start the stream.
-  if (static_cast<double>(mAudioStream->GetWritten()) / mAudioStream->GetRate() >=
-      static_cast<double>(MIN_WRITE_BEFORE_START_USECS) / USECS_PER_S) {
-    mAudioStream->Start();
-  }
-}
-
-void
-AudioSink::WriteSilence(uint32_t aFrames)
-{
-  uint32_t numSamples = aFrames * mInfo.mChannels;
-  nsAutoTArray<AudioDataValue, 1000> buf;
-  buf.SetLength(numSamples);
-  memset(buf.Elements(), 0, numSamples * sizeof(AudioDataValue));
-  mAudioStream->Write(buf.Elements(), aFrames);
-
-  StartAudioStreamPlaybackIfNeeded();
-}
-
-int64_t
-AudioSink::GetEndTime()
-{
-  CheckedInt64 playedUsecs = FramesToUsecs(mWritten, mInfo.mRate) + mStartTime;
-  if (!playedUsecs.isValid()) {
-    NS_WARNING("Int overflow calculating audio end time");
-    return -1;
-  }
-  return playedUsecs.value();
-}
-
-MediaQueue<AudioData>&
-AudioSink::AudioQueue()
-{
-  return mStateMachine->mReader->AudioQueue();
-}
-
-ReentrantMonitor&
-AudioSink::GetReentrantMonitor()
-{
-  return mStateMachine->mDecoder->GetReentrantMonitor();
-}
-
-void
-AudioSink::AssertCurrentThreadInMonitor()
-{
-  return mStateMachine->AssertCurrentThreadInMonitor();
-}
-
-void
-AudioSink::AssertOnAudioThread()
-{
-  MOZ_ASSERT(IsCurrentThread(mThread));
-}
-
-} // namespace mozilla
deleted file mode 100644
--- a/content/media/AudioSink.h
+++ /dev/null
@@ -1,136 +0,0 @@
-/* -*- 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/. */
-#if !defined(AudioSink_h__)
-#define AudioSink_h__
-
-#include "nsISupportsImpl.h"
-#include "MediaDecoderReader.h"
-#include "AudioChannelCommon.h"
-
-namespace mozilla {
-
-class AudioAvailableEventManager;
-class AudioStream;
-class MediaDecoderStateMachine;
-
-class AudioSink {
-public:
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioSink)
-
-  AudioSink(MediaDecoderStateMachine* aStateMachine,
-            int64_t aStartTime, AudioInfo aInfo, dom::AudioChannelType aChannelType);
-
-  nsresult Init();
-
-  int64_t GetPosition();
-
-  // Tell the AudioSink to stop processing and initiate shutdown.  Must be
-  // called with the decoder monitor held.
-  void PrepareToShutdown();
-
-  // Shut down the AudioSink's resources.  The decoder monitor must not be
-  // held during this call, as it may block processing thread event queues.
-  void Shutdown();
-
-  void SetVolume(double aVolume);
-  void SetPlaybackRate(double aPlaybackRate);
-  void SetPreservesPitch(bool aPreservesPitch);
-
-  void StartPlayback();
-  void StopPlayback();
-
-private:
-  // The main loop for the audio thread. Sent to the thread as
-  // an nsRunnableMethod. This continually does blocking writes to
-  // to audio stream to play audio data.
-  void AudioLoop();
-
-  // Allocate and initialize mAudioStream.  Returns NS_OK on success.
-  nsresult InitializeAudioStream();
-
-  void Drain();
-
-  void Cleanup();
-
-  bool ExpectMoreAudioData();
-
-  // Wait on the decoder monitor until playback is ready or the sink is told to shut down.
-  void WaitForAudioToPlay();
-
-  // Check if the sink has been told to shut down, resuming mAudioStream if
-  // not.  Returns true if processing should continue, false if AudioLoop
-  // should shutdown.
-  bool IsPlaybackContinuing();
-
-  // Write aFrames of audio frames of silence to the audio hardware. Returns
-  // the number of frames actually written. The write size is capped at
-  // SILENCE_BYTES_CHUNK (32kB), so must be called in a loop to write the
-  // desired number of frames. This ensures that the playback position
-  // advances smoothly, and guarantees that we don't try to allocate an
-  // impossibly large chunk of memory in order to play back silence. Called
-  // on the audio thread.
-  uint32_t PlaySilence(uint32_t aFrames);
-
-  // Pops an audio chunk from the front of the audio queue, and pushes its
-  // audio data to the audio hardware.  Called on the audio thread.
-  uint32_t PlayFromAudioQueue();
-
-  void UpdateStreamSettings();
-
-  // If we have already written enough frames to the AudioStream, start the
-  // playback.
-  void StartAudioStreamPlaybackIfNeeded();
-  void WriteSilence(uint32_t aFrames);
-
-  int64_t GetEndTime();
-
-  MediaQueue<AudioData>& AudioQueue();
-
-  ReentrantMonitor& GetReentrantMonitor();
-  void AssertCurrentThreadInMonitor();
-  void AssertOnAudioThread();
-
-  nsRefPtr<MediaDecoderStateMachine> mStateMachine;
-
-  // Thread for pushing audio onto the audio hardware.
-  // The "audio push thread".
-  nsCOMPtr<nsIThread> mThread;
-
-  // The audio stream resource. Used on the state machine, and audio threads.
-  // This is created and destroyed on the audio thread, while holding the
-  // decoder monitor, so if this is used off the audio thread, you must
-  // first acquire the decoder monitor and check that it is non-null.
-  nsAutoPtr<AudioStream> mAudioStream;
-
-  // The presentation time of the first audio frame that was played in
-  // microseconds. We can add this to the audio stream position to determine
-  // the current audio time. Accessed on audio and state machine thread.
-  // Synchronized by decoder monitor.
-  int64_t mStartTime;
-
-  // PCM frames written to the stream so far.
-  int64_t mWritten;
-
-  AudioInfo mInfo;
-
-  dom::AudioChannelType mChannelType;
-
-  double mVolume;
-  double mPlaybackRate;
-  bool mPreservesPitch;
-
-  bool mStopAudioThread;
-
-  bool mSetVolume;
-  bool mSetPlaybackRate;
-  bool mSetPreservesPitch;
-
-  bool mPlaying;
-};
-
-} // namespace mozilla
-
-#endif
--- a/content/media/AudioStream.cpp
+++ b/content/media/AudioStream.cpp
@@ -522,24 +522,16 @@ void
 AudioStream::SetVolume(double aVolume)
 {
   MonitorAutoLock mon(mMonitor);
   NS_ABORT_IF_FALSE(aVolume >= 0.0 && aVolume <= 1.0, "Invalid volume");
   mVolume = aVolume;
 }
 
 void
-AudioStream::Cancel()
-{
-  MonitorAutoLock mon(mMonitor);
-  mState = ERRORED;
-  mon.NotifyAll();
-}
-
-void
 AudioStream::Drain()
 {
   MonitorAutoLock mon(mMonitor);
   if (mState != STARTED) {
     NS_ASSERTION(mBuffer.Available() == 0, "Draining with unplayed audio");
     return;
   }
   mState = DRAINING;
--- a/content/media/AudioStream.h
+++ b/content/media/AudioStream.h
@@ -216,19 +216,16 @@ public:
 
   // Set the current volume of the audio playback. This is a value from
   // 0 (meaning muted) to 1 (meaning full volume).  Thread-safe.
   void SetVolume(double aVolume);
 
   // Block until buffered audio data has been consumed.
   void Drain();
 
-  // Break any blocking operation and set the stream to shutdown.
-  void Cancel();
-
   // Start the stream.
   void Start();
 
   // Return the number of frames written so far in the stream. This allow the
   // caller to check if it is safe to start the stream, if needed.
   int64_t GetWritten();
 
   // Pause audio playback.
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -8,17 +8,17 @@
 #include "windows.h"
 #include "mmsystem.h"
 #endif
 
 #include "mozilla/DebugOnly.h"
 #include <stdint.h>
 
 #include "MediaDecoderStateMachine.h"
-#include "AudioSink.h"
+#include "AudioStream.h"
 #include "nsTArray.h"
 #include "MediaDecoder.h"
 #include "MediaDecoderReader.h"
 #include "mozilla/mozalloc.h"
 #include "VideoUtils.h"
 #include "mozilla/dom/TimeRanges.h"
 #include "nsDeque.h"
 #include "AudioSegment.h"
@@ -76,16 +76,23 @@ const int64_t AMPLE_AUDIO_USECS = 100000
 
 // When we're only playing audio and we don't have a video stream, we divide
 // AMPLE_AUDIO_USECS and LOW_AUDIO_USECS by the following value. This reduces
 // the amount of decoded audio we buffer, reducing our memory usage. We only
 // need to decode far ahead when we're decoding video using software decoding,
 // as otherwise a long video decode could cause an audio underrun.
 const int64_t NO_VIDEO_AMPLE_AUDIO_DIVISOR = 8;
 
+// Maximum number of bytes we'll allocate and write at once to the audio
+// hardware when the audio stream contains missing frames and we're
+// writing silence in order to fill the gap. We limit our silence-writes
+// to 32KB in order to avoid allocating an impossibly large chunk of
+// memory if we encounter a large chunk of silence.
+const uint32_t SILENCE_BYTES_CHUNK = 32 * 1024;
+
 // If we have fewer than LOW_VIDEO_FRAMES decoded frames, and
 // we're not "prerolling video", we'll skip the video up to the next keyframe
 // which is at or after the current playback position.
 static const uint32_t LOW_VIDEO_FRAMES = 1;
 
 // Arbitrary "frame duration" when playing only audio.
 static const int AUDIO_DURATION_USECS = 40000;
 
@@ -126,16 +133,19 @@ static const uint32_t QUICK_BUFFERING_LO
 
 // If QUICK_BUFFERING_LOW_DATA_USECS is > AMPLE_AUDIO_USECS, we won't exit
 // quick buffering in a timely fashion, as the decode pauses when it
 // reaches AMPLE_AUDIO_USECS decoded data, and thus we'll never reach
 // QUICK_BUFFERING_LOW_DATA_USECS.
 static_assert(QUICK_BUFFERING_LOW_DATA_USECS <= AMPLE_AUDIO_USECS,
               "QUICK_BUFFERING_LOW_DATA_USECS is too large");
 
+// This value has been chosen empirically.
+static const uint32_t AUDIOSTREAM_MIN_WRITE_BEFORE_START_USECS = 200000;
+
 // The amount of instability we tollerate in calls to
 // MediaDecoderStateMachine::UpdateEstimatedDuration(); changes of duration
 // less than this are ignored, as they're assumed to be the result of
 // instability in the duration estimation.
 static const int64_t ESTIMATED_DURATION_FUZZ_FACTOR_USECS = USECS_PER_S / 2;
 
 static TimeDuration UsecsToDuration(int64_t aUsecs) {
   return TimeDuration::FromMilliseconds(static_cast<double>(aUsecs) / USECS_PER_MS);
@@ -146,19 +156,19 @@ static int64_t DurationToUsecs(TimeDurat
 }
 
 MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
                                                    MediaDecoderReader* aReader,
                                                    bool aRealTime) :
   mDecoder(aDecoder),
   mState(DECODER_STATE_DECODING_METADATA),
   mInRunningStateMachine(false),
-  mResetPlayStartTime(false),
   mSyncPointInMediaStream(-1),
   mSyncPointInDecodedStream(-1),
+  mResetPlayStartTime(false),
   mPlayDuration(0),
   mStartTime(-1),
   mEndTime(-1),
   mFragmentEndTime(-1),
   mReader(aReader),
   mCurrentFrameTime(0),
   mAudioStartTime(-1),
   mAudioEndTime(-1),
@@ -275,17 +285,17 @@ void MediaDecoderStateMachine::SendStrea
 
   if (aAudio->mTime <= aStream->mLastAudioPacketTime) {
     // ignore packet that we've already processed
     return;
   }
   aStream->mLastAudioPacketTime = aAudio->mTime;
   aStream->mLastAudioPacketEndTime = aAudio->GetEndTime();
 
-  // This logic has to mimic AudioSink closely to make sure we write
+  // This logic has to mimic AudioLoop closely to make sure we write
   // the exact same silences
   CheckedInt64 audioWrittenOffset = UsecsToFrames(mInfo.mAudio.mRate,
       aStream->mInitialTime + mStartTime) + aStream->mAudioFramesWritten;
   CheckedInt64 frameOffset = UsecsToFrames(mInfo.mAudio.mRate, aAudio->mTime);
   if (!audioWrittenOffset.isValid() || !frameOffset.isValid())
     return;
   if (audioWrittenOffset.value() < frameOffset.value()) {
     // Write silence to catch up
@@ -345,21 +355,21 @@ void MediaDecoderStateMachine::SendStrea
 
   DecodedStreamData* stream = mDecoder->GetDecodedStream();
   if (!stream)
     return;
 
   if (mState == DECODER_STATE_DECODING_METADATA)
     return;
 
-  // If there's still an audio sink alive, then we can't send any stream
-  // data yet since both SendStreamData and the audio sink want to be in
-  // charge of popping the audio queue. We're waiting for the audio sink
+  // If there's still an audio thread alive, then we can't send any stream
+  // data yet since both SendStreamData and the audio thread want to be in
+  // charge of popping the audio queue. We're waiting for the audio thread
   // to die before sending anything to our stream.
-  if (mAudioSink)
+  if (mAudioThread)
     return;
 
   int64_t minLastAudioPacketTime = INT64_MAX;
   bool finished =
       (!mInfo.HasAudio() || mReader->AudioQueue().IsFinished()) &&
       (!mInfo.HasVideo() || mReader->VideoQueue().IsFinished());
   if (mDecoder->IsSameOriginMedia()) {
     SourceMediaStream* mediaStream = stream->mStream;
@@ -464,17 +474,17 @@ void MediaDecoderStateMachine::SendStrea
       // Packet times are not 100% reliable so this may discard packets that
       // actually contain data for mCurrentFrameTime. This means if someone might
       // create a new output stream and we actually don't have the audio for the
       // very start. That's OK, we'll play silence instead for a brief moment.
       // That's OK. Seeking to this time would have a similar issue for such
       // badly muxed resources.
       if (!a || a->GetEndTime() >= minLastAudioPacketTime)
         break;
-      OnAudioEndTimeUpdate(std::max(mAudioEndTime, a->GetEndTime()));
+      mAudioEndTime = std::max(mAudioEndTime, a->GetEndTime());
       delete mReader->AudioQueue().PopFront();
     }
 
     if (finished) {
       mAudioCompleted = true;
       UpdateReadyState();
     }
   }
@@ -668,17 +678,17 @@ MediaDecoderStateMachine::DecodeAudio()
   if (!mIsAudioDecoding) {
     // Playback ended for this stream, close the sample queue.
     mReader->AudioQueue().Finish();
     CheckIfDecodeComplete();
   }
 
   SendStreamData();
 
-  // Notify to ensure that the AudioSink is not waiting, in case it was
+  // Notify to ensure that the AudioLoop() is not waiting, in case it was
   // waiting for more audio to be decoded.
   mDecoder->GetReentrantMonitor().NotifyAll();
 
   // The ready state can change when we've decoded data, so update the
   // ready state, so that DOM events can fire.
   UpdateReadyState();
 
   mDispatchedAudioDecodeTask = false;
@@ -712,16 +722,300 @@ MediaDecoderStateMachine::CheckIfDecodeC
 
 bool MediaDecoderStateMachine::IsPlaying()
 {
   AssertCurrentThreadInMonitor();
 
   return !mPlayStartTime.IsNull();
 }
 
+// If we have already written enough frames to the AudioStream, start the
+// playback.
+static void
+StartAudioStreamPlaybackIfNeeded(AudioStream* aStream)
+{
+  // We want to have enough data in the buffer to start the stream.
+  if (static_cast<double>(aStream->GetWritten()) / aStream->GetRate() >=
+      static_cast<double>(AUDIOSTREAM_MIN_WRITE_BEFORE_START_USECS) / USECS_PER_S) {
+    aStream->Start();
+  }
+}
+
+static void WriteSilence(AudioStream* aStream, uint32_t aFrames)
+{
+  uint32_t numSamples = aFrames * aStream->GetChannels();
+  nsAutoTArray<AudioDataValue, 1000> buf;
+  buf.SetLength(numSamples);
+  memset(buf.Elements(), 0, numSamples * sizeof(AudioDataValue));
+  aStream->Write(buf.Elements(), aFrames);
+
+  StartAudioStreamPlaybackIfNeeded(aStream);
+}
+
+void MediaDecoderStateMachine::AudioLoop()
+{
+  NS_ASSERTION(OnAudioThread(), "Should be on audio thread.");
+  DECODER_LOG(PR_LOG_DEBUG, ("%p Begun audio thread/loop", mDecoder.get()));
+  int64_t audioDuration = 0;
+  int64_t audioStartTime = -1;
+  uint32_t channels, rate;
+  double volume = -1;
+  bool setVolume;
+  double playbackRate = -1;
+  bool setPlaybackRate;
+  bool preservesPitch;
+  bool setPreservesPitch;
+  AudioChannelType audioChannelType;
+
+  {
+    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+    mAudioCompleted = false;
+    audioStartTime = mAudioStartTime;
+    NS_ASSERTION(audioStartTime != -1, "Should have audio start time by now");
+    channels = mInfo.mAudio.mChannels;
+    rate = mInfo.mAudio.mRate;
+
+    audioChannelType = mDecoder->GetAudioChannelType();
+    volume = mVolume;
+    preservesPitch = mPreservesPitch;
+    playbackRate = mPlaybackRate;
+  }
+
+  {
+    // AudioStream initialization can block for extended periods in unusual
+    // circumstances, so we take care to drop the decoder monitor while
+    // initializing.
+    nsAutoPtr<AudioStream> audioStream(new AudioStream());
+    audioStream->Init(channels, rate, audioChannelType, AudioStream::HighLatency);
+    audioStream->SetVolume(volume);
+    if (audioStream->SetPreservesPitch(preservesPitch) != NS_OK) {
+      NS_WARNING("Setting the pitch preservation failed at AudioLoop start.");
+    }
+    if (playbackRate != 1.0) {
+      NS_ASSERTION(playbackRate != 0,
+                   "Don't set the playbackRate to 0 on an AudioStream.");
+      if (audioStream->SetPlaybackRate(playbackRate) != NS_OK) {
+        NS_WARNING("Setting the playback rate failed at AudioLoop start.");
+      }
+    }
+
+    {
+      ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+      mAudioStream = audioStream;
+    }
+  }
+
+  while (1) {
+    // Wait while we're not playing, and we're not shutting down, or we're
+    // playing and we've got no audio to play.
+    {
+      ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+      NS_ASSERTION(mState != DECODER_STATE_DECODING_METADATA,
+                   "Should have meta data before audio started playing.");
+      while (mState != DECODER_STATE_SHUTDOWN &&
+             !mStopAudioThread &&
+             (!IsPlaying() ||
+              mState == DECODER_STATE_BUFFERING ||
+              (mReader->AudioQueue().GetSize() == 0 &&
+               !mReader->AudioQueue().AtEndOfStream())))
+      {
+        if (!IsPlaying() && !mAudioStream->IsPaused()) {
+          mAudioStream->Pause();
+        }
+        mon.Wait();
+      }
+
+      // If we're shutting down, break out and exit the audio thread.
+      // Also break out if audio is being captured.
+      if (mState == DECODER_STATE_SHUTDOWN ||
+          mStopAudioThread ||
+          mReader->AudioQueue().AtEndOfStream())
+      {
+        break;
+      }
+
+      // We only want to go to the expense of changing the volume if
+      // the volume has changed.
+      setVolume = volume != mVolume;
+      volume = mVolume;
+
+      // Same for the playbackRate.
+      setPlaybackRate = playbackRate != mPlaybackRate;
+      playbackRate = mPlaybackRate;
+
+      // Same for the pitch preservation.
+      setPreservesPitch = preservesPitch != mPreservesPitch;
+      preservesPitch = mPreservesPitch;
+
+      if (IsPlaying() && mAudioStream->IsPaused()) {
+        mAudioStream->Resume();
+      }
+    }
+
+    if (setVolume) {
+      mAudioStream->SetVolume(volume);
+    }
+    if (setPlaybackRate) {
+      NS_ASSERTION(playbackRate != 0,
+                   "Don't set the playbackRate to 0 in the AudioStreams");
+      if (mAudioStream->SetPlaybackRate(playbackRate) != NS_OK) {
+        NS_WARNING("Setting the playback rate failed in AudioLoop.");
+      }
+    }
+    if (setPreservesPitch) {
+      if (mAudioStream->SetPreservesPitch(preservesPitch) != NS_OK) {
+        NS_WARNING("Setting the pitch preservation failed in AudioLoop.");
+      }
+    }
+    NS_ASSERTION(mReader->AudioQueue().GetSize() > 0,
+                 "Should have data to play");
+    // See if there's a gap in the audio. If there is, push silence into the
+    // audio hardware, so we can play across the gap.
+    const AudioData* s = mReader->AudioQueue().PeekFront();
+
+    // Calculate the number of frames that have been pushed onto the audio
+    // hardware.
+    CheckedInt64 playedFrames = UsecsToFrames(audioStartTime, rate) +
+                                              audioDuration;
+    // Calculate the timestamp of the next chunk of audio in numbers of
+    // samples.
+    CheckedInt64 sampleTime = UsecsToFrames(s->mTime, rate);
+    CheckedInt64 missingFrames = sampleTime - playedFrames;
+    if (!missingFrames.isValid() || !sampleTime.isValid()) {
+      NS_WARNING("Int overflow adding in AudioLoop()");
+      break;
+    }
+
+    int64_t framesWritten = 0;
+    if (missingFrames.value() > 0) {
+      // The next audio chunk begins some time after the end of the last chunk
+      // we pushed to the audio hardware. We must push silence into the audio
+      // hardware so that the next audio chunk begins playback at the correct
+      // time.
+      missingFrames = std::min<int64_t>(UINT32_MAX, missingFrames.value());
+      DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder playing %d frames of silence",
+                                 mDecoder.get(), int32_t(missingFrames.value())));
+      framesWritten = PlaySilence(static_cast<uint32_t>(missingFrames.value()),
+                                  channels, playedFrames.value());
+    } else {
+      framesWritten = PlayFromAudioQueue(sampleTime.value(), channels);
+    }
+    audioDuration += framesWritten;
+    {
+      ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+      CheckedInt64 playedUsecs = FramesToUsecs(audioDuration, rate) + audioStartTime;
+      if (!playedUsecs.isValid()) {
+        NS_WARNING("Int overflow calculating audio end time");
+        break;
+      }
+      mAudioEndTime = playedUsecs.value();
+    }
+  }
+  {
+    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+    if (mReader->AudioQueue().AtEndOfStream() &&
+        mState != DECODER_STATE_SHUTDOWN &&
+        !mStopAudioThread)
+    {
+      // If the media was too short to trigger the start of the audio stream,
+      // start it now.
+      mAudioStream->Start();
+      // Last frame pushed to audio hardware, wait for the audio to finish,
+      // before the audio thread terminates.
+      bool seeking = false;
+      {
+        int64_t oldPosition = -1;
+        int64_t position = GetMediaTime();
+        while (oldPosition != position &&
+               mAudioEndTime - position > 0 &&
+               mState != DECODER_STATE_SEEKING &&
+               mState != DECODER_STATE_SHUTDOWN)
+        {
+          const int64_t DRAIN_BLOCK_USECS = 100000;
+          Wait(std::min(mAudioEndTime - position, DRAIN_BLOCK_USECS));
+          oldPosition = position;
+          position = GetMediaTime();
+        }
+        seeking = mState == DECODER_STATE_SEEKING;
+      }
+
+      if (!seeking && !mAudioStream->IsPaused()) {
+        {
+          ReentrantMonitorAutoExit exit(mDecoder->GetReentrantMonitor());
+          mAudioStream->Drain();
+        }
+      }
+    }
+  }
+  DECODER_LOG(PR_LOG_DEBUG, ("%p Reached audio stream end.", mDecoder.get()));
+  {
+    // Must hold lock while shutting down and anulling the audio stream to prevent
+    // state machine thread trying to use it while we're destroying it.
+    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+    mAudioStream->Shutdown();
+    mAudioStream = nullptr;
+    if (!mAudioCaptured) {
+      mAudioCompleted = true;
+      UpdateReadyState();
+      // Kick the decode thread; it may be sleeping waiting for this to finish.
+      mDecoder->GetReentrantMonitor().NotifyAll();
+    }
+  }
+
+  DECODER_LOG(PR_LOG_DEBUG, ("%p Audio stream finished playing, audio thread exit", mDecoder.get()));
+}
+
+uint32_t MediaDecoderStateMachine::PlaySilence(uint32_t aFrames,
+                                                   uint32_t aChannels,
+                                                   uint64_t aFrameOffset)
+
+{
+  NS_ASSERTION(OnAudioThread(), "Only call on audio thread.");
+  NS_ASSERTION(!mAudioStream->IsPaused(), "Don't play when paused");
+  uint32_t maxFrames = SILENCE_BYTES_CHUNK / aChannels / sizeof(AudioDataValue);
+  uint32_t frames = std::min(aFrames, maxFrames);
+  WriteSilence(mAudioStream, frames);
+  return frames;
+}
+
+uint32_t MediaDecoderStateMachine::PlayFromAudioQueue(uint64_t aFrameOffset,
+                                                      uint32_t aChannels)
+{
+  NS_ASSERTION(OnAudioThread(), "Only call on audio thread.");
+  NS_ASSERTION(!mAudioStream->IsPaused(), "Don't play when paused");
+  nsAutoPtr<AudioData> audio(mReader->AudioQueue().PopFront());
+  {
+    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+    NS_WARN_IF_FALSE(IsPlaying(), "Should be playing");
+    // Awaken the decode loop if it's waiting for space to free up in the
+    // audio queue.
+    mDecoder->GetReentrantMonitor().NotifyAll();
+  }
+  int64_t offset = -1;
+  uint32_t frames = 0;
+  if (!PR_GetEnv("MOZ_QUIET")) {
+    DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder playing %d frames of data to stream for AudioData at %lld",
+                               mDecoder.get(), audio->mFrames, audio->mTime));
+  }
+  mAudioStream->Write(audio->mAudioData,
+                      audio->mFrames);
+
+  aChannels = mAudioStream->GetOutChannels();
+
+  StartAudioStreamPlaybackIfNeeded(mAudioStream);
+
+  offset = audio->mOffset;
+  frames = audio->mFrames;
+
+  if (offset != -1) {
+    mDecoder->UpdatePlaybackOffset(offset);
+  }
+  return frames;
+}
+
 nsresult MediaDecoderStateMachine::Init(MediaDecoderStateMachine* aCloneDonor)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   RefPtr<SharedThreadPool> decodePool(
     SharedThreadPool::Get(NS_LITERAL_CSTRING("Media Decode"),
                           Preferences::GetUint("media.num-decode-threads", 25)));
   NS_ENSURE_TRUE(decodePool, NS_ERROR_FAILURE);
@@ -754,19 +1048,19 @@ void MediaDecoderStateMachine::StopPlayb
   DECODER_LOG(PR_LOG_DEBUG, ("%p StopPlayback()", mDecoder.get()));
 
   AssertCurrentThreadInMonitor();
 
   mDecoder->NotifyPlaybackStopped();
 
   if (IsPlaying()) {
     mPlayDuration = GetClock();
-    SetPlayStartTime(TimeStamp());
+    mPlayStartTime = TimeStamp();
   }
-  // Notify the audio sink, so that it notices that we've stopped playing,
+  // Notify the audio thread, so that it notices that we've stopped playing,
   // so it can pause audio playback.
   mDecoder->GetReentrantMonitor().NotifyAll();
   NS_ASSERTION(!IsPlaying(), "Should report not playing at end of StopPlayback()");
   mDecoder->UpdateStreamBlockingForStateMachinePlaying();
 
   DispatchDecodeTasksIfNeeded();
 }
 
@@ -795,17 +1089,17 @@ int64_t MediaDecoderStateMachine::GetCur
 void MediaDecoderStateMachine::StartPlayback()
 {
   DECODER_LOG(PR_LOG_DEBUG, ("%p StartPlayback()", mDecoder.get()));
 
   NS_ASSERTION(!IsPlaying(), "Shouldn't be playing when StartPlayback() is called");
   AssertCurrentThreadInMonitor();
 
   mDecoder->NotifyPlaybackStarted();
-  SetPlayStartTime(TimeStamp::Now());
+  mPlayStartTime = TimeStamp::Now();
 
   NS_ASSERTION(IsPlaying(), "Should report playing by end of StartPlayback()");
   if (NS_FAILED(StartAudioThread())) {
     NS_WARNING("Failed to create audio thread");
   }
   mDecoder->GetReentrantMonitor().NotifyAll();
   mDecoder->UpdateStreamBlockingForStateMachinePlaying();
 }
@@ -867,29 +1161,26 @@ MediaDecoderOwner::NextFrameStatus Media
   return MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
 }
 
 void MediaDecoderStateMachine::SetVolume(double volume)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   mVolume = volume;
-  if (mAudioSink) {
-    mAudioSink->SetVolume(mVolume);
-  }
 }
 
 void MediaDecoderStateMachine::SetAudioCaptured(bool aCaptured)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   if (!mAudioCaptured && aCaptured && !mStopAudioThread) {
     // Make sure the state machine runs as soon as possible. That will
-    // stop the audio sink.
-    // If mStopAudioThread is true then we're already stopping the audio sink
+    // stop the audio thread.
+    // If mStopAudioThread is true then we're already stopping the audio thread
     // and since we set mAudioCaptured to true, nothing can start it again.
     ScheduleStateMachine();
   }
   mAudioCaptured = aCaptured;
 }
 
 double MediaDecoderStateMachine::GetCurrentTime() const
 {
@@ -1007,19 +1298,16 @@ void MediaDecoderStateMachine::Shutdown(
   // Once we've entered the shutdown state here there's no going back.
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   // Change state before issuing shutdown request to threads so those
   // threads can start exiting cleanly during the Shutdown call.
   DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state to SHUTDOWN", mDecoder.get()));
   ScheduleStateMachine();
   mState = DECODER_STATE_SHUTDOWN;
-  if (mAudioSink) {
-    mAudioSink->PrepareToShutdown();
-  }
   mDecoder->GetReentrantMonitor().NotifyAll();
 }
 
 void MediaDecoderStateMachine::StartDecoding()
 {
   NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
                "Should be on state machine or decode thread.");
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
@@ -1170,25 +1458,24 @@ void MediaDecoderStateMachine::StopAudio
 
   if (mStopAudioThread) {
     // Nothing to do, since the thread is already stopping
     return;
   }
 
   mStopAudioThread = true;
   mDecoder->GetReentrantMonitor().NotifyAll();
-  if (mAudioSink) {
+  if (mAudioThread) {
     DECODER_LOG(PR_LOG_DEBUG, ("%p Shutdown audio thread", mDecoder.get()));
-    mAudioSink->PrepareToShutdown();
     {
       ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
-      mAudioSink->Shutdown();
+      mAudioThread->Shutdown();
     }
-    mAudioSink = nullptr;
-    // Now that the audio sink is dead, try sending data to our MediaStream(s).
+    mAudioThread = nullptr;
+    // Now that the audio thread is dead, try sending data to our MediaStream(s).
     // That may have been waiting for the audio thread to stop.
     SendStreamData();
   }
 }
 
 nsresult
 MediaDecoderStateMachine::EnqueueDecodeMetadataTask()
 {
@@ -1400,30 +1687,30 @@ MediaDecoderStateMachine::StartAudioThre
                "Should be on state machine or decode thread.");
   AssertCurrentThreadInMonitor();
   if (mAudioCaptured) {
     NS_ASSERTION(mStopAudioThread, "mStopAudioThread must always be true if audio is captured");
     return NS_OK;
   }
 
   mStopAudioThread = false;
-  if (HasAudio() && !mAudioSink) {
-    mAudioCompleted = false;
-    mAudioSink = new AudioSink(this,
-                               mAudioStartTime, mInfo.mAudio, mDecoder->GetAudioChannelType());
-    nsresult rv = mAudioSink->Init();
+  if (HasAudio() && !mAudioThread) {
+    nsresult rv = NS_NewNamedThread("Media Audio",
+                                    getter_AddRefs(mAudioThread),
+                                    nullptr,
+                                    MEDIA_THREAD_STACK_SIZE);
     if (NS_FAILED(rv)) {
-      DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state to SHUTDOWN because failed to create audio sink", mDecoder.get()));
+      DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state to SHUTDOWN because failed to create audio thread", mDecoder.get()));
       mState = DECODER_STATE_SHUTDOWN;
       return rv;
     }
 
-    mAudioSink->SetVolume(mVolume);
-    mAudioSink->SetPlaybackRate(mPlaybackRate);
-    mAudioSink->SetPreservesPitch(mPreservesPitch);
+    nsCOMPtr<nsIRunnable> event =
+      NS_NewRunnableMethod(this, &MediaDecoderStateMachine::AudioLoop);
+    mAudioThread->Dispatch(event, NS_DISPATCH_NORMAL);
   }
   return NS_OK;
 }
 
 int64_t MediaDecoderStateMachine::AudioDecodedUsecs() const
 {
   NS_ASSERTION(HasAudio(),
                "Should only call AudioDecodedUsecs() when we have audio");
@@ -1663,17 +1950,17 @@ void MediaDecoderStateMachine::DecodeSee
       NS_NewRunnableMethod(mDecoder, &MediaDecoder::SeekingStarted);
     NS_DispatchToMainThread(startEvent, NS_DISPATCH_SYNC);
   }
 
   int64_t newCurrentTime = seekTime;
   if (currentTimeChanged) {
     // The seek target is different than the current playback position,
     // we'll need to seek the playback position, so shutdown our decode
-    // thread and audio sink.
+    // and audio threads.
     StopAudioThread();
     ResetPlayback();
     nsresult res;
     {
       ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
       // Now perform the seek. We must not hold the state machine monitor
       // while we seek, since the seek reads, which could block on I/O.
       res = mReader->Seek(seekTime,
@@ -1823,21 +2110,21 @@ nsresult MediaDecoderStateMachine::RunSt
   NS_ENSURE_TRUE(resource, NS_ERROR_NULL_POINTER);
 
   switch (mState) {
     case DECODER_STATE_SHUTDOWN: {
       if (IsPlaying()) {
         StopPlayback();
       }
       StopAudioThread();
-      // If mAudioSink is non-null after StopAudioThread completes, we are
+      // If mAudioThread is non-null after StopAudioThread completes, we are
       // running in a nested event loop waiting for Shutdown() on
-      // mAudioSink to complete.  Return to the event loop and let it
+      // mAudioThread to complete.  Return to the event loop and let it
       // finish processing before continuing with shutdown.
-      if (mAudioSink) {
+      if (mAudioThread) {
         MOZ_ASSERT(mStopAudioThread);
         return NS_OK;
       }
 
       // The reader's listeners hold references to the state machine,
       // creating a cycle which keeps the state machine and its shared
       // thread pools alive. So break it here.
       mReader->AudioQueue().ClearListeners();
@@ -1999,17 +2286,17 @@ nsresult MediaDecoderStateMachine::RunSt
       }
 
       StopAudioThread();
       // When we're decoding to a stream, the stream's main-thread finish signal
       // will take care of calling MediaDecoder::PlaybackEnded.
       if (mDecoder->GetState() == MediaDecoder::PLAY_STATE_PLAYING &&
           !mDecoder->GetDecodedStream()) {
         int64_t videoTime = HasVideo() ? mVideoFrameEndTime : 0;
-        int64_t clockTime = std::max(mEndTime, videoTime);
+        int64_t clockTime = std::max(mEndTime, std::max(videoTime, GetAudioClock()));
         UpdatePlaybackPosition(clockTime);
 
         {
           // Wait for the state change is completed in the main thread,
           // otherwise we might see |mDecoder->GetState() == MediaDecoder::PLAY_STATE_PLAYING|
           // in next loop and send |MediaDecoder::PlaybackEnded| again to trigger 'ended'
           // event twice in the media element.
           ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
@@ -2047,40 +2334,40 @@ void MediaDecoderStateMachine::RenderVid
                                aTarget);
   }
 }
 
 int64_t
 MediaDecoderStateMachine::GetAudioClock()
 {
   // We must hold the decoder monitor while using the audio stream off the
-  // audio sink to ensure that it doesn't get destroyed on the audio sink 
+  // audio thread to ensure that it doesn't get destroyed on the audio thread
   // while we're using it.
   AssertCurrentThreadInMonitor();
   if (!HasAudio() || mAudioCaptured)
     return -1;
-  if (!mAudioSink) {
-    // Audio sink hasn't played any data yet.
+  if (!mAudioStream) {
+    // Audio thread hasn't played any data yet.
     return mAudioStartTime;
   }
-  int64_t t = mAudioSink->GetPosition();
+  int64_t t = mAudioStream->GetPosition();
   return (t == -1) ? -1 : t + mAudioStartTime;
 }
 
 int64_t MediaDecoderStateMachine::GetVideoStreamPosition()
 {
   AssertCurrentThreadInMonitor();
 
   if (!IsPlaying()) {
     return mPlayDuration + mStartTime;
   }
 
   // The playbackRate has been just been changed, reset the playstartTime.
   if (mResetPlayStartTime) {
-    SetPlayStartTime(TimeStamp::Now());
+    mPlayStartTime = TimeStamp::Now();
     mResetPlayStartTime = false;
   }
 
   int64_t pos = DurationToUsecs(TimeStamp::Now() - mPlayStartTime) + mPlayDuration;
   pos -= mBasePosition;
   NS_ASSERTION(pos >= 0, "Video stream position should be positive.");
   return mBasePosition + pos * mPlaybackRate + mStartTime;
 }
@@ -2101,17 +2388,17 @@ int64_t MediaDecoderStateMachine::GetClo
     clock_time = GetCurrentTimeViaMediaStreamSync();
   } else {
     int64_t audio_time = GetAudioClock();
     if (HasAudio() && !mAudioCompleted && audio_time != -1) {
       clock_time = audio_time;
       // Resync against the audio clock, while we're trusting the
       // audio clock. This ensures no "drift", particularly on Linux.
       mPlayDuration = clock_time - mStartTime;
-      SetPlayStartTime(TimeStamp::Now());
+      mPlayStartTime = TimeStamp::Now();
     } else {
       // Audio is disabled on this system. Sync to the system clock.
       clock_time = GetVideoStreamPosition();
       // Ensure the clock can never go backwards.
       NS_ASSERTION(mCurrentFrameTime <= clock_time || mPlaybackRate <= 0,
           "Clock should go forwards if the playback rate is > 0.");
     }
   }
@@ -2157,17 +2444,17 @@ void MediaDecoderStateMachine::AdvanceFr
                       mDecoder.get(), frame->mTime, droppedFrames - 1));
         }
       }
 #endif
       mReader->VideoQueue().PopFront();
       // Notify the decode thread that the video queue's buffers may have
       // free'd up space for more frames.
       mDecoder->GetReentrantMonitor().NotifyAll();
-      OnPlaybackOffsetUpdate(frame->mOffset);
+      mDecoder->UpdatePlaybackOffset(frame->mOffset);
       if (mReader->VideoQueue().GetSize() == 0)
         break;
       frame = mReader->VideoQueue().PeekFront();
     }
     // Current frame has already been presented, wait until it's time to
     // present the next frame.
     if (frame && !currentFrame) {
       int64_t now = IsPlaying() ? clock_time : mPlayDuration;
@@ -2243,16 +2530,35 @@ void MediaDecoderStateMachine::AdvanceFr
   // this function popping and playing a video frame, or by the audio
   // thread popping and playing an audio frame, we may need to update our
   // ready state. Post an update to do so.
   UpdateReadyState();
 
   ScheduleStateMachine(remainingTime);
 }
 
+void MediaDecoderStateMachine::Wait(int64_t aUsecs) {
+  NS_ASSERTION(OnAudioThread(), "Only call on the audio thread");
+  AssertCurrentThreadInMonitor();
+  TimeStamp end = TimeStamp::Now() + UsecsToDuration(std::max<int64_t>(USECS_PER_MS, aUsecs));
+  TimeStamp now;
+  while ((now = TimeStamp::Now()) < end &&
+         mState != DECODER_STATE_SHUTDOWN &&
+         mState != DECODER_STATE_SEEKING &&
+         !mStopAudioThread &&
+         IsPlaying())
+  {
+    int64_t ms = static_cast<int64_t>(NS_round((end - now).ToSeconds() * 1000));
+    if (ms == 0 || ms > UINT32_MAX) {
+      break;
+    }
+    mDecoder->GetReentrantMonitor().Wait(PR_MillisecondsToInterval(static_cast<uint32_t>(ms)));
+  }
+}
+
 VideoData* MediaDecoderStateMachine::FindStartTime()
 {
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
   AssertCurrentThreadInMonitor();
   int64_t startTime = 0;
   mStartTime = 0;
   VideoData* v = nullptr;
   {
@@ -2359,36 +2665,22 @@ nsresult MediaDecoderStateMachine::GetBu
   MediaResource* resource = mDecoder->GetResource();
   NS_ENSURE_TRUE(resource, NS_ERROR_FAILURE);
   resource->Pin();
   nsresult res = mReader->GetBuffered(aBuffered, mStartTime);
   resource->Unpin();
   return res;
 }
 
-void MediaDecoderStateMachine::SetPlayStartTime(const TimeStamp& aTimeStamp)
-{
-  AssertCurrentThreadInMonitor();
-  mPlayStartTime = aTimeStamp;
-  if (!mAudioSink) {
-    return;
-  }
-  if (!mPlayStartTime.IsNull()) {
-    mAudioSink->StartPlayback();
-  } else {
-    mAudioSink->StopPlayback();
-  }
-}
-
 nsresult MediaDecoderStateMachine::CallRunStateMachine()
 {
   AssertCurrentThreadInMonitor();
   NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
 
-  // If audio is being captured, stop the audio sink if it's running
+  // If audio is being captured, stop the audio thread if it's running
   if (mAudioCaptured) {
     StopAudioThread();
   }
 
   MOZ_ASSERT(!mInRunningStateMachine, "State machine cycles must run in sequence!");
   mTimeout = TimeStamp();
   mInRunningStateMachine = true;
   nsresult res = RunStateMachine();
@@ -2469,17 +2761,17 @@ void MediaDecoderStateMachine::SetPlayba
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   NS_ASSERTION(aPlaybackRate != 0,
       "PlaybackRate == 0 should be handled before this function.");
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   // We don't currently support more than two channels when changing playback
   // rate.
-  if (mInfo.mAudio.mChannels > 2) {
+  if (mAudioStream && mAudioStream->GetChannels() > 2) {
     return;
   }
 
   if (mPlaybackRate == aPlaybackRate) {
     return;
   }
 
   // Get position of the last time we changed the rate.
@@ -2487,34 +2779,28 @@ void MediaDecoderStateMachine::SetPlayba
     // mBasePosition is a position in the video stream, not an absolute time.
     if (mState == DECODER_STATE_SEEKING) {
       mBasePosition = mSeekTarget.mTime - mStartTime;
     } else {
       mBasePosition = GetVideoStreamPosition();
     }
     mPlayDuration = mBasePosition;
     mResetPlayStartTime = true;
-    SetPlayStartTime(TimeStamp::Now());
+    mPlayStartTime = TimeStamp::Now();
   }
 
   mPlaybackRate = aPlaybackRate;
-  if (mAudioSink) {
-    mAudioSink->SetPlaybackRate(mPlaybackRate);
-  }
 }
 
 void MediaDecoderStateMachine::SetPreservesPitch(bool aPreservesPitch)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   mPreservesPitch = aPreservesPitch;
-  if (mAudioSink) {
-    mAudioSink->SetPreservesPitch(mPreservesPitch);
-  }
 }
 
 void
 MediaDecoderStateMachine::SetMinimizePrerollUntilPlaybackStarts()
 {
   AssertCurrentThreadInMonitor();
   mMinimizePreroll = true;
 }
@@ -2539,34 +2825,10 @@ void MediaDecoderStateMachine::QueueMeta
   metadata->mChannels = aChannels;
   metadata->mRate = aRate;
   metadata->mHasAudio = aHasAudio;
   metadata->mHasVideo = aHasVideo;
   metadata->mTags = aTags;
   mMetadataManager.QueueMetadata(metadata);
 }
 
-void MediaDecoderStateMachine::OnAudioEndTimeUpdate(int64_t aAudioEndTime)
-{
-  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-  MOZ_ASSERT(aAudioEndTime >= mAudioEndTime);
-  mAudioEndTime = aAudioEndTime;
-}
-
-void MediaDecoderStateMachine::OnPlaybackOffsetUpdate(int64_t aPlaybackOffset)
-{
-  mDecoder->UpdatePlaybackOffset(aPlaybackOffset);
-}
-
-void MediaDecoderStateMachine::OnAudioSinkComplete()
-{
-  AssertCurrentThreadInMonitor();
-  if (mAudioCaptured) {
-    return;
-  }
-  mAudioCompleted = true;
-  UpdateReadyState();
-  // Kick the decode thread; it may be sleeping waiting for this to finish.
-  mDecoder->GetReentrantMonitor().NotifyAll();
-}
-
 } // namespace mozilla
 
--- a/content/media/MediaDecoderStateMachine.h
+++ b/content/media/MediaDecoderStateMachine.h
@@ -87,17 +87,16 @@ hardware (via AudioStream).
 class nsITimer;
 
 namespace mozilla {
 
 class AudioSegment;
 class VideoSegment;
 class MediaTaskQueue;
 class SharedThreadPool;
-class AudioSink;
 
 // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
 // GetTickCount() and conflicts with MediaDecoderStateMachine::GetCurrentTime
 // implementation.
 #ifdef GetCurrentTime
 #undef GetCurrentTime
 #endif
 
@@ -110,17 +109,16 @@ class AudioSink;
   are either propagated by NotifyAll on the monitor (typically when state
   changes need to be propagated to non-state machine threads) or by scheduling
   the state machine to run another cycle on the shared state machine thread.
 
   See MediaDecoder.h for more details.
 */
 class MediaDecoderStateMachine
 {
-  friend class AudioSink;
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderStateMachine)
 public:
   typedef MediaDecoder::DecodedStreamData DecodedStreamData;
   MediaDecoderStateMachine(MediaDecoder* aDecoder,
                                MediaDecoderReader* aReader,
                                bool aRealTime = false);
   ~MediaDecoderStateMachine();
 
@@ -176,16 +174,19 @@ public:
   // often is unstable as more data is read and the estimate is updated.
   // Can result in a durationchangeevent. aDuration is in microseconds.
   void UpdateEstimatedDuration(int64_t aDuration);
 
   // Functions used by assertions to ensure we're calling things
   // on the appropriate threads.
   bool OnDecodeThread() const;
   bool OnStateMachineThread() const;
+  bool OnAudioThread() const {
+    return IsCurrentThread(mAudioThread);
+  }
 
   MediaDecoderOwner::NextFrameStatus GetNextFrameStatus();
 
   // Cause state transitions. These methods obtain the decoder monitor
   // to synchronise the change of state, and to notify other threads
   // that the state has changed.
   void Play();
 
@@ -430,16 +431,26 @@ private:
 
   // Returns true when there's decoded audio waiting to play.
   // The decoder monitor must be held.
   bool HasFutureAudio() const;
 
   // Returns true if we recently exited "quick buffering" mode.
   bool JustExitedQuickBuffering();
 
+  // Waits on the decoder ReentrantMonitor for aUsecs microseconds. If the decoder
+  // monitor is awoken by a Notify() call, we'll continue waiting, unless
+  // we've moved into shutdown state. This enables us to ensure that we
+  // wait for a specified time, and that the myriad of Notify()s we do on
+  // the decoder monitor don't cause the audio thread to be starved. aUsecs
+  // values of less than 1 millisecond are rounded up to 1 millisecond
+  // (see bug 651023). The decoder monitor must be held. Called only on the
+  // audio thread.
+  void Wait(int64_t aUsecs);
+
   // Dispatches an asynchronous event to update the media element's ready state.
   void UpdateReadyState();
 
   // Resets playback timing data. Called when we seek, on the decode thread.
   void ResetPlayback();
 
   // Returns the audio clock, if we have audio, or -1 if we don't.
   // Called on the state machine thread.
@@ -473,24 +484,44 @@ private:
 
   // If we have video, display a video frame if it's time for display has
   // arrived, otherwise sleep until it's time for the next frame. Update the
   // current frame time as appropriate, and trigger ready state update.  The
   // decoder monitor must be held with exactly one lock count. Called on the
   // state machine thread.
   void AdvanceFrame();
 
+  // Write aFrames of audio frames of silence to the audio hardware. Returns
+  // the number of frames actually written. The write size is capped at
+  // SILENCE_BYTES_CHUNK (32kB), so must be called in a loop to write the
+  // desired number of frames. This ensures that the playback position
+  // advances smoothly, and guarantees that we don't try to allocate an
+  // impossibly large chunk of memory in order to play back silence. Called
+  // on the audio thread.
+  uint32_t PlaySilence(uint32_t aFrames,
+                       uint32_t aChannels,
+                       uint64_t aFrameOffset);
+
+  // Pops an audio chunk from the front of the audio queue, and pushes its
+  // audio data to the audio hardware.
+  uint32_t PlayFromAudioQueue(uint64_t aFrameOffset, uint32_t aChannels);
+
   // Stops the audio thread. The decoder monitor must be held with exactly
   // one lock count. Called on the state machine thread.
   void StopAudioThread();
 
   // Starts the audio thread. The decoder monitor must be held with exactly
   // one lock count. Called on the state machine thread.
   nsresult StartAudioThread();
 
+  // The main loop for the audio thread. Sent to the thread as
+  // an nsRunnableMethod. This continually does blocking writes to
+  // to audio stream to play audio data.
+  void AudioLoop();
+
   // Sets internal state which causes playback of media to pause.
   // The decoder monitor must be held.
   void StopPlayback();
 
   // Sets internal state which causes playback of media to begin or resume.
   // Must be called with the decode monitor held.
   void StartPlayback();
 
@@ -606,30 +637,16 @@ private:
     return !mTimeout.IsNull();
   }
 
   // Returns true if we're not playing and the decode thread has filled its
   // decode buffers and is waiting. We can shut the decode thread down in this
   // case as it may not be needed again.
   bool IsPausedAndDecoderWaiting();
 
-  // Set the time that playback started from the system clock.
-  // Can only be called on the state machine thread.
-  void SetPlayStartTime(const TimeStamp& aTimeStamp);
-
-  // Update mAudioEndTime.
-  void OnAudioEndTimeUpdate(int64_t aAudioEndTime);
-
-  // Update mDecoder's playback offset.
-  void OnPlaybackOffsetUpdate(int64_t aPlaybackOffset);
-
-  // Called by the AudioSink to signal that all outstanding work is complete
-  // and the sink is shutting down.
-  void OnAudioSinkComplete();
-
   // The decoder object that created this state machine. The state machine
   // holds a strong reference to the decoder to ensure that the decoder stays
   // alive once media element has started the decoder shutdown process, and has
   // dropped its reference to the decoder. This enables the state machine to
   // keep using the decoder's monitor until the state machine has finished
   // shutting down, without fear of the monitor being destroyed. After
   // shutting down, the state machine will then release this reference,
   // causing the decoder to be destroyed. This is accessed on the decode,
@@ -637,16 +654,20 @@ private:
   nsRefPtr<MediaDecoder> mDecoder;
 
   // The decoder monitor must be obtained before modifying this state.
   // NotifyAll on the monitor must be called when the state is changed so
   // that interested threads can wake up and alter behaviour if appropriate
   // Accessed on state machine, audio, main, and AV thread.
   State mState;
 
+  // Thread for pushing audio onto the audio hardware.
+  // The "audio push thread".
+  nsCOMPtr<nsIThread> mAudioThread;
+
   // The task queue in which we run decode tasks. This is referred to as
   // the "decode thread", though in practise tasks can run on a different
   // thread every time they're called.
   RefPtr<MediaTaskQueue> mDecodeTaskQueue;
 
   RefPtr<SharedThreadPool> mStateMachineThreadPool;
 
   // Timer to run the state machine cycles. Used by
@@ -657,30 +678,30 @@ private:
   // Access protected by decoder monitor.
   TimeStamp mTimeout;
 
   // Used to check if there are state machine cycles are running in sequence.
   DebugOnly<bool> mInRunningStateMachine;
 
   // The time that playback started from the system clock. This is used for
   // timing the presentation of video frames when there's no audio.
-  // Accessed only via the state machine thread.  Must be set via SetPlayStartTime.
+  // Accessed only via the state machine thread.
   TimeStamp mPlayStartTime;
 
-  // When the playbackRate changes, and there is no audio clock, it is necessary
-  // to reset the mPlayStartTime. This is done next time the clock is queried,
-  // when this member is true. Access protected by decoder monitor.
-  bool mResetPlayStartTime;
-
   // When we start writing decoded data to a new DecodedDataStream, or we
   // restart writing due to PlaybackStarted(), we record where we are in the
   // MediaStream and what that corresponds to in the media.
   StreamTime mSyncPointInMediaStream;
   int64_t mSyncPointInDecodedStream; // microseconds
 
+  // When the playbackRate changes, and there is no audio clock, it is necessary
+  // to reset the mPlayStartTime. This is done next time the clock is queried,
+  // when this member is true. Access protected by decoder monitor.
+  bool mResetPlayStartTime;
+
   // The amount of time we've spent playing already the media. The current
   // playback position is therefore |Now() - mPlayStartTime +
   // mPlayDuration|, which must be adjusted by mStartTime if used with media
   // timestamps.  Accessed only via the state machine thread.
   int64_t mPlayDuration;
 
   // Time that buffering started. Used for buffering timeout and only
   // accessed on the state machine thread. This is null while we're not
@@ -701,17 +722,21 @@ private:
   // Position to seek to in microseconds when the seek state transition occurs.
   // The decoder monitor lock must be obtained before reading or writing
   // this value. Accessed on main and decode thread.
   SeekTarget mSeekTarget;
 
   // Media Fragment end time in microseconds. Access controlled by decoder monitor.
   int64_t mFragmentEndTime;
 
-  nsRefPtr<AudioSink> mAudioSink;
+  // The audio stream resource. Used on the state machine, and audio threads.
+  // This is created and destroyed on the audio thread, while holding the
+  // decoder monitor, so if this is used off the audio thread, you must
+  // first acquire the decoder monitor and check that it is non-null.
+  nsAutoPtr<AudioStream> mAudioStream;
 
   // The reader, don't call its methods with the decoder monitor held.
   // This is created in the play state machine's constructor, and destroyed
   // in the play state machine's destructor.
   nsAutoPtr<MediaDecoderReader> mReader;
 
   // Accessed only on the state machine thread.
   // Not an nsRevocableEventPtr since we must Revoke() it well before
--- a/content/media/moz.build
+++ b/content/media/moz.build
@@ -111,17 +111,16 @@ EXPORTS.mozilla.dom += [
 
 UNIFIED_SOURCES += [
     'AudioChannelFormat.cpp',
     'AudioCompactor.cpp',
     'AudioNodeEngine.cpp',
     'AudioNodeExternalInputStream.cpp',
     'AudioNodeStream.cpp',
     'AudioSegment.cpp',
-    'AudioSink.cpp',
     'AudioStream.cpp',
     'AudioStreamTrack.cpp',
     'BufferDecoder.cpp',
     'DOMMediaStream.cpp',
     'EncodedBufferCache.cpp',
     'FileBlockCache.cpp',
     'MediaCache.cpp',
     'MediaData.cpp',