Bug 820588 - Aggregate and forward statistics in DASH decoder classes r=cpearce
authorSteve Workman <sworkman@mozilla.com>
Thu, 13 Dec 2012 11:42:45 -0800
changeset 115966 c926d32f8ace
parent 115965 5b6583b20a80
child 115967 02a0cad51a22
push id24034
push useremorley@mozilla.com
push dateFri, 14 Dec 2012 15:28:57 +0000
treeherdermozilla-central@50d8f411d305 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce
bugs820588
milestone20.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 820588 - Aggregate and forward statistics in DASH decoder classes r=cpearce
content/media/MediaDecoder.cpp
content/media/MediaDecoder.h
content/media/MediaDecoderStateMachine.cpp
content/media/MediaResource.cpp
content/media/MediaResource.h
content/media/dash/DASHDecoder.cpp
content/media/dash/DASHDecoder.h
content/media/dash/DASHRepDecoder.h
--- a/content/media/MediaDecoder.cpp
+++ b/content/media/MediaDecoder.cpp
@@ -920,16 +920,20 @@ void MediaDecoder::NotifySuspendedStatus
     mOwner->NotifySuspendedByCache(suspended);
     UpdateReadyStateForData();
   }
 }
 
 void MediaDecoder::NotifyBytesDownloaded()
 {
   MOZ_ASSERT(NS_IsMainThread());
+  {
+    ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+    UpdatePlaybackRate();
+  }
   UpdateReadyStateForData();
   Progress(false);
 }
 
 void MediaDecoder::NotifyDownloadEnded(nsresult aStatus)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
--- a/content/media/MediaDecoder.h
+++ b/content/media/MediaDecoder.h
@@ -613,20 +613,34 @@ public:
   void StopProgressUpdates();
 
   // Allow updating the bytes downloaded for progress notifications. Must
   // be called with the decoder monitor held.
   void StartProgressUpdates();
 
   // Something has changed that could affect the computed playback rate,
   // so recompute it. The monitor must be held.
-  void UpdatePlaybackRate();
+  virtual void UpdatePlaybackRate();
+
+  // Used to estimate rates of data passing through the decoder's channel.
+  // Records activity stopping on the channel. The monitor must be held.
+  virtual void NotifyPlaybackStarted() {
+    GetReentrantMonitor().AssertCurrentThreadIn();
+    mPlaybackStatistics.Start();
+  }
+
+  // Used to estimate rates of data passing through the decoder's channel.
+  // Records activity stopping on the channel. The monitor must be held.
+  virtual void NotifyPlaybackStopped() {
+    GetReentrantMonitor().AssertCurrentThreadIn();
+    mPlaybackStatistics.Stop();
+  }
 
   // The actual playback rate computation. The monitor must be held.
-  double ComputePlaybackRate(bool* aReliable);
+  virtual double ComputePlaybackRate(bool* aReliable);
 
   // Returns true if we can play the entire media through without stopping
   // to buffer, given the current download and playback rates.
   bool CanPlayThrough();
 
   // Make the decoder state machine update the playback position. Called by
   // the reader on the decoder thread (Assertions for this checked by
   // mDecoderStateMachine). This must be called with the decode monitor
@@ -782,17 +796,17 @@ public:
     // a short time).
     bool mPlaybackRateReliable;
   };
 
   // Return statistics. This is used for progress events and other things.
   // This can be called from any thread. It's only a snapshot of the
   // current state, since other threads might be changing the state
   // at any time.
-  Statistics GetStatistics();
+  virtual Statistics GetStatistics();
 
   // Frame decoding/painting related performance counters.
   // Threadsafe.
   class FrameStatistics {
   public:
 
     FrameStatistics() :
         mReentrantMonitor("MediaDecoder::FrameStats"),
@@ -857,17 +871,17 @@ public:
     uint32_t mPresentedFrames;
   };
 
   // Return the frame decode/paint related statistics.
   FrameStatistics& GetFrameStatistics() { return mFrameStats; }
 
   // Increments the parsed and decoded frame counters by the passed in counts.
   // Can be called on any thread.
-  virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded) MOZ_FINAL MOZ_OVERRIDE
+  virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded) MOZ_OVERRIDE
   {
     GetFrameStatistics().NotifyDecodedFrames(aParsed, aDecoded);
   }
 
   /******
    * The following members should be accessed with the decoder lock held.
    ******/
 
@@ -876,20 +890,16 @@ public:
   // seek operations, but it's updated at the end when we start playing
   // back again.
   int64_t mDecoderPosition;
   // Current playback position in the stream. This is (approximately)
   // where we're up to playing back the stream. This is not adjusted
   // during decoder seek operations, but it's updated at the end when we
   // start playing back again.
   int64_t mPlaybackPosition;
-  // Data needed to estimate playback data rate. The timeline used for
-  // this estimate is "decode time" (where the "current time" is the
-  // time of the last decoded video frame).
-  MediaChannelStatistics mPlaybackStatistics;
 
   // The current playback position of the media resource in units of
   // seconds. This is updated approximately at the framerate of the
   // video (if it is a video) or the callback period of the audio.
   // It is read and written from the main thread only.
   double mCurrentTime;
 
   // Volume that playback should start at.  0.0 = muted. 1.0 = full
@@ -1045,16 +1055,21 @@ protected:
 
   // Time that data was last read from the media resource. Used for
   // computing if the download has stalled and to rate limit progress events
   // when data is arriving slower than PROGRESS_MS. A value of null indicates
   // that a stall event has already fired and not to fire another one until
   // more data is received. Read/Write from the main thread only.
   TimeStamp mDataTime;
 
+  // Data needed to estimate playback data rate. The timeline used for
+  // this estimate is "decode time" (where the "current time" is the
+  // time of the last decoded video frame).
+  MediaChannelStatistics mPlaybackStatistics;
+
   // The framebuffer size to use for audioavailable events.
   uint32_t mFrameBufferLength;
 
   // True when our media stream has been pinned. We pin the stream
   // while seeking.
   bool mPinnedForSeek;
 
   // True if the decoder is being shutdown. At this point all events that
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -1217,17 +1217,17 @@ nsresult MediaDecoderStateMachine::Init(
 void MediaDecoderStateMachine::StopPlayback()
 {
   LOG(PR_LOG_DEBUG, ("%p StopPlayback()", mDecoder.get()));
 
   NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
                "Should be on state machine thread or the decoder thread.");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
-  mDecoder->mPlaybackStatistics.Stop(TimeStamp::Now());
+  mDecoder->NotifyPlaybackStopped();
 
   if (IsPlaying()) {
     mPlayDuration += DurationToUsecs(TimeStamp::Now() - mPlayStartTime);
     mPlayStartTime = TimeStamp();
   }
   // Notify the audio thread, so that it notices that we've stopped playing,
   // so it can pause audio playback.
   mDecoder->GetReentrantMonitor().NotifyAll();
@@ -1236,17 +1236,17 @@ void MediaDecoderStateMachine::StopPlayb
 
 void MediaDecoderStateMachine::StartPlayback()
 {
   LOG(PR_LOG_DEBUG, ("%p StartPlayback()", mDecoder.get()));
 
   NS_ASSERTION(!IsPlaying(), "Shouldn't be playing when StartPlayback() is called");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
   LOG(PR_LOG_DEBUG, ("%p StartPlayback", mDecoder.get()));
-  mDecoder->mPlaybackStatistics.Start(TimeStamp::Now());
+  mDecoder->NotifyPlaybackStarted();
   mPlayStartTime = TimeStamp::Now();
 
   NS_ASSERTION(IsPlaying(), "Should report playing by end of StartPlayback()");
   if (NS_FAILED(StartAudioThread())) {
     NS_WARNING("Failed to create audio thread"); 
   }
   mDecoder->GetReentrantMonitor().NotifyAll();
 }
--- a/content/media/MediaResource.cpp
+++ b/content/media/MediaResource.cpp
@@ -320,17 +320,17 @@ ChannelMediaResource::OnStartRequest(nsI
     rv = cc->IsFromCache(&fromCache);
     if (NS_SUCCEEDED(rv) && !fromCache) {
       cc->SetCacheAsFile(true);
     }
   }
 
   {
     MutexAutoLock lock(mLock);
-    mChannelStatistics.Start(TimeStamp::Now());
+    mChannelStatistics->Start();
   }
 
   mReopenOnError = false;
   // If we are seeking to get metadata, because we are playing an OGG file,
   // ignore if the channel gets closed without us suspending it explicitly. We
   // don't want to tell the element that the download has finished whereas we
   // just happended to have reached the end of the media while seeking.
   mIgnoreClose = mSeekingForMetadata;
@@ -401,17 +401,17 @@ nsresult
 ChannelMediaResource::OnStopRequest(nsIRequest* aRequest, nsresult aStatus)
 {
   NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
   NS_ASSERTION(mSuspendCount == 0,
                "How can OnStopRequest fire while we're suspended?");
 
   {
     MutexAutoLock lock(mLock);
-    mChannelStatistics.Stop(TimeStamp::Now());
+    mChannelStatistics->Stop();
   }
 
   // If we were loading a byte range, notify decoder and return.
   // Skip this for unterminated byte range requests, e.g. seeking for whole
   // file downloads.
   if (mByteRangeDownloads) {
     mDecoder->NotifyDownloadEnded(aStatus);
     return NS_OK;
@@ -507,17 +507,17 @@ nsresult
 ChannelMediaResource::OnDataAvailable(nsIRequest* aRequest,
                                       nsIInputStream* aStream,
                                       uint32_t aCount)
 {
   NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
 
   {
     MutexAutoLock lock(mLock);
-    mChannelStatistics.AddBytes(aCount);
+    mChannelStatistics->AddBytes(aCount);
   }
 
   CopySegmentClosure closure;
   nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
   if (secMan && mChannel) {
     secMan->GetChannelPrincipal(mChannel, getter_AddRefs(closure.mPrincipal));
   }
   closure.mResource = this;
@@ -578,16 +578,20 @@ ChannelMediaResource::CancelByteRangeOpe
     mCacheStream.NotifyDownloadCancelled();
   }
 }
 
 nsresult ChannelMediaResource::Open(nsIStreamListener **aStreamListener)
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
+  if (!mChannelStatistics) {
+    mChannelStatistics = new MediaChannelStatistics();
+  }
+
   nsresult rv = mCacheStream.Init();
   if (NS_FAILED(rv))
     return rv;
   NS_ASSERTION(mOffset == 0, "Who set mOffset already?");
 
   if (!mChannel) {
     // When we're a clone, the decoder might ask us to Open even though
     // we haven't established an mChannel (because we might not need one)
@@ -726,28 +730,28 @@ MediaResource* ChannelMediaResource::Clo
     // we don't have a channel. If the cache needs to read data from the clone
     // it will call CacheClientResume (or CacheClientSeek with aResume true)
     // which will recreate the channel. This way, if all of the media data
     // is already in the cache we don't create an unneccesary HTTP channel
     // and perform a useless HTTP transaction.
     resource->mSuspendCount = 1;
     resource->mCacheStream.InitAsClone(&mCacheStream);
     resource->mChannelStatistics = mChannelStatistics;
-    resource->mChannelStatistics.Stop(TimeStamp::Now());
+    resource->mChannelStatistics->Stop();
   }
   return resource;
 }
 
 void ChannelMediaResource::CloseChannel()
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
   {
     MutexAutoLock lock(mLock);
-    mChannelStatistics.Stop(TimeStamp::Now());
+    mChannelStatistics->Stop();
   }
 
   if (mListener) {
     mListener->Revoke();
     mListener = nullptr;
   }
 
   if (mChannel) {
@@ -839,17 +843,17 @@ void ChannelMediaResource::Suspend(bool 
     if (aCloseImmediately && mCacheStream.IsTransportSeekable()) {
       // Kill off our channel right now, but don't tell anyone about it.
       mIgnoreClose = true;
       CloseChannel();
       element->DownloadSuspended();
     } else if (mSuspendCount == 0) {
       {
         MutexAutoLock lock(mLock);
-        mChannelStatistics.Stop(TimeStamp::Now());
+        mChannelStatistics->Stop();
       }
       PossiblySuspend();
       element->DownloadSuspended();
     }
   }
 
   ++mSuspendCount;
 }
@@ -872,17 +876,17 @@ void ChannelMediaResource::Resume()
 
   NS_ASSERTION(mSuspendCount > 0, "Resume without previous Suspend!");
   --mSuspendCount;
   if (mSuspendCount == 0) {
     if (mChannel) {
       // Just wake up our existing channel
       {
         MutexAutoLock lock(mLock);
-        mChannelStatistics.Start(TimeStamp::Now());
+        mChannelStatistics->Start();
       }
       // if an error occurs after Resume, assume it's because the server
       // timed out the connection and we should reopen it.
       mReopenOnError = true;
       PossiblyResume();
       element->DownloadResumed();
     } else {
       int64_t totalLength = mCacheStream.GetLength();
@@ -1214,17 +1218,17 @@ ChannelMediaResource::Unpin()
 {
   mCacheStream.Unpin();
 }
 
 double
 ChannelMediaResource::GetDownloadRate(bool* aIsReliable)
 {
   MutexAutoLock lock(mLock);
-  return mChannelStatistics.GetRate(TimeStamp::Now(), aIsReliable);
+  return mChannelStatistics->GetRate(aIsReliable);
 }
 
 int64_t
 ChannelMediaResource::GetLength()
 {
   return mCacheStream.GetLength();
 }
 
--- a/content/media/MediaResource.h
+++ b/content/media/MediaResource.h
@@ -41,32 +41,35 @@ class MediaDecoder;
  * channel is active.
  * 
  * All methods take "now" as a parameter so the user of this class can
  * control the timeline used.
  */
 class MediaChannelStatistics {
 public:
   MediaChannelStatistics() { Reset(); }
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaChannelStatistics)
+
   void Reset() {
     mLastStartTime = TimeStamp();
     mAccumulatedTime = TimeDuration(0);
     mAccumulatedBytes = 0;
     mIsStarted = false;
   }
-  void Start(TimeStamp aNow) {
+  void Start() {
     if (mIsStarted)
       return;
-    mLastStartTime = aNow;
+    mLastStartTime = TimeStamp::Now();
     mIsStarted = true;
   }
-  void Stop(TimeStamp aNow) {
+  void Stop() {
     if (!mIsStarted)
       return;
-    mAccumulatedTime += aNow - mLastStartTime;
+    mAccumulatedTime += TimeStamp::Now() - mLastStartTime;
     mIsStarted = false;
   }
   void AddBytes(int64_t aBytes) {
     if (!mIsStarted) {
       // ignore this data, it may be related to seeking or some other
       // operation we don't care about
       return;
     }
@@ -74,20 +77,20 @@ public:
   }
   double GetRateAtLastStop(bool* aReliable) {
     double seconds = mAccumulatedTime.ToSeconds();
     *aReliable = seconds >= 1.0;
     if (seconds <= 0.0)
       return 0.0;
     return static_cast<double>(mAccumulatedBytes)/seconds;
   }
-  double GetRate(TimeStamp aNow, bool* aReliable) {
+  double GetRate(bool* aReliable) {
     TimeDuration time = mAccumulatedTime;
     if (mIsStarted) {
-      time += aNow - mLastStartTime;
+      time += TimeStamp::Now() - mLastStartTime;
     }
     double seconds = time.ToSeconds();
     *aReliable = seconds >= 3.0;
     if (seconds <= 0.0)
       return 0.0;
     return static_cast<double>(mAccumulatedBytes)/seconds;
   }
 private:
@@ -208,16 +211,18 @@ public:
   // because the underlying connection has been lost, or this resource
   // just can't be safely cloned). If this returns true, CloneData could
   // still fail. If this returns false, CloneData should not be called.
   virtual bool CanClone() { return false; }
   // Create a new stream of the same type that refers to the same URI
   // with a new channel. Any cached data associated with the original
   // stream should be accessible in the new stream too.
   virtual MediaResource* CloneData(MediaDecoder* aDecoder) = 0;
+  // Set statistics to be recorded to the object passed in.
+  virtual void RecordStatisticsTo(MediaChannelStatistics *aStatistics) { }
 
   // These methods are called off the main thread.
   // The mode is initially MODE_PLAYBACK.
   virtual void SetReadMode(MediaCacheStream::ReadMode aMode) = 0;
   // This is the client's estimate of the playback rate assuming
   // the media plays continuously. The cache can't guess this itself
   // because it doesn't know when the decoder was paused, buffering, etc.
   virtual void SetPlaybackRate(uint32_t aBytesPerSecond) = 0;
@@ -454,16 +459,25 @@ public:
   virtual nsresult Close();
   virtual void     Suspend(bool aCloseImmediately);
   virtual void     Resume();
   virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
   // Return true if the stream has been closed.
   bool IsClosed() const { return mCacheStream.IsClosed(); }
   virtual bool     CanClone();
   virtual MediaResource* CloneData(MediaDecoder* aDecoder);
+  // Set statistics to be recorded to the object passed in. If not called,
+  // |ChannelMediaResource| will create it's own statistics objects in |Open|.
+  void RecordStatisticsTo(MediaChannelStatistics *aStatistics) MOZ_OVERRIDE {
+    NS_ASSERTION(aStatistics, "Statistics param cannot be null!");
+    MutexAutoLock lock(mLock);
+    if (!mChannelStatistics) {
+      mChannelStatistics = aStatistics;
+    }
+  }
   virtual nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount);
   virtual void     EnsureCacheUpToDate();
 
   // Other thread
   virtual void     SetReadMode(MediaCacheStream::ReadMode aMode);
   virtual void     SetPlaybackRate(uint32_t aBytesPerSecond);
   virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes);
   virtual nsresult Seek(int32_t aWhence, int64_t aOffset);
@@ -562,17 +576,17 @@ protected:
   // channel.
   bool               mIgnoreClose;
 
   // Any thread access
   MediaCacheStream mCacheStream;
 
   // This lock protects mChannelStatistics
   Mutex               mLock;
-  MediaChannelStatistics mChannelStatistics;
+  nsRefPtr<MediaChannelStatistics> mChannelStatistics;
 
   // True if we couldn't suspend the stream and we therefore don't want
   // to resume later. This is usually due to the channel not being in the
   // isPending state at the time of the suspend request.
   bool mIgnoreResume;
 
   // True if we are seeking to get the real duration of the file.
   bool mSeekingForMetadata;
--- a/content/media/dash/DASHDecoder.cpp
+++ b/content/media/dash/DASHDecoder.cpp
@@ -151,19 +151,22 @@ DASHDecoder::DASHDecoder() :
   mDASHReader(nullptr),
   mVideoAdaptSetIdx(-1),
   mAudioRepDecoderIdx(-1),
   mVideoRepDecoderIdx(-1),
   mAudioSubsegmentIdx(0),
   mVideoSubsegmentIdx(0),
   mAudioMetadataReadCount(0),
   mVideoMetadataReadCount(0),
-  mSeeking(false)
+  mSeeking(false),
+  mStatisticsLock("DASHDecoder.mStatisticsLock")
 {
   MOZ_COUNT_CTOR(DASHDecoder);
+  mAudioStatistics = new MediaChannelStatistics();
+  mVideoStatistics = new MediaChannelStatistics();
 }
 
 DASHDecoder::~DASHDecoder()
 {
   MOZ_COUNT_DTOR(DASHDecoder);
 }
 
 MediaDecoderStateMachine*
@@ -531,16 +534,17 @@ DASHDecoder::CreateAudioSubResource(nsIU
   nsresult rv = CreateSubChannel(aUrl, getter_AddRefs(channel));
   NS_ENSURE_SUCCESS(rv, nullptr);
 
   // Create resource for representation.
   MediaResource* audioResource
     = MediaResource::Create(aAudioDecoder, channel);
   NS_ENSURE_TRUE(audioResource, nullptr);
 
+  audioResource->RecordStatisticsTo(mAudioStatistics);
   return audioResource;
 }
 
 MediaResource*
 DASHDecoder::CreateVideoSubResource(nsIURI* aUrl,
                                     MediaDecoder* aVideoDecoder)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
@@ -552,16 +556,17 @@ DASHDecoder::CreateVideoSubResource(nsIU
   nsresult rv = CreateSubChannel(aUrl, getter_AddRefs(channel));
   NS_ENSURE_SUCCESS(rv, nullptr);
 
   // Create resource for representation.
   MediaResource* videoResource
     = MediaResource::Create(aVideoDecoder, channel);
   NS_ENSURE_TRUE(videoResource, nullptr);
 
+  videoResource->RecordStatisticsTo(mVideoStatistics);
   return videoResource;
 }
 
 nsresult
 DASHDecoder::CreateSubChannel(nsIURI* aUrl, nsIChannel** aChannel)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   NS_ENSURE_ARG(aUrl);
@@ -905,29 +910,33 @@ DASHDecoder::PossiblySwitchDecoder(DASHR
   NS_ASSERTION((uint32_t)mVideoRepDecoderIdx < mVideoRepDecoders.Length(),
                "Index for video decoder is out of bounds!");
   NS_ASSERTION((uint32_t)mVideoSubsegmentIdx < VideoRepDecoder()->GetNumDataByteRanges(),
                "Can't switch to a byte range out of bounds.");
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
 
   // Now, determine if and which decoder to switch to.
   // XXX This download rate is averaged over time, and only refers to the bytes
-  // downloaded for the given decoder. A running average would be better, and
+  // downloaded for the video decoder. A running average would be better, and
   // something that includes all downloads. But this will do for now.
   NS_ASSERTION(VideoRepDecoder(), "Video decoder should not be null.");
   NS_ASSERTION(VideoRepDecoder()->GetResource(),
                "Video resource should not be null");
   bool reliable = false;
-  double downloadRate = VideoRepDecoder()->GetResource()->GetDownloadRate(&reliable);
+  double downloadRate = 0;
+  {
+    MutexAutoLock lock(mStatisticsLock);
+    downloadRate = mVideoStatistics->GetRate(&reliable);
+  }
   uint32_t bestRepIdx = UINT32_MAX;
   bool noRepAvailable = !mMPDManager->GetBestRepForBandwidth(mVideoAdaptSetIdx,
                                                              downloadRate,
                                                              bestRepIdx);
-  LOG("downloadRate [%f] reliable [%s] bestRepIdx [%d] noRepAvailable",
-      downloadRate, (reliable ? "yes" : "no"), bestRepIdx,
+  LOG("downloadRate [%0.2f kbps] reliable [%s] bestRepIdx [%d] noRepAvailable [%s]",
+      downloadRate/1000.0, (reliable ? "yes" : "no"), bestRepIdx,
       (noRepAvailable ? "yes" : "no"));
 
   // If there is a higher bitrate stream that can be downloaded with the
   // current estimated bandwidth, step up to the next stream, for a graceful
   // increase in quality.
   uint32_t toDecoderIdx = mVideoRepDecoderIdx;
   if (bestRepIdx > toDecoderIdx) {
     toDecoderIdx = NS_MIN(toDecoderIdx+1, mVideoRepDecoders.Length()-1);
@@ -1114,10 +1123,129 @@ DASHDecoder::SetSubsegmentIndex(DASHRepD
     // from other rep decoders, or all rep decoders if we're not seeking in the
     // current subsegment.
     // Note: NotifySeekInSubsegment called from DASHReader will already have
     // set the current decoder.
     mVideoSubsegmentIdx = aSubsegmentIdx;
   }
 }
 
+double
+DASHDecoder::ComputePlaybackRate(bool* aReliable)
+{
+  GetReentrantMonitor().AssertCurrentThreadIn();
+  MOZ_ASSERT(NS_IsMainThread() || OnStateMachineThread());
+  NS_ASSERTION(aReliable, "Bool pointer aRelible should not be null!");
+
+  // While downloading the MPD, return 0; do not count manifest as media data.
+  if (mResource && !mMPDManager) {
+    return 0;
+  }
+
+  // Once MPD is downloaded, use the rate from the video decoder.
+  // XXX Not ideal, but since playback rate is used to estimate if we have
+  // enough data to continue playing, this should be sufficient.
+  double videoRate = 0;
+  if (VideoRepDecoder()) {
+    videoRate = VideoRepDecoder()->ComputePlaybackRate(aReliable);
+  }
+  return videoRate;
+}
+
+void
+DASHDecoder::UpdatePlaybackRate()
+{
+  MOZ_ASSERT(NS_IsMainThread() || OnStateMachineThread());
+  GetReentrantMonitor().AssertCurrentThreadIn();
+  // While downloading the MPD, return silently; playback rate has no meaning
+  // for the manifest.
+  if (mResource && !mMPDManager) {
+    return;
+  }
+  // Once MPD is downloaded and audio/video decoder(s) are loading, forward to
+  // active rep decoders.
+  if (AudioRepDecoder()) {
+    AudioRepDecoder()->UpdatePlaybackRate();
+  }
+  if (VideoRepDecoder()) {
+    VideoRepDecoder()->UpdatePlaybackRate();
+  }
+}
+
+void
+DASHDecoder::NotifyPlaybackStarted()
+{
+  GetReentrantMonitor().AssertCurrentThreadIn();
+  // While downloading the MPD, return silently; playback rate has no meaning
+  // for the manifest.
+  if (mResource && !mMPDManager) {
+    return;
+  }
+  // Once MPD is downloaded and audio/video decoder(s) are loading, forward to
+  // active rep decoders.
+  if (AudioRepDecoder()) {
+    AudioRepDecoder()->NotifyPlaybackStarted();
+  }
+  if (VideoRepDecoder()) {
+    VideoRepDecoder()->NotifyPlaybackStarted();
+  }
+}
+
+void
+DASHDecoder::NotifyPlaybackStopped()
+{
+  GetReentrantMonitor().AssertCurrentThreadIn();
+  // While downloading the MPD, return silently; playback rate has no meaning
+  // for the manifest.
+  if (mResource && !mMPDManager) {
+    return;
+  }
+  // Once  // Once MPD is downloaded and audio/video decoder(s) are loading, forward to
+  // active rep decoders.
+  if (AudioRepDecoder()) {
+    AudioRepDecoder()->NotifyPlaybackStopped();
+  }
+  if (VideoRepDecoder()) {
+    VideoRepDecoder()->NotifyPlaybackStopped();
+  }
+}
+
+MediaDecoder::Statistics
+DASHDecoder::GetStatistics()
+{
+  MOZ_ASSERT(NS_IsMainThread() || OnStateMachineThread());
+  Statistics result;
+
+  ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+  if (mResource && !mMPDManager) {
+    return MediaDecoder::GetStatistics();
+  }
+
+  // XXX Use video decoder and its media resource to get stats.
+  // This assumes that the following getter functions are getting relevant
+  // video data only.
+  if (VideoRepDecoder() && VideoRepDecoder()->GetResource()) {
+    MediaResource *resource = VideoRepDecoder()->GetResource();
+    // Note: this rate reflects the rate observed for all video downloads.
+    result.mDownloadRate =
+      resource->GetDownloadRate(&result.mDownloadRateReliable);
+    result.mDownloadPosition =
+      resource->GetCachedDataEnd(VideoRepDecoder()->mDecoderPosition);
+    result.mTotalBytes = resource->GetLength();
+    result.mPlaybackRate = ComputePlaybackRate(&result.mPlaybackRateReliable);
+    result.mDecoderPosition = VideoRepDecoder()->mDecoderPosition;
+    result.mPlaybackPosition = VideoRepDecoder()->mPlaybackPosition;
+  }
+  else {
+    result.mDownloadRate = 0;
+    result.mDownloadRateReliable = true;
+    result.mPlaybackRate = 0;
+    result.mPlaybackRateReliable = true;
+    result.mDecoderPosition = 0;
+    result.mPlaybackPosition = 0;
+    result.mDownloadPosition = 0;
+    result.mTotalBytes = 0;
+  }
+
+  return result;
+}
+
 } // namespace mozilla
-
--- a/content/media/dash/DASHDecoder.h
+++ b/content/media/dash/DASHDecoder.h
@@ -174,16 +174,41 @@ public:
          i++) {
       if (mVideoSubsegmentLoads[i-1] != mVideoSubsegmentLoads[i]) {
         switchCount++;
       }
     }
     return switchCount;
   }
 
+  // The actual playback rate computation. The monitor must be held.
+  // XXX Computes playback for the current video rep decoder only.
+  double ComputePlaybackRate(bool* aReliable) MOZ_OVERRIDE;
+
+  // Something has changed that could affect the computed playback rate,
+  // so recompute it. The monitor must be held. Will be forwarded to current
+  // audio and video rep decoders.
+  void UpdatePlaybackRate() MOZ_OVERRIDE;
+
+  // Used to estimate rates of data passing through the decoder's channel.
+  // Records activity starting on the channel. The monitor must be held.
+  virtual void NotifyPlaybackStarted() MOZ_OVERRIDE;
+
+  // Used to estimate rates of data passing through the decoder's channel.
+  // Records activity stopping on the channel. The monitor must be held.
+  virtual void NotifyPlaybackStopped() MOZ_OVERRIDE;
+
+  // Return statistics. This is used for progress events and other things.
+  // This can be called from any thread. It's only a snapshot of the
+  // current state, since other threads might be changing the state
+  // at any time.
+  // XXX Stats are calculated based on the current video rep decoder, with the
+  // exception of download rate, which is based on all video downloads.
+  virtual Statistics GetStatistics() MOZ_OVERRIDE;
+
   // Drop reference to state machine and tell sub-decoders to do the same.
   // Only called during shutdown dance, on main thread only.
   void ReleaseStateMachine();
 
   // Overridden to forward |Shutdown| to sub-decoders.
   // Called on the main thread only.
   void Shutdown();
 
@@ -315,13 +340,20 @@ private:
   // Array records the index of the decoder/Representation which loaded each
   // subsegment.
   nsTArray<int32_t> mVideoSubsegmentLoads;
 
   // True when Seek is called; will block any downloads until
   // |NotifySeekInSubsegment| is called, which will set it to false, and will
   // start a new series of downloads from the seeked subsegment.
   bool mSeeking;
+
+  // Mutex for statistics.
+  Mutex mStatisticsLock;
+  // Stores snapshot statistics, such as download rate, for the audio|video
+  // data streams. |mStatisticsLock| must be locked for access.
+  nsRefPtr<MediaChannelStatistics> mAudioStatistics;
+  nsRefPtr<MediaChannelStatistics> mVideoStatistics;
 };
 
 } // namespace mozilla
 
 #endif
--- a/content/media/dash/DASHRepDecoder.h
+++ b/content/media/dash/DASHRepDecoder.h
@@ -131,16 +131,22 @@ public:
   void DoNotifyDownloadEnded();
 
   // Called by MediaResource when the "cache suspended" status changes.
   // If MediaResource::IsSuspendedByCache returns true, then the decoder
   // should stop buffering or otherwise waiting for download progress and
   // start consuming data, if possible, because the cache is full.
   void NotifySuspendedStatusChanged();
 
+  // Increments the parsed and decoded frame counters by the passed in counts.
+  // Can be called on any thread.
+  void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded) MOZ_OVERRIDE {
+    if (mMainDecoder) {mMainDecoder->NotifyDecodedFrames(aParsed, aDecoded); }
+  }
+
   // Gets a byte range containing the byte offset. Call on main thread only.
   nsresult GetByteRangeForSeek(int64_t const aOffset,
                                MediaByteRange& aByteRange);
 
   // Gets the number of data byte ranges (not inc. metadata).
   uint32_t GetNumDataByteRanges() {
     return mByteRanges.Length();
   }