Bug 486991. Convert nsMediaStream and media decoders to use TimeStamp/TimeDuration. r=doublec

This commit is contained in:
Robert O'Callahan 2009-04-11 21:39:24 +12:00
parent 7a2ad352d5
commit 642498a112
6 changed files with 114 additions and 81 deletions

View File

@ -45,7 +45,7 @@
#include "gfxContext.h"
#include "gfxRect.h"
#include "nsITimer.h"
#include "prinrval.h"
#include "nsTimeStamp.h"
#ifdef PR_LOGGING
extern PRLogModuleInfo* gVideoDecoderLog;
@ -61,7 +61,10 @@ class nsHTMLMediaElement;
// called from any thread.
class nsMediaDecoder : public nsIObserver
{
public:
public:
typedef mozilla::TimeStamp TimeStamp;
typedef mozilla::TimeDuration TimeDuration;
nsMediaDecoder();
virtual ~nsMediaDecoder();
@ -265,14 +268,14 @@ protected:
// Time that the last progress event was fired. Read/Write from the
// main thread only.
PRIntervalTime mProgressTime;
TimeStamp mProgressTime;
// 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 0 indicates
// 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.
PRIntervalTime mDataTime;
TimeStamp mDataTime;
// Lock around the video RGB, width and size data. This
// is used in the decoder backend threads and the main thread

View File

@ -46,6 +46,7 @@
#include "nsIStreamListener.h"
#include "prlock.h"
#include "nsMediaCache.h"
#include "nsTimeStamp.h"
// For HTTP seeking, if number of bytes needing to be
// seeked forward is less than this value then a read is
@ -64,25 +65,28 @@ class nsMediaDecoder;
* kind of average of the data passing through over the time the
* channel is active.
*
* Timestamps and time durations are measured in PRIntervalTimes, but
* all methods take "now" as a parameter so the user of this class can
* define what the timeline means.
* All methods take "now" as a parameter so the user of this class can
* control the timeline used.
*/
class nsChannelStatistics {
public:
typedef mozilla::TimeStamp TimeStamp;
typedef mozilla::TimeDuration TimeDuration;
nsChannelStatistics() { Reset(); }
void Reset() {
mLastStartTime = mAccumulatedTime = 0;
mLastStartTime = TimeStamp();
mAccumulatedTime = TimeDuration(0);
mAccumulatedBytes = 0;
mIsStarted = PR_FALSE;
}
void Start(PRIntervalTime aNow) {
void Start(TimeStamp aNow) {
if (mIsStarted)
return;
mLastStartTime = aNow;
mIsStarted = PR_TRUE;
}
void Stop(PRIntervalTime aNow) {
void Stop(TimeStamp aNow) {
if (!mIsStarted)
return;
mAccumulatedTime += aNow - mLastStartTime;
@ -97,25 +101,28 @@ public:
mAccumulatedBytes += aBytes;
}
double GetRateAtLastStop(PRPackedBool* aReliable) {
*aReliable = mAccumulatedTime >= PR_TicksPerSecond();
return double(mAccumulatedBytes)*PR_TicksPerSecond()/mAccumulatedTime;
double seconds = mAccumulatedTime.ToSeconds();
*aReliable = seconds >= 1.0;
if (seconds <= 0.0)
return 0.0;
return double(mAccumulatedBytes)/seconds;
}
double GetRate(PRIntervalTime aNow, PRPackedBool* aReliable) {
PRIntervalTime time = mAccumulatedTime;
double GetRate(TimeStamp aNow, PRPackedBool* aReliable) {
TimeDuration time = mAccumulatedTime;
if (mIsStarted) {
time += aNow - mLastStartTime;
}
*aReliable = time >= PR_TicksPerSecond();
NS_ASSERTION(time >= 0, "Time wraparound?");
if (time <= 0)
double seconds = time.ToSeconds();
*aReliable = seconds >= 1.0;
if (seconds <= 0.0)
return 0.0;
return double(mAccumulatedBytes)*PR_TicksPerSecond()/time;
return double(mAccumulatedBytes)/seconds;
}
private:
PRInt64 mAccumulatedBytes;
PRIntervalTime mAccumulatedTime;
PRIntervalTime mLastStartTime;
PRPackedBool mIsStarted;
PRInt64 mAccumulatedBytes;
TimeDuration mAccumulatedTime;
TimeStamp mLastStartTime;
PRPackedBool mIsStarted;
};
/*

View File

@ -68,8 +68,8 @@ nsMediaDecoder::nsMediaDecoder() :
mElement(0),
mRGBWidth(-1),
mRGBHeight(-1),
mProgressTime(0),
mDataTime(0),
mProgressTime(),
mDataTime(),
mVideoUpdateLock(nsnull),
mFramerate(0.0),
mSizeChanged(PR_FALSE),
@ -153,25 +153,27 @@ void nsMediaDecoder::Progress(PRBool aTimer)
if (!mElement)
return;
PRIntervalTime now = PR_IntervalNow();
TimeStamp now = TimeStamp::Now();
if (!aTimer) {
mDataTime = now;
}
PRUint32 progressDelta = PR_IntervalToMilliseconds(now - mProgressTime);
PRUint32 networkDelta = PR_IntervalToMilliseconds(now - mDataTime);
// If PROGRESS_MS has passed since the last progress event fired and more
// data has arrived since then, fire another progress event.
if (progressDelta >= PROGRESS_MS && networkDelta <= PROGRESS_MS) {
if ((mProgressTime.IsNull() ||
now - mProgressTime >= TimeDuration::FromMilliseconds(PROGRESS_MS)) &&
!mDataTime.IsNull() &&
now - mDataTime <= TimeDuration::FromMilliseconds(PROGRESS_MS)) {
mElement->DispatchAsyncProgressEvent(NS_LITERAL_STRING("progress"));
mProgressTime = now;
}
if (mDataTime != 0 && networkDelta >= STALL_MS) {
if (!mDataTime.IsNull() &&
now - mDataTime >= TimeDuration::FromMilliseconds(STALL_MS)) {
mElement->DispatchAsyncProgressEvent(NS_LITERAL_STRING("stalled"));
mDataTime = 0;
// Null it out
mDataTime = TimeStamp();
}
}

View File

@ -60,6 +60,8 @@
#define HTTP_OK_CODE 200
#define HTTP_PARTIAL_RESPONSE_CODE 206
using mozilla::TimeStamp;
nsMediaChannelStream::nsMediaChannelStream(nsMediaDecoder* aDecoder,
nsIChannel* aChannel, nsIURI* aURI)
: nsMediaStream(aDecoder, aChannel, aURI),
@ -207,7 +209,7 @@ nsMediaChannelStream::OnStartRequest(nsIRequest* aRequest)
{
nsAutoLock lock(mLock);
mChannelStatistics.Start(PR_IntervalNow());
mChannelStatistics.Start(TimeStamp::Now());
}
if (mSuspendCount > 0) {
@ -229,7 +231,7 @@ nsMediaChannelStream::OnStopRequest(nsIRequest* aRequest, nsresult aStatus)
{
nsAutoLock lock(mLock);
mChannelStatistics.Stop(PR_IntervalNow());
mChannelStatistics.Stop(TimeStamp::Now());
}
mCacheStream.NotifyDataEnded(aStatus);
@ -376,7 +378,7 @@ void nsMediaChannelStream::CloseChannel()
{
nsAutoLock lock(mLock);
mChannelStatistics.Stop(PR_IntervalNow());
mChannelStatistics.Stop(TimeStamp::Now());
}
if (mListener) {
@ -429,7 +431,7 @@ void nsMediaChannelStream::Suspend()
if (mSuspendCount == 0 && mChannel) {
{
nsAutoLock lock(mLock);
mChannelStatistics.Stop(PR_IntervalNow());
mChannelStatistics.Stop(TimeStamp::Now());
}
mChannel->Suspend();
}
@ -444,7 +446,7 @@ void nsMediaChannelStream::Resume()
if (mSuspendCount == 0 && mChannel) {
{
nsAutoLock lock(mLock);
mChannelStatistics.Start(PR_IntervalNow());
mChannelStatistics.Start(TimeStamp::Now());
}
mChannel->Resume();
// XXX need to do something fancier here because we often won't
@ -586,7 +588,7 @@ double
nsMediaChannelStream::GetDownloadRate(PRPackedBool* aIsReliable)
{
nsAutoLock lock(mLock);
return mChannelStatistics.GetRate(PR_IntervalNow(), aIsReliable);
return mChannelStatistics.GetRate(TimeStamp::Now(), aIsReliable);
}
PRInt64

View File

@ -53,6 +53,9 @@
#include "nsNetUtil.h"
#include "nsOggDecoder.h"
using mozilla::TimeDuration;
using mozilla::TimeStamp;
/*
The maximum height and width of the video. Used for
sanitizing the memory allocation of the RGB buffer
@ -395,18 +398,18 @@ private:
// for synchronising frames. It is reset after a seek as the mTime member
// of FrameData is reset to start at 0 from the first frame after a seek.
// Accessed only via the decoder thread.
PRIntervalTime mPlayStartTime;
TimeStamp mPlayStartTime;
// The time that playback was most recently paused, either via
// buffering or pause. This is used to compute mPauseDuration for
// a/v sync adjustments. Accessed only via the decoder thread.
PRIntervalTime mPauseStartTime;
TimeStamp mPauseStartTime;
// The total time that has been spent in completed pauses (via
// 'pause' or buffering). This is used to adjust for these
// pauses when computing a/v synchronisation. Accessed only via the
// decoder thread.
PRIntervalTime mPauseDuration;
TimeDuration mPauseDuration;
// PR_TRUE if the media is playing and the decoder has started
// the sound and adjusted the sync time for pauses. PR_FALSE
@ -432,7 +435,7 @@ private:
// Time that buffering started. Used for buffering timeout and only
// accessed in the decoder thread.
PRIntervalTime mBufferingStart;
TimeStamp mBufferingStart;
// Download position where we should stop buffering. Only
// accessed in the decoder thread.
@ -497,8 +500,8 @@ private:
nsOggDecodeStateMachine::nsOggDecodeStateMachine(nsOggDecoder* aDecoder) :
mDecoder(aDecoder),
mPlayer(0),
mPlayStartTime(0),
mPauseStartTime(0),
mPlayStartTime(),
mPauseStartTime(),
mPauseDuration(0),
mPlaying(PR_FALSE),
mCallbackPeriod(1.0),
@ -507,7 +510,7 @@ nsOggDecodeStateMachine::nsOggDecodeStateMachine(nsOggDecoder* aDecoder) :
mAudioRate(0),
mAudioChannels(0),
mAudioTrack(-1),
mBufferingStart(0),
mBufferingStart(),
mBufferingEndOffset(0),
mLastFrameTime(0),
mLastFramePosition(-1),
@ -554,9 +557,17 @@ nsOggDecodeStateMachine::FrameData* nsOggDecodeStateMachine::NextFrame()
if (mLastFramePosition >= 0) {
NS_ASSERTION(frame->mEndStreamPosition >= mLastFramePosition,
"Playback positions must not decrease without an intervening reset");
mDecoder->mPlaybackStatistics.Start(frame->mTime*PR_TicksPerSecond());
TimeStamp base = mPlayStartTime;
if (base.IsNull()) {
// It doesn't really matter what 'base' is, so just use 'now' if
// we haven't started playback.
base = TimeStamp::Now();
}
mDecoder->mPlaybackStatistics.Start(
base + TimeDuration::FromMilliseconds(NS_round(frame->mTime*1000)));
mDecoder->mPlaybackStatistics.AddBytes(frame->mEndStreamPosition - mLastFramePosition);
mDecoder->mPlaybackStatistics.Stop(mLastFrameTime*PR_TicksPerSecond());
mDecoder->mPlaybackStatistics.Stop(
base + TimeDuration::FromMilliseconds(NS_round(mLastFrameTime*1000)));
mDecoder->UpdatePlaybackRate();
}
mLastFramePosition = frame->mEndStreamPosition;
@ -653,16 +664,17 @@ void nsOggDecodeStateMachine::PlayFrame() {
if (!mDecodedFrames.IsEmpty()) {
FrameData* frame = mDecodedFrames.Peek();
TimeStamp now = TimeStamp::Now();
if (frame->mState == OGGPLAY_STREAM_JUST_SEEKED) {
// After returning from a seek all mTime members of
// FrameData start again from a time position of 0.
// Reset the play start time.
mPlayStartTime = PR_IntervalNow();
mPauseDuration = 0;
mPlayStartTime = now;
mPauseDuration = TimeDuration(0);
frame->mState = OGGPLAY_STREAM_INITIALISED;
}
double time = (PR_IntervalToMilliseconds(PR_IntervalNow()-mPlayStartTime-mPauseDuration)/1000.0);
double time = (now - mPlayStartTime - mPauseDuration).ToSeconds();
if (time >= frame->mTime) {
// Audio for the current frame is played, but video for the next frame
// is displayed, to account for lag from the time the audio is written
@ -786,13 +798,15 @@ void nsOggDecodeStateMachine::StartPlayback()
mPlaying = PR_TRUE;
// If this is the very first play, then set the initial start time
if (mPlayStartTime == 0) {
mPlayStartTime = PR_IntervalNow();
if (mPlayStartTime.IsNull()) {
mPlayStartTime = TimeStamp::Now();
}
// If we have been paused previously, then compute duration spent paused
if (mPauseStartTime != 0) {
mPauseDuration += PR_IntervalNow() - mPauseStartTime;
if (!mPauseStartTime.IsNull()) {
mPauseDuration += TimeStamp::Now() - mPauseStartTime;
// Null out mPauseStartTime
mPauseStartTime = TimeStamp();
}
}
@ -801,7 +815,7 @@ void nsOggDecodeStateMachine::StopPlayback()
// NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "StopPlayback() called without acquiring decoder monitor");
StopAudio();
mPlaying = PR_FALSE;
mPauseStartTime = PR_IntervalNow();
mPauseStartTime = TimeStamp::Now();
}
void nsOggDecodeStateMachine::UpdatePlaybackPosition(float aTime)
@ -1010,7 +1024,7 @@ nsresult nsOggDecodeStateMachine::Run()
NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, UpdateReadyStateForData);
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
mBufferingStart = PR_IntervalNow();
mBufferingStart = TimeStamp::Now();
PRPackedBool reliable;
double playbackRate = mDecoder->ComputePlaybackRate(&reliable);
mBufferingEndOffset = mDecoder->mDecoderPosition +
@ -1100,15 +1114,15 @@ nsresult nsOggDecodeStateMachine::Run()
case DECODER_STATE_BUFFERING:
{
PRIntervalTime now = PR_IntervalNow();
if ((PR_IntervalToMilliseconds(now - mBufferingStart) < BUFFERING_WAIT*1000) &&
TimeStamp now = TimeStamp::Now();
if (now - mBufferingStart < TimeDuration::FromSeconds(BUFFERING_WAIT) &&
mDecoder->mReader->Stream()->GetCachedDataEnd(mDecoder->mDecoderPosition) < mBufferingEndOffset &&
!mDecoder->mReader->Stream()->IsDataCachedToEndOfStream(mDecoder->mDecoderPosition) &&
!mDecoder->mReader->Stream()->IsSuspendedByCache()) {
LOG(PR_LOG_DEBUG,
("In buffering: buffering data until %d bytes available or %d milliseconds",
("In buffering: buffering data until %d bytes available or %f seconds",
PRUint32(mBufferingEndOffset - mDecoder->mReader->Stream()->GetCachedDataEnd(mDecoder->mDecoderPosition)),
BUFFERING_WAIT*1000 - (PR_IntervalToMilliseconds(now - mBufferingStart))));
BUFFERING_WAIT - (now - mBufferingStart).ToSeconds()));
mon.Wait(PR_MillisecondsToInterval(1000));
if (mState == DECODER_STATE_SHUTDOWN)
continue;

View File

@ -51,6 +51,9 @@
#include "nsThreadUtils.h"
#include "nsWaveDecoder.h"
using mozilla::TimeDuration;
using mozilla::TimeStamp;
// Maximum number of seconds to wait when buffering.
#define BUFFERING_TIMEOUT 3
@ -114,7 +117,7 @@ class nsWaveStateMachine : public nsRunnable
{
public:
nsWaveStateMachine(nsWaveDecoder* aDecoder,
PRUint32 aBufferWaitTime, float aInitialVolume);
TimeDuration aBufferWaitTime, float aInitialVolume);
~nsWaveStateMachine();
void SetStream(nsMediaStream* aStream) { mStream = aStream; }
@ -257,12 +260,12 @@ private:
// playback resumes, so it is possible for this to be null.
nsAutoPtr<nsAudioStream> mAudioStream;
// Maximum time in milliseconds to spend waiting for data during buffering.
PRUint32 mBufferingWait;
// Maximum time to spend waiting for data during buffering.
TimeDuration mBufferingWait;
// Machine time that buffering began, used with mBufferingWait to time out
// buffering.
PRIntervalTime mBufferingStart;
TimeStamp mBufferingStart;
// Download position where we should stop buffering. Only accessed
// in the decoder thread.
@ -334,11 +337,12 @@ private:
};
nsWaveStateMachine::nsWaveStateMachine(nsWaveDecoder* aDecoder,
PRUint32 aBufferWaitTime, float aInitialVolume)
TimeDuration aBufferWaitTime,
float aInitialVolume)
: mDecoder(aDecoder),
mStream(nsnull),
mBufferingWait(aBufferWaitTime),
mBufferingStart(0),
mBufferingStart(),
mBufferingEndOffset(0),
mSampleRate(0),
mChannels(0),
@ -512,15 +516,15 @@ nsWaveStateMachine::Run()
break;
case STATE_BUFFERING: {
PRIntervalTime now = PR_IntervalNow();
if ((PR_IntervalToMilliseconds(now - mBufferingStart) < mBufferingWait) &&
TimeStamp now = TimeStamp::Now();
if (now - mBufferingStart < mBufferingWait &&
mStream->GetCachedDataEnd(mPlaybackPosition) < mBufferingEndOffset &&
!mStream->IsDataCachedToEndOfStream(mPlaybackPosition) &&
!mStream->IsSuspendedByCache()) {
LOG(PR_LOG_DEBUG,
("In buffering: buffering data until %d bytes available or %d milliseconds\n",
("In buffering: buffering data until %d bytes available or %f seconds\n",
PRUint32(mBufferingEndOffset - mStream->GetCachedDataEnd(mPlaybackPosition)),
mBufferingWait - (PR_IntervalToMilliseconds(now - mBufferingStart))));
(mBufferingWait - (now - mBufferingStart)).ToSeconds()));
monitor.Wait(PR_MillisecondsToInterval(1000));
} else {
ChangeState(mNextState);
@ -541,13 +545,12 @@ nsWaveStateMachine::Run()
}
}
PRUint32 startTime = PR_IntervalToMilliseconds(PR_IntervalNow());
startTime -= AUDIO_BUFFER_LENGTH;
PRIntervalTime lastWakeup = PR_MillisecondsToInterval(startTime);
TimeStamp now = TimeStamp::Now();
TimeStamp lastWakeup = now -
TimeDuration::FromMilliseconds(AUDIO_BUFFER_LENGTH);
do {
PRIntervalTime now = PR_IntervalNow();
PRInt32 sleepTime = PR_IntervalToMilliseconds(now - lastWakeup);
TimeDuration sleepTime = now - lastWakeup;
lastWakeup = now;
// We aim to have AUDIO_BUFFER_LENGTH milliseconds of audio
@ -556,12 +559,13 @@ nsWaveStateMachine::Run()
// wake early, we only buffer sleepTime milliseconds of audio since
// there is still AUDIO_BUFFER_LENGTH - sleepTime milliseconds of
// audio buffered.
PRInt32 targetTime = AUDIO_BUFFER_LENGTH;
TimeDuration targetTime =
TimeDuration::FromMilliseconds(AUDIO_BUFFER_LENGTH);
if (sleepTime < targetTime) {
targetTime = sleepTime;
}
PRInt64 len = TimeToBytes(float(targetTime) / 1000.0f);
PRInt64 len = TimeToBytes(targetTime.ToSeconds());
PRInt64 leftToPlay =
GetDataLength() - (mPlaybackPosition - mWavePCMOffset);
@ -578,9 +582,9 @@ nsWaveStateMachine::Run()
// we need to advance playback to free up cache space)
if (mState != STATE_ENDED && available < len &&
!mStream->IsSuspendedByCache()) {
mBufferingStart = PR_IntervalNow();
mBufferingStart = now;
mBufferingEndOffset = mPlaybackPosition +
TimeToBytes(float(mBufferingWait) / 1000.0f);
TimeToBytes(mBufferingWait.ToSeconds());
mNextState = mState;
ChangeState(STATE_BUFFERING);
@ -633,6 +637,7 @@ nsWaveStateMachine::Run()
if (mState == STATE_PLAYING) {
monitor.Wait(PR_MillisecondsToInterval(AUDIO_BUFFER_WAKEUP));
now = TimeStamp::Now();
}
} while (mState == STATE_PLAYING);
break;
@ -1295,8 +1300,8 @@ nsWaveDecoder::Load(nsIURI* aURI, nsIChannel* aChannel, nsIStreamListener** aStr
RegisterShutdownObserver();
mPlaybackStateMachine = new nsWaveStateMachine(this,
BUFFERING_TIMEOUT * 1000,
mInitialVolume);
TimeDuration::FromMilliseconds(BUFFERING_TIMEOUT),
mInitialVolume);
NS_ENSURE_TRUE(mPlaybackStateMachine, NS_ERROR_OUT_OF_MEMORY);
// Open the stream *after* setting mPlaybackStateMachine, to ensure