Bug 1132796: Evict data we likely previously read. r=cajbir a=lmandel
authorJean-Yves Avenard <jyavenard@mozilla.com>
Fri, 13 Feb 2015 16:52:42 +1100
changeset 249943 65a768a861a56c191eeb54b0a7d98cb1d0acb980
parent 249942 8be06da4f051c13bbfefdb164b9197eafb54867b
child 249944 9fb98996305ce5c4a79a511fc1ddba25c6946685
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)
reviewerscajbir, lmandel
bugs1132796
milestone37.0a2
Bug 1132796: Evict data we likely previously read. r=cajbir a=lmandel Also attempt to evict future data, the furthest away from playback position.
dom/media/mediasource/MediaSourceDecoder.cpp
dom/media/mediasource/MediaSourceDecoder.h
dom/media/mediasource/MediaSourceReader.cpp
dom/media/mediasource/MediaSourceReader.h
dom/media/mediasource/TrackBuffer.cpp
dom/media/mediasource/TrackBuffer.h
--- a/dom/media/mediasource/MediaSourceDecoder.cpp
+++ b/dom/media/mediasource/MediaSourceDecoder.cpp
@@ -8,16 +8,17 @@
 #include "prlog.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "mozilla/dom/TimeRanges.h"
 #include "MediaDecoderStateMachine.h"
 #include "MediaSource.h"
 #include "MediaSourceReader.h"
 #include "MediaSourceResource.h"
 #include "MediaSourceUtils.h"
+#include "SourceBufferDecoder.h"
 #include "VideoUtils.h"
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* GetMediaSourceLog();
 extern PRLogModuleInfo* GetMediaSourceAPILog();
 
 #define MSE_DEBUG(...) PR_LOG(GetMediaSourceLog(), PR_LOG_DEBUG, (__VA_ARGS__))
 #define MSE_DEBUGV(...) PR_LOG(GetMediaSourceLog(), PR_LOG_DEBUG+1, (__VA_ARGS__))
@@ -318,9 +319,40 @@ MediaSourceDecoder::IsActiveReader(Media
 
 double
 MediaSourceDecoder::GetDuration()
 {
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   return mMediaSourceDuration;
 }
 
+already_AddRefed<SourceBufferDecoder>
+MediaSourceDecoder::SelectDecoder(int64_t aTarget,
+                                  int64_t aTolerance,
+                                  const nsTArray<nsRefPtr<SourceBufferDecoder>>& aTrackDecoders)
+{
+  ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+
+  // Consider decoders in order of newest to oldest, as a newer decoder
+  // providing a given buffered range is expected to replace an older one.
+  for (int32_t i = aTrackDecoders.Length() - 1; i >= 0; --i) {
+    nsRefPtr<SourceBufferDecoder> newDecoder = aTrackDecoders[i];
+
+    nsRefPtr<dom::TimeRanges> ranges = new dom::TimeRanges();
+    newDecoder->GetBuffered(ranges);
+    if (ranges->Find(double(aTarget) / USECS_PER_S,
+                     double(aTolerance) / USECS_PER_S) == dom::TimeRanges::NoIndex) {
+      MSE_DEBUGV("SelectDecoder(%lld fuzz:%lld) newDecoder=%p (%d/%d) target not in ranges=%s",
+                 aTarget, aTolerance, newDecoder.get(), i+1,
+                 aTrackDecoders.Length(), DumpTimeRanges(ranges).get());
+      continue;
+    }
+
+    return newDecoder.forget();
+  }
+
+  return nullptr;
+}
+
+#undef MSE_DEBUG
+#undef MSE_DEBUGV
+
 } // namespace mozilla
--- a/dom/media/mediasource/MediaSourceDecoder.h
+++ b/dom/media/mediasource/MediaSourceDecoder.h
@@ -77,16 +77,22 @@ public:
 #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);
 
+  // Return a decoder from the set available in aTrackDecoders that has data
+  // available in the range requested by aTarget.
+  already_AddRefed<SourceBufferDecoder> SelectDecoder(int64_t aTarget /* microseconds */,
+                                                      int64_t aTolerance /* microseconds */,
+                                                      const nsTArray<nsRefPtr<SourceBufferDecoder>>& aTrackDecoders);
+
   // Returns a string describing the state of the MediaSource internal
   // buffered data. Used for debugging purposes.
   void GetMozDebugReaderData(nsAString& aString);
 
 private:
   void DoSetMediaSourceDuration(double aDuration);
   void ScheduleDurationChange(double aOldDuration,
                               double aNewDuration,
--- a/dom/media/mediasource/MediaSourceReader.cpp
+++ b/dom/media/mediasource/MediaSourceReader.cpp
@@ -448,37 +448,18 @@ MediaSourceReader::BreakCycles()
   mShutdownTrackBuffers.Clear();
 }
 
 already_AddRefed<SourceBufferDecoder>
 MediaSourceReader::SelectDecoder(int64_t aTarget,
                                  int64_t aTolerance,
                                  const nsTArray<nsRefPtr<SourceBufferDecoder>>& aTrackDecoders)
 {
-  mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
-
-  // Consider decoders in order of newest to oldest, as a newer decoder
-  // providing a given buffered range is expected to replace an older one.
-  for (int32_t i = aTrackDecoders.Length() - 1; i >= 0; --i) {
-    nsRefPtr<SourceBufferDecoder> newDecoder = aTrackDecoders[i];
-
-    nsRefPtr<dom::TimeRanges> ranges = new dom::TimeRanges();
-    newDecoder->GetBuffered(ranges);
-    if (ranges->Find(double(aTarget) / USECS_PER_S,
-                     double(aTolerance) / USECS_PER_S) == dom::TimeRanges::NoIndex) {
-      MSE_DEBUGV("SelectDecoder(%lld fuzz:%lld) newDecoder=%p (%d/%d) target not in ranges=%s",
-                 aTarget, aTolerance, newDecoder.get(), i+1,
-                 aTrackDecoders.Length(), DumpTimeRanges(ranges).get());
-      continue;
-    }
-
-    return newDecoder.forget();
-  }
-
-  return nullptr;
+  return static_cast<MediaSourceDecoder*>(mDecoder)
+      ->SelectDecoder(aTarget, aTolerance, aTrackDecoders);
 }
 
 bool
 MediaSourceReader::HaveData(int64_t aTarget, MediaData::Type aType)
 {
   TrackBuffer* trackBuffer = aType == MediaData::AUDIO_DATA ? mAudioTrack : mVideoTrack;
   MOZ_ASSERT(trackBuffer);
   nsRefPtr<SourceBufferDecoder> decoder = SelectDecoder(aTarget, EOS_FUZZ_US, trackBuffer->Decoders());
--- a/dom/media/mediasource/MediaSourceReader.h
+++ b/dom/media/mediasource/MediaSourceReader.h
@@ -199,18 +199,18 @@ private:
   int64_t GetReaderVideoTime(int64_t aTime) const;
 
   // Will reject the MediaPromise with END_OF_STREAM if mediasource has ended
   // or with WAIT_FOR_DATA otherwise.
   void CheckForWaitOrEndOfStream(MediaData::Type aType, int64_t aTime /* microseconds */);
 
   // Return a decoder from the set available in aTrackDecoders that has data
   // available in the range requested by aTarget.
-  already_AddRefed<SourceBufferDecoder> SelectDecoder(int64_t aTarget,
-                                                      int64_t aTolerance,
+  already_AddRefed<SourceBufferDecoder> SelectDecoder(int64_t aTarget /* microseconds */,
+                                                      int64_t aTolerance /* microseconds */,
                                                       const nsTArray<nsRefPtr<SourceBufferDecoder>>& aTrackDecoders);
   bool HaveData(int64_t aTarget, MediaData::Type aType);
 
   void AttemptSeek();
   bool IsSeeking() { return mPendingSeekTime != -1; }
 
   nsRefPtr<SourceBufferDecoder> mAudioSourceDecoder;
   nsRefPtr<SourceBufferDecoder> mVideoSourceDecoder;
--- a/dom/media/mediasource/TrackBuffer.cpp
+++ b/dom/media/mediasource/TrackBuffer.cpp
@@ -37,16 +37,18 @@ extern PRLogModuleInfo* GetMediaSourceAP
 // 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
 // prevent evicting the current playback point.
 #define MSE_EVICT_THRESHOLD_TIME 2.0
 
 // Time in microsecond under which a timestamp will be considered to be 0.
 #define FUZZ_TIMESTAMP_OFFSET 100000
 
+#define EOS_FUZZ_US 125000
+
 namespace mozilla {
 
 TrackBuffer::TrackBuffer(MediaSourceDecoder* aParentDecoder, const nsACString& aType)
   : mParentDecoder(aParentDecoder)
   , mType(aType)
   , mLastStartTimestamp(0)
   , mLastTimestampOffset(0)
   , mAdjustedTimestamp(0)
@@ -312,98 +314,150 @@ TrackBuffer::EvictData(double aPlaybackT
     totalSize += mDecoders[i]->GetResource()->GetSize();
   }
 
   int64_t toEvict = totalSize - aThreshold;
   if (toEvict <= 0 || mInitializedDecoders.IsEmpty()) {
     return false;
   }
 
-  // Get a list of initialized decoders
+  // Get a list of initialized decoders.
   nsTArray<SourceBufferDecoder*> decoders;
   decoders.AppendElements(mInitializedDecoders);
 
   // First try to evict data before the current play position, starting
-  // with the earliest time.
-  uint32_t i = 0;
-  bool pastCurrentDecoder = true;
-  for (; i < decoders.Length() && toEvict > 0; ++i) {
+  // with the oldest decoder.
+  for (uint32_t i = 0; 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;
-    }
 
-    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");
+    MSE_DEBUG("decoder=%u/%u threshold=%u toEvict=%lld",
+              i, decoders.Length(), aThreshold, toEvict);
 
-    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,
-                                                           playbackOffset);
-        }
+    // 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("evicting some bufferedEnd=%f"
+                "aPlaybackTime=%f time=%f, playbackOffset=%lld size=%lld",
+                buffered->GetEndTime(), aPlaybackTime, time,
+                playbackOffset, decoders[i]->GetResource()->GetSize());
+      if (playbackOffset > 0) {
+        toEvict -= decoders[i]->GetResource()->EvictData(playbackOffset,
+                                                         toEvict);
       }
     }
   }
 
-  // Remove decoders that have no data in them
-  for (i = 0; i < decoders.Length(); ++i) {
+  // Evict all data from decoders we've likely already read from.
+  for (uint32_t i = 0; i < decoders.Length() && toEvict > 0; ++i) {
+    if (mParentDecoder->IsActiveReader(decoders[i]->GetReader())) {
+      break;
+    }
+    if (decoders[i] == mCurrentDecoder) {
+      continue;
+    }
     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())) {
+
+    // Remove data from older decoders than the current one.
+    MSE_DEBUG("evicting all "
+              "bufferedStart=%f bufferedEnd=%f aPlaybackTime=%f size=%lld",
+              buffered->GetStartTime(), buffered->GetEndTime(),
+              aPlaybackTime, decoders[i]->GetResource()->GetSize());
+    toEvict -= decoders[i]->GetResource()->EvictAll();
+  }
+
+  // Evict all data from future decoders, starting furthest away from
+  // current playback position.
+  // We will ignore the currently playing decoder and the one playing after that
+  // in order to ensure we give enough time to the DASH player to re-buffer
+  // as necessary.
+  // TODO: This step should be done using RangeRemoval:
+  // Something like: RangeRemoval(aPlaybackTime + 60s, End);
+
+  // Find the reader currently being played with.
+  SourceBufferDecoder* playingDecoder = nullptr;
+  for (uint32_t i = 0; i < decoders.Length() && toEvict > 0; ++i) {
+    if (mParentDecoder->IsActiveReader(decoders[i]->GetReader())) {
+      playingDecoder = decoders[i];
+      break;
+    }
+  }
+  // Find the next decoder we're likely going to play with.
+  nsRefPtr<SourceBufferDecoder> nextPlayingDecoder = nullptr;
+  if (playingDecoder) {
+    nsRefPtr<dom::TimeRanges> buffered = new dom::TimeRanges();
+    playingDecoder->GetBuffered(buffered);
+    nextPlayingDecoder =
+      mParentDecoder->SelectDecoder(buffered->GetEndTime() * USECS_PER_S + 1,
+                                    EOS_FUZZ_US,
+                                    mInitializedDecoders);
+  }
+
+  // Sort decoders by their start times.
+  decoders.Sort(DecoderSorter());
+
+  for (int32_t i = int32_t(decoders.Length()) - 1; i >= 0 && toEvict > 0; --i) {
+    if (decoders[i] == playingDecoder || decoders[i] == nextPlayingDecoder ||
+        decoders[i] == mCurrentDecoder) {
       continue;
     }
+    nsRefPtr<dom::TimeRanges> buffered = new dom::TimeRanges();
+    decoders[i]->GetBuffered(buffered);
 
-    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]);
-    }
+    MSE_DEBUG("evicting all "
+              "bufferedStart=%f bufferedEnd=%f aPlaybackTime=%f size=%lld",
+              buffered->GetStartTime(), buffered->GetEndTime(),
+              aPlaybackTime, decoders[i]->GetResource()->GetSize());
+    toEvict -= decoders[i]->GetResource()->EvictAll();
   }
 
+  RemoveEmptyDecoders(decoders);
+
   bool evicted = toEvict < (totalSize - aThreshold);
   if (evicted) {
     nsRefPtr<dom::TimeRanges> ranges = new dom::TimeRanges();
     mCurrentDecoder->GetBuffered(ranges);
     *aBufferStartTime = std::max(0.0, ranges->GetStartTime());
   }
 
   return evicted;
 }
 
 void
+TrackBuffer::RemoveEmptyDecoders(nsTArray<mozilla::SourceBufferDecoder*>& aDecoders)
+{
+  ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
+
+  // Remove decoders that have no data in them
+  for (uint32_t i = 0; i < aDecoders.Length(); ++i) {
+    nsRefPtr<dom::TimeRanges> buffered = new dom::TimeRanges();
+    aDecoders[i]->GetBuffered(buffered);
+    MSE_DEBUG("maybe remove empty decoders=%d "
+              "size=%lld start=%f end=%f",
+              i, aDecoders[i]->GetResource()->GetSize(),
+              buffered->GetStartTime(), buffered->GetEndTime());
+    if (aDecoders[i] == mCurrentDecoder ||
+        mParentDecoder->IsActiveReader(aDecoders[i]->GetReader())) {
+      continue;
+    }
+
+    if (aDecoders[i]->GetResource()->GetSize() == 0 ||
+        buffered->GetStartTime() < 0.0 ||
+        buffered->GetEndTime() < 0.0) {
+      MSE_DEBUG("remove empty decoders=%d", i);
+      RemoveDecoder(aDecoders[i]);
+    }
+  }
+}
+
+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);
--- a/dom/media/mediasource/TrackBuffer.h
+++ b/dom/media/mediasource/TrackBuffer.h
@@ -151,16 +151,19 @@ private:
   bool ValidateTrackFormats(const MediaInfo& aInfo);
 
   // Remove aDecoder from mDecoders and dispatch an event to the main thread
   // to clean up the decoder.  If aDecoder was added to
   // mInitializedDecoders, it must have been removed before calling this
   // function.
   void RemoveDecoder(SourceBufferDecoder* aDecoder);
 
+  // Remove all empty decoders from the provided list;
+  void RemoveEmptyDecoders(nsTArray<SourceBufferDecoder*>& aDecoders);
+
   nsAutoPtr<ContainerParser> mParser;
 
   // A task queue using the shared media thread pool.  Used exclusively to
   // initialize (i.e. call ReadMetadata on) decoders as they are created via
   // NewDecoder.
   RefPtr<MediaTaskQueue> mTaskQueue;
 
   // All of the decoders managed by this TrackBuffer.  Access protected by