Bug 930829 - Add MediaData class as base for VideoData and AudioData to simplify writing a generic stream decoder. r=kinetik
authorChris Pearce <cpearce@mozilla.com>
Fri, 25 Oct 2013 15:44:58 +1300
changeset 166820 2fd66c1fac2361392c84cc3bbfdfcc112f615462
parent 166819 31e679e3e4d7d847247cc57c6e3e00d75a9288ca
child 166821 87a32e0b05ba73fe8a9826e420a03887108ac53d
push id428
push userbbajaj@mozilla.com
push dateTue, 28 Jan 2014 00:16:25 +0000
treeherdermozilla-release@cd72a7ff3a75 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs930829
milestone27.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 930829 - Add MediaData class as base for VideoData and AudioData to simplify writing a generic stream decoder. r=kinetik
content/media/MediaDecoderReader.cpp
content/media/MediaDecoderReader.h
content/media/MediaDecoderStateMachine.cpp
content/media/gstreamer/GStreamerReader.cpp
content/media/ogg/OggReader.cpp
content/media/omx/MediaOmxReader.cpp
content/media/plugins/MediaPluginReader.cpp
content/media/plugins/MediaPluginReader.h
content/media/raw/RawReader.cpp
content/media/webm/WebMReader.cpp
content/media/wmf/WMFReader.cpp
--- a/content/media/MediaDecoderReader.cpp
+++ b/content/media/MediaDecoderReader.cpp
@@ -79,18 +79,18 @@ IsYV12Format(const VideoData::YCbCrBuffe
     aYPlane.mWidth / 2 == aCbPlane.mWidth &&
     aYPlane.mHeight / 2 == aCbPlane.mHeight &&
     aCbPlane.mWidth == aCrPlane.mWidth &&
     aCbPlane.mHeight == aCrPlane.mHeight;
 }
 
 bool
 VideoInfo::ValidateVideoRegion(const nsIntSize& aFrame,
-                                 const nsIntRect& aPicture,
-                                 const nsIntSize& aDisplay)
+                               const nsIntRect& aPicture,
+                               const nsIntSize& aDisplay)
 {
   return
     aFrame.width <= PlanarYCbCrImage::MAX_DIMENSION &&
     aFrame.height <= PlanarYCbCrImage::MAX_DIMENSION &&
     aFrame.width * aFrame.height <= MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
     aFrame.width * aFrame.height != 0 &&
     aPicture.width <= PlanarYCbCrImage::MAX_DIMENSION &&
     aPicture.x < PlanarYCbCrImage::MAX_DIMENSION &&
@@ -101,69 +101,78 @@ VideoInfo::ValidateVideoRegion(const nsI
     aPicture.width * aPicture.height <= MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
     aPicture.width * aPicture.height != 0 &&
     aDisplay.width <= PlanarYCbCrImage::MAX_DIMENSION &&
     aDisplay.height <= PlanarYCbCrImage::MAX_DIMENSION &&
     aDisplay.width * aDisplay.height <= MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
     aDisplay.width * aDisplay.height != 0;
 }
 
-VideoData::  VideoData(int64_t aOffset, int64_t aTime, int64_t aEndTime, int64_t aTimecode)
-  : mOffset(aOffset),
-    mTime(aTime),
-    mEndTime(aEndTime),
+VideoData::VideoData(int64_t aOffset, int64_t aTime, int64_t aDuration, int64_t aTimecode)
+  : MediaData(VIDEO_FRAME, aOffset, aTime, aDuration),
     mTimecode(aTimecode),
     mDuplicate(true),
     mKeyframe(false)
 {
   MOZ_COUNT_CTOR(VideoData);
-  NS_ASSERTION(aEndTime >= aTime, "Frame must start before it ends.");
+  NS_ASSERTION(mDuration >= 0, "Frame must have non-negative duration.");
 }
 
 VideoData::VideoData(int64_t aOffset,
-          int64_t aTime,
-          int64_t aEndTime,
-          bool aKeyframe,
-          int64_t aTimecode,
-          nsIntSize aDisplay)
-  : mDisplay(aDisplay),
-    mOffset(aOffset),
-    mTime(aTime),
-    mEndTime(aEndTime),
+                     int64_t aTime,
+                     int64_t aDuration,
+                     bool aKeyframe,
+                     int64_t aTimecode,
+                     nsIntSize aDisplay)
+  : MediaData(VIDEO_FRAME, aOffset, aTime, aDuration),
+    mDisplay(aDisplay),
     mTimecode(aTimecode),
     mDuplicate(false),
     mKeyframe(aKeyframe)
 {
   MOZ_COUNT_CTOR(VideoData);
-  NS_ASSERTION(aEndTime >= aTime, "Frame must start before it ends.");
+  NS_ASSERTION(mDuration >= 0, "Frame must have non-negative duration.");
 }
 
 VideoData::~VideoData()
 {
   MOZ_COUNT_DTOR(VideoData);
 }
 
+/* static */
+VideoData* VideoData::ShallowCopyUpdateDuration(VideoData* aOther,
+                                                int64_t aDuration)
+{
+  VideoData* v = new VideoData(aOther->mOffset,
+                               aOther->mTime,
+                               aDuration,
+                               aOther->mKeyframe,
+                               aOther->mTimecode,
+                               aOther->mDisplay);
+  v->mImage = aOther->mImage;
+  return v;
+}
 
 VideoData* VideoData::Create(VideoInfo& aInfo,
                              ImageContainer* aContainer,
                              Image* aImage,
                              int64_t aOffset,
                              int64_t aTime,
-                             int64_t aEndTime,
+                             int64_t aDuration,
                              const YCbCrBuffer& aBuffer,
                              bool aKeyframe,
                              int64_t aTimecode,
                              nsIntRect aPicture)
 {
   if (!aImage && !aContainer) {
     // Create a dummy VideoData with no image. This gives us something to
     // send to media streams if necessary.
     nsAutoPtr<VideoData> v(new VideoData(aOffset,
                                          aTime,
-                                         aEndTime,
+                                         aDuration,
                                          aKeyframe,
                                          aTimecode,
                                          aInfo.mDisplay));
     return v.forget();
   }
 
   // The following situation should never happen unless there is a bug
   // in the decoder
@@ -194,17 +203,17 @@ VideoData* VideoData::Create(VideoInfo& 
     // The specified picture dimensions can't be contained inside the video
     // frame, we'll stomp memory if we try to copy it. Fail.
     NS_WARNING("Overflowing picture rect");
     return nullptr;
   }
 
   nsAutoPtr<VideoData> v(new VideoData(aOffset,
                                        aTime,
-                                       aEndTime,
+                                       aDuration,
                                        aKeyframe,
                                        aTimecode,
                                        aInfo.mDisplay));
   const YCbCrBuffer::Plane &Y = aBuffer.mPlanes[0];
   const YCbCrBuffer::Plane &Cb = aBuffer.mPlanes[1];
   const YCbCrBuffer::Plane &Cr = aBuffer.mPlanes[2];
 
   if (!aImage) {
@@ -253,77 +262,77 @@ VideoData* VideoData::Create(VideoInfo& 
 
   return v.forget();
 }
 
 VideoData* VideoData::Create(VideoInfo& aInfo,
                              ImageContainer* aContainer,
                              int64_t aOffset,
                              int64_t aTime,
-                             int64_t aEndTime,
+                             int64_t aDuration,
                              const YCbCrBuffer& aBuffer,
                              bool aKeyframe,
                              int64_t aTimecode,
                              nsIntRect aPicture)
 {
-  return Create(aInfo, aContainer, nullptr, aOffset, aTime, aEndTime, aBuffer,
+  return Create(aInfo, aContainer, nullptr, aOffset, aTime, aDuration, aBuffer,
                 aKeyframe, aTimecode, aPicture);
 }
 
 VideoData* VideoData::Create(VideoInfo& aInfo,
                              Image* aImage,
                              int64_t aOffset,
                              int64_t aTime,
-                             int64_t aEndTime,
+                             int64_t aDuration,
                              const YCbCrBuffer& aBuffer,
                              bool aKeyframe,
                              int64_t aTimecode,
                              nsIntRect aPicture)
 {
-  return Create(aInfo, nullptr, aImage, aOffset, aTime, aEndTime, aBuffer,
+  return Create(aInfo, nullptr, aImage, aOffset, aTime, aDuration, aBuffer,
                 aKeyframe, aTimecode, aPicture);
 }
 
 VideoData* VideoData::CreateFromImage(VideoInfo& aInfo,
                                       ImageContainer* aContainer,
                                       int64_t aOffset,
                                       int64_t aTime,
-                                      int64_t aEndTime,
+                                      int64_t aDuration,
                                       const nsRefPtr<Image>& aImage,
                                       bool aKeyframe,
                                       int64_t aTimecode,
                                       nsIntRect aPicture)
 {
   nsAutoPtr<VideoData> v(new VideoData(aOffset,
                                        aTime,
-                                       aEndTime,
+                                       aDuration,
                                        aKeyframe,
                                        aTimecode,
                                        aInfo.mDisplay));
   v->mImage = aImage;
   return v.forget();
 }
 
 #ifdef MOZ_OMX_DECODER
 VideoData* VideoData::Create(VideoInfo& aInfo,
                              ImageContainer* aContainer,
                              int64_t aOffset,
                              int64_t aTime,
-                             int64_t aEndTime,
+                             int64_t aDuration,
                              mozilla::layers::GraphicBufferLocked* aBuffer,
                              bool aKeyframe,
                              int64_t aTimecode,
                              nsIntRect aPicture)
 {
   if (!aContainer) {
     // Create a dummy VideoData with no image. This gives us something to
     // send to media streams if necessary.
     nsAutoPtr<VideoData> v(new VideoData(aOffset,
                                          aTime,
-                                         aEndTime,
+                                         aDuration,
                                          aKeyframe,
                                          aTimecode,
                                          aInfo.mDisplay));
     return v.forget();
   }
 
   // The following situations could be triggered by invalid input
   if (aPicture.width <= 0 || aPicture.height <= 0) {
@@ -340,17 +349,17 @@ VideoData* VideoData::Create(VideoInfo& 
     // The specified picture dimensions can't be contained inside the video
     // frame, we'll stomp memory if we try to copy it. Fail.
     NS_WARNING("Overflowing picture rect");
     return nullptr;
   }
 
   nsAutoPtr<VideoData> v(new VideoData(aOffset,
                                        aTime,
-                                       aEndTime,
+                                       aDuration,
                                        aKeyframe,
                                        aTimecode,
                                        aInfo.mDisplay));
 
   ImageFormat format = GRALLOC_PLANAR_YCBCR;
   v->mImage = aContainer->CreateImage(&format, 1);
   if (!v->mImage) {
     return nullptr;
@@ -496,17 +505,17 @@ nsresult MediaDecoderReader::DecodeToTar
         if (video) {
           VideoQueue().PushFront(video.forget());
         }
         break;
       }
       video = VideoQueue().PeekFront();
       // If the frame end time is less than the seek target, we won't want
       // to display this frame after the seek, so discard it.
-      if (video && video->mEndTime <= aTarget) {
+      if (video && video->GetEndTime() <= aTarget) {
         if (startTime == -1) {
           startTime = video->mTime;
         }
         VideoQueue().PopFront();
       } else {
         video.forget();
         break;
       }
--- a/content/media/MediaDecoderReader.h
+++ b/content/media/MediaDecoderReader.h
@@ -89,67 +89,94 @@ public:
   {
     return HasVideo() || HasAudio();
   }
 
   VideoInfo mVideo;
   AudioInfo mAudio;
 };
 
+// Container that holds media samples.
+class MediaData {
+public:
+
+  enum Type {
+    AUDIO_SAMPLES = 0,
+    VIDEO_FRAME = 1
+  };
+
+  MediaData(Type aType,
+            int64_t aOffset,
+            int64_t aTimestamp,
+            int64_t aDuration)
+    : mType(aType),
+      mOffset(aOffset),
+      mTime(aTimestamp),
+      mDuration(aDuration)
+  {}
+
+  virtual ~MediaData() {}
+
+  // Type of contained data.
+  const Type mType;
+
+  // Approximate byte offset where this data was demuxed from its media.
+  const int64_t mOffset;
+
+  // Start time of sample, in microseconds.
+  const int64_t mTime;
+
+  // Duration of sample, in microseconds.
+  const int64_t mDuration;
+
+  int64_t GetEndTime() const { return mTime + mDuration; }
+
+};
+
 // Holds chunk a decoded audio frames.
-class AudioData {
+class AudioData : public MediaData {
 public:
 
   AudioData(int64_t aOffset,
             int64_t aTime,
             int64_t aDuration,
             uint32_t aFrames,
             AudioDataValue* aData,
             uint32_t aChannels)
-  : mOffset(aOffset),
-    mTime(aTime),
-    mDuration(aDuration),
+  : MediaData(AUDIO_SAMPLES, aOffset, aTime, aDuration),
     mFrames(aFrames),
     mChannels(aChannels),
     mAudioData(aData)
   {
     MOZ_COUNT_CTOR(AudioData);
   }
 
   ~AudioData()
   {
     MOZ_COUNT_DTOR(AudioData);
   }
 
   // If mAudioBuffer is null, creates it from mAudioData.
   void EnsureAudioBuffer();
 
-  int64_t GetEnd() { return mTime + mDuration; }
-
-  // Approximate byte offset of the end of the page on which this chunk
-  // ends.
-  const int64_t mOffset;
-
-  int64_t mTime; // Start time of data in usecs.
-  const int64_t mDuration; // In usecs.
   const uint32_t mFrames;
   const uint32_t mChannels;
   // At least one of mAudioBuffer/mAudioData must be non-null.
   // mChannels channels, each with mFrames frames
   nsRefPtr<SharedBuffer> mAudioBuffer;
   // mFrames frames, each with mChannels values
   nsAutoArrayPtr<AudioDataValue> mAudioData;
 };
 
 namespace layers {
 class GraphicBufferLocked;
 }
 
 // Holds a decoded video frame, in YCbCr format. These are queued in the reader.
-class VideoData {
+class VideoData : public MediaData {
 public:
   typedef layers::ImageContainer ImageContainer;
   typedef layers::Image Image;
 
   // YCbCr data obtained from decoding the video. The index's are:
   //   0 = Y
   //   1 = Cb
   //   2 = Cr
@@ -174,111 +201,113 @@ public:
   // Returns nsnull if an error occurs. This may indicate that memory couldn't
   // be allocated to create the VideoData object, or it may indicate some
   // problem with the input data (e.g. negative stride).
   static VideoData* Create(VideoInfo& aInfo,
                            ImageContainer* aContainer,
                            Image* aImage,
                            int64_t aOffset,
                            int64_t aTime,
-                           int64_t aEndTime,
+                           int64_t aDuration,
                            const YCbCrBuffer &aBuffer,
                            bool aKeyframe,
                            int64_t aTimecode,
                            nsIntRect aPicture);
 
   // Variant that always makes a copy of aBuffer
   static VideoData* Create(VideoInfo& aInfo,
                            ImageContainer* aContainer,
                            int64_t aOffset,
                            int64_t aTime,
-                           int64_t aEndTime,
+                           int64_t aDuration,
                            const YCbCrBuffer &aBuffer,
                            bool aKeyframe,
                            int64_t aTimecode,
                            nsIntRect aPicture);
 
   // Variant to create a VideoData instance given an existing aImage
   static VideoData* Create(VideoInfo& aInfo,
                            Image* aImage,
                            int64_t aOffset,
                            int64_t aTime,
-                           int64_t aEndTime,
+                           int64_t aDuration,
                            const YCbCrBuffer &aBuffer,
                            bool aKeyframe,
                            int64_t aTimecode,
                            nsIntRect aPicture);
 
   static VideoData* Create(VideoInfo& aInfo,
                            ImageContainer* aContainer,
                            int64_t aOffset,
                            int64_t aTime,
-                           int64_t aEndTime,
+                           int64_t aDuration,
                            layers::GraphicBufferLocked* aBuffer,
                            bool aKeyframe,
                            int64_t aTimecode,
                            nsIntRect aPicture);
 
   static VideoData* CreateFromImage(VideoInfo& aInfo,
                                     ImageContainer* aContainer,
                                     int64_t aOffset,
                                     int64_t aTime,
-                                    int64_t aEndTime,
+                                    int64_t aDuration,
                                     const nsRefPtr<Image>& aImage,
                                     bool aKeyframe,
                                     int64_t aTimecode,
                                     nsIntRect aPicture);
 
+  // Creates a new VideoData identical to aOther, but with a different
+  // specified duration. All data from aOther is copied into the new
+  // VideoData. The new VideoData's mImage field holds a reference to
+  // aOther's mImage, i.e. the Image is not copied. This function is useful
+  // in reader backends that can't determine the duration of a VideoData
+  // until the next frame is decoded, i.e. it's a way to change the const
+  // duration field on a VideoData.
+  static VideoData* ShallowCopyUpdateDuration(VideoData* aOther,
+                                              int64_t aDuration);
+
   // Constructs a duplicate VideoData object. This intrinsically tells the
   // player that it does not need to update the displayed frame when this
   // frame is played; this frame is identical to the previous.
   static VideoData* CreateDuplicate(int64_t aOffset,
                                     int64_t aTime,
-                                    int64_t aEndTime,
+                                    int64_t aDuration,
                                     int64_t aTimecode)
   {
-    return new VideoData(aOffset, aTime, aEndTime, aTimecode);
+    return new VideoData(aOffset, aTime, aDuration, aTimecode);
   }
 
   ~VideoData();
 
-  int64_t GetEnd() { return mEndTime; }
-
   // Dimensions at which to display the video frame. The picture region
   // will be scaled to this size. This is should be the picture region's
   // dimensions scaled with respect to its aspect ratio.
-  nsIntSize mDisplay;
-
-  // Approximate byte offset of the end of the frame in the media.
-  int64_t mOffset;
-
-  // Start time of frame in microseconds.
-  int64_t mTime;
-
-  // End time of frame in microseconds.
-  int64_t mEndTime;
+  const nsIntSize mDisplay;
 
   // Codec specific internal time code. For Ogg based codecs this is the
   // granulepos.
-  int64_t mTimecode;
+  const int64_t mTimecode;
 
   // This frame's image.
   nsRefPtr<Image> mImage;
 
   // When true, denotes that this frame is identical to the frame that
   // came before; it's a duplicate. mBuffer will be empty.
-  bool mDuplicate;
-  bool mKeyframe;
+  const bool mDuplicate;
+  const bool mKeyframe;
 
 public:
-  VideoData(int64_t aOffset, int64_t aTime, int64_t aEndTime, int64_t aTimecode);
+  VideoData(int64_t aOffset,
+            int64_t aTime,
+            int64_t aDuration,
+            int64_t aTimecode);
 
   VideoData(int64_t aOffset,
             int64_t aTime,
-            int64_t aEndTime,
+            int64_t aDuration,
             bool aKeyframe,
             int64_t aTimecode,
             nsIntSize aDisplay);
 
 };
 
 // Thread and type safe wrapper around nsDeque.
 template <class T>
@@ -395,17 +424,17 @@ template <class T> class MediaQueue : pr
   // Elements whose start time is before aTime are ignored.
   void GetElementsAfter(int64_t aTime, nsTArray<T*>* aResult) {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     if (!GetSize())
       return;
     int32_t i;
     for (i = GetSize() - 1; i > 0; --i) {
       T* v = static_cast<T*>(ObjectAt(i));
-      if (v->GetEnd() < aTime)
+      if (v->GetEndTime() < aTime)
         break;
     }
     // Elements less than i have a end time before aTime. It's also possible
     // that the element at i has a end time before aTime, but that's OK.
     for (; i < GetSize(); ++i) {
       aResult->AppendElement(static_cast<T*>(ObjectAt(i)));
     }
   }
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -549,17 +549,17 @@ void MediaDecoderStateMachine::SendStrea
                OnStateMachineThread(), "Should be on decode thread or state machine thread");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
   if (aAudio->mTime <= aStream->mLastAudioPacketTime) {
     // ignore packet that we've already processed
     return;
   }
   aStream->mLastAudioPacketTime = aAudio->mTime;
-  aStream->mLastAudioPacketEndTime = aAudio->GetEnd();
+  aStream->mLastAudioPacketEndTime = aAudio->GetEndTime();
 
   // This logic has to mimic AudioLoop closely to make sure we write
   // the exact same silences
   CheckedInt64 audioWrittenOffset = UsecsToFrames(mInfo.mAudio.mRate,
       aStream->mInitialTime + mStartTime) + aStream->mAudioFramesWritten;
   CheckedInt64 frameOffset = UsecsToFrames(mInfo.mAudio.mRate, aAudio->mTime);
   if (!audioWrittenOffset.isValid() || !frameOffset.isValid())
     return;
@@ -687,24 +687,24 @@ void MediaDecoderStateMachine::SendStrea
                            v->mTime - (stream->mNextVideoTime + mStartTime)));
         // Write last video frame to catch up. mLastVideoImage can be null here
         // which is fine, it just means there's no video.
         WriteVideoToMediaStream(stream->mLastVideoImage,
           v->mTime - (stream->mNextVideoTime + mStartTime), stream->mLastVideoImageDisplaySize,
             &output);
         stream->mNextVideoTime = v->mTime - mStartTime;
       }
-      if (stream->mNextVideoTime + mStartTime < v->mEndTime) {
+      if (stream->mNextVideoTime + mStartTime < v->GetEndTime()) {
         LOG(PR_LOG_DEBUG, ("%p Decoder writing video frame %lld to MediaStream %p for %lld ms",
                            mDecoder.get(), v->mTime, mediaStream,
-                           v->mEndTime - (stream->mNextVideoTime + mStartTime)));
+                           v->GetEndTime() - (stream->mNextVideoTime + mStartTime)));
         WriteVideoToMediaStream(v->mImage,
-            v->mEndTime - (stream->mNextVideoTime + mStartTime), v->mDisplay,
+            v->GetEndTime() - (stream->mNextVideoTime + mStartTime), v->mDisplay,
             &output);
-        stream->mNextVideoTime = v->mEndTime - mStartTime;
+        stream->mNextVideoTime = v->GetEndTime() - mStartTime;
         stream->mLastVideoImage = v->mImage;
         stream->mLastVideoImageDisplaySize = v->mDisplay;
       } else {
         LOG(PR_LOG_DEBUG, ("%p Decoder skipping writing video frame %lld to MediaStream",
                            mDecoder.get(), v->mTime));
       }
     }
     if (output.GetDuration() > 0) {
@@ -737,17 +737,17 @@ void MediaDecoderStateMachine::SendStrea
       if (!a)
         break;
       // Packet times are not 100% reliable so this may discard packets that
       // actually contain data for mCurrentFrameTime. This means if someone might
       // create a new output stream and we actually don't have the audio for the
       // very start. That's OK, we'll play silence instead for a brief moment.
       // That's OK. Seeking to this time would have a similar issue for such
       // badly muxed resources.
-      if (a->GetEnd() >= minLastAudioPacketTime) {
+      if (a->GetEndTime() >= minLastAudioPacketTime) {
         mReader->AudioQueue().PushFront(a.forget());
         break;
       }
     }
 
     if (finished) {
       mAudioCompleted = true;
       UpdateReadyState();
@@ -2053,17 +2053,17 @@ void MediaDecoderStateMachine::DecodeSee
                               seekTime <= audio->mTime + audio->mDuration),
                     "Seek target should lie inside the first audio block after seek");
       int64_t startTime = (audio && audio->mTime < seekTime) ? audio->mTime : seekTime;
       mAudioStartTime = startTime;
       mPlayDuration = startTime - mStartTime;
       if (HasVideo()) {
         VideoData* video = mReader->VideoQueue().PeekFront();
         if (video) {
-          NS_ASSERTION((video->mTime <= seekTime && seekTime <= video->mEndTime) ||
+          NS_ASSERTION((video->mTime <= seekTime && seekTime <= video->GetEndTime()) ||
                         mReader->VideoQueue().IsFinished(),
             "Seek target should lie inside the first frame after seek, unless it's the last frame.");
           {
             ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
             RenderVideoFrame(video, TimeStamp::Now());
           }
           nsCOMPtr<nsIRunnable> event =
             NS_NewRunnableMethod(mDecoder, &MediaDecoder::Invalidate);
@@ -2491,17 +2491,17 @@ void MediaDecoderStateMachine::AdvanceFr
   NS_ASSERTION(clock_time >= mStartTime, "Should have positive clock time.");
   nsAutoPtr<VideoData> currentFrame;
 #ifdef PR_LOGGING
   int32_t droppedFrames = 0;
 #endif
   if (mReader->VideoQueue().GetSize() > 0) {
     VideoData* frame = mReader->VideoQueue().PeekFront();
     while (mRealTime || clock_time >= frame->mTime) {
-      mVideoFrameEndTime = frame->mEndTime;
+      mVideoFrameEndTime = frame->GetEndTime();
       currentFrame = frame;
 #ifdef PR_LOGGING
       if (!PR_GetEnv("MOZ_QUIET")) {
         LOG(PR_LOG_DEBUG, ("%p Decoder discarding video frame %lld", mDecoder.get(), frame->mTime));
         if (droppedFrames++) {
           LOG(PR_LOG_DEBUG, ("%p Decoder discarding video frame %lld (%d so far)",
             mDecoder.get(), frame->mTime, droppedFrames - 1));
         }
@@ -2567,17 +2567,17 @@ void MediaDecoderStateMachine::AdvanceFr
       ScheduleStateMachine();
       return;
     }
     MediaDecoder::FrameStatistics& frameStats = mDecoder->GetFrameStatistics();
     frameStats.NotifyPresentedFrame();
     double frameDelay = double(clock_time - currentFrame->mTime) / USECS_PER_S;
     NS_ASSERTION(frameDelay >= 0.0, "Frame should never be displayed early.");
     frameStats.NotifyFrameDelay(frameDelay);
-    remainingTime = currentFrame->mEndTime - clock_time;
+    remainingTime = currentFrame->GetEndTime() - clock_time;
     currentFrame = nullptr;
   }
 
   // Cap the current time to the larger of the audio and video end time.
   // This ensures that if we're running off the system clock, we don't
   // advance the clock to after the media end time.
   if (mVideoFrameEndTime != -1 || mAudioEndTime != -1) {
     // These will be non -1 if we've displayed a video frame, or played an audio frame.
--- a/content/media/gstreamer/GStreamerReader.cpp
+++ b/content/media/gstreamer/GStreamerReader.cpp
@@ -554,35 +554,37 @@ bool GStreamerReader::DecodeVideoFrame(b
   int64_t timestamp = GST_BUFFER_TIMESTAMP(buffer);
   {
     ReentrantMonitorAutoEnter mon(mGstThreadsMonitor);
     timestamp = gst_segment_to_stream_time(&mVideoSegment,
                                            GST_FORMAT_TIME, timestamp);
   }
   NS_ASSERTION(GST_CLOCK_TIME_IS_VALID(timestamp),
                "frame has invalid timestamp");
-  int64_t nextTimestamp = timestamp = GST_TIME_AS_USECONDS(timestamp);
-  if (GST_CLOCK_TIME_IS_VALID(GST_BUFFER_DURATION(buffer)))
-    nextTimestamp += GST_TIME_AS_USECONDS(GST_BUFFER_DURATION(buffer));
-  else if (fpsNum && fpsDen)
-    /* add 1-frame duration */
-    nextTimestamp += gst_util_uint64_scale(GST_USECOND, fpsNum, fpsDen);
 
+  timestamp = GST_TIME_AS_USECONDS(timestamp);
   if (timestamp < aTimeThreshold) {
     LOG(PR_LOG_DEBUG, ("skipping frame %" GST_TIME_FORMAT
                        " threshold %" GST_TIME_FORMAT,
                        GST_TIME_ARGS(timestamp), GST_TIME_ARGS(aTimeThreshold)));
     gst_buffer_unref(buffer);
     return true;
   }
 
   if (!buffer)
     /* no more frames */
     return false;
 
+  int64_t duration = 0;
+  if (GST_CLOCK_TIME_IS_VALID(GST_BUFFER_DURATION(buffer)))
+    duration = GST_TIME_AS_USECONDS(GST_BUFFER_DURATION(buffer));
+  else if (fpsNum && fpsDen)
+    /* 1-frame duration */
+    duration = gst_util_uint64_scale(GST_USECOND, fpsNum, fpsDen);
+
   nsRefPtr<PlanarYCbCrImage> image;
   GstMozVideoBufferData* bufferdata = reinterpret_cast<GstMozVideoBufferData*>
       GST_IS_MOZ_VIDEO_BUFFER(buffer)?gst_moz_video_buffer_get_data(GST_MOZ_VIDEO_BUFFER(buffer)):nullptr;
 
   if(bufferdata)
     image = bufferdata->mImage;
 
   if (!image) {
@@ -616,20 +618,19 @@ bool GStreamerReader::DecodeVideoFrame(b
         i, height);
     b.mPlanes[i].mWidth = gst_video_format_get_component_width(format,
         i, width);
     b.mPlanes[i].mOffset = 0;
     b.mPlanes[i].mSkip = 0;
   }
 
   isKeyframe = !GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_DELTA_UNIT);
-  /* XXX ? */
-  int64_t offset = 0;
+  int64_t offset = mDecoder->GetResource()->Tell(); // Estimate location in media.
   VideoData* video = VideoData::Create(mInfo.mVideo, image, offset,
-                                       timestamp, nextTimestamp, b,
+                                       timestamp, duration, b,
                                        isKeyframe, -1, mPicture);
   mVideoQueue.Push(video);
   gst_buffer_unref(buffer);
 
   return true;
 }
 
 nsresult GStreamerReader::Seek(int64_t aTarget,
--- a/content/media/ogg/OggReader.cpp
+++ b/content/media/ogg/OggReader.cpp
@@ -798,17 +798,17 @@ nsresult OggReader::DecodeTheora(ogg_pac
     // The end time of this frame is already before the current playback
     // position. It will never be displayed, don't bother enqueing it.
     return NS_OK;
   }
 
   if (ret == TH_DUPFRAME) {
     VideoData* v = VideoData::CreateDuplicate(mDecoder->GetResource()->Tell(),
                                               time,
-                                              endTime,
+                                              endTime - time,
                                               aPacket->granulepos);
     mVideoQueue.Push(v);
   } else if (ret == 0) {
     th_ycbcr_buffer buffer;
     ret = th_decode_ycbcr_out(mTheoraState->mCtx, buffer);
     NS_ASSERTION(ret == 0, "th_decode_ycbcr_out failed");
     bool isKeyframe = th_packet_iskeyframe(aPacket) == 1;
     VideoData::YCbCrBuffer b;
@@ -819,17 +819,17 @@ nsresult OggReader::DecodeTheora(ogg_pac
       b.mPlanes[i].mStride = buffer[i].stride;
       b.mPlanes[i].mOffset = b.mPlanes[i].mSkip = 0;
     }
 
     VideoData *v = VideoData::Create(mInfo.mVideo,
                                      mDecoder->GetImageContainer(),
                                      mDecoder->GetResource()->Tell(),
                                      time,
-                                     endTime,
+                                     endTime - time,
                                      b,
                                      isKeyframe,
                                      aPacket->granulepos,
                                      mPicture);
     if (!v) {
       // There may be other reasons for this error, but for
       // simplicity just assume the worst case: out of memory.
       NS_WARNING("Failed to allocate memory for video frame");
--- a/content/media/omx/MediaOmxReader.cpp
+++ b/content/media/omx/MediaOmxReader.cpp
@@ -257,27 +257,27 @@ bool MediaOmxReader::DecodeVideoFrame(bo
       b.mPlanes[2].mWidth = frame.Cr.mWidth;
       b.mPlanes[2].mOffset = frame.Cr.mOffset;
       b.mPlanes[2].mSkip = frame.Cr.mSkip;
 
       v = VideoData::Create(mInfo.mVideo,
                             mDecoder->GetImageContainer(),
                             pos,
                             frame.mTimeUs,
-                            frame.mTimeUs+1, // We don't know the end time.
+                            1, // We don't know the duration.
                             b,
                             frame.mKeyFrame,
                             -1,
                             picture);
     } else {
       v = VideoData::Create(mInfo.mVideo,
                             mDecoder->GetImageContainer(),
                             pos,
                             frame.mTimeUs,
-                            frame.mTimeUs+1, // We don't know the end time.
+                            1, // We don't know the duration.
                             frame.mGraphicBuffer,
                             frame.mKeyFrame,
                             -1,
                             picture);
     }
 
     if (!v) {
       NS_WARNING("Unable to create VideoData");
--- a/content/media/plugins/MediaPluginReader.cpp
+++ b/content/media/plugins/MediaPluginReader.cpp
@@ -21,18 +21,17 @@ typedef mozilla::layers::Image Image;
 MediaPluginReader::MediaPluginReader(AbstractMediaDecoder *aDecoder,
                                      const nsACString& aContentType) :
   MediaDecoderReader(aDecoder),
   mType(aContentType),
   mPlugin(nullptr),
   mHasAudio(false),
   mHasVideo(false),
   mVideoSeekTimeUs(-1),
-  mAudioSeekTimeUs(-1),
-  mLastVideoFrame(nullptr)
+  mAudioSeekTimeUs(-1)
 {
 }
 
 MediaPluginReader::~MediaPluginReader()
 {
   ResetDecode();
 }
 
@@ -99,58 +98,57 @@ nsresult MediaPluginReader::ReadMetadata
  *aTags = nullptr;
   return NS_OK;
 }
 
 // Resets all state related to decoding, emptying all buffers etc.
 nsresult MediaPluginReader::ResetDecode()
 {
   if (mLastVideoFrame) {
-    delete mLastVideoFrame;
     mLastVideoFrame = nullptr;
   }
   if (mPlugin) {
     GetMediaPluginHost()->DestroyDecoder(mPlugin);
     mPlugin = nullptr;
   }
 
   return NS_OK;
 }
 
 bool MediaPluginReader::DecodeVideoFrame(bool &aKeyframeSkip,
-                                           int64_t aTimeThreshold)
+                                         int64_t aTimeThreshold)
 {
   // Record number of frames decoded and parsed. Automatically update the
   // stats counters using the AutoNotifyDecoded stack-based class.
   uint32_t parsed = 0, decoded = 0;
   AbstractMediaDecoder::AutoNotifyDecoded autoNotify(mDecoder, parsed, decoded);
 
   // Throw away the currently buffered frame if we are seeking.
   if (mLastVideoFrame && mVideoSeekTimeUs != -1) {
-    delete mLastVideoFrame;
     mLastVideoFrame = nullptr;
   }
 
   ImageBufferCallback bufferCallback(mDecoder->GetImageContainer());
   nsRefPtr<Image> currentImage;
 
   // Read next frame
   while (true) {
     MPAPI::VideoFrame frame;
     if (!mPlugin->ReadVideo(mPlugin, &frame, mVideoSeekTimeUs, &bufferCallback)) {
       // We reached the end of the video stream. If we have a buffered
       // video frame, push it the video queue using the total duration
       // of the video as the end time.
       if (mLastVideoFrame) {
         int64_t durationUs;
         mPlugin->GetDuration(mPlugin, &durationUs);
-        mLastVideoFrame->mEndTime = (durationUs > mLastVideoFrame->mTime)
-                                  ? durationUs
-                                  : mLastVideoFrame->mTime;
-        mVideoQueue.Push(mLastVideoFrame);
+        if (durationUs < mLastVideoFrame->mTime) {
+          durationUs = 0;
+        }
+        mVideoQueue.Push(VideoData::ShallowCopyUpdateDuration(mLastVideoFrame,
+                                                              durationUs));
         mLastVideoFrame = nullptr;
       }
       return false;
     }
     mVideoSeekTimeUs = -1;
 
     if (aKeyframeSkip) {
       // Disable keyframe skipping for now as
@@ -167,17 +165,17 @@ bool MediaPluginReader::DecodeVideoFrame
 
     if (frame.mSize == 0)
       return true;
 
     currentImage = bufferCallback.GetImage();
     int64_t pos = mDecoder->GetResource()->Tell();
     nsIntRect picture = mPicture;
  
-    VideoData *v;
+    nsAutoPtr<VideoData> v;
     if (currentImage) {
       gfxIntSize frameSize = currentImage->GetSize();
       if (frameSize.width != mInitialFrame.width ||
           frameSize.height != mInitialFrame.height) {
         // Frame size is different from what the container reports. This is legal,
         // and we will preserve the ratio of the crop rectangle as it
         // was reported relative to the picture size reported by the container.
         picture.x = (mPicture.x * frameSize.width) / mInitialFrame.width;
@@ -185,17 +183,17 @@ bool MediaPluginReader::DecodeVideoFrame
         picture.width = (frameSize.width * mPicture.width) / mInitialFrame.width;
         picture.height = (frameSize.height * mPicture.height) / mInitialFrame.height;
       }
 
       v = VideoData::CreateFromImage(mInfo.mVideo,
                                      mDecoder->GetImageContainer(),
                                      pos,
                                      frame.mTimeUs,
-                                     frame.mTimeUs+1, // We don't know the end time.
+                                     1, // We don't know the duration yet.
                                      currentImage,
                                      frame.mKeyFrame,
                                      -1,
                                      picture);
     } else {
       // Assume YUV
       VideoData::YCbCrBuffer b;
       b.mPlanes[0].mData = static_cast<uint8_t *>(frame.Y.mData);
@@ -231,17 +229,17 @@ bool MediaPluginReader::DecodeVideoFrame
         picture.height = (frame.Y.mHeight * mPicture.height) / mInitialFrame.height;
       }
 
       // This is the approximate byte position in the stream.
       v = VideoData::Create(mInfo.mVideo,
                             mDecoder->GetImageContainer(),
                             pos,
                             frame.mTimeUs,
-                            frame.mTimeUs+1, // We don't know the end time.
+                            1, // We don't know the duration yet.
                             b,
                             frame.mKeyFrame,
                             -1,
                             picture);
     }
  
     if (!v) {
       return false;
@@ -254,28 +252,31 @@ bool MediaPluginReader::DecodeVideoFrame
     // buffered in MediaPluginReader and push it into the queue as soon
     // we read the following frame so we can use that frame's start time as
     // the end time of the buffered frame.
     if (!mLastVideoFrame) {
       mLastVideoFrame = v;
       continue;
     }
 
-    mLastVideoFrame->mEndTime = v->mTime;
+    // Calculate the duration as the timestamp of the current frame minus the
+    // timestamp of the previous frame. We can then return the previously
+    // decoded frame, and it will have a valid timestamp.
+    int64_t duration = v->mTime - mLastVideoFrame->mTime;
+    mLastVideoFrame = VideoData::ShallowCopyUpdateDuration(mLastVideoFrame, duration);
 
     // We have the start time of the next frame, so we can push the previous
     // frame into the queue, except if the end time is below the threshold,
     // in which case it wouldn't be displayed anyway.
-    if (mLastVideoFrame->mEndTime < aTimeThreshold) {
-      delete mLastVideoFrame;
+    if (mLastVideoFrame->GetEndTime() < aTimeThreshold) {
       mLastVideoFrame = nullptr;
       continue;
     }
 
-    mVideoQueue.Push(mLastVideoFrame);
+    mVideoQueue.Push(mLastVideoFrame.forget());
 
     // Buffer the current frame we just decoded.
     mLastVideoFrame = v;
 
     break;
   }
 
   return true;
--- a/content/media/plugins/MediaPluginReader.h
+++ b/content/media/plugins/MediaPluginReader.h
@@ -5,16 +5,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 #if !defined(MediaPluginReader_h_)
 #define MediaPluginReader_h_
 
 #include "mozilla/Attributes.h"
 #include "MediaResource.h"
 #include "MediaDecoderReader.h"
 #include "ImageContainer.h"
+#include "nsAutoPtr.h"
 #include "mozilla/layers/SharedRGBImage.h"
  
 #include "MPAPI.h"
 
 class nsACString;
 
 namespace mozilla {
 
@@ -33,17 +34,17 @@ class MediaPluginReader : public MediaDe
   nsCString mType;
   MPAPI::Decoder *mPlugin;
   bool mHasAudio;
   bool mHasVideo;
   nsIntRect mPicture;
   nsIntSize mInitialFrame;
   int64_t mVideoSeekTimeUs;
   int64_t mAudioSeekTimeUs;
-  VideoData *mLastVideoFrame;
+  nsAutoPtr<VideoData> mLastVideoFrame;
 public:
   MediaPluginReader(AbstractMediaDecoder* aDecoder,
                     const nsACString& aContentType);
   ~MediaPluginReader();
 
   virtual nsresult Init(MediaDecoderReader* aCloneDonor);
   virtual nsresult ResetDecode();
 
--- a/content/media/raw/RawReader.cpp
+++ b/content/media/raw/RawReader.cpp
@@ -206,17 +206,17 @@ bool RawReader::DecodeVideoFrame(bool &a
   b.mPlanes[2].mHeight = mMetadata.frameHeight / 2;
   b.mPlanes[2].mWidth = mMetadata.frameWidth / 2;
   b.mPlanes[2].mOffset = b.mPlanes[2].mSkip = 0;
 
   VideoData *v = VideoData::Create(mInfo.mVideo,
                                    mDecoder->GetImageContainer(),
                                    -1,
                                    currentFrameTime,
-                                   currentFrameTime + (USECS_PER_S / mFrameRate),
+                                   (USECS_PER_S / mFrameRate),
                                    b,
                                    1, // In raw video every frame is a keyframe
                                    -1,
                                    mPicture);
   if (!v)
     return false;
 
   mVideoQueue.Push(v);
@@ -260,17 +260,17 @@ nsresult RawReader::Seek(int64_t aTime, 
       ReentrantMonitorAutoEnter autoMonitor(mDecoder->GetReentrantMonitor());
       if (mDecoder->IsShutdown()) {
         mCurrentFrame = frame;
         return NS_ERROR_FAILURE;
       }
     }
 
     nsAutoPtr<VideoData> video(mVideoQueue.PeekFront());
-    if (video && video->mEndTime < aTime) {
+    if (video && video->GetEndTime() < aTime) {
       mVideoQueue.PopFront();
       video = nullptr;
     } else {
       video.forget();
     }
   }
 
   return NS_OK;
--- a/content/media/webm/WebMReader.cpp
+++ b/content/media/webm/WebMReader.cpp
@@ -901,17 +901,17 @@ bool WebMReader::DecodeVideoFrame(bool &
         picture.width = (img->d_w * mPicture.width) / mInitialFrame.width;
         picture.height = (img->d_h * mPicture.height) / mInitialFrame.height;
       }
 
       VideoData *v = VideoData::Create(mInfo.mVideo,
                                        mDecoder->GetImageContainer(),
                                        holder->mOffset,
                                        tstamp_usecs,
-                                       next_tstamp / NS_PER_USEC,
+                                       (next_tstamp / NS_PER_USEC) - tstamp_usecs,
                                        b,
                                        si.is_kf,
                                        -1,
                                        picture);
       if (!v) {
         return false;
       }
       parsed++;
--- a/content/media/wmf/WMFReader.cpp
+++ b/content/media/wmf/WMFReader.cpp
@@ -840,17 +840,17 @@ WMFReader::CreateBasicVideoFrame(IMFSamp
   b.mPlanes[2].mWidth = halfWidth;
   b.mPlanes[2].mOffset = 0;
   b.mPlanes[2].mSkip = 0;
 
   VideoData *v = VideoData::Create(mInfo.mVideo,
                                    mDecoder->GetImageContainer(),
                                    aOffsetBytes,
                                    aTimestampUsecs,
-                                   aTimestampUsecs + aDurationUsecs,
+                                   aDurationUsecs,
                                    b,
                                    false,
                                    -1,
                                    mPictureRegion);
   if (twoDBuffer) {
     twoDBuffer->Unlock2D();
   } else {
     buffer->Unlock();
@@ -883,17 +883,17 @@ WMFReader::CreateD3DVideoFrame(IMFSample
                                   getter_AddRefs(image));
   NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
   NS_ENSURE_TRUE(image, E_FAIL);
 
   VideoData *v = VideoData::CreateFromImage(mInfo.mVideo,
                                             mDecoder->GetImageContainer(),
                                             aOffsetBytes,
                                             aTimestampUsecs,
-                                            aTimestampUsecs + aDurationUsecs,
+                                            aDurationUsecs,
                                             image.forget(),
                                             false,
                                             -1,
                                             mPictureRegion);
 
   NS_ENSURE_TRUE(v, E_FAIL);
   *aOutVideoData = v;