Bug 1109437 - Implement non-polling buffering. r=cpearce
☠☠ backed out by b6db7735f698 ☠ ☠
authorBobby Holley <bobbyholley@gmail.com>
Fri, 19 Dec 2014 11:39:15 -0800
changeset 246528 e6350e4fb18ba8198a7f4913954a0e4b2b8d3782
parent 246527 4c38dda0688052e7074c04a7a25065cb7e3246c6
child 246529 dc45e97d102d2f814b7a8da84ac251ef32991cce
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce
bugs1109437
milestone37.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 1109437 - Implement non-polling buffering. r=cpearce
dom/media/MediaDecoderReader.h
dom/media/MediaDecoderStateMachine.cpp
dom/media/MediaDecoderStateMachine.h
dom/media/mediasource/MediaSourceReader.cpp
dom/media/mediasource/MediaSourceReader.h
dom/media/mediasource/TrackBuffer.cpp
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -8,25 +8,38 @@
 
 #include "AbstractMediaDecoder.h"
 #include "MediaInfo.h"
 #include "MediaData.h"
 #include "MediaPromise.h"
 #include "MediaQueue.h"
 #include "AudioCompactor.h"
 
+#include "mozilla/TypedEnum.h"
+
 namespace mozilla {
 
 namespace dom {
 class TimeRanges;
 }
 
 class MediaDecoderReader;
 class SharedDecoderManager;
 
+struct WaitForDataRejectValue {
+  enum Reason {
+    SHUTDOWN
+  };
+
+  WaitForDataRejectValue(MediaData::Type aType, Reason aReason)
+    :mType(aType), mReason(aReason) {}
+  MediaData::Type mType;
+  Reason mReason;
+};
+
 // Encapsulates the decoding and reading of media data. Reading can either
 // synchronous and done on the calling "decode" thread, or asynchronous and
 // performed on a background thread, with the result being returned by
 // callback. Never hold the decoder monitor when calling into this class.
 // Unless otherwise specified, methods and fields of this class can only
 // be accessed on the decode task queue.
 class MediaDecoderReader {
 public:
@@ -35,16 +48,17 @@ public:
     DECODE_ERROR,
     WAITING_FOR_DATA,
     CANCELED
   };
 
   typedef MediaPromise<nsRefPtr<AudioData>, NotDecodedReason> AudioDataPromise;
   typedef MediaPromise<nsRefPtr<VideoData>, NotDecodedReason> VideoDataPromise;
   typedef MediaPromise<bool, nsresult> SeekPromise;
+  typedef MediaPromise<MediaData::Type, WaitForDataRejectValue> WaitForDataPromise;
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderReader)
 
   explicit MediaDecoderReader(AbstractMediaDecoder* aDecoder);
 
   // Initializes the reader, returns NS_OK on success, or NS_ERROR_FAILURE
   // on failure.
   virtual nsresult Init(MediaDecoderReader* aCloneDonor) = 0;
@@ -108,16 +122,22 @@ public:
   //
   // Don't hold the decoder monitor while calling this, as the implementation
   // may try to wait on something that needs the monitor and deadlock.
   // If aSkipToKeyframe is true, the decode should skip ahead to the
   // the next keyframe at or after aTimeThreshold microseconds.
   virtual nsRefPtr<VideoDataPromise>
   RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold);
 
+  // By default, the state machine polls the reader once per second when it's
+  // in buffering mode. Some readers support a promise-based mechanism by which
+  // they notify the state machine when the data arrives.
+  virtual bool IsWaitForDataSupported() { return false; }
+  virtual nsRefPtr<WaitForDataPromise> WaitForData(MediaData::Type aType) { MOZ_CRASH(); }
+
   virtual bool HasAudio() = 0;
   virtual bool HasVideo() = 0;
 
   // A function that is called before ReadMetadata() call.
   virtual void PreReadMetadata() {};
 
   // Read header data for all bitstreams in the file. Fills aInfo with
   // the data required to present the media, and optionally fills *aTags
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -724,18 +724,20 @@ MediaDecoderStateMachine::OnAudioDecoded
   switch (mState) {
     case DECODER_STATE_DECODING_FIRSTFRAME: {
       Push(audio);
       MaybeFinishDecodeFirstFrame();
       return;
     }
 
     case DECODER_STATE_BUFFERING:
+      // If we're buffering, this may be the sample we need to stop buffering.
+      // Schedule the state machine and then fall through.
+      ScheduleStateMachine();
     case DECODER_STATE_DECODING: {
-      // In buffering and decoding state, we simply enqueue samples.
       Push(audio);
       return;
     }
 
     case DECODER_STATE_SEEKING: {
       if (!mCurrentSeekTarget.IsValid()) {
         // We've received a sample from a previous decode. Discard it.
         return;
@@ -828,25 +830,25 @@ MediaDecoderStateMachine::OnNotDecoded(M
   RequestStatusRef(aType) = RequestStatus::Idle;
 
   // If this is a decode error, delegate to the generic error path.
   if (aReason == MediaDecoderReader::DECODE_ERROR) {
     DecodeError();
     return;
   }
 
-  // If the decoder is waiting for data, we need to make sure that the requests
-  // are cleared, which happened above. Additionally, if we're out of decoded
-  // samples, we need to switch to buffering mode.
+  // If the decoder is waiting for data, we tell it to call us back when the
+  // data arrives.
   if (aReason == MediaDecoderReader::WAITING_FOR_DATA) {
-    bool outOfSamples = isAudio ? !AudioQueue().GetSize() : !VideoQueue().GetSize();
-    if (outOfSamples) {
-      StartBuffering();
-    }
-
+    MOZ_ASSERT(mReader->IsWaitForDataSupported(),
+               "Readers that send WAITING_FOR_DATA need to implement WaitForData");
+    RequestStatusRef(aType) = RequestStatus::Waiting;
+    mReader->WaitForData(aType)->Then(DecodeTaskQueue(), __func__, this,
+                                      &MediaDecoderStateMachine::OnWaitForDataResolved,
+                                      &MediaDecoderStateMachine::OnWaitForDataRejected);
     return;
   }
 
   if (aReason == MediaDecoderReader::CANCELED) {
     DispatchDecodeTasksIfNeeded();
     return;
   }
 
@@ -936,16 +938,19 @@ MediaDecoderStateMachine::OnVideoDecoded
   switch (mState) {
     case DECODER_STATE_DECODING_FIRSTFRAME: {
       Push(video);
       MaybeFinishDecodeFirstFrame();
       return;
     }
 
     case DECODER_STATE_BUFFERING:
+      // If we're buffering, this may be the sample we need to stop buffering.
+      // Schedule the state machine and then fall through.
+      ScheduleStateMachine();
     case DECODER_STATE_DECODING: {
       Push(video);
       // If the requested video sample was slow to arrive, increase the
       // amount of audio we buffer to ensure that we don't run out of audio.
       // TODO: Detect when we're truly async, and don't do this if so, as
       // it's not necessary.
       TimeDuration decodeTime = TimeStamp::Now() - mVideoDecodeStartTime;
       if (THRESHOLD_FACTOR * DurationToUsecs(decodeTime) > mLowAudioThresholdUsecs &&
@@ -2642,19 +2647,24 @@ nsresult MediaDecoderStateMachine::RunSt
         {
           DECODER_LOG("Buffering: wait %ds, timeout in %.3lfs %s",
                       mBufferingWait, mBufferingWait - elapsed.ToSeconds(),
                       (mQuickBuffering ? "(quick exit)" : ""));
           ScheduleStateMachine(USECS_PER_S);
           return NS_OK;
         }
       } else if (outOfAudio || outOfVideo) {
-        DECODER_LOG("Out of decoded data - polling for 1s");
+        MOZ_ASSERT(mReader->IsWaitForDataSupported(),
+                   "Don't yet have a strategy for non-heuristic + non-WaitForData");
         DispatchDecodeTasksIfNeeded();
-        ScheduleStateMachine(USECS_PER_S);
+        MOZ_ASSERT_IF(outOfAudio, mAudioRequestStatus != RequestStatus::Idle);
+        MOZ_ASSERT_IF(outOfVideo, mVideoRequestStatus != RequestStatus::Idle);
+        DECODER_LOG("In buffering mode, waiting to be notified: outOfAudio: %d, "
+                    "mAudioStatus: %d, outOfVideo: %d, mVideoStatus: %d",
+                    outOfAudio, mAudioRequestStatus, outOfVideo, mVideoRequestStatus);
         return NS_OK;
       }
 
       DECODER_LOG("Changed state from BUFFERING to DECODING");
       DECODER_LOG("Buffered for %.3lfs", (now - mBufferingStart).ToSeconds());
       StartDecoding();
 
       // Notify to allow blocked decoder thread to continue
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -384,16 +384,33 @@ public:
   {
     MOZ_ASSERT(OnDecodeThread());
     OnNotDecoded(MediaData::VIDEO_DATA, aReason);
   }
 
   void OnSeekCompleted();
   void OnSeekFailed(nsresult aResult);
 
+  void OnWaitForDataResolved(MediaData::Type aType)
+  {
+    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+    if (RequestStatusRef(aType) == RequestStatus::Waiting) {
+      RequestStatusRef(aType) = RequestStatus::Idle;
+      DispatchDecodeTasksIfNeeded();
+    }
+  }
+
+  void OnWaitForDataRejected(WaitForDataRejectValue aRejection)
+  {
+    MOZ_ASSERT(aRejection.mReason == WaitForDataRejectValue::SHUTDOWN);
+    if (RequestStatusRef(aRejection.mType) == RequestStatus::Waiting) {
+      RequestStatusRef(aRejection.mType) = RequestStatus::Idle;
+    }
+  }
+
 private:
   void AcquireMonitorAndInvokeDecodeError();
 
 protected:
   virtual ~MediaDecoderStateMachine();
 
   void AssertCurrentThreadInMonitor() const { mDecoder->GetReentrantMonitor().AssertCurrentThreadIn(); }
 
--- a/dom/media/mediasource/MediaSourceReader.cpp
+++ b/dom/media/mediasource/MediaSourceReader.cpp
@@ -340,16 +340,19 @@ MediaSourceReader::ContinueShutdown()
   mAudioTrack = nullptr;
   mAudioReader = nullptr;
   mVideoTrack = nullptr;
   mVideoReader = nullptr;
 
   MOZ_ASSERT(mAudioPromise.IsEmpty());
   MOZ_ASSERT(mVideoPromise.IsEmpty());
 
+  mAudioWaitPromise.RejectIfExists(WaitForDataRejectValue(MediaData::AUDIO_DATA, WaitForDataRejectValue::SHUTDOWN), __func__);
+  mVideoWaitPromise.RejectIfExists(WaitForDataRejectValue(MediaData::VIDEO_DATA, WaitForDataRejectValue::SHUTDOWN), __func__);
+
   MediaDecoderReader::Shutdown()->ChainTo(mMediaSourceShutdownPromise.Steal(), __func__);
 }
 
 void
 MediaSourceReader::BreakCycles()
 {
   MediaDecoderReader::BreakCycles();
 
@@ -387,16 +390,25 @@ MediaSourceReader::SelectReader(int64_t 
 
     return newReader.forget();
   }
 
   return nullptr;
 }
 
 bool
+MediaSourceReader::HaveData(int64_t aTarget, MediaData::Type aType)
+{
+  TrackBuffer* trackBuffer = aType == MediaData::AUDIO_DATA ? mAudioTrack : mVideoTrack;
+  MOZ_ASSERT(trackBuffer);
+  nsRefPtr<MediaDecoderReader> reader = SelectReader(aTarget, trackBuffer->Decoders());
+  return !!reader;
+}
+
+bool
 MediaSourceReader::SwitchAudioReader(int64_t aTarget)
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   // XXX: Can't handle adding an audio track after ReadMetadata.
   if (!mAudioTrack) {
     return false;
   }
   nsRefPtr<MediaDecoderReader> newReader = SelectReader(aTarget, mAudioTrack->Decoders());
@@ -715,16 +727,42 @@ MediaSourceReader::GetBuffered(dom::Time
 
     intersectionRanges->Intersection(sourceRanges);
   }
 
   MSE_DEBUG("MediaSourceReader(%p)::GetBuffered ranges=%s", this, DumpTimeRanges(intersectionRanges).get());
   return NS_OK;
 }
 
+nsRefPtr<MediaDecoderReader::WaitForDataPromise>
+MediaSourceReader::WaitForData(MediaData::Type aType)
+{
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  nsRefPtr<WaitForDataPromise> p = WaitPromise(aType).Ensure(__func__);
+  MaybeNotifyHaveData();
+  return p;
+}
+
+void
+MediaSourceReader::MaybeNotifyHaveData()
+{
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  bool haveAudio = false, haveVideo = false;
+  if (!mAudioIsSeeking && mAudioTrack && HaveData(mLastAudioTime, MediaData::AUDIO_DATA)) {
+    haveAudio = true;
+    WaitPromise(MediaData::AUDIO_DATA).ResolveIfExists(MediaData::AUDIO_DATA, __func__);
+  }
+  if (!mVideoIsSeeking && mVideoTrack && HaveData(mLastVideoTime, MediaData::VIDEO_DATA)) {
+    haveVideo = true;
+    WaitPromise(MediaData::VIDEO_DATA).ResolveIfExists(MediaData::VIDEO_DATA, __func__);
+  }
+  MSE_DEBUG("MediaSourceReader(%p)::MaybeNotifyHaveData haveAudio=%d, haveVideo=%d", this,
+            haveAudio, haveVideo);
+}
+
 nsresult
 MediaSourceReader::ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags)
 {
   MSE_DEBUG("MediaSourceReader(%p)::ReadMetadata tracks=%u/%u audio=%p video=%p",
             this, mEssentialTrackBuffers.Length(), mTrackBuffers.Length(),
             mAudioTrack.get(), mVideoTrack.get());
 
   {
--- a/dom/media/mediasource/MediaSourceReader.h
+++ b/dom/media/mediasource/MediaSourceReader.h
@@ -56,16 +56,20 @@ public:
   void OnAudioDecoded(AudioData* aSample);
   void OnAudioNotDecoded(NotDecodedReason aReason);
   void OnVideoDecoded(VideoData* aSample);
   void OnVideoNotDecoded(NotDecodedReason aReason);
 
   void OnSeekCompleted();
   void OnSeekFailed(nsresult aResult);
 
+  virtual bool IsWaitForDataSupported() MOZ_OVERRIDE { return true; }
+  virtual nsRefPtr<WaitForDataPromise> WaitForData(MediaData::Type aType) MOZ_OVERRIDE;
+  void MaybeNotifyHaveData();
+
   bool HasVideo() MOZ_OVERRIDE
   {
     return mInfo.HasVideo();
   }
 
   bool HasAudio() MOZ_OVERRIDE
   {
     return mInfo.HasAudio();
@@ -127,32 +131,40 @@ public:
 private:
   bool SwitchAudioReader(int64_t aTarget);
   bool SwitchVideoReader(int64_t aTarget);
 
   // Return a reader from the set available in aTrackDecoders that has data
   // available in the range requested by aTarget.
   already_AddRefed<MediaDecoderReader> SelectReader(int64_t aTarget,
                                                     const nsTArray<nsRefPtr<SourceBufferDecoder>>& aTrackDecoders);
+  bool HaveData(int64_t aTarget, MediaData::Type aType);
 
   void AttemptSeek();
   void FinalizeSeek();
 
   nsRefPtr<MediaDecoderReader> mAudioReader;
   nsRefPtr<MediaDecoderReader> mVideoReader;
 
   nsTArray<nsRefPtr<TrackBuffer>> mTrackBuffers;
   nsTArray<nsRefPtr<TrackBuffer>> mShutdownTrackBuffers;
   nsTArray<nsRefPtr<TrackBuffer>> mEssentialTrackBuffers;
   nsRefPtr<TrackBuffer> mAudioTrack;
   nsRefPtr<TrackBuffer> mVideoTrack;
 
   MediaPromiseHolder<AudioDataPromise> mAudioPromise;
   MediaPromiseHolder<VideoDataPromise> mVideoPromise;
 
+  MediaPromiseHolder<WaitForDataPromise> mAudioWaitPromise;
+  MediaPromiseHolder<WaitForDataPromise> mVideoWaitPromise;
+  MediaPromiseHolder<WaitForDataPromise>& WaitPromise(MediaData::Type aType)
+  {
+    return aType == MediaData::AUDIO_DATA ? mAudioWaitPromise : mVideoWaitPromise;
+  }
+
 #ifdef MOZ_EME
   nsRefPtr<CDMProxy> mCDMProxy;
 #endif
 
   // These are read and written on the decode task queue threads.
   int64_t mLastAudioTime;
   int64_t mLastVideoTime;
 
--- a/dom/media/mediasource/TrackBuffer.cpp
+++ b/dom/media/mediasource/TrackBuffer.cpp
@@ -178,19 +178,19 @@ TrackBuffer::AppendData(const uint8_t* a
     mLastEndTimestamp.reset();
     mLastEndTimestamp.emplace(end);
   }
 
   if (!AppendDataToCurrentResource(aData, aLength)) {
     return false;
   }
 
-  // Schedule the state machine thread to ensure playback starts if required
-  // when data is appended.
-  mParentDecoder->ScheduleStateMachineThread();
+  // Tell our reader that we have more data to ensure that playback starts if
+  // required when data is appended.
+  mParentDecoder->GetReader()->MaybeNotifyHaveData();
   return true;
 }
 
 bool
 TrackBuffer::AppendDataToCurrentResource(const uint8_t* aData, uint32_t aLength)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!mCurrentDecoder) {
@@ -435,16 +435,21 @@ TrackBuffer::InitializeDecoder(SourceBuf
   }
 
   if (!RegisterDecoder(aDecoder)) {
     // XXX: Need to signal error back to owning SourceBuffer.
     MSE_DEBUG("TrackBuffer(%p): Reader %p not activated", this, reader);
     RemoveDecoder(aDecoder);
     return;
   }
+
+  // Tell our reader that we have more data to ensure that playback starts if
+  // required when data is appended.
+  mParentDecoder->GetReader()->MaybeNotifyHaveData();
+
   MSE_DEBUG("TrackBuffer(%p): Reader %p activated", this, reader);
 }
 
 bool
 TrackBuffer::ValidateTrackFormats(const MediaInfo& aInfo)
 {
   if (mInfo.HasAudio() != aInfo.HasAudio() ||
       mInfo.HasVideo() != aInfo.HasVideo()) {