Bug 1055904 - Improve MSE eviction calculation. r=jya, a=sledru
authorChris Double <chris.double@double.co.nz>
Fri, 16 Jan 2015 16:14:56 +1300
changeset 242925 595835cd60a0
parent 242924 2b2b697613eb
child 242926 3e58a43384cd
push id4341
push userryanvm@gmail.com
push date2015-01-20 15:33 +0000
treeherdermozilla-beta@595835cd60a0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjya, sledru
bugs1055904
milestone36.0
Bug 1055904 - Improve MSE eviction calculation. r=jya, a=sledru Fixes a bug in the SourceBufferResource eviction code where it was using the mOffset of the resource as the min bound for what to evict. This offset is almost always zero though due to ReadFromCache being used which never updates the offset. This prevented eviction from happening in most cases. Moves the code to remove old decoders so that it does this during the same loop as that which remove data from existing decoders. This more aggressively prunes old decoders and is more likely to keep data in the current playing decoder around for seeking, etc. Prevent removing any decoder that the MediaSourceReader is currently using for playback to prevent RemoveDecoder crashes. Add a threshold to subtract from the current time when working out the time bound to evict before to make it less likely to evict current data that is needed for current playback. Remove all data from evicted decoders in the initial iteration then iterate after to remove empty decoders to put the RemoveDecoder logic in one place. Iterate decoders in order that they were added rather than sorted by time so the logic that removes entire decoders can do it only to those old decoders that existed before the existing one was created. Keeps track of the time that was evicted from the current decoder and uses that as the time to EvictBefore for all decoders in the track buffer when doing MediaSource::NotifyEvict.
dom/media/MediaResource.h
dom/media/mediasource/MediaSourceDecoder.cpp
dom/media/mediasource/MediaSourceDecoder.h
dom/media/mediasource/MediaSourceReader.cpp
dom/media/mediasource/MediaSourceReader.h
dom/media/mediasource/ResourceQueue.h
dom/media/mediasource/SourceBuffer.cpp
dom/media/mediasource/SourceBufferResource.cpp
dom/media/mediasource/SourceBufferResource.h
dom/media/mediasource/TrackBuffer.cpp
dom/media/mediasource/TrackBuffer.h
--- a/dom/media/MediaResource.h
+++ b/dom/media/MediaResource.h
@@ -128,17 +128,17 @@ class TimestampedMediaByteRange;
 // Used to denote ranges of data which are cached.
 class MediaByteRange {
 public:
   MediaByteRange() : mStart(0), mEnd(0) {}
 
   MediaByteRange(int64_t aStart, int64_t aEnd)
     : mStart(aStart), mEnd(aEnd)
   {
-    NS_ASSERTION(mStart < mEnd, "Range should end after start!");
+    NS_ASSERTION(mStart <= mEnd, "Range should end after start!");
   }
 
   explicit MediaByteRange(TimestampedMediaByteRange& aByteRange);
 
   bool IsNull() const {
     return mStart == 0 && mEnd == 0;
   }
 
--- a/dom/media/mediasource/MediaSourceDecoder.cpp
+++ b/dom/media/mediasource/MediaSourceDecoder.cpp
@@ -263,9 +263,15 @@ MediaSourceDecoder::SetCDMProxy(CDMProxy
   NS_ENSURE_SUCCESS(rv, rv);
   rv = mReader->SetCDMProxy(aProxy);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 #endif
 
+bool
+MediaSourceDecoder::IsActiveReader(MediaDecoderReader* aReader)
+{
+  return mReader->IsActiveReader(aReader);
+}
+
 } // namespace mozilla
--- a/dom/media/mediasource/MediaSourceDecoder.h
+++ b/dom/media/mediasource/MediaSourceDecoder.h
@@ -69,16 +69,20 @@ public:
   void PrepareReaderInitialization();
 
 #ifdef MOZ_EME
   virtual nsresult SetCDMProxy(CDMProxy* aProxy) MOZ_OVERRIDE;
 #endif
 
   MediaSourceReader* GetReader() { return mReader; }
 
+  // Returns true if aReader is a currently active audio or video
+  // reader in this decoders MediaSourceReader.
+  bool IsActiveReader(MediaDecoderReader* aReader);
+
 private:
   // The owning MediaSource holds a strong reference to this decoder, and
   // calls Attach/DetachMediaSource on this decoder to set and clear
   // mMediaSource.
   dom::MediaSource* mMediaSource;
   nsRefPtr<MediaSourceReader> mReader;
 
   // Protected by GetReentrantMonitor()
--- a/dom/media/mediasource/MediaSourceReader.cpp
+++ b/dom/media/mediasource/MediaSourceReader.cpp
@@ -883,9 +883,16 @@ MediaSourceReader::SetCDMProxy(CDMProxy*
     nsresult rv = mTrackBuffers[i]->SetCDMProxy(aProxy);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 #endif
 
+bool
+MediaSourceReader::IsActiveReader(MediaDecoderReader* aReader)
+{
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  return aReader == mVideoReader.get() || aReader == mAudioReader.get();
+}
+
 } // namespace mozilla
--- a/dom/media/mediasource/MediaSourceReader.h
+++ b/dom/media/mediasource/MediaSourceReader.h
@@ -130,16 +130,19 @@ public:
   nsresult SetCDMProxy(CDMProxy* aProxy);
 #endif
 
   virtual bool IsAsync() const MOZ_OVERRIDE {
     return (!mAudioReader || mAudioReader->IsAsync()) &&
            (!mVideoReader || mVideoReader->IsAsync());
   }
 
+  // Returns true if aReader is a currently active audio or video
+  bool IsActiveReader(MediaDecoderReader* aReader);
+
 private:
   // Switch the current audio/video reader to the reader that
   // contains aTarget (or up to aError after target). Both
   // aTarget and aError are in microseconds.
   bool SwitchAudioReader(int64_t aTarget, int64_t aError = 0);
   bool SwitchVideoReader(int64_t aTarget, int64_t aError = 0);
   void RequestAudioDataComplete(int64_t aTime);
   void RequestAudioDataFailed(nsresult aResult);
--- a/dom/media/mediasource/ResourceQueue.h
+++ b/dom/media/mediasource/ResourceQueue.h
@@ -100,29 +100,46 @@ public:
   void AppendItem(const uint8_t* aData, uint32_t aLength) {
     mLogicalLength += aLength;
     Push(new ResourceItem(aData, aLength));
   }
 
   // Tries to evict at least aSizeToEvict from the queue up until
   // aOffset. Returns amount evicted.
   uint32_t Evict(uint64_t aOffset, uint32_t aSizeToEvict) {
+    SBR_DEBUG("ResourceQueue(%p)::Evict(aOffset=%llu, aSizeToEvict=%u)",
+              this, aOffset, aSizeToEvict);
+    return EvictBefore(std::min(aOffset, (uint64_t)aSizeToEvict));
+  }
+
+  uint32_t EvictBefore(uint64_t aOffset) {
+    SBR_DEBUG("ResourceQueue(%p)::EvictBefore(%llu)", this, aOffset);
     uint32_t evicted = 0;
     while (ResourceItem* item = ResourceAt(0)) {
-      if (item->mData.Length() + mOffset > aOffset) {
+      SBR_DEBUG("ResourceQueue(%p)::EvictBefore item=%p length=%d offset=%llu",
+                this, item, item->mData.Length(), mOffset);
+      if (item->mData.Length() + mOffset >= aOffset) {
         break;
       }
       mOffset += item->mData.Length();
       evicted += item->mData.Length();
-      SBR_DEBUGV("ResourceQueue(%p)::Evict(%llu, %u) removed chunk length=%u",
-                 this, aOffset, aSizeToEvict, item->mData.Length());
       delete PopFront();
-      if (aSizeToEvict && evicted >= aSizeToEvict) {
-        break;
-      }
+    }
+    return evicted;
+  }
+
+  uint32_t EvictAll() {
+    SBR_DEBUG("ResourceQueue(%p)::EvictAll()", this);
+    uint32_t evicted = 0;
+    while (ResourceItem* item = ResourceAt(0)) {
+      SBR_DEBUG("ResourceQueue(%p)::EvictAll item=%p length=%d offset=%llu",
+                this, item, item->mData.Length(), mOffset);
+      mOffset += item->mData.Length();
+      evicted += item->mData.Length();
+      delete PopFront();
     }
     return evicted;
   }
 
   size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
     // Calculate the size of the internal deque.
     size_t size = nsDeque::SizeOfExcludingThis(aMallocSizeOf);
 
--- a/dom/media/mediasource/SourceBuffer.cpp
+++ b/dom/media/mediasource/SourceBuffer.cpp
@@ -405,24 +405,27 @@ SourceBuffer::PrepareAppend(ErrorResult&
   // Eviction uses a byte threshold. If the buffer is greater than the
   // number of bytes then data is evicted. The time range for this
   // eviction is reported back to the media source. It will then
   // evict data before that range across all SourceBuffers it knows
   // about.
   // TODO: Make the eviction threshold smaller for audio-only streams.
   // TODO: Drive evictions off memory pressure notifications.
   // TODO: Consider a global eviction threshold  rather than per TrackBuffer.
-  bool evicted = mTrackBuffer->EvictData(mEvictionThreshold);
+  double newBufferStartTime = 0.0;
+  bool evicted =
+    mTrackBuffer->EvictData(mMediaSource->GetDecoder()->GetCurrentTime(),
+                            mEvictionThreshold, &newBufferStartTime);
   if (evicted) {
     MSE_DEBUG("SourceBuffer(%p)::AppendData Evict; current buffered start=%f",
               this, GetBufferedStart());
 
     // We notify that we've evicted from the time range 0 through to
     // the current start point.
-    mMediaSource->NotifyEvicted(0.0, GetBufferedStart());
+    mMediaSource->NotifyEvicted(0.0, newBufferStartTime);
   }
 
   // TODO: Test buffer full flag.
   return true;
 }
 
 double
 SourceBuffer::GetBufferedStart()
--- a/dom/media/mediasource/SourceBufferResource.cpp
+++ b/dom/media/mediasource/SourceBufferResource.cpp
@@ -167,34 +167,43 @@ SourceBufferResource::ReadFromCache(char
   mOffset = oldOffset; // ReadFromCache isn't supposed to affect the seek position.
   NS_ENSURE_SUCCESS(rv, rv);
 
   // ReadFromCache return failure if not all the data is cached.
   return bytesRead == aCount ? NS_OK : NS_ERROR_FAILURE;
 }
 
 uint32_t
-SourceBufferResource::EvictData(uint32_t aThreshold)
+SourceBufferResource::EvictData(uint64_t aPlaybackOffset, uint32_t aThreshold)
 {
-  SBR_DEBUG("SourceBufferResource(%p)::EvictData(aThreshold=%u)", this, aThreshold);
+  SBR_DEBUG("SourceBufferResource(%p)::EvictData(aPlaybackOffset=%llu,"
+            "aThreshold=%u)", this, aPlaybackOffset, aThreshold);
   ReentrantMonitorAutoEnter mon(mMonitor);
-  return mInputBuffer.Evict(mOffset, aThreshold);
+  return mInputBuffer.Evict(aPlaybackOffset, aThreshold);
 }
 
 void
 SourceBufferResource::EvictBefore(uint64_t aOffset)
 {
   SBR_DEBUG("SourceBufferResource(%p)::EvictBefore(aOffset=%llu)", this, aOffset);
   ReentrantMonitorAutoEnter mon(mMonitor);
   // If aOffset is past the current playback offset we don't evict.
   if (aOffset < mOffset) {
-    mInputBuffer.Evict(aOffset, 0);
+    mInputBuffer.EvictBefore(aOffset);
   }
 }
 
+uint32_t
+SourceBufferResource::EvictAll()
+{
+  SBR_DEBUG("SourceBufferResource(%p)::EvictAll()", this);
+  ReentrantMonitorAutoEnter mon(mMonitor);
+  return mInputBuffer.EvictAll();
+}
+
 void
 SourceBufferResource::AppendData(const uint8_t* aData, uint32_t aLength)
 {
   SBR_DEBUG("SourceBufferResource(%p)::AppendData(aData=%p, aLength=%u)", this, aData, aLength);
   ReentrantMonitorAutoEnter mon(mMonitor);
   mInputBuffer.AppendItem(aData, aLength);
   mEnded = false;
   mon.NotifyAll();
--- a/dom/media/mediasource/SourceBufferResource.h
+++ b/dom/media/mediasource/SourceBufferResource.h
@@ -110,21 +110,24 @@ public:
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
   // Used by SourceBuffer.
   void AppendData(const uint8_t* aData, uint32_t aLength);
   void Ended();
   // Remove data from resource if it holds more than the threshold
   // number of bytes. Returns amount evicted.
-  uint32_t EvictData(uint32_t aThreshold);
+  uint32_t EvictData(uint64_t aPlaybackOffset, uint32_t aThreshold);
 
   // Remove data from resource before the given offset.
   void EvictBefore(uint64_t aOffset);
 
+  // Remove all data from the resource
+  uint32_t EvictAll();
+
   // Returns the amount of data currently retained by this resource.
   int64_t GetSize() {
     ReentrantMonitorAutoEnter mon(mMonitor);
     return mInputBuffer.GetLength() - mInputBuffer.GetOffset();
   }
 
 #if defined(DEBUG)
   void Dump(const char* aPath) {
--- a/dom/media/mediasource/TrackBuffer.cpp
+++ b/dom/media/mediasource/TrackBuffer.cpp
@@ -28,16 +28,21 @@ extern PRLogModuleInfo* GetMediaSourceAP
 #define MSE_DEBUGV(...) PR_LOG(GetMediaSourceLog(), PR_LOG_DEBUG+1, (__VA_ARGS__))
 #define MSE_API(...) PR_LOG(GetMediaSourceAPILog(), PR_LOG_DEBUG, (__VA_ARGS__))
 #else
 #define MSE_DEBUG(...)
 #define MSE_DEBUGV(...)
 #define MSE_API(...)
 #endif
 
+// Time in seconds to substract from the current time when deciding the
+// time point to evict data before in a decoder. This is used to help
+// precent evicting the current playback point.
+#define MSE_EVICT_THRESHOLD_TIME 2.0
+
 namespace mozilla {
 
 TrackBuffer::TrackBuffer(MediaSourceDecoder* aParentDecoder, const nsACString& aType)
   : mParentDecoder(aParentDecoder)
   , mType(aType)
   , mLastStartTimestamp(0)
   , mLastTimestampOffset(0)
   , mShutdown(false)
@@ -235,87 +240,128 @@ public:
     nsRefPtr<dom::TimeRanges> second = new dom::TimeRanges();
     aSecond->GetBuffered(second);
 
     return first->GetStartTime() == second->GetStartTime();
   }
 };
 
 bool
-TrackBuffer::EvictData(uint32_t aThreshold)
+TrackBuffer::EvictData(double aPlaybackTime,
+                       uint32_t aThreshold,
+                       double* aBufferStartTime)
 {
   MOZ_ASSERT(NS_IsMainThread());
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
 
+  if (!mCurrentDecoder) {
+    return false;
+  }
+
   int64_t totalSize = 0;
   for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
     totalSize += mDecoders[i]->GetResource()->GetSize();
   }
 
   int64_t toEvict = totalSize - aThreshold;
   if (toEvict <= 0 || mInitializedDecoders.IsEmpty()) {
     return false;
   }
 
-  // Get a list of initialized decoders, sorted by their start times.
+  // Get a list of initialized decoders
   nsTArray<SourceBufferDecoder*> decoders;
   decoders.AppendElements(mInitializedDecoders);
-  decoders.Sort(DecoderSorter());
 
   // First try to evict data before the current play position, starting
   // with the earliest time.
   uint32_t i = 0;
-  for (; i < decoders.Length(); ++i) {
-    MSE_DEBUG("TrackBuffer(%p)::EvictData decoder=%u threshold=%u toEvict=%lld",
-              this, i, aThreshold, toEvict);
-    toEvict -= decoders[i]->GetResource()->EvictData(toEvict);
-    if (!decoders[i]->GetResource()->GetSize() &&
-        decoders[i] != mCurrentDecoder) {
-      RemoveDecoder(decoders[i]);
+  bool pastCurrentDecoder = true;
+  for (; i < decoders.Length() && toEvict > 0; ++i) {
+    nsRefPtr<dom::TimeRanges> buffered = new dom::TimeRanges();
+    decoders[i]->GetBuffered(buffered);
+    bool onCurrent = decoders[i] == mCurrentDecoder;
+    if (onCurrent) {
+      pastCurrentDecoder = false;
     }
-    if (toEvict <= 0 || decoders[i] == mCurrentDecoder) {
-      break;
+
+    MSE_DEBUG("TrackBuffer(%p)::EvictData decoder=%u/%u threshold=%u "
+              "toEvict=%lld current=%s pastCurrent=%s",
+              this, i, decoders.Length(), aThreshold, toEvict,
+              onCurrent ? "true" : "false",
+              pastCurrentDecoder ? "true" : "false");
+
+    if (pastCurrentDecoder
+        && !mParentDecoder->IsActiveReader(decoders[i]->GetReader())) {
+      // Remove data from older decoders than the current one.
+      // Don't remove data if it is currently active.
+      MSE_DEBUG("TrackBuffer(%p)::EvictData evicting all before start "
+                "bufferedStart=%f bufferedEnd=%f aPlaybackTime=%f size=%lld",
+                this, buffered->GetStartTime(), buffered->GetEndTime(),
+                aPlaybackTime, decoders[i]->GetResource()->GetSize());
+      toEvict -= decoders[i]->GetResource()->EvictAll();
+    } else {
+      // To ensure we don't evict data past the current playback position
+      // we apply a threshold of a few seconds back and evict data up to
+      // that point.
+      if (aPlaybackTime > MSE_EVICT_THRESHOLD_TIME) {
+        double time = aPlaybackTime - MSE_EVICT_THRESHOLD_TIME;
+        int64_t playbackOffset = decoders[i]->ConvertToByteOffset(time);
+        MSE_DEBUG("TrackBuffer(%p)::EvictData evicting some bufferedEnd=%f"
+                  "aPlaybackTime=%f time=%f, playbackOffset=%lld size=%lld",
+                  this, buffered->GetEndTime(), aPlaybackTime, time,
+                  playbackOffset, decoders[i]->GetResource()->GetSize());
+        if (playbackOffset > 0) {
+          toEvict -= decoders[i]->GetResource()->EvictData(playbackOffset,
+                                                           toEvict);
+        }
+      }
     }
   }
 
-  // If we still need to evict more, then try to evict entire decoders,
-  // starting from the end.
-  if (toEvict > 0) {
-    uint32_t end = i;
-    MOZ_ASSERT(decoders[end] == mCurrentDecoder);
+  // Remove decoders that have no data in them
+  for (i = 0; i < decoders.Length(); ++i) {
+    nsRefPtr<dom::TimeRanges> buffered = new dom::TimeRanges();
+    decoders[i]->GetBuffered(buffered);
+    MSE_DEBUG("TrackBuffer(%p):EvictData maybe remove empty decoders=%d "
+              "size=%lld start=%f end=%f",
+              this, i, decoders[i]->GetResource()->GetSize(),
+              buffered->GetStartTime(), buffered->GetEndTime());
+    if (decoders[i] == mCurrentDecoder
+        || mParentDecoder->IsActiveReader(decoders[i]->GetReader())) {
+      continue;
+    }
 
-    for (i = decoders.Length() - 1; i > end; --i) {
-      MSE_DEBUG("TrackBuffer(%p)::EvictData removing entire decoder=%u from end toEvict=%lld",
-                this, i, toEvict);
-      // TODO: We could implement forward-eviction within a decoder and
-      // be able to evict within the current decoder.
-      toEvict -= decoders[i]->GetResource()->GetSize();
+    if (decoders[i]->GetResource()->GetSize() == 0 ||
+        buffered->GetStartTime() < 0.0 ||
+        buffered->GetEndTime() < 0.0) {
+      MSE_DEBUG("TrackBuffer(%p):EvictData remove empty decoders=%d", this, i);
       RemoveDecoder(decoders[i]);
-      if (toEvict <= 0) {
-        break;
-      }
     }
   }
-  return toEvict < (totalSize - aThreshold);
+
+  bool evicted = toEvict < (totalSize - aThreshold);
+  if (evicted) {
+    nsRefPtr<TimeRanges> ranges = new TimeRanges();
+    mCurrentDecoder->GetBuffered(ranges);
+    *aBufferStartTime = std::max(0.0, ranges->GetStartTime());
+  }
+
+  return evicted;
 }
 
 void
 TrackBuffer::EvictBefore(double aTime)
 {
   MOZ_ASSERT(NS_IsMainThread());
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
   for (uint32_t i = 0; i < mInitializedDecoders.Length(); ++i) {
     int64_t endOffset = mInitializedDecoders[i]->ConvertToByteOffset(aTime);
     if (endOffset > 0) {
       MSE_DEBUG("TrackBuffer(%p)::EvictBefore decoder=%u offset=%lld", this, i, endOffset);
       mInitializedDecoders[i]->GetResource()->EvictBefore(endOffset);
-      if (!mInitializedDecoders[i]->GetResource()->GetSize() &&
-          mInitializedDecoders[i] != mCurrentDecoder) {
-        RemoveDecoder(mInitializedDecoders[i]);
-      }
     }
   }
 }
 
 double
 TrackBuffer::Buffered(dom::TimeRanges* aRanges)
 {
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
@@ -656,16 +702,19 @@ private:
 
 void
 TrackBuffer::RemoveDecoder(SourceBufferDecoder* aDecoder)
 {
   RefPtr<nsIRunnable> task = new DelayedDispatchToMainThread(aDecoder);
 
   {
     ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
+    // There should be no other references to the decoder. Assert that
+    // we aren't using it in the MediaSourceReader.
+    MOZ_ASSERT(!mParentDecoder->IsActiveReader(aDecoder->GetReader()));
     mInitializedDecoders.RemoveElement(aDecoder);
     mDecoders.RemoveElement(aDecoder);
 
     if (mCurrentDecoder == aDecoder) {
       DiscardDecoder();
     }
   }
   aDecoder->GetReader()->GetTaskQueue()->Dispatch(task);
--- a/dom/media/mediasource/TrackBuffer.h
+++ b/dom/media/mediasource/TrackBuffer.h
@@ -35,17 +35,27 @@ public:
   TrackBuffer(MediaSourceDecoder* aParentDecoder, const nsACString& aType);
 
   nsRefPtr<ShutdownPromise> Shutdown();
 
   // Append data to the current decoder.  Also responsible for calling
   // NotifyDataArrived on the decoder to keep buffered range computation up
   // to date.  Returns false if the append failed.
   bool AppendData(const uint8_t* aData, uint32_t aLength, int64_t aTimestampOffset /* microseconds */);
-  bool EvictData(uint32_t aThreshold);
+
+  // Evicts data held in the current decoders SourceBufferResource from the
+  // start of the buffer through to aPlaybackTime. aThreshold is used to
+  // bound the data being evicted. It will not evict more than aThreshold
+  // bytes. aBufferStartTime contains the new start time of the current
+  // decoders buffered data after the eviction. Returns true if data was
+  // evicted.
+  bool EvictData(double aPlaybackTime, uint32_t aThreshold, double* aBufferStartTime);
+
+  // Evicts data held in all the decoders SourceBufferResource from the start
+  // of the buffer through to aTime.
   void EvictBefore(double aTime);
 
   // Returns the highest end time of all of the buffered ranges in the
   // decoders managed by this TrackBuffer, and returns the union of the
   // decoders buffered ranges in aRanges. This may be called on any thread.
   double Buffered(dom::TimeRanges* aRanges);
 
   // Mark the current decoder's resource as ended, clear mCurrentDecoder and