Bug 962385 - Update PlatformDecoderModule interface to run asynchronously. r=kinetik
authorChris Pearce <cpearce@mozilla.com>
Wed, 05 Feb 2014 14:29:28 +1300
changeset 166873 8b3c4d6edf9a78d7d954640614f6ec1c7ae0252f
parent 166872 519a6a48fb8e15ae5d23bb82642639c24f65c22d
child 166874 144021ba1c5c2dff2230f4e4992d705674a546cb
push id4853
push usercbook@mozilla.com
push dateWed, 05 Feb 2014 13:54:35 +0000
treeherderfx-team@0965914f979c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs962385
milestone30.0a1
Bug 962385 - Update PlatformDecoderModule interface to run asynchronously. r=kinetik
content/media/fmp4/BlankDecoderModule.cpp
content/media/fmp4/MP4Reader.cpp
content/media/fmp4/MP4Reader.h
content/media/fmp4/PlatformDecoderModule.h
--- a/content/media/fmp4/BlankDecoderModule.cpp
+++ b/content/media/fmp4/BlankDecoderModule.cpp
@@ -7,126 +7,137 @@
 #include "MediaDecoderReader.h"
 #include "PlatformDecoderModule.h"
 #include "nsRect.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/CheckedInt.h"
 #include "VideoUtils.h"
 #include "ImageContainer.h"
 #include "mp4_demuxer/mp4_demuxer.h"
-#include "mp4_demuxer/audio_decoder_config.h"
+#include "MediaTaskQueue.h"
 
 namespace mozilla {
 
 // Decoder that uses a passed in object's Create function to create blank
 // MediaData objects.
 template<class BlankMediaDataCreator>
 class BlankMediaDataDecoder : public MediaDataDecoder {
 public:
 
-  BlankMediaDataDecoder(BlankMediaDataCreator* aCreator)
-    : mCreator(aCreator),
-      mNextTimeStamp(-1),
-      mNextOffset(-1)
+  BlankMediaDataDecoder(BlankMediaDataCreator* aCreator,
+                        MediaTaskQueue* aTaskQueue,
+                        MediaDataDecoderCallback* aCallback)
+    : mCreator(aCreator)
+    , mTaskQueue(aTaskQueue)
+    , mCallback(aCallback)
   {
   }
 
   virtual nsresult Init() MOZ_OVERRIDE {
     return NS_OK;
   }
 
   virtual nsresult Shutdown() MOZ_OVERRIDE {
     return NS_OK;
   }
 
-  virtual DecoderStatus Input(nsAutoPtr<mp4_demuxer::MP4Sample>& aSample) MOZ_OVERRIDE
-  {
-    // Accepts input, and outputs on the second input, using the difference
-    // in DTS as the duration.
-    if (mOutput) {
-      return DECODE_STATUS_NOT_ACCEPTING;
+  class OutputEvent : public nsRunnable {
+  public:
+    OutputEvent(mp4_demuxer::MP4Sample* aSample,
+                MediaDataDecoderCallback* aCallback,
+                BlankMediaDataCreator* aCreator)
+      : mSample(aSample)
+      , mCallback(aCallback)
+      , mCreator(aCreator)
+    {
     }
+    NS_IMETHOD Run() MOZ_OVERRIDE
+    {
+      mCallback->Output(mCreator->Create(mSample->composition_timestamp,
+                                         mSample->duration,
+                                         mSample->byte_offset));
+      return NS_OK;
+    }
+  private:
+    nsAutoPtr<mp4_demuxer::MP4Sample> mSample;
+    BlankMediaDataCreator* mCreator;
+    MediaDataDecoderCallback* mCallback;
+  };
 
-    Microseconds timestamp = aSample->composition_timestamp;
-    if (mNextTimeStamp != -1 && mNextOffset != -1) {
-      Microseconds duration = timestamp - mNextTimeStamp;
-      mOutput = mCreator->Create(mNextTimeStamp, duration, mNextOffset);
-    }
-
-    mNextTimeStamp = timestamp;
-    mNextOffset = aSample->byte_offset;
-
-    return DECODE_STATUS_OK;
+  virtual nsresult Input(mp4_demuxer::MP4Sample* aSample) MOZ_OVERRIDE
+  {
+    // The MediaDataDecoder must delete the sample when we're finished
+    // with it, so the OutputEvent stores it in an nsAutoPtr and deletes
+    // it once it's run.
+    RefPtr<nsIRunnable> r(new OutputEvent(aSample, mCallback, mCreator));
+    mTaskQueue->Dispatch(r);
+    return NS_OK;
   }
 
-  virtual DecoderStatus Output(nsAutoPtr<MediaData>& aOutData) MOZ_OVERRIDE
-  {
-    if (!mOutput) {
-      return DECODE_STATUS_NEED_MORE_INPUT;
-    }
-    aOutData = mOutput.forget();
-    return DECODE_STATUS_OK;
+  virtual nsresult Flush() MOZ_OVERRIDE {
+    return NS_OK;
   }
 
-  virtual DecoderStatus Flush()  MOZ_OVERRIDE {
-    return DECODE_STATUS_OK;
+  virtual nsresult Drain() MOZ_OVERRIDE {
+    return NS_OK;
   }
+
 private:
   nsAutoPtr<BlankMediaDataCreator> mCreator;
-  Microseconds mNextTimeStamp;
-  int64_t mNextOffset;
   nsAutoPtr<MediaData> mOutput;
-  bool mHasInput;
+  RefPtr<MediaTaskQueue> mTaskQueue;
+  MediaDataDecoderCallback* mCallback;
 };
 
-static const uint32_t sFrameWidth = 320;
-static const uint32_t sFrameHeight = 240;
-
 class BlankVideoDataCreator {
 public:
-  BlankVideoDataCreator(layers::ImageContainer* aImageContainer)
-    : mImageContainer(aImageContainer)
+  BlankVideoDataCreator(uint32_t aFrameWidth,
+                        uint32_t aFrameHeight,
+                        layers::ImageContainer* aImageContainer)
+    : mFrameWidth(aFrameWidth)
+    , mFrameHeight(aFrameHeight)
+    , mImageContainer(aImageContainer)
   {
-    mInfo.mDisplay = nsIntSize(sFrameWidth, sFrameHeight);
-    mPicture = nsIntRect(0, 0, sFrameWidth, sFrameHeight);
+    mInfo.mDisplay = nsIntSize(mFrameWidth, mFrameHeight);
+    mPicture = nsIntRect(0, 0, mFrameWidth, mFrameHeight);
   }
 
   MediaData* Create(Microseconds aDTS,
                     Microseconds aDuration,
                     int64_t aOffsetInStream)
   {
     // Create a fake YUV buffer in a 420 format. That is, an 8bpp Y plane,
     // with a U and V plane that are half the size of the Y plane, i.e 8 bit,
     // 2x2 subsampled. Have the data pointers of each frame point to the
     // first plane, they'll always be zero'd memory anyway.
-    uint8_t* frame = new uint8_t[sFrameWidth * sFrameHeight];
-    memset(frame, 0, sFrameWidth * sFrameHeight);
+    uint8_t* frame = new uint8_t[mFrameWidth * mFrameHeight];
+    memset(frame, 0, mFrameWidth * mFrameHeight);
     VideoData::YCbCrBuffer buffer;
 
     // Y plane.
     buffer.mPlanes[0].mData = frame;
-    buffer.mPlanes[0].mStride = sFrameWidth;
-    buffer.mPlanes[0].mHeight = sFrameHeight;
-    buffer.mPlanes[0].mWidth = sFrameWidth;
+    buffer.mPlanes[0].mStride = mFrameWidth;
+    buffer.mPlanes[0].mHeight = mFrameHeight;
+    buffer.mPlanes[0].mWidth = mFrameWidth;
     buffer.mPlanes[0].mOffset = 0;
     buffer.mPlanes[0].mSkip = 0;
 
     // Cb plane.
     buffer.mPlanes[1].mData = frame;
-    buffer.mPlanes[1].mStride = sFrameWidth / 2;
-    buffer.mPlanes[1].mHeight = sFrameHeight / 2;
-    buffer.mPlanes[1].mWidth = sFrameWidth / 2;
+    buffer.mPlanes[1].mStride = mFrameWidth / 2;
+    buffer.mPlanes[1].mHeight = mFrameHeight / 2;
+    buffer.mPlanes[1].mWidth = mFrameWidth / 2;
     buffer.mPlanes[1].mOffset = 0;
     buffer.mPlanes[1].mSkip = 0;
 
     // Cr plane.
     buffer.mPlanes[2].mData = frame;
-    buffer.mPlanes[2].mStride = sFrameWidth / 2;
-    buffer.mPlanes[2].mHeight = sFrameHeight / 2;
-    buffer.mPlanes[2].mWidth = sFrameWidth / 2;
+    buffer.mPlanes[2].mStride = mFrameWidth / 2;
+    buffer.mPlanes[2].mHeight = mFrameHeight / 2;
+    buffer.mPlanes[2].mWidth = mFrameWidth / 2;
     buffer.mPlanes[2].mOffset = 0;
     buffer.mPlanes[2].mSkip = 0;
 
     return VideoData::Create(mInfo,
                              mImageContainer,
                              nullptr,
                              aOffsetInStream,
                              aDTS,
@@ -134,16 +145,18 @@ public:
                              buffer,
                              true,
                              aDTS,
                              mPicture);
   }
 private:
   VideoInfo mInfo;
   nsIntRect mPicture;
+  uint32_t mFrameWidth;
+  uint32_t mFrameHeight;
   RefPtr<layers::ImageContainer> mImageContainer;
 };
 
 
 class BlankAudioDataCreator {
 public:
   BlankAudioDataCreator(uint32_t aChannelCount,
                         uint32_t aSampleRate,
@@ -200,28 +213,38 @@ public:
   // Called when the decoders have shutdown. Main thread only.
   virtual nsresult Shutdown() MOZ_OVERRIDE {
     return NS_OK;
   }
 
   // Decode thread.
   virtual MediaDataDecoder* CreateH264Decoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
                                               layers::LayersBackend aLayersBackend,
-                                              layers::ImageContainer* aImageContainer) MOZ_OVERRIDE {
-    BlankVideoDataCreator* decoder = new BlankVideoDataCreator(aImageContainer);
-    return new BlankMediaDataDecoder<BlankVideoDataCreator>(decoder);
+                                              layers::ImageContainer* aImageContainer,
+                                              MediaTaskQueue* aVideoTaskQueue,
+                                              MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE {
+    BlankVideoDataCreator* decoder = new BlankVideoDataCreator(aConfig.visible_rect().width(),
+                                                               aConfig.visible_rect().height(),
+                                                               aImageContainer);
+    return new BlankMediaDataDecoder<BlankVideoDataCreator>(decoder,
+                                                            aVideoTaskQueue,
+                                                            aCallback);
   }
 
   // Decode thread.
-  virtual MediaDataDecoder* CreateAACDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig) MOZ_OVERRIDE {
+  virtual MediaDataDecoder* CreateAACDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
+                                             MediaTaskQueue* aAudioTaskQueue,
+                                             MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE {
     BlankAudioDataCreator* decoder =
       new BlankAudioDataCreator(ChannelLayoutToChannelCount(aConfig.channel_layout()),
                                 aConfig.samples_per_second(),
                                 aConfig.bits_per_channel());
-    return new BlankMediaDataDecoder<BlankAudioDataCreator>(decoder);
+    return new BlankMediaDataDecoder<BlankAudioDataCreator>(decoder,
+                                                            aAudioTaskQueue,
+                                                            aCallback);
   }
 };
 
 PlatformDecoderModule* CreateBlankDecoderModule()
 {
   return new BlankDecoderModule();
 }
 
--- a/content/media/fmp4/MP4Reader.cpp
+++ b/content/media/fmp4/MP4Reader.cpp
@@ -8,16 +8,18 @@
 #include "MediaResource.h"
 #include "mp4_demuxer/mp4_demuxer.h"
 #include "mp4_demuxer/Streams.h"
 #include "nsSize.h"
 #include "VideoUtils.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "ImageContainer.h"
 #include "Layers.h"
+#include "SharedThreadPool.h"
+#include "mozilla/Preferences.h"
 
 using mozilla::layers::Image;
 using mozilla::layers::LayerManager;
 using mozilla::layers::LayersBackend;
 
 #ifdef PR_LOGGING
 PRLogModuleInfo* GetDemuxerLog() {
   static PRLogModuleInfo* log = nullptr;
@@ -75,27 +77,54 @@ public:
     return mResource->GetLength();
   }
 
 private:
   RefPtr<MediaResource> mResource;
 };
 
 MP4Reader::MP4Reader(AbstractMediaDecoder* aDecoder)
-  : MediaDecoderReader(aDecoder),
-    mLayersBackendType(layers::LayersBackend::LAYERS_NONE),
-    mHasAudio(false),
-    mHasVideo(false)
+  : MediaDecoderReader(aDecoder)
+  , mLayersBackendType(layers::LayersBackend::LAYERS_NONE)
+  , mAudio("MP4 audio decoder data", Preferences::GetUint("media.mp4-audio-decode-ahead", 2))
+  , mVideo("MP4 video decoder data", Preferences::GetUint("media.mp4-video-decode-ahead", 2))
+  , mLastReportedNumDecodedFrames(0)
 {
+  MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
   MOZ_COUNT_CTOR(MP4Reader);
 }
 
 MP4Reader::~MP4Reader()
 {
+  MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
   MOZ_COUNT_DTOR(MP4Reader);
+  Shutdown();
+}
+
+void
+MP4Reader::Shutdown()
+{
+  if (mAudio.mDecoder) {
+    Flush(kAudio);
+    mAudio.mDecoder->Shutdown();
+    mAudio.mDecoder = nullptr;
+  }
+  if (mAudio.mTaskQueue) {
+    mAudio.mTaskQueue->Shutdown();
+    mAudio.mTaskQueue = nullptr;
+  }
+  if (mVideo.mDecoder) {
+    Flush(kVideo);
+    mVideo.mDecoder->Shutdown();
+    mVideo.mDecoder = nullptr;
+  }
+  if (mVideo.mTaskQueue) {
+    mVideo.mTaskQueue->Shutdown();
+    mVideo.mTaskQueue = nullptr;
+  }
 }
 
 void
 MP4Reader::InitLayersBackendType()
 {
   if (!IsVideoContentType(mDecoder->GetResource()->GetContentType())) {
     // Not playing video, we don't care about the layers backend type.
     return;
@@ -124,63 +153,76 @@ MP4Reader::Init(MediaDecoderReader* aClo
 {
   MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
   PlatformDecoderModule::Init();
   mMP4Stream = new MP4Stream(mDecoder->GetResource());
   mDemuxer = new MP4Demuxer(mMP4Stream);
 
   InitLayersBackendType();
 
+  mAudio.mTaskQueue = new MediaTaskQueue(
+    SharedThreadPool::Get(NS_LITERAL_CSTRING("MP4 Audio Decode")));
+  NS_ENSURE_TRUE(mAudio.mTaskQueue, NS_ERROR_FAILURE);
+
+  mVideo.mTaskQueue = new MediaTaskQueue(
+    SharedThreadPool::Get(NS_LITERAL_CSTRING("MP4 Video Decode")));
+  NS_ENSURE_TRUE(mVideo.mTaskQueue, NS_ERROR_FAILURE);
+
   return NS_OK;
 }
 
 nsresult
 MP4Reader::ReadMetadata(MediaInfo* aInfo,
                         MetadataTags** aTags)
 {
   bool ok = mDemuxer->Init();
   NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
 
   const AudioDecoderConfig& audio = mDemuxer->AudioConfig();
-  mInfo.mAudio.mHasAudio = mHasAudio = mDemuxer->HasAudio() &&
-                                       audio.IsValidConfig();
+  mInfo.mAudio.mHasAudio = mAudio.mActive = mDemuxer->HasAudio() &&
+                                            audio.IsValidConfig();
   // If we have audio, we *only* allow AAC to be decoded.
-  if (mHasAudio && audio.codec() != kCodecAAC) {
+  if (HasAudio() && audio.codec() != kCodecAAC) {
     return NS_ERROR_FAILURE;
   }
 
   const VideoDecoderConfig& video = mDemuxer->VideoConfig();
-  mInfo.mVideo.mHasVideo = mHasVideo = mDemuxer->HasVideo() &&
-                                       video.IsValidConfig();
+  mInfo.mVideo.mHasVideo = mVideo.mActive = mDemuxer->HasVideo() &&
+                                            video.IsValidConfig();
   // If we have video, we *only* allow H.264 to be decoded.
-  if (mHasVideo && video.codec() != kCodecH264) {
+  if (HasVideo() && video.codec() != kCodecH264) {
     return NS_ERROR_FAILURE;
   }
 
   mPlatform = PlatformDecoderModule::Create();
   NS_ENSURE_TRUE(mPlatform, NS_ERROR_FAILURE);
 
-  if (mHasAudio) {
+  if (HasAudio()) {
     mInfo.mAudio.mRate = audio.samples_per_second();
     mInfo.mAudio.mChannels = ChannelLayoutToChannelCount(audio.channel_layout());
-    mAudioDecoder = mPlatform->CreateAACDecoder(audio);
-    NS_ENSURE_TRUE(mAudioDecoder != nullptr, NS_ERROR_FAILURE);
-    nsresult rv = mAudioDecoder->Init();
+    mAudio.mCallback = new DecoderCallback(this, kAudio);
+    mAudio.mDecoder = mPlatform->CreateAACDecoder(audio,
+                                                  mAudio.mTaskQueue,
+                                                  mAudio.mCallback);
+    NS_ENSURE_TRUE(mAudio.mDecoder != nullptr, NS_ERROR_FAILURE);
+    nsresult rv = mAudio.mDecoder->Init();
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  mInfo.mVideo.mHasVideo = mHasVideo = mDemuxer->HasVideo();
-  if (mHasVideo) {
+  if (HasVideo()) {
     IntSize sz = video.natural_size();
     mInfo.mVideo.mDisplay = nsIntSize(sz.width(), sz.height());
-    mVideoDecoder = mPlatform->CreateH264Decoder(video,
-                                                 mLayersBackendType,
-                                                 mDecoder->GetImageContainer());
-    NS_ENSURE_TRUE(mVideoDecoder != nullptr, NS_ERROR_FAILURE);
-    nsresult rv = mVideoDecoder->Init();
+    mVideo.mCallback = new  DecoderCallback(this, kVideo);
+    mVideo.mDecoder = mPlatform->CreateH264Decoder(video,
+                                                   mLayersBackendType,
+                                                   mDecoder->GetImageContainer(),
+                                                   mVideo.mTaskQueue,
+                                                   mVideo.mCallback);
+    NS_ENSURE_TRUE(mVideo.mDecoder != nullptr, NS_ERROR_FAILURE);
+    nsresult rv = mVideo.mDecoder->Init();
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Get the duration, and report it to the decoder if we have it.
   Microseconds duration = mDemuxer->Duration();
   if (duration != -1) {
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     mDecoder->SetMediaDuration(duration);
@@ -195,39 +237,42 @@ MP4Reader::ReadMetadata(MediaInfo* aInfo
   *aTags = nullptr;
 
   return NS_OK;
 }
 
 bool
 MP4Reader::HasAudio()
 {
-  return mHasAudio;
+  return mAudio.mActive;
 }
 
 bool
 MP4Reader::HasVideo()
 {
-  return mHasVideo;
+  return mVideo.mActive;
+}
+
+MP4Reader::DecoderData&
+MP4Reader::GetDecoderData(TrackType aTrack)
+{
+  MOZ_ASSERT(aTrack == kAudio || aTrack == kVideo);
+  return (aTrack == kAudio) ? mAudio : mVideo;
 }
 
 MP4SampleQueue&
 MP4Reader::SampleQueue(TrackType aTrack)
 {
-  MOZ_ASSERT(aTrack == kAudio || aTrack == kVideo);
-  return (aTrack == kAudio) ? mCompressedAudioQueue
-                            : mCompressedVideoQueue;
+  return GetDecoderData(aTrack).mDemuxedSamples;
 }
 
 MediaDataDecoder*
 MP4Reader::Decoder(mp4_demuxer::TrackType aTrack)
 {
-  MOZ_ASSERT(aTrack == kAudio || aTrack == kVideo);
-  return (aTrack == kAudio) ? mAudioDecoder
-                            : mVideoDecoder;
+  return GetDecoderData(aTrack).mDecoder;
 }
 
 MP4Sample*
 MP4Reader::PopSample(TrackType aTrack)
 {
   // Unfortunately the demuxer outputs in the order samples appear in the
   // media, not on a per stream basis. We cache the samples we get from
   // streams other than the one we want.
@@ -245,175 +290,249 @@ MP4Reader::PopSample(TrackType aTrack)
     SampleQueue(s->type).push_back(s);
   }
   MOZ_ASSERT(!sampleQueue.empty());
   MP4Sample* sample = sampleQueue.front();
   sampleQueue.pop_front();
   return sample;
 }
 
+// How async decoding works:
+//
+// When MP4Reader::Decode() is called:
+// * Lock the DecoderData. We assume the state machine wants
+//   output from the decoder (in future, we'll assume decoder wants input
+//   when the output MediaQueue isn't "full").
+// * Cache the value of mNumSamplesOutput, as prevFramesOutput.
+// * While we've not output data (mNumSamplesOutput != prevNumFramesOutput)
+//   and while we still require input, we demux and input data in the reader.
+//   We assume we require input if
+//   ((mNumSamplesInput - mNumSamplesOutput) < sDecodeAheadMargin) or
+//   mInputExhausted is true. Before we send input, we reset mInputExhausted
+//   and increment mNumFrameInput, and drop the lock on DecoderData.
+// * Once we no longer require input, we wait on the DecoderData
+//   lock for output, or for the input exhausted callback. If we receive the
+//   input exhausted callback, we go back and input more data.
+// * When our output callback is called, we take the DecoderData lock and
+//   increment mNumSamplesOutput. We notify the DecoderData lock. This will
+//   awaken the Decode thread, and unblock it, and it will return.
 bool
-MP4Reader::Decode(TrackType aTrack, nsAutoPtr<MediaData>& aOutData)
+MP4Reader::Decode(TrackType aTrack)
 {
-  MP4SampleQueue& sampleQueue = SampleQueue(aTrack);
-  MediaDataDecoder* decoder = Decoder(aTrack);
-
-  MOZ_ASSERT(decoder);
+  DecoderData& data = GetDecoderData(aTrack);
+  MOZ_ASSERT(data.mDecoder);
 
-  // Loop until we hit a return condition; we produce samples, or hit an error.
-  while (true) {
-    DecoderStatus status = decoder->Output(aOutData);
-    if (status == DECODE_STATUS_OK) {
-      MOZ_ASSERT(aOutData);
-      return true;
-    }
-    // |aOutData| should only be non-null in success case.
-    MOZ_ASSERT(!aOutData);
-
-    if (status == DECODE_STATUS_ERROR) {
+  data.mMonitor.Lock();
+  uint64_t prevNumFramesOutput = data.mNumSamplesOutput;
+  while (prevNumFramesOutput == data.mNumSamplesOutput) {
+    data.mMonitor.AssertCurrentThreadOwns();
+    if (data.mError) {
+      // Decode error!
+      data.mMonitor.Unlock();
       return false;
     }
-
-    if (status == DECODE_STATUS_NEED_MORE_INPUT) {
-      // We need to push more data from the demuxer into the decoder.
-      // Now loop back and try to extract output again.
-      nsAutoPtr<MP4Sample> compressed;
-      do {
-        compressed = PopSample(aTrack);
-        if (!compressed) {
-          // EOS, or error. Let the state machine know there are no more
-          // frames coming.
-          return false;
-        }
-        status = decoder->Input(compressed);
-      } while (status == DECODE_STATUS_OK);
-      if (status == DECODE_STATUS_NOT_ACCEPTING) {
-        // Decoder should now be able to produce an output.
-        if (compressed != nullptr) {
-          // Decoder didn't consume data, attempt to decode the same
-          // sample next time.
-          SampleQueue(aTrack).push_front(compressed.forget());
-        }
-        continue;
+    // Send input to the decoder, if we need to. We assume the decoder
+    // needs input if it's told us it's out of input, or we're beneath the
+    // "low water mark" for the amount of input we've sent it vs the amount
+    // out output we've received. We always try to keep the decoder busy if
+    // possible, so we try to maintain at least a few input samples ahead,
+    // if we need output.
+    while (prevNumFramesOutput == data.mNumSamplesOutput &&
+           (data.mInputExhausted ||
+           (data.mNumSamplesInput - data.mNumSamplesOutput) < data.mDecodeAhead)) {
+      data.mMonitor.AssertCurrentThreadOwns();
+      data.mMonitor.Unlock();
+      nsAutoPtr<MP4Sample> compressed = PopSample(aTrack);
+      if (!compressed) {
+        // EOS, or error. Let the state machine know there are no more
+        // frames coming.
+        return false;
       }
-      LOG("MP4Reader decode failure. track=%d status=%d\n", aTrack, status);
-      return false;
-    } else {
-      LOG("MP4Reader unexpected error. track=%d status=%d\n", aTrack, status);
-      return false;
+      data.mMonitor.Lock();
+      data.mInputExhausted = false;
+      data.mNumSamplesInput++;
+      data.mMonitor.Unlock();
+      if (NS_FAILED(data.mDecoder->Input(compressed))) {
+        return false;
+      }
+      // If Input() failed, we let the auto pointer delete |compressed|.
+      // Otherwise, we assume the decoder will delete it when it's finished
+      // with it.
+      compressed.forget();
+      data.mMonitor.Lock();
+    }
+    data.mMonitor.AssertCurrentThreadOwns();
+    while (!data.mError &&
+           prevNumFramesOutput == data.mNumSamplesOutput &&
+           !data.mInputExhausted ) {
+      data.mMonitor.Wait();
     }
   }
+  data.mMonitor.AssertCurrentThreadOwns();
+  data.mMonitor.Unlock();
+  return true;
+}
+
+static const char*
+TrackTypeToStr(TrackType aTrack)
+{
+  MOZ_ASSERT(aTrack == kAudio || aTrack == kVideo);
+  switch (aTrack) {
+    case kAudio: return "Audio";
+    case kVideo: return "Video";
+    default: return "Unknown";
+  }
+}
+
+void
+MP4Reader::Output(mp4_demuxer::TrackType aTrack, MediaData* aSample)
+{
+#ifdef LOG_SAMPLE_DECODE
+  LOG("Decoded %s sample time=%lld dur=%lld",
+      TrackTypeToStr(aTrack), aSample->mTime, aSample->mDuration);
+#endif
+
+  DecoderData& data = GetDecoderData(aTrack);
+  // Don't accept output while we're flushing.
+  MonitorAutoLock mon(data.mMonitor);
+  if (data.mIsFlushing) {
+    mon.NotifyAll();
+    return;
+  }
+
+  switch (aTrack) {
+    case kAudio: {
+      MOZ_ASSERT(aSample->mType == MediaData::AUDIO_SAMPLES);
+      AudioQueue().Push(static_cast<AudioData*>(aSample));
+      break;
+    }
+    case kVideo: {
+      MOZ_ASSERT(aSample->mType == MediaData::VIDEO_FRAME);
+      VideoQueue().Push(static_cast<VideoData*>(aSample));
+      break;
+    }
+    default:
+      break;
+  }
+
+  data.mNumSamplesOutput++;
+  mon.NotifyAll();
+}
+
+void
+MP4Reader::InputExhausted(mp4_demuxer::TrackType aTrack)
+{
+  DecoderData& data = GetDecoderData(aTrack);
+  MonitorAutoLock mon(data.mMonitor);
+  data.mInputExhausted = true;
+  mon.NotifyAll();
+}
+
+void
+MP4Reader::Error(mp4_demuxer::TrackType aTrack)
+{
+  DecoderData& data = GetDecoderData(aTrack);
+  MonitorAutoLock mon(data.mMonitor);
+  data.mError = true;
+  mon.NotifyAll();
 }
 
 bool
 MP4Reader::DecodeAudioData()
 {
-  MOZ_ASSERT(mHasAudio && mPlatform && mAudioDecoder);
-  nsAutoPtr<MediaData> audio;
-  bool ok = Decode(kAudio, audio);
-  if (ok && audio && audio->mType == MediaData::AUDIO_SAMPLES) {
-#ifdef LOG_SAMPLE_DECODE
-    LOG("DecodeAudioData time=%lld dur=%lld", audio->mTime, audio->mDuration);
-#endif
-    mAudioQueue.Push(static_cast<AudioData*>(audio.forget()));
+  MOZ_ASSERT(HasAudio() && mPlatform && mAudio.mDecoder);
+  return Decode(kAudio);
+}
+
+void
+MP4Reader::Flush(mp4_demuxer::TrackType aTrack)
+{
+  DecoderData& data = GetDecoderData(aTrack);
+  if (!data.mDecoder) {
+    return;
   }
-  return ok;
+  // Purge the current decoder's state.
+  // Set a flag so that we ignore all output while we call
+  // MediaDataDecoder::Flush().
+  {
+    data.mIsFlushing = true;
+    MonitorAutoLock mon(data.mMonitor);
+  }
+  data.mDecoder->Flush();
+  {
+    data.mIsFlushing = false;
+    MonitorAutoLock mon(data.mMonitor);
+  }
 }
 
 bool
 MP4Reader::SkipVideoDemuxToNextKeyFrame(int64_t aTimeThreshold, uint32_t& parsed)
 {
-  MOZ_ASSERT(mVideoDecoder);
+  MOZ_ASSERT(mVideo.mDecoder);
 
-  // Purge the current decoder's state.
-  mVideoDecoder->Flush();
+  Flush(kVideo);
 
   // Loop until we reach the next keyframe after the threshold.
   while (true) {
     nsAutoPtr<MP4Sample> compressed(PopSample(kVideo));
     if (!compressed) {
       // EOS, or error. Let the state machine know.
       return false;
     }
     parsed++;
     if (!compressed->is_sync_point ||
         compressed->composition_timestamp < aTimeThreshold) {
       continue;
     }
-    mCompressedVideoQueue.push_front(compressed.forget());
+    mVideo.mDemuxedSamples.push_front(compressed.forget());
     break;
   }
 
   return true;
 }
 
 bool
 MP4Reader::DecodeVideoFrame(bool &aKeyframeSkip,
                             int64_t aTimeThreshold)
 {
   // Record number of frames decoded and parsed. Automatically update the
   // stats counters using the AutoNotifyDecoded stack-based class.
   uint32_t parsed = 0, decoded = 0;
   AbstractMediaDecoder::AutoNotifyDecoded autoNotify(mDecoder, parsed, decoded);
 
-  MOZ_ASSERT(mHasVideo && mPlatform && mVideoDecoder);
+  MOZ_ASSERT(HasVideo() && mPlatform && mVideo.mDecoder);
 
   if (aKeyframeSkip) {
     bool ok = SkipVideoDemuxToNextKeyFrame(aTimeThreshold, parsed);
     if (!ok) {
       NS_WARNING("Failed to skip demux up to next keyframe");
       return false;
     }
     aKeyframeSkip = false;
+    nsresult rv = mVideo.mDecoder->Flush();
+    NS_ENSURE_SUCCESS(rv, false);
   }
 
-  nsAutoPtr<MediaData> data;
-  bool ok = Decode(kVideo, data);
-  MOZ_ASSERT(!data || data->mType == MediaData::VIDEO_FRAME);
-  if (ok && data) {
-    parsed++;
-    if (data->mTime < aTimeThreshold) {
-      // Skip frame, it's too late to be displayed.
-      return true;
-    }
-    decoded++;
-    VideoData* video = static_cast<VideoData*>(data.forget());
-#ifdef LOG_SAMPLE_DECODE
-    LOG("DecodeVideoData time=%lld dur=%lld", video->mTime, video->mDuration);
-#endif
-    mVideoQueue.Push(video);
+  bool rv = Decode(kVideo);
+  {
+    // Report the number of "decoded" frames as the difference in the
+    // mNumSamplesOutput field since the last time we were called.
+    MonitorAutoLock mon(mVideo.mMonitor);
+    uint64_t delta = mVideo.mNumSamplesOutput - mLastReportedNumDecodedFrames;
+    decoded = static_cast<uint32_t>(delta);
+    mLastReportedNumDecodedFrames = mVideo.mNumSamplesOutput;
   }
-  return ok;
+  return rv;
 }
 
 nsresult
 MP4Reader::Seek(int64_t aTime,
                 int64_t aStartTime,
                 int64_t aEndTime,
                 int64_t aCurrentTime)
 {
   if (!mDemuxer->CanSeek()) {
     return NS_ERROR_FAILURE;
   }
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
-void
-MP4Reader::OnDecodeThreadStart()
-{
-  MOZ_ASSERT(!NS_IsMainThread(), "Must not be on main thread.");
-  MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");
-  if (mPlatform) {
-    mPlatform->OnDecodeThreadStart();
-  }
-}
-
-void
-MP4Reader::OnDecodeThreadFinish()
-{
-  MOZ_ASSERT(!NS_IsMainThread(), "Must not be on main thread.");
-  MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");
-  if (mPlatform) {
-    mPlatform->OnDecodeThreadFinish();
-  }
-}
-
 } // namespace mozilla
--- a/content/media/fmp4/MP4Reader.h
+++ b/content/media/fmp4/MP4Reader.h
@@ -7,16 +7,17 @@
 #if !defined(MP4Reader_h_)
 #define MP4Reader_h_
 
 #include "MediaDecoderReader.h"
 #include "nsAutoPtr.h"
 #include "PlatformDecoderModule.h"
 #include "mp4_demuxer/mp4_demuxer.h"
 #include "mp4_demuxer/box_definitions.h"
+#include "MediaTaskQueue.h"
 
 #include <deque>
 #include "mozilla/Monitor.h"
 
 namespace mozilla {
 
 namespace dom {
 class TimeRanges;
@@ -44,47 +45,110 @@ public:
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags) MOZ_OVERRIDE;
 
   virtual nsresult Seek(int64_t aTime,
                         int64_t aStartTime,
                         int64_t aEndTime,
                         int64_t aCurrentTime) MOZ_OVERRIDE;
+private:
 
-  virtual void OnDecodeThreadStart() MOZ_OVERRIDE;
-  virtual void OnDecodeThreadFinish() MOZ_OVERRIDE;
+  // Destroys all decoder resources.
+  void Shutdown();
 
-private:
   // Initializes mLayersBackendType if possible.
   void InitLayersBackendType();
 
-  MP4SampleQueue& SampleQueue(mp4_demuxer::TrackType aTrack);
-
   // Blocks until the demuxer produces an sample of specified type.
   // Returns nullptr on error on EOS. Caller must delete sample.
   mp4_demuxer::MP4Sample* PopSample(mp4_demuxer::TrackType aTrack);
 
-  bool Decode(mp4_demuxer::TrackType aTrack,
-              nsAutoPtr<MediaData>& aOutData);
+  bool SkipVideoDemuxToNextKeyFrame(int64_t aTimeThreshold, uint32_t& parsed);
 
-  MediaDataDecoder* Decoder(mp4_demuxer::TrackType aTrack);
-
-  bool SkipVideoDemuxToNextKeyFrame(int64_t aTimeThreshold, uint32_t& parsed);
+  void Output(mp4_demuxer::TrackType aType, MediaData* aSample);
+  void InputExhausted(mp4_demuxer::TrackType aTrack);
+  void Error(mp4_demuxer::TrackType aTrack);
+  bool Decode(mp4_demuxer::TrackType aTrack);
+  void Flush(mp4_demuxer::TrackType aTrack);
 
   nsAutoPtr<mp4_demuxer::MP4Demuxer> mDemuxer;
   nsAutoPtr<MP4Stream> mMP4Stream;
   nsAutoPtr<PlatformDecoderModule> mPlatform;
-  nsAutoPtr<MediaDataDecoder> mVideoDecoder;
-  nsAutoPtr<MediaDataDecoder> mAudioDecoder;
+
+  class DecoderCallback : public MediaDataDecoderCallback {
+  public:
+    DecoderCallback(MP4Reader* aReader,
+                    mp4_demuxer::TrackType aType)
+      : mReader(aReader)
+      , mType(aType)
+    {
+    }
+    virtual void Output(MediaData* aSample) MOZ_OVERRIDE {
+      mReader->Output(mType, aSample);
+    }
+    virtual void InputExhausted() MOZ_OVERRIDE {
+      mReader->InputExhausted(mType);
+    }
+    virtual void Error() MOZ_OVERRIDE {
+      mReader->Error(mType);
+    }
+  private:
+    MP4Reader* mReader;
+    mp4_demuxer::TrackType mType;
+  };
 
-  MP4SampleQueue mCompressedAudioQueue;
-  MP4SampleQueue mCompressedVideoQueue;
+  struct DecoderData {
+    DecoderData(const char* aMonitorName,
+                uint32_t aDecodeAhead)
+      : mMonitor(aMonitorName)
+      , mNumSamplesInput(0)
+      , mNumSamplesOutput(0)
+      , mDecodeAhead(aDecodeAhead)
+      , mActive(false)
+      , mInputExhausted(false)
+      , mError(false)
+      , mIsFlushing(false)
+    {
+    }
+
+    // The platform decoder.
+    RefPtr<MediaDataDecoder> mDecoder;
+    // Queue of input extracted by the demuxer, but not yet sent to the
+    // platform decoder.
+    MP4SampleQueue mDemuxedSamples;
+    // TaskQueue on which decoder can choose to decode.
+    // Only non-null up until the decoder is created.
+    RefPtr<MediaTaskQueue> mTaskQueue;
+    // Callback that receives output and error notifications from the decoder.
+    nsAutoPtr<DecoderCallback> mCallback;
+    // Monitor that protects all non-threadsafe state; the primitives
+    // that follow.
+    Monitor mMonitor;
+    uint64_t mNumSamplesInput;
+    uint64_t mNumSamplesOutput;
+    uint32_t mDecodeAhead;
+    // Whether this stream exists in the media.
+    bool mActive;
+    bool mInputExhausted;
+    bool mError;
+    bool mIsFlushing;
+  };
+  DecoderData mAudio;
+  DecoderData mVideo;
+
+  // The last number of decoded output frames that we've reported to
+  // MediaDecoder::NotifyDecoded(). We diff the number of output video
+  // frames every time that DecodeVideoData() is called, and report the
+  // delta there.
+  uint64_t mLastReportedNumDecodedFrames;
+
+  DecoderData& GetDecoderData(mp4_demuxer::TrackType aTrack);
+  MP4SampleQueue& SampleQueue(mp4_demuxer::TrackType aTrack);
+  MediaDataDecoder* Decoder(mp4_demuxer::TrackType aTrack);
 
   layers::LayersBackend mLayersBackendType;
 
-  bool mHasAudio;
-  bool mHasVideo;
 };
 
 } // namespace mozilla
 
 #endif
--- a/content/media/fmp4/PlatformDecoderModule.h
+++ b/content/media/fmp4/PlatformDecoderModule.h
@@ -5,182 +5,189 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #if !defined(PlatformDecoderModule_h_)
 #define PlatformDecoderModule_h_
 
 #include "MediaDecoderReader.h"
 #include "mozilla/layers/LayersTypes.h"
 #include "nsTArray.h"
+#include "mozilla/RefPtr.h"
+#include <queue>
 
 namespace mp4_demuxer {
 class VideoDecoderConfig;
 class AudioDecoderConfig;
 struct MP4Sample;
 }
 
+class nsIThreadPool;
+
 namespace mozilla {
 
 namespace layers {
 class ImageContainer;
 }
 
 class MediaDataDecoder;
+class MediaDataDecoderCallback;
+class MediaInputQueue;
+class MediaTaskQueue;
 typedef int64_t Microseconds;
 
 // The PlatformDecoderModule interface is used by the MP4Reader to abstract
 // access to the H264 and AAC decoders provided by various platforms. It
 // may be extended to support other codecs in future. Each platform (Windows,
-// MacOSX, Linux etc) must implement a PlatformDecoderModule to provide access
-// to its decoders in order to get decompressed H.264/AAC from the MP4Reader.
+// MacOSX, Linux, B2G etc) must implement a PlatformDecoderModule to provide
+// access to its decoders in order to get decompressed H.264/AAC from the
+// MP4Reader.
+//
+// Video decoding is asynchronous, and should be performed on the task queue
+// provided if the underlying platform isn't already exposing an async API.
 //
 // Platforms that don't have a corresponding PlatformDecoderModule won't be
 // able to play the H.264/AAC data output by the MP4Reader. In practice this
 // means that we won't have fragmented MP4 supported in Media Source
-// Extensions on platforms without PlatformDecoderModules.
+// Extensions.
 //
 // A cross-platform decoder module that discards input and produces "blank"
-// output samples exists for testing, and is created if the pref
+// output samples exists for testing, and is created when the pref
 // "media.fragmented-mp4.use-blank-decoder" is true.
 class PlatformDecoderModule {
 public:
   // Call on the main thread to initialize the static state
   // needed by Create().
   static void Init();
 
   // Factory method that creates the appropriate PlatformDecoderModule for
   // the platform we're running on. Caller is responsible for deleting this
   // instance. It's expected that there will be multiple
   // PlatformDecoderModules alive at the same time. There is one
-  // PlatformDecoderModule's created per MP4Reader.
-  // This is called on the main thread.
+  // PlatformDecoderModule created per MP4Reader.
+  // This is called on the decode thread.
   static PlatformDecoderModule* Create();
 
   // Called to shutdown the decoder module and cleanup state. This should
   // block until shutdown is complete. This is called after Shutdown() has
   // been called on all MediaDataDecoders created from this
   // PlatformDecoderModule.
   // Called on the main thread only.
   virtual nsresult Shutdown() = 0;
 
-  // Creates and initializes an H.264 decoder. The layers backend is
-  // passed in so that decoders can determine whether hardware accelerated
-  // decoding can be used. Returns nullptr if the decoder can't be
-  // initialized.
+  // Creates an H.264 decoder. The layers backend is passed in so that
+  // decoders can determine whether hardware accelerated decoding can be used.
+  // Asynchronous decoding of video should be done in runnables dispatched
+  // to aVideoTaskQueue. If the task queue isn't needed, the decoder should
+  // not hold a reference to it.
+  // Output and errors should be returned to the reader via aCallback.
+  // On Windows the task queue's threads in have MSCOM initialized with
+  // COINIT_MULTITHREADED.
+  // Returns nullptr if the decoder can't be created.
+  // It is safe to store a reference to aConfig.
   // Called on decode thread.
   virtual MediaDataDecoder* CreateH264Decoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
                                               layers::LayersBackend aLayersBackend,
-                                              layers::ImageContainer* aImageContainer) = 0;
+                                              layers::ImageContainer* aImageContainer,
+                                              MediaTaskQueue* aVideoTaskQueue,
+                                              MediaDataDecoderCallback* aCallback) = 0;
 
-  // Creates and initializes an AAC decoder with the specified properties.
-  // The raw AAC AudioSpecificConfig as contained in the esds box. Some
-  // decoders need that to initialize. The caller owns the AAC config,
-  // so it must be copied if it is to be retained by the decoder.
-  // Returns nullptr if the decoder can't be initialized.
+  // Creates an AAC decoder with the specified properties.
+  // Asynchronous decoding of audio should be done in runnables dispatched to
+  // aAudioTaskQueue. If the task queue isn't needed, the decoder should
+  // not hold a reference to it.
+  // Output and errors should be returned to the reader via aCallback.
+  // Returns nullptr if the decoder can't be created.
+  // On Windows the task queue's threads in have MSCOM initialized with
+  // COINIT_MULTITHREADED.
+  // It is safe to store a reference to aConfig.
   // Called on decode thread.
-  virtual MediaDataDecoder* CreateAACDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig) = 0;
-
-  // Called when a decode thread is started. Called on decode thread.
-  virtual void OnDecodeThreadStart() {}
-
-  // Called just before a decode thread is finishing. Called on decode thread.
-  virtual void OnDecodeThreadFinish() {}
+  virtual MediaDataDecoder* CreateAACDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
+                                             MediaTaskQueue* aAudioTaskQueue,
+                                             MediaDataDecoderCallback* aCallback) = 0;
 
   virtual ~PlatformDecoderModule() {}
+
 protected:
   PlatformDecoderModule() {}
   // Caches pref media.fragmented-mp4.use-blank-decoder
   static bool sUseBlankDecoder;
 };
 
-// Return value of the MediaDataDecoder functions.
-enum DecoderStatus {
-  DECODE_STATUS_NOT_ACCEPTING, // Can't accept input at this time. Decoder can produce output.
-  DECODE_STATUS_NEED_MORE_INPUT, // Can't produce output. Decoder can accept input.
-  DECODE_STATUS_OK,
-  DECODE_STATUS_ERROR
+// A callback used by MediaDataDecoder to return output/errors to the
+// MP4Reader. Implementation is threadsafe, and can be called on any thread.
+class MediaDataDecoderCallback {
+public:
+  virtual ~MediaDataDecoderCallback() {}
+
+  // Called by MediaDataDecoder when a sample has been decoded. Callee is
+  // responsibile for deleting aData.
+  virtual void Output(MediaData* aData) = 0;
+
+  // Denotes an error in the decoding process. The reader will stop calling
+  // the decoder.
+  virtual void Error() = 0;
+
+  // Denotes that the last input sample has been inserted into the decoder,
+  // and no more output can be produced unless more input is sent.
+  virtual void InputExhausted() = 0;
 };
 
 // MediaDataDecoder is the interface exposed by decoders created by the
 // PlatformDecoderModule's Create*Decoder() functions. The type of
 // media data that the decoder accepts as valid input and produces as
 // output is determined when the MediaDataDecoder is created.
-// The decoder is assumed to be in one of three mutually exclusive and
-// implicit states: able to accept input, able to produce output, and
-// shutdown. The decoder is assumed to be able to accept input by the time
-// that it's returned by PlatformDecoderModule::Create*Decoder().
-class MediaDataDecoder {
+//
+// All functions must be threadsafe, and be able to be called on an
+// arbitrary thread.
+//
+// Decoding is done asynchronously. Any async work can be done on the
+// MediaTaskQueue passed into the PlatformDecoderModules's Create*Decoder()
+// function. This may not be necessary for platforms with async APIs
+// for decoding.
+class MediaDataDecoder : public AtomicRefCounted<MediaDataDecoder> {
 public:
   virtual ~MediaDataDecoder() {};
 
   // Initialize the decoder. The decoder should be ready to decode after
   // this returns. The decoder should do any initialization here, rather
-  // than in its constructor, so that if the MP4Reader needs to Shutdown()
-  // during initialization it can call Shutdown() to cancel this.
-  // Any initialization that requires blocking the calling thread *must*
+  // than in its constructor or PlatformDecoderModule::Create*Decoder(),
+  // so that if the MP4Reader needs to shutdown during initialization,
+  // it can call Shutdown() to cancel this operation. Any initialization
+  // that requires blocking the calling thread in this function *must*
   // be done here so that it can be canceled by calling Shutdown()!
   virtual nsresult Init() = 0;
 
-  // Inserts aData into the decoding pipeline. Decoding may begin
-  // asynchronously.
-  //
-  // If the decoder needs to assume ownership of the sample it may do so by
-  // calling forget() on aSample.
-  //
-  // If Input() returns DECODE_STATUS_NOT_ACCEPTING without forget()ing
-  // aSample, then the next call will have the same aSample. Otherwise
-  // the caller will delete aSample after Input() returns.
-  //
-  // The MP4Reader calls Input() in a loop until Input() stops returning
-  // DECODE_STATUS_OK. Input() should return DECODE_STATUS_NOT_ACCEPTING
-  // once the underlying decoder should have enough data to output decoded
-  // data.
-  //
-  // Called on the media decode thread.
-  // Returns:
-  //  - DECODE_STATUS_OK if input was successfully inserted into
-  //    the decode pipeline.
-  //  - DECODE_STATUS_NOT_ACCEPTING if the decoder cannot accept any input
-  //    at this time. The MP4Reader will assume that the decoder can now
-  //    produce one or more output samples, and call the Output() function.
-  //    The MP4Reader will call Input() again with the same data later,
-  //    after the decoder's Output() function has stopped producing output,
-  //    except if Input() called forget() on aSample, whereupon a new sample
-  //    will come in next call.
-  //  - DECODE_STATUS_ERROR if the decoder has been shutdown, or some
-  //    unspecified error.
-  // This function should not return DECODE_STATUS_NEED_MORE_INPUT.
-  virtual DecoderStatus Input(nsAutoPtr<mp4_demuxer::MP4Sample>& aSample) = 0;
-
-  // Blocks until a decoded sample is produced by the deoder. The MP4Reader
-  // calls this until it stops returning DECODE_STATUS_OK.
-  // Called on the media decode thread.
-  // Returns:
-  //  - DECODE_STATUS_OK if an output sample was successfully placed in
-  //    aOutData. More samples for output may still be available, the
-  //    MP4Reader will call again to check.
-  //  - DECODE_STATUS_NEED_MORE_INPUT if the decoder needs more input to
-  //    produce a sample. The decoder should return this once it can no
-  //    longer produce output. This signals to the MP4Reader that it should
-  //    start feeding in data via the Input() function.
-  //  - DECODE_STATUS_ERROR if the decoder has been shutdown, or some
-  //    unspecified error.
-  // This function should not return DECODE_STATUS_NOT_ACCEPTING.
-  virtual DecoderStatus Output(nsAutoPtr<MediaData>& aOutData) = 0;
+  // Inserts a sample into the decoder's decode pipeline. The decoder must
+  // delete the sample once its been decoded. If Input() returns an error,
+  // aSample will be deleted by the caller.
+  virtual nsresult Input(mp4_demuxer::MP4Sample* aSample) = 0;
 
   // Causes all samples in the decoding pipeline to be discarded. When
   // this function returns, the decoder must be ready to accept new input
   // for decoding. This function is called when the demuxer seeks, before
   // decoding resumes after the seek.
-  // Called on the media decode thread.
-  virtual DecoderStatus Flush() = 0;
+  // While the reader calls Flush(), it ignores all output sent to it;
+  // it is safe (but pointless) to send output while Flush is called.
+  // The MP4Reader will not call Input() while it's calling Flush().
+  virtual nsresult Flush() = 0;
 
-  // Cancels all decode operations, and shuts down the decoder. This should
-  // block until shutdown is complete. The decoder should return
-  // DECODE_STATUS_ERROR for all calls to its functions once this is called.
-  // Called on main thread.
+  // Causes all complete samples in the pipeline that can be decoded to be
+  // output. If the decoder can't produce samples from the current output,
+  // it drops the input samples. The decoder may be holding onto samples
+  // that are required to decode samples that it expects to get in future.
+  // This is called when the demuxer reaches end of stream.
+  // The MP4Reader will not call Input() while it's calling Drain().
+  virtual nsresult Drain() = 0;
+
+  // Cancels all init/input/drain operations, and shuts down the
+  // decoder. The platform decoder should clean up any resources it's using
+  // and release memory etc. Shutdown() must block until the decoder has
+  // completed shutdown. The reader calls Flush() before calling Shutdown().
+  // The reader will delete the decoder once Shutdown() returns.
+  // The MediaDataDecoderCallback *must* not be called after Shutdown() has
+  // returned.
   virtual nsresult Shutdown() = 0;
 
 };
 
 } // namespace mozilla
 
 #endif