Bug 1125581: Cache buffered TimeRanges and only recalculate as necessary. r=mattwoodrow
☠☠ backed out by 982c4dbb6927 ☠ ☠
authorJean-Yves Avenard <jyavenard@mozilla.com>
Mon, 26 Jan 2015 11:26:20 +1100
changeset 225919 0e9b37fd47c6bac589ec1b93fa8375cff57068d7
parent 225918 b7b87042f25415ae47e5175004e34b8fb05b4e31
child 225920 d72836297b6b3e4d550e03ed7386b5cfa28138c9
push id54697
push userjyavenard@mozilla.com
push dateTue, 27 Jan 2015 05:13:20 +0000
treeherdermozilla-inbound@0e9b37fd47c6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow
bugs1125581
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 1125581: Cache buffered TimeRanges and only recalculate as necessary. r=mattwoodrow Also change the relationship between SourceBufferResource object and its callee. Data is now required to be removed or added via the SourceBufferDecoder. This fixes a potential race between the time we add data to the resource and the time we retrieve the buffered time ranges.
dom/media/mediasource/SourceBufferDecoder.cpp
dom/media/mediasource/SourceBufferDecoder.h
dom/media/mediasource/SourceBufferResource.h
dom/media/mediasource/TrackBuffer.cpp
--- a/dom/media/mediasource/SourceBufferDecoder.cpp
+++ b/dom/media/mediasource/SourceBufferDecoder.cpp
@@ -38,16 +38,18 @@ SourceBufferDecoder::SourceBufferDecoder
                                          int64_t aTimestampOffset)
   : mResource(aResource)
   , mParentDecoder(aParentDecoder)
   , mReader(nullptr)
   , mTimestampOffset(aTimestampOffset)
   , mMediaDuration(-1)
   , mRealMediaDuration(0)
   , mTrimmedOffset(-1)
+  , mCacheMonitor("SourceBufferDecoder")
+  , mCacheBufferedRangeStale(true)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_COUNT_CTOR(SourceBufferDecoder);
 }
 
 SourceBufferDecoder::~SourceBufferDecoder()
 {
   MOZ_COUNT_DTOR(SourceBufferDecoder);
@@ -197,17 +199,57 @@ void
 SourceBufferDecoder::SetRealMediaDuration(int64_t aDuration)
 {
   mRealMediaDuration = aDuration;
 }
 
 void
 SourceBufferDecoder::Trim(int64_t aDuration)
 {
+  MonitorAutoLock mon(mCacheMonitor);
   mTrimmedOffset = (double)aDuration / USECS_PER_S;
+  mCacheBufferedRangeStale = true;
+}
+
+void SourceBufferDecoder::AppendData(LargeDataBuffer* aData)
+{
+  MonitorAutoLock mon(mCacheMonitor);
+  mCacheBufferedRangeStale = true;
+  int64_t appendOffset = GetResource()->GetLength();
+  GetResource()->AppendData(aData);
+  {
+    MonitorAutoUnlock mon(mCacheMonitor);
+    NotifyDataArrived(reinterpret_cast<const char*>(aData->Elements()),
+                      aData->Length(), appendOffset);
+  }
+}
+
+uint32_t
+SourceBufferDecoder::EvictData(uint64_t aPlaybackOffset, uint32_t aThreshold)
+{
+  MonitorAutoLock mon(mCacheMonitor);
+  mCacheBufferedRangeStale = true;
+  return GetResource()->EvictData(aPlaybackOffset, aThreshold);
+}
+
+// Remove data from resource before the given offset.
+void
+SourceBufferDecoder::EvictBefore(uint64_t aOffset)
+{
+  MonitorAutoLock mon(mCacheMonitor);
+  mCacheBufferedRangeStale = true;
+  GetResource()->EvictBefore(aOffset);
+}
+
+// Remove all data from the attached resource
+uint32_t SourceBufferDecoder::EvictAll()
+{
+  MonitorAutoLock mon(mCacheMonitor);
+  mCacheBufferedRangeStale = true;
+  return GetResource()->EvictAll();
 }
 
 void
 SourceBufferDecoder::UpdateEstimatedMediaDuration(int64_t aDuration)
 {
   MSE_DEBUG("SourceBufferDecoder(%p)::UpdateEstimatedMediaDuration UNIMPLEMENTED", this);
 }
 
@@ -236,29 +278,59 @@ SourceBufferDecoder::NotifyDataArrived(c
 
   // XXX: Params make no sense to parent decoder as it relates to a
   // specific SourceBufferDecoder's data stream.  Pass bogus values here to
   // force parent decoder's state machine to recompute end time for
   // infinite length media.
   mParentDecoder->NotifyDataArrived(nullptr, 0, 0);
 }
 
+void
+SourceBufferDecoder::BuildTimeRangesFromCache(dom::TimeRanges* aBuffered)
+{
+  for (size_t i = 0; i < mCacheBufferedRanges.Length(); i++) {
+    aBuffered->Add(mCacheBufferedRanges[i].mStart, mCacheBufferedRanges[i].mEnd);
+  }
+}
+
+void
+SourceBufferDecoder::BuildCacheFromTimeRanges(dom::TimeRanges* aBuffered)
+{
+  mCacheBufferedRanges.Clear();
+  for (uint32_t i = 0; i < aBuffered->Length(); i++) {
+    double start;
+    aBuffered->Start(i, &start);
+    double end;
+    aBuffered->End(i, &end);
+    mCacheBufferedRanges.AppendElement(TimeRange(start, end));
+  }
+  mCacheBufferedRangeStale = false;
+}
+
 nsresult
 SourceBufferDecoder::GetBuffered(dom::TimeRanges* aBuffered)
 {
+  MonitorAutoLock mon(mCacheMonitor);
+
+  if (!mCacheBufferedRangeStale) {
+    BuildTimeRangesFromCache(aBuffered);
+    return NS_OK;
+  }
   nsresult rv = mReader->GetBuffered(aBuffered);
   if (NS_FAILED(rv)) {
     return rv;
   }
   if (!WasTrimmed()) {
+    BuildCacheFromTimeRanges(aBuffered);
     return NS_OK;
   }
   nsRefPtr<dom::TimeRanges> tr = new dom::TimeRanges();
   tr->Add(0, mTrimmedOffset);
   aBuffered->Intersection(tr);
+  BuildCacheFromTimeRanges(aBuffered);
   return NS_OK;
 }
 
 int64_t
 SourceBufferDecoder::ConvertToByteOffset(double aTime)
 {
   int64_t readerOffset = mReader->GetEvictionOffset(aTime);
   if (readerOffset >= 0) {
--- a/dom/media/mediasource/SourceBufferDecoder.h
+++ b/dom/media/mediasource/SourceBufferDecoder.h
@@ -9,34 +9,34 @@
 
 #include "AbstractMediaDecoder.h"
 #include "MediaDecoderReader.h"
 #include "SourceBufferResource.h"
 #include "mozilla/Attributes.h"
 #ifdef MOZ_EME
 #include "mozilla/CDMProxy.h"
 #endif
+#include "mozilla/Monitor.h"
 #include "mozilla/ReentrantMonitor.h"
 
 namespace mozilla {
 
 class MediaResource;
 class MediaDecoderReader;
+class LargeDataBuffer;
 
 namespace dom {
 
 class TimeRanges;
 
 } // namespace dom
 
 class SourceBufferDecoder MOZ_FINAL : public AbstractMediaDecoder
 {
 public:
-  // This class holds a weak pointer to MediaResource.  It's the responsibility
-  // of the caller to manage the memory of the MediaResource object.
   SourceBufferDecoder(MediaResource* aResource, AbstractMediaDecoder* aParentDecoder,
                       int64_t aTimestampOffset /* microseconds */);
 
   NS_DECL_THREADSAFE_ISUPPORTS
 
   virtual bool IsMediaSeekable() MOZ_FINAL MOZ_OVERRIDE;
   virtual bool IsShutdown() const MOZ_FINAL MOZ_OVERRIDE;
   virtual bool IsTransportSeekable() MOZ_FINAL MOZ_OVERRIDE;
@@ -66,16 +66,33 @@ public:
   virtual bool HasInitializationData() MOZ_FINAL MOZ_OVERRIDE;
 
   // SourceBufferResource specific interface below.
 
   // Warning: this mirrors GetBuffered in MediaDecoder, but this class's base is
   // AbstractMediaDecoder, which does not supply this interface.
   nsresult GetBuffered(dom::TimeRanges* aBuffered);
 
+  // This mirrors SourceBufferResource data management, while holding the lock
+  // to manage mCacheBuffered.
+  void AppendData(LargeDataBuffer* aData);
+  void Ended()
+  {
+    GetResource()->Ended();
+  }
+  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();
+  int64_t GetSize()
+  {
+    return GetResource()->GetSize();
+  }
+
   void SetReader(MediaDecoderReader* aReader)
   {
     MOZ_ASSERT(!mReader);
     mReader = aReader;
   }
 
   MediaDecoderReader* GetReader()
   {
@@ -138,16 +155,18 @@ public:
   void SetRealMediaDuration(int64_t aDuration);
   int64_t GetRealMediaDuration()
   {
     return mRealMediaDuration;
   }
 
 private:
   virtual ~SourceBufferDecoder();
+  void BuildTimeRangesFromCache(dom::TimeRanges* aBuffered);
+  void BuildCacheFromTimeRanges(dom::TimeRanges* aBuffered);
 
   // Our TrackBuffer's task queue, this is only non-null during initialization.
   RefPtr<MediaTaskQueue> mTaskQueue;
 
   nsRefPtr<MediaResource> mResource;
 
   AbstractMediaDecoder* mParentDecoder;
   nsRefPtr<MediaDecoderReader> mReader;
@@ -155,16 +174,33 @@ private:
   int64_t mTimestampOffset;
   // mMediaDuration contains the apparent buffer duration, excluding trimmed data.
   int64_t mMediaDuration;
   // mRealMediaDuration contains the real buffer duration, including trimmed data.
   int64_t mRealMediaDuration;
   // in seconds
   double mTrimmedOffset;
 
+  Monitor mCacheMonitor;
+  // We use our own version of TimeRanges.
+  // dom::TimeRanges is normalized, and keep it so. We don't need to normalize
+  // our cache.
+  // dom::TimeRanges is also marked as not thread-safe and will error when
+  // accessed from a different thread that the one that created it.
+  struct TimeRange
+  {
+    TimeRange(double aStart, double aEnd)
+      : mStart(aStart),
+        mEnd(aEnd) {}
+    double mStart;
+    double mEnd;
+  };
+  nsAutoTArray<TimeRange,4> mCacheBufferedRanges;
+  bool mCacheBufferedRangeStale;
+
 #ifdef MOZ_EME
   nsRefPtr<CDMProxy> mCDMProxy;
 #endif
 };
 
 } // namespace mozilla
 
 #endif /* MOZILLA_SOURCEBUFFERDECODER_H_ */
--- a/dom/media/mediasource/SourceBufferResource.h
+++ b/dom/media/mediasource/SourceBufferResource.h
@@ -41,16 +41,18 @@ class LargeDataBuffer;
 namespace dom {
 
 class SourceBuffer;
 
 }  // namespace dom
 
 class SourceBufferResource MOZ_FINAL : public MediaResource
 {
+  // When used in combination with a SourceBufferDecoder, data management is to
+  // be handled by owning SourceBufferDecoder.
 public:
   explicit SourceBufferResource(const nsACString& aType);
   virtual nsresult Close() MOZ_OVERRIDE;
   virtual void Suspend(bool aCloseImmediately) MOZ_OVERRIDE { UNIMPLEMENTED(); }
   virtual void Resume() MOZ_OVERRIDE { UNIMPLEMENTED(); }
   virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal() MOZ_OVERRIDE { UNIMPLEMENTED(); return nullptr; }
   virtual already_AddRefed<MediaResource> CloneData(MediaDecoder* aDecoder) MOZ_OVERRIDE { UNIMPLEMENTED(); return nullptr; }
   virtual void SetReadMode(MediaCacheStream::ReadMode aMode) MOZ_OVERRIDE { UNIMPLEMENTED(); }
@@ -106,17 +108,18 @@ public:
   }
 
   virtual size_t SizeOfIncludingThis(
                       MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
   {
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
-  // Used by SourceBuffer.
+  // Used by SourceBuffer, and only to be called by SourceBufferDecoder owner if
+  // present.
   void AppendData(LargeDataBuffer* aData);
   void Ended();
   // Remove data from resource if it holds more than the threshold
   // number of bytes. Returns amount evicted.
   uint32_t EvictData(uint64_t aPlaybackOffset, uint32_t aThreshold);
 
   // Remove data from resource before the given offset.
   void EvictBefore(uint64_t aOffset);
--- a/dom/media/mediasource/TrackBuffer.cpp
+++ b/dom/media/mediasource/TrackBuffer.cpp
@@ -202,23 +202,18 @@ TrackBuffer::AppendData(LargeDataBuffer*
 bool
 TrackBuffer::AppendDataToCurrentResource(LargeDataBuffer* aData, uint32_t aDuration)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!mCurrentDecoder) {
     return false;
   }
 
-  SourceBufferResource* resource = mCurrentDecoder->GetResource();
-  int64_t appendOffset = resource->GetLength();
-  resource->AppendData(aData);
   mCurrentDecoder->SetRealMediaDuration(mCurrentDecoder->GetRealMediaDuration() + aDuration);
-  // XXX: For future reference: NDA call must run on the main thread.
-  mCurrentDecoder->NotifyDataArrived(reinterpret_cast<const char*>(aData->Elements()),
-                                     aData->Length(), appendOffset);
+  mCurrentDecoder->AppendData(aData);
   mParentDecoder->NotifyBytesDownloaded();
   mParentDecoder->NotifyTimeRangesChanged();
 
   return true;
 }
 
 class DecoderSorter
 {
@@ -255,17 +250,17 @@ TrackBuffer::EvictData(double aPlaybackT
   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();
+    totalSize += mDecoders[i]->GetSize();
   }
 
   int64_t toEvict = totalSize - aThreshold;
   if (toEvict <= 0 || mInitializedDecoders.IsEmpty()) {
     return false;
   }
 
   // Get a list of initialized decoders
@@ -292,51 +287,51 @@ TrackBuffer::EvictData(double aPlaybackT
 
     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();
+                aPlaybackTime, decoders[i]->GetSize());
+      toEvict -= decoders[i]->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());
+                  playbackOffset, decoders[i]->GetSize());
         if (playbackOffset > 0) {
-          toEvict -= decoders[i]->GetResource()->EvictData(playbackOffset,
+          toEvict -= decoders[i]->EvictData(playbackOffset,
                                                            toEvict);
         }
       }
     }
   }
 
   // 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(),
+              this, i, decoders[i]->GetSize(),
               buffered->GetStartTime(), buffered->GetEndTime());
     if (decoders[i] == mCurrentDecoder
         || mParentDecoder->IsActiveReader(decoders[i]->GetReader())) {
       continue;
     }
 
-    if (decoders[i]->GetResource()->GetSize() == 0 ||
+    if (decoders[i]->GetSize() == 0 ||
         buffered->GetStartTime() < 0.0 ||
         buffered->GetEndTime() < 0.0) {
       MSE_DEBUG("TrackBuffer(%p):EvictData remove empty decoders=%d", this, i);
       RemoveDecoder(decoders[i]);
     }
   }
 
   bool evicted = toEvict < (totalSize - aThreshold);
@@ -353,17 +348,17 @@ 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);
+      mInitializedDecoders[i]->EvictBefore(endOffset);
     }
   }
 }
 
 double
 TrackBuffer::Buffered(dom::TimeRanges* aRanges)
 {
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
@@ -540,27 +535,27 @@ TrackBuffer::RegisterDecoder(SourceBuffe
   return true;
 }
 
 void
 TrackBuffer::DiscardDecoder()
 {
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
   if (mCurrentDecoder) {
-    mCurrentDecoder->GetResource()->Ended();
+    mCurrentDecoder->Ended();
   }
   mCurrentDecoder = nullptr;
 }
 
 void
 TrackBuffer::EndCurrentDecoder()
 {
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
   if (mCurrentDecoder) {
-    mCurrentDecoder->GetResource()->Ended();
+    mCurrentDecoder->Ended();
   }
 }
 
 void
 TrackBuffer::Detach()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mCurrentDecoder) {
@@ -746,18 +741,18 @@ TrackBuffer::RangeRemoval(int64_t aStart
   nsTArray<SourceBufferDecoder*> decoders;
   decoders.AppendElements(mInitializedDecoders);
 
   // Only trimming existing buffers.
   for (size_t i = 0; i < decoders.Length(); ++i) {
     decoders[i]->Trim(aStart);
     if (aStart <= buffered->GetStartTime()) {
       // We've completely emptied it, can clear the data.
-      int64_t size = decoders[i]->GetResource()->GetSize();
-      decoders[i]->GetResource()->EvictData(size, size);
+      int64_t size = decoders[i]->GetSize();
+      decoders[i]->EvictData(size, size);
       if (decoders[i] == mCurrentDecoder ||
           mParentDecoder->IsActiveReader(decoders[i]->GetReader())) {
         continue;
       }
       MSE_DEBUG("TrackBuffer(%p):RangeRemoval remove empty decoders=%d", this, i);
       RemoveDecoder(decoders[i]);
     }
   }