Bug 1333341 - Fix accumulating rounding error in VP8TrackEncoder. r=jesup, a=lizzard
authorAndreas Pehrson <pehrsons@gmail.com>
Tue, 24 Jan 2017 16:19:00 +0100
changeset 375715 b55972079c354cee4f9a29382d3d71b72b1a0b02
parent 375714 74c77cc41d328dd0f89317e310ed8d76e98c9d4b
child 375716 c82f913134cba5626f4421372afa545a0c9d94a1
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjesup, lizzard
bugs1333341
milestone53.0a2
Bug 1333341 - Fix accumulating rounding error in VP8TrackEncoder. r=jesup, a=lizzard MozReview-Commit-ID: 8XOKAuvpNuL
dom/media/encoder/VP8TrackEncoder.cpp
dom/media/encoder/VP8TrackEncoder.h
--- a/dom/media/encoder/VP8TrackEncoder.cpp
+++ b/dom/media/encoder/VP8TrackEncoder.cpp
@@ -182,17 +182,17 @@ VP8TrackEncoder::GetMetadata()
   meta->mWidth = mFrameWidth;
   meta->mHeight = mFrameHeight;
   meta->mDisplayWidth = mDisplayWidth;
   meta->mDisplayHeight = mDisplayHeight;
 
   return meta.forget();
 }
 
-bool
+nsresult
 VP8TrackEncoder::GetEncodedPartitions(EncodedFrameContainer& aData)
 {
   vpx_codec_iter_t iter = nullptr;
   EncodedFrame::FrameType frameType = EncodedFrame::VP8_P_FRAME;
   nsTArray<uint8_t> frameData;
   const vpx_codec_cx_pkt_t *pkt = nullptr;
   while ((pkt = vpx_codec_get_cx_data(mVPXContext, &iter)) != nullptr) {
     switch (pkt->kind) {
@@ -214,34 +214,55 @@ VP8TrackEncoder::GetEncodedPartitions(En
       break;
     }
   }
 
   if (!frameData.IsEmpty()) {
     // Copy the encoded data to aData.
     EncodedFrame* videoData = new EncodedFrame();
     videoData->SetFrameType(frameType);
+
     // Convert the timestamp and duration to Usecs.
     CheckedInt64 timestamp = FramesToUsecs(pkt->data.frame.pts, mTrackRate);
-    if (timestamp.isValid()) {
-      videoData->SetTimeStamp((uint64_t)timestamp.value());
+    if (!timestamp.isValid()) {
+      NS_ERROR("Microsecond timestamp overflow");
+      return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
+    }
+    videoData->SetTimeStamp((uint64_t)timestamp.value());
+
+    mExtractedDuration += pkt->data.frame.duration;
+    if (!mExtractedDuration.isValid()) {
+      NS_ERROR("Duration overflow");
+      return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
     }
-    CheckedInt64 duration = FramesToUsecs(pkt->data.frame.duration, mTrackRate);
-    if (duration.isValid()) {
-      videoData->SetDuration((uint64_t)duration.value());
+
+    CheckedInt64 totalDuration =
+      FramesToUsecs(mExtractedDuration.value(), mTrackRate);
+    if (!totalDuration.isValid()) {
+      NS_ERROR("Duration overflow");
+      return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
     }
+
+    CheckedInt64 duration = totalDuration - mExtractedDurationUs;
+    if (!duration.isValid()) {
+      NS_ERROR("Duration overflow");
+      return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
+    }
+
+    mExtractedDurationUs = totalDuration;
+    videoData->SetDuration((uint64_t)duration.value());
     videoData->SwapInFrameData(frameData);
     VP8LOG(LogLevel::Verbose,
            "GetEncodedPartitions TimeStamp %lld, Duration %lld, FrameType %d",
            videoData->GetTimeStamp(), videoData->GetDuration(),
            videoData->GetFrameType());
     aData.AppendEncodedFrame(videoData);
   }
 
-  return !!pkt;
+  return pkt ? NS_OK : NS_ERROR_NOT_AVAILABLE;
 }
 
 static bool isYUV420(const PlanarYCbCrImage::Data *aData)
 {
   if (aData->mYSize == aData->mCbCrSize * 2) {
     return true;
   }
   return false;
@@ -544,29 +565,40 @@ VP8TrackEncoder::GetEncodedTrack(Encoded
         flags |= VPX_EFLAG_FORCE_KF;
       }
       if (vpx_codec_encode(mVPXContext, mVPXImageWrapper, mEncodedTimestamp,
                            (unsigned long)chunk.GetDuration(), flags,
                            VPX_DL_REALTIME)) {
         return NS_ERROR_FAILURE;
       }
       // Get the encoded data from VP8 encoder.
-      GetEncodedPartitions(aData);
+      rv = GetEncodedPartitions(aData);
+      NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
     } else {
       // SKIP_FRAME
       // Extend the duration of the last encoded data in aData
       // because this frame will be skipped.
       VP8LOG(LogLevel::Warning, "MediaRecorder lagging behind. Skipping a frame.");
       RefPtr<EncodedFrame> last = aData.GetEncodedFrames().LastElement();
       if (last) {
-        CheckedInt64 skippedDuration = FramesToUsecs(chunk.mDuration, mTrackRate);
-        if (skippedDuration.isValid() && skippedDuration.value() > 0) {
-          last->SetDuration(last->GetDuration() +
-                            (static_cast<uint64_t>(skippedDuration.value())));
+        mExtractedDuration += chunk.mDuration;
+        if (!mExtractedDuration.isValid()) {
+          NS_ERROR("skipped duration overflow");
+          return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
         }
+
+        CheckedInt64 totalDuration = FramesToUsecs(mExtractedDuration.value(), mTrackRate);
+        CheckedInt64 skippedDuration = totalDuration - mExtractedDurationUs;
+        mExtractedDurationUs = totalDuration;
+        if (!skippedDuration.isValid()) {
+          NS_ERROR("skipped duration overflow");
+          return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
+        }
+        last->SetDuration(last->GetDuration() +
+                          (static_cast<uint64_t>(skippedDuration.value())));
       }
     }
 
     // Move forward the mEncodedTimestamp.
     mEncodedTimestamp += chunk.GetDuration();
     totalProcessedDuration += chunk.GetDuration();
 
     // Check what to do next.
@@ -585,15 +617,15 @@ VP8TrackEncoder::GetEncodedTrack(Encoded
     // Bug 1243611, keep calling vpx_codec_encode and vpx_codec_get_cx_data
     // until vpx_codec_get_cx_data return null.
 
     do {
       if (vpx_codec_encode(mVPXContext, nullptr, mEncodedTimestamp,
                            0, 0, VPX_DL_REALTIME)) {
         return NS_ERROR_FAILURE;
       }
-    } while(GetEncodedPartitions(aData));
+    } while(NS_SUCCEEDED(GetEncodedPartitions(aData)));
   }
 
   return NS_OK ;
 }
 
 } // namespace mozilla
--- a/dom/media/encoder/VP8TrackEncoder.h
+++ b/dom/media/encoder/VP8TrackEncoder.h
@@ -42,24 +42,30 @@ protected:
 private:
   // Get the EncodeOperation for next target frame.
   EncodeOperation GetNextEncodeOperation(TimeDuration aTimeElapsed,
                                          StreamTime aProcessedDuration);
 
   // Get the encoded data from encoder to aData.
   // Return value: false if the vpx_codec_get_cx_data returns null
   //               for EOS detection.
-  bool GetEncodedPartitions(EncodedFrameContainer& aData);
+  nsresult GetEncodedPartitions(EncodedFrameContainer& aData);
 
   // Prepare the input data to the mVPXImageWrapper for encoding.
   nsresult PrepareRawFrame(VideoChunk &aChunk);
 
   // Encoded timestamp.
   StreamTime mEncodedTimestamp;
 
+  // Total duration in mTrackRate extracted by GetEncodedPartitions().
+  CheckedInt64 mExtractedDuration;
+
+  // Total duration in microseconds extracted by GetEncodedPartitions().
+  CheckedInt64 mExtractedDurationUs;
+
   // Muted frame, we only create it once.
   RefPtr<layers::Image> mMuteFrame;
 
   // I420 frame, for converting to I420.
   nsTArray<uint8_t> mI420Frame;
 
   /**
    * A local segment queue which takes the raw data out from mRawSegment in the