Bug 1132796: Evict data we likely previously read. r=cajbir
authorJean-Yves Avenard <jyavenard@mozilla.com>
Fri, 13 Feb 2015 16:52:42 +1100
changeset 256543 dad2b32454574aabe1b66d5ee36ecd18a3bd3de7
parent 256542 355f5e2dee58fb204d01701d4e3ed08b599239a6
child 256544 9fc9d2e35916ccaadd06c98095808361e7b2ed5a
push id4610
push userjlund@mozilla.com
push dateMon, 30 Mar 2015 18:32:55 +0000
treeherdermozilla-beta@4df54044d9ef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscajbir
bugs1132796
milestone38.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 1132796: Evict data we likely previously read. r=cajbir 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();
 
 #define MSE_DEBUG(arg, ...) PR_LOG(GetMediaSourceLog(), PR_LOG_DEBUG, ("MediaSourceDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
 #define MSE_DEBUGV(arg, ...) PR_LOG(GetMediaSourceLog(), PR_LOG_DEBUG + 1, ("MediaSourceDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
 #else
@@ -315,12 +316,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
@@ -458,37 +458,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
@@ -201,18 +201,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
@@ -32,16 +32,18 @@ extern PRLogModuleInfo* GetMediaSourceLo
 // 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)
@@ -309,98 +311,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("decoder=%u/%u threshold=%u "
-              "toEvict=%lld current=%s pastCurrent=%s",
-              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("evicting all before start "
-                "bufferedStart=%f bufferedEnd=%f aPlaybackTime=%f size=%lld",
-                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("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,
-                                                           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("maybe remove empty decoders=%d "
-              "size=%lld start=%f end=%f",
-              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("remove empty decoders=%d", 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("decoder=%u offset=%lld",
--- 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