Bug 962385 - Update PlatformDecoderModule interface to run asynchronously. r=kinetik
authorChris Pearce <cpearce@mozilla.com>
Wed, 05 Feb 2014 14:29:28 +1300
changeset 185052 8b3c4d6edf9a78d7d954640614f6ec1c7ae0252f
parent 185051 519a6a48fb8e15ae5d23bb82642639c24f65c22d
child 185053 144021ba1c5c2dff2230f4e4992d705674a546cb
push id474
push userasasaki@mozilla.com
push dateMon, 02 Jun 2014 21:01:02 +0000
treeherdermozilla-release@967f4cf1b31c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs962385
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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