Bug 820588 - Aggregate and forward statistics in DASH decoder classes r=cpearce

This commit is contained in:
Steve Workman 2012-12-13 11:42:45 -08:00
parent daabc3d610
commit a60e66b6fb
8 changed files with 234 additions and 31 deletions

View File

@ -925,6 +925,10 @@ void MediaDecoder::NotifySuspendedStatusChanged()
void MediaDecoder::NotifyBytesDownloaded()
{
MOZ_ASSERT(NS_IsMainThread());
{
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
UpdatePlaybackRate();
}
UpdateReadyStateForData();
Progress(false);
}

View File

@ -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;

View File

@ -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()");

View File

@ -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

View File

@ -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<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;
@ -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<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

View File

@ -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

View File

@ -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<MediaChannelStatistics> mAudioStatistics;
nsRefPtr<MediaChannelStatistics> mVideoStatistics;
};
} // namespace mozilla

View File

@ -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);