diff --git a/content/media/MediaDecoder.cpp b/content/media/MediaDecoder.cpp index c9c258196803..5792fa129896 100644 --- a/content/media/MediaDecoder.cpp +++ b/content/media/MediaDecoder.cpp @@ -925,6 +925,10 @@ void MediaDecoder::NotifySuspendedStatusChanged() void MediaDecoder::NotifyBytesDownloaded() { MOZ_ASSERT(NS_IsMainThread()); + { + ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); + UpdatePlaybackRate(); + } UpdateReadyStateForData(); Progress(false); } diff --git a/content/media/MediaDecoder.h b/content/media/MediaDecoder.h index cb1140137f63..03f3fda3bb87 100644 --- a/content/media/MediaDecoder.h +++ b/content/media/MediaDecoder.h @@ -618,10 +618,24 @@ public: // 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. @@ -787,7 +801,7 @@ public: // 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. @@ -862,7 +876,7 @@ public: // 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); } @@ -881,10 +895,6 @@ public: // 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 @@ -1050,6 +1060,11 @@ protected: // 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; diff --git a/content/media/MediaDecoderStateMachine.cpp b/content/media/MediaDecoderStateMachine.cpp index 4db2a9859f93..2174ba9da96c 100644 --- a/content/media/MediaDecoderStateMachine.cpp +++ b/content/media/MediaDecoderStateMachine.cpp @@ -1222,7 +1222,7 @@ void MediaDecoderStateMachine::StopPlayback() "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); @@ -1241,7 +1241,7 @@ void MediaDecoderStateMachine::StartPlayback() 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()"); diff --git a/content/media/MediaResource.cpp b/content/media/MediaResource.cpp index ee872fbed24f..d40855db1033 100644 --- a/content/media/MediaResource.cpp +++ b/content/media/MediaResource.cpp @@ -325,7 +325,7 @@ ChannelMediaResource::OnStartRequest(nsIRequest* aRequest) { MutexAutoLock lock(mLock); - mChannelStatistics.Start(TimeStamp::Now()); + mChannelStatistics->Start(); } mReopenOnError = false; @@ -406,7 +406,7 @@ ChannelMediaResource::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) { MutexAutoLock lock(mLock); - mChannelStatistics.Stop(TimeStamp::Now()); + mChannelStatistics->Stop(); } // If we were loading a byte range, notify decoder and return. @@ -512,7 +512,7 @@ ChannelMediaResource::OnDataAvailable(nsIRequest* aRequest, { MutexAutoLock lock(mLock); - mChannelStatistics.AddBytes(aCount); + mChannelStatistics->AddBytes(aCount); } CopySegmentClosure closure; @@ -583,6 +583,10 @@ 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; @@ -731,7 +735,7 @@ MediaResource* ChannelMediaResource::CloneData(MediaDecoder* aDecoder) resource->mSuspendCount = 1; resource->mCacheStream.InitAsClone(&mCacheStream); resource->mChannelStatistics = mChannelStatistics; - resource->mChannelStatistics.Stop(TimeStamp::Now()); + resource->mChannelStatistics->Stop(); } return resource; } @@ -742,7 +746,7 @@ void ChannelMediaResource::CloseChannel() { MutexAutoLock lock(mLock); - mChannelStatistics.Stop(TimeStamp::Now()); + mChannelStatistics->Stop(); } if (mListener) { @@ -844,7 +848,7 @@ void ChannelMediaResource::Suspend(bool aCloseImmediately) } else if (mSuspendCount == 0) { { MutexAutoLock lock(mLock); - mChannelStatistics.Stop(TimeStamp::Now()); + mChannelStatistics->Stop(); } PossiblySuspend(); element->DownloadSuspended(); @@ -877,7 +881,7 @@ void ChannelMediaResource::Resume() // 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. @@ -1219,7 +1223,7 @@ double ChannelMediaResource::GetDownloadRate(bool* aIsReliable) { MutexAutoLock lock(mLock); - return mChannelStatistics.GetRate(TimeStamp::Now(), aIsReliable); + return mChannelStatistics->GetRate(aIsReliable); } int64_t diff --git a/content/media/MediaResource.h b/content/media/MediaResource.h index 2277a22c52a3..35b3b70e35a2 100644 --- a/content/media/MediaResource.h +++ b/content/media/MediaResource.h @@ -46,22 +46,25 @@ class MediaDecoder; 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) { @@ -79,10 +82,10 @@ public: return 0.0; return static_cast(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; @@ -213,6 +216,8 @@ public: // 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. @@ -459,6 +464,15 @@ public: 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(); @@ -567,7 +581,7 @@ protected: // This lock protects mChannelStatistics Mutex mLock; - MediaChannelStatistics mChannelStatistics; + nsRefPtr 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 diff --git a/content/media/dash/DASHDecoder.cpp b/content/media/dash/DASHDecoder.cpp index a6465f5b0a56..5d34e218b932 100644 --- a/content/media/dash/DASHDecoder.cpp +++ b/content/media/dash/DASHDecoder.cpp @@ -156,9 +156,12 @@ DASHDecoder::DASHDecoder() : 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() @@ -536,6 +539,7 @@ DASHDecoder::CreateAudioSubResource(nsIURI* aUrl, = MediaResource::Create(aAudioDecoder, channel); NS_ENSURE_TRUE(audioResource, nullptr); + audioResource->RecordStatisticsTo(mAudioStatistics); return audioResource; } @@ -557,6 +561,7 @@ DASHDecoder::CreateVideoSubResource(nsIURI* aUrl, = MediaResource::Create(aVideoDecoder, channel); NS_ENSURE_TRUE(videoResource, nullptr); + videoResource->RecordStatisticsTo(mVideoStatistics); return videoResource; } @@ -910,19 +915,23 @@ DASHDecoder::PossiblySwitchDecoder(DASHRepDecoder* aRepDecoder) // 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 @@ -1119,5 +1128,124 @@ DASHDecoder::SetSubsegmentIndex(DASHRepDecoder* aRepDecoder, } } -} // namespace mozilla +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 diff --git a/content/media/dash/DASHDecoder.h b/content/media/dash/DASHDecoder.h index 2c6a4f1bd1e5..8edd13ef775a 100644 --- a/content/media/dash/DASHDecoder.h +++ b/content/media/dash/DASHDecoder.h @@ -179,6 +179,31 @@ public: 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(); @@ -320,6 +345,13 @@ private: // |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 mAudioStatistics; + nsRefPtr mVideoStatistics; }; } // namespace mozilla diff --git a/content/media/dash/DASHRepDecoder.h b/content/media/dash/DASHRepDecoder.h index df63e9b80167..5338faf6ec83 100644 --- a/content/media/dash/DASHRepDecoder.h +++ b/content/media/dash/DASHRepDecoder.h @@ -136,6 +136,12 @@ public: // 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);