Bug 506061 - Calculate frame time based on frame number and frame rate. r=chris.double
authorMatthew Gregan <kinetik@flim.org>
Fri, 04 Sep 2009 14:25:40 +1200
changeset 32269 525ab3a1e8f117a06ad982d54d5ea67d2cd85950
parent 32268 4c1a63fce358c7d43c679aebeacd0afefb900b33
child 32270 bc794ff23f3b43e9aeeb5c2c1878c204a1075245
push id316
push usermgregan@mozilla.com
push dateMon, 05 Oct 2009 00:06:54 +0000
reviewerschris.double
bugs506061
milestone1.9.2b1pre
Bug 506061 - Calculate frame time based on frame number and frame rate. r=chris.double Fixes sync problem caused by FP error accumulation in the old frame time calculation.
content/media/ogg/nsOggDecoder.cpp
--- a/content/media/ogg/nsOggDecoder.cpp
+++ b/content/media/ogg/nsOggDecoder.cpp
@@ -223,47 +223,47 @@ public:
       return result;
     }
 
     PRBool IsEmpty() const
     {
       return mCount == 0;
     }
 
-    PRInt32 GetCount() const
+    PRUint32 GetCount() const
     {
       return mCount;
     }
 
     PRBool IsFull() const
     {
       return mCount == OGGPLAY_BUFFER_SIZE;
     }
 
-    float ResetTimes(float aPeriod)
+    PRUint32 ResetTimes(float aPeriod)
     {
-      float time = 0.0;
+      PRUint32 frames = 0;
       if (mCount > 0) {
-        PRInt32 current = mHead;
+        PRUint32 current = mHead;
         do {
-          mQueue[current]->mTime = time;
-          time += aPeriod;
+          mQueue[current]->mTime = frames * aPeriod;
+          frames += 1;
           current = (current + 1) % OGGPLAY_BUFFER_SIZE;
         } while (current != mTail);
       }
-      return time;
+      return frames;
     }
 
   private:
     FrameData* mQueue[OGGPLAY_BUFFER_SIZE];
-    PRInt32 mHead;
-    PRInt32 mTail;
+    PRUint32 mHead;
+    PRUint32 mTail;
     // This isn't redundant with mHead/mTail, since when mHead == mTail
     // it's ambiguous whether the queue is full or empty
-    PRInt32 mCount;
+    PRUint32 mCount;
   };
 
   // Enumeration for the valid states
   enum State {
     DECODER_STATE_DECODING_METADATA,
     DECODER_STATE_DECODING,
     DECODER_STATE_SEEKING,
     DECODER_STATE_BUFFERING,
@@ -488,17 +488,17 @@ private:
   // the sound and adjusted the sync time for pauses. PR_FALSE
   // if the media is paused and the decoder has stopped the sound
   // and adjusted the sync time for pauses. Accessed only via the
   // decoder thread.
   PRPackedBool mPlaying;
 
   // Number of seconds of data video/audio data held in a frame.
   // Accessed only via the decoder thread.
-  float mCallbackPeriod;
+  double mCallbackPeriod;
 
   // Video data. These are initially set when the metadata is loaded.
   // They are only accessed from the decoder thread.
   PRInt32 mVideoTrack;
   float   mFramerate;
   float   mAspectRatio;
 
   // Audio data. These are initially set when the metadata is loaded.
@@ -510,20 +510,19 @@ private:
   // Time that buffering started. Used for buffering timeout and only
   // accessed in the decoder thread.
   TimeStamp mBufferingStart;
 
   // Download position where we should stop buffering. Only
   // accessed in the decoder thread.
   PRInt64 mBufferingEndOffset;
 
-  // The time value of the last decoded video frame. Used for
-  // computing the sleep period between frames for a/v sync.
-  // Read/Write from the decode thread only.
-  float mLastFrameTime;
+  // The last decoded video frame. Used for computing the sleep period
+  // between frames for a/v sync.  Read/Write from the decode thread only.
+  PRUint64 mLastFrame;
 
   // The decoder position of the end of the last decoded video frame.
   // Read/Write from the decode thread only.
   PRInt64 mLastFramePosition;
 
   // Thread that steps through decoding each frame using liboggplay. Only accessed
   // via the decode thread.
   nsCOMPtr<nsIThread> mStepDecodeThread;
@@ -697,17 +696,17 @@ nsOggDecodeStateMachine::nsOggDecodeStat
   mVideoTrack(-1),
   mFramerate(0.0),
   mAspectRatio(1.0),
   mAudioRate(0),
   mAudioChannels(0),
   mAudioTrack(-1),
   mBufferingStart(),
   mBufferingEndOffset(0),
-  mLastFrameTime(0),
+  mLastFrame(0),
   mLastFramePosition(-1),
   mState(DECODER_STATE_DECODING_METADATA),
   mSeekTime(0.0),
   mCurrentFrameTime(0.0),
   mPlaybackStartTime(0.0), 
   mVolume(1.0),
   mDuration(-1),
   mSeekable(PR_TRUE),
@@ -755,34 +754,34 @@ nsOggDecodeStateMachine::FrameData* nsOg
   if (!info)
     return nsnull;
 
   FrameData* frame = new FrameData();
   if (!frame) {
     return nsnull;
   }
 
-  frame->mTime = mLastFrameTime;
+  frame->mTime = mCallbackPeriod * mLastFrame;
   frame->mEndStreamPosition = mDecoder->mDecoderPosition;
-  mLastFrameTime += mCallbackPeriod;
+  mLastFrame += 1;
 
   if (mLastFramePosition >= 0) {
     NS_ASSERTION(frame->mEndStreamPosition >= mLastFramePosition,
                  "Playback positions must not decrease without an intervening reset");
     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(
-        base + TimeDuration::FromMilliseconds(NS_round(mLastFrameTime*1000)));
+        base + TimeDuration::FromMilliseconds(NS_round(mCallbackPeriod*mLastFrame*1000)));
     mDecoder->UpdatePlaybackRate();
   }
   mLastFramePosition = frame->mEndStreamPosition;
 
   int num_tracks = oggplay_get_num_tracks(mPlayer);
   float audioTime = -1.0;
   float videoTime = -1.0;
 
@@ -1068,33 +1067,33 @@ void nsOggDecodeStateMachine::StartPlayb
   mPlayStartTime = TimeStamp::Now();
   mPauseDuration = 0;
 
 }
 
 void nsOggDecodeStateMachine::StopPlayback()
 {
   //  NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "StopPlayback() called without acquiring decoder monitor");
-  mLastFrameTime = mDecodedFrames.ResetTimes(mCallbackPeriod);
+  mLastFrame = mDecodedFrames.ResetTimes(mCallbackPeriod);
   StopAudio();
   mPlaying = PR_FALSE;
   mPauseStartTime = TimeStamp::Now();
 }
 
 void nsOggDecodeStateMachine::PausePlayback()
 {
   if (!mAudioStream) {
     StopPlayback();
     return;
   }
   mAudioStream->Pause();
   mPlaying = PR_FALSE;
   mPauseStartTime = TimeStamp::Now();
   if (mAudioStream->GetPosition() < 0) {
-    mLastFrameTime = mDecodedFrames.ResetTimes(mCallbackPeriod);
+    mLastFrame = mDecodedFrames.ResetTimes(mCallbackPeriod);
   }
 }
 
 void nsOggDecodeStateMachine::ResumePlayback()
 {
  if (!mAudioStream) {
     StartPlayback();
     return;
@@ -1298,17 +1297,18 @@ nsresult nsOggDecodeStateMachine::Seek(f
 
 void nsOggDecodeStateMachine::DecodeToFrame(nsAutoMonitor& aMonitor,
                                             float aTime)
 {
   // Drop frames before the target time.
   float target = aTime - mCallbackPeriod / 2.0;
   FrameData* frame = nsnull;
   OggPlayErrorCode r;
-  mLastFrameTime = 0;
+  mLastFrame = 0;
+
   // Some of the audio data from previous frames actually belongs
   // to this frame and later frames. So rescue that data and stuff
   // it into the first frame.
   float audioTime = 0;
   nsTArray<float> audioData;
   do {
     if (frame) {
       audioData.AppendElements(frame->mAudioData);
@@ -1348,17 +1348,17 @@ void nsOggDecodeStateMachine::DecodeToFr
       // numExtraSamples must be evenly divisble by number of channels.
       size_t numExtraSamples = mAudioChannels *
         PRInt32(NS_ceil(mAudioRate*audioTime));
       float* data = audioData.Elements() + audioData.Length() - numExtraSamples;
       float* dst = frame->mAudioData.InsertElementsAt(0, numExtraSamples);
       memcpy(dst, data, numExtraSamples * sizeof(float));
     }
 
-    mLastFrameTime = 0;
+    mLastFrame = 0;
     frame->mTime = 0;
     frame->mState = OGGPLAY_STREAM_JUST_SEEKED;
     mDecodedFrames.Push(frame);
     UpdatePlaybackPosition(frame->mDecodedFrameTime);
     PlayVideo(frame);
   }
 }
 
@@ -1415,17 +1415,17 @@ nsresult nsOggDecodeStateMachine::Run()
           mon.Enter();
         }
 
         HandleDecodeErrors(r);
 
         if (mState == DECODER_STATE_SHUTDOWN)
           continue;
 
-        mLastFrameTime = 0;
+        mLastFrame = 0;
         FrameData* frame = NextFrame();
         if (frame) {
           mDecodedFrames.Push(frame);
           mDecoder->mPlaybackPosition = frame->mEndStreamPosition;
           mPlaybackStartTime = frame->mDecodedFrameTime;
           UpdatePlaybackPosition(frame->mDecodedFrameTime);
           // Now that we know the start offset, we can tell the channel
           // reader the last frame time.