Bug 1232043 - Reconfigure VP8 encoder when resolution change. r?pehrsons draft
authorAlex Chronopoulos <achronop@gmail.com>
Fri, 31 Mar 2017 18:14:10 +0300
changeset 555630 288d18e765abbc11c0e93b59610af01070991a1b
parent 554422 13f5ae940c4eb099b987484885f9edf71ed0380b
child 555631 3550866b428bcc8b1904606a768016e4d5a89801
push id52291
push userachronop@gmail.com
push dateTue, 04 Apr 2017 16:02:17 +0000
reviewerspehrsons
bugs1232043
milestone55.0a1
Bug 1232043 - Reconfigure VP8 encoder when resolution change. r?pehrsons * * * Bug 1232043 - Apply review comments. r?pehrsons * * * [mq]: new-comments MozReview-Commit-ID: ADZ5uquv0Dp
dom/media/encoder/VP8TrackEncoder.cpp
dom/media/encoder/VP8TrackEncoder.h
--- a/dom/media/encoder/VP8TrackEncoder.cpp
+++ b/dom/media/encoder/VP8TrackEncoder.cpp
@@ -59,54 +59,124 @@ VP8TrackEncoder::VP8TrackEncoder(TrackRa
   , mVPXContext(new vpx_codec_ctx_t())
   , mVPXImageWrapper(new vpx_image_t())
 {
   MOZ_COUNT_CTOR(VP8TrackEncoder);
 }
 
 VP8TrackEncoder::~VP8TrackEncoder()
 {
+  Destroy();
+  MOZ_COUNT_DTOR(VP8TrackEncoder);
+}
+
+void
+VP8TrackEncoder::Destroy()
+{
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
   if (mInitialized) {
     vpx_codec_destroy(mVPXContext);
   }
 
   if (mVPXImageWrapper) {
     vpx_img_free(mVPXImageWrapper);
   }
-  MOZ_COUNT_DTOR(VP8TrackEncoder);
+  mInitialized = false;
 }
 
 nsresult
 VP8TrackEncoder::Init(int32_t aWidth, int32_t aHeight, int32_t aDisplayWidth,
                       int32_t aDisplayHeight)
 {
   if (aWidth < 1 || aHeight < 1 || aDisplayWidth < 1 || aDisplayHeight < 1) {
     return NS_ERROR_FAILURE;
   }
 
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+  if (mInitialized) {
+    MOZ_ASSERT(false);
+    return NS_ERROR_FAILURE;
+  }
 
+  // Encoder configuration structure.
+  vpx_codec_enc_cfg_t config;
+  nsresult rv = SetConfigurationValues(aWidth, aHeight, aDisplayWidth, aDisplayHeight, config);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+  // Creating a wrapper to the image - setting image data to NULL. Actual
+  // pointer will be set in encode. Setting align to 1, as it is meaningless
+  // (actual memory is not allocated).
+  vpx_img_wrap(mVPXImageWrapper, VPX_IMG_FMT_I420,
+               mFrameWidth, mFrameHeight, 1, nullptr);
+
+  vpx_codec_flags_t flags = 0;
+  flags |= VPX_CODEC_USE_OUTPUT_PARTITION;
+  if (vpx_codec_enc_init(mVPXContext, vpx_codec_vp8_cx(), &config, flags)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  vpx_codec_control(mVPXContext, VP8E_SET_STATIC_THRESHOLD, 1);
+  vpx_codec_control(mVPXContext, VP8E_SET_CPUUSED, -6);
+  vpx_codec_control(mVPXContext, VP8E_SET_TOKEN_PARTITIONS,
+                    VP8_ONE_TOKENPARTITION);
+
+  mInitialized = true;
+  mon.NotifyAll();
+
+  return NS_OK;
+}
+
+nsresult
+VP8TrackEncoder::Reconfigure(int32_t aWidth, int32_t aHeight,
+                             int32_t aDisplayWidth, int32_t aDisplayHeight)
+{
+  if(aWidth <= 0 && aHeight <= 0 && aDisplayWidth <= 0 &&aDisplayHeight <= 0) {
+    MOZ_ASSERT(false);
+    return NS_ERROR_FAILURE;
+  }
+
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+  if (!mInitialized) {
+    MOZ_ASSERT(false);
+    return NS_ERROR_FAILURE;
+  }
+
+  mInitialized = false;
+  // Recreate image wrapper
+  vpx_img_free(mVPXImageWrapper);
+  vpx_img_wrap(mVPXImageWrapper, VPX_IMG_FMT_I420, aWidth, aHeight, 1, nullptr);
+  // Encoder configuration structure.
+  vpx_codec_enc_cfg_t config;
+  nsresult rv = SetConfigurationValues(aWidth, aHeight, aDisplayWidth, aDisplayHeight, config);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+  // Set new configuration
+  if (vpx_codec_enc_config_set(mVPXContext.get(), &config) != VPX_CODEC_OK) {
+    VP8LOG(LogLevel::Error, "Failed to set new configuration");
+    return NS_ERROR_FAILURE;
+  }
+  mInitialized = true;
+  return NS_OK;
+}
+
+nsresult
+VP8TrackEncoder::SetConfigurationValues(int32_t aWidth, int32_t aHeight, int32_t aDisplayWidth,
+                                        int32_t aDisplayHeight, vpx_codec_enc_cfg_t& config)
+{
   mFrameWidth = aWidth;
   mFrameHeight = aHeight;
   mDisplayWidth = aDisplayWidth;
   mDisplayHeight = aDisplayHeight;
 
   // Encoder configuration structure.
-  vpx_codec_enc_cfg_t config;
   memset(&config, 0, sizeof(vpx_codec_enc_cfg_t));
   if (vpx_codec_enc_config_default(vpx_codec_vp8_cx(), &config, 0)) {
+    VP8LOG(LogLevel::Error, "Failed to get default configuration");
     return NS_ERROR_FAILURE;
   }
 
-  // Creating a wrapper to the image - setting image data to NULL. Actual
-  // pointer will be set in encode. Setting align to 1, as it is meaningless
-  // (actual memory is not allocated).
-  vpx_img_wrap(mVPXImageWrapper, VPX_IMG_FMT_I420,
-               mFrameWidth, mFrameHeight, 1, nullptr);
-
   config.g_w = mFrameWidth;
   config.g_h = mFrameHeight;
   // TODO: Maybe we should have various aFrameRate bitrate pair for each devices?
   // or for different platform
 
   // rc_target_bitrate needs kbit/s
   config.rc_target_bitrate = (mVideoBitrate != 0 ? mVideoBitrate : DEFAULT_BITRATE_BPS)/1000;
 
@@ -139,30 +209,16 @@ VP8TrackEncoder::Init(int32_t aWidth, in
   config.rc_buf_initial_sz = 500;
   config.rc_buf_optimal_sz = 600;
   config.rc_buf_sz = 1000;
 
   config.kf_mode = VPX_KF_AUTO;
   // Ensure that we can output one I-frame per second.
   config.kf_max_dist = 60;
 
-  vpx_codec_flags_t flags = 0;
-  flags |= VPX_CODEC_USE_OUTPUT_PARTITION;
-  if (vpx_codec_enc_init(mVPXContext, vpx_codec_vp8_cx(), &config, flags)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  vpx_codec_control(mVPXContext, VP8E_SET_STATIC_THRESHOLD, 1);
-  vpx_codec_control(mVPXContext, VP8E_SET_CPUUSED, -6);
-  vpx_codec_control(mVPXContext, VP8E_SET_TOKEN_PARTITIONS,
-                    VP8_ONE_TOKENPARTITION);
-
-  mInitialized = true;
-  mon.NotifyAll();
-
   return NS_OK;
 }
 
 already_AddRefed<TrackMetadataBase>
 VP8TrackEncoder::GetMetadata()
 {
   PROFILER_LABEL("VP8TrackEncoder", "GetMetadata",
     js::ProfileEntry::Category::OTHER);
@@ -294,20 +350,41 @@ nsresult VP8TrackEncoder::PrepareRawFram
       MOZ_ASSERT(mMuteFrame);
     }
     img = mMuteFrame;
   } else {
     img = aChunk.mFrame.GetImage();
   }
 
   if (img->GetSize() != IntSize(mFrameWidth, mFrameHeight)) {
-    VP8LOG(LogLevel::Error,
-           "Dynamic resolution changes (was %dx%d, now %dx%d) are unsupported",
+    VP8LOG(LogLevel::Info,
+           "Dynamic resolution change (was %dx%d, now %dx%d).",
            mFrameWidth, mFrameHeight, img->GetSize().width, img->GetSize().height);
-    return NS_ERROR_FAILURE;
+
+
+    gfx::IntSize intrinsicSize = aChunk.mFrame.GetIntrinsicSize();
+    gfx::IntSize imgSize = aChunk.mFrame.GetImage()->GetSize();
+    if (imgSize <= IntSize(mFrameWidth, mFrameHeight) && // check buffer size instead
+        // If the new size is less than or equal to old,
+        // the existing encoder instance can continue.
+        NS_SUCCEEDED(Reconfigure(imgSize.width,
+                                 imgSize.height,
+                                 intrinsicSize.width,
+                                 intrinsicSize.height))) {
+      VP8LOG(LogLevel::Info, "Reconfigured VP8 encoder.");
+    } else {
+      // New frame size is larger; re-create the encoder.
+      Destroy();
+      nsresult rv = Init(imgSize.width,
+                         imgSize.height,
+                         intrinsicSize.width,
+                         intrinsicSize.height);
+      VP8LOG(LogLevel::Info, "Recreated VP8 encoder.");
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
   }
 
   ImageFormat format = img->GetFormat();
   if (format == ImageFormat::PLANAR_YCBCR) {
     PlanarYCbCrImage* yuv = static_cast<PlanarYCbCrImage *>(img.get());
 
     MOZ_RELEASE_ASSERT(yuv);
     if (!yuv->IsValid()) {
@@ -330,17 +407,17 @@ nsresult VP8TrackEncoder::PrepareRawFram
   }
 
   // Not 420 planar, have to convert
   uint32_t yPlaneSize = mFrameWidth * mFrameHeight;
   uint32_t halfWidth = (mFrameWidth + 1) / 2;
   uint32_t halfHeight = (mFrameHeight + 1) / 2;
   uint32_t uvPlaneSize = halfWidth * halfHeight;
 
-  if (mI420Frame.IsEmpty()) {
+  if (mI420Frame.Length() != yPlaneSize + uvPlaneSize * 2) {
     mI420Frame.SetLength(yPlaneSize + uvPlaneSize * 2);
   }
 
   uint8_t *y = mI420Frame.Elements();
   uint8_t *cb = mI420Frame.Elements() + yPlaneSize;
   uint8_t *cr = mI420Frame.Elements() + yPlaneSize + uvPlaneSize;
 
   if (format == ImageFormat::PLANAR_YCBCR) {
@@ -562,16 +639,17 @@ VP8TrackEncoder::GetEncodedTrack(Encoded
       int flags = 0;
       if (nextEncodeOperation == ENCODE_I_FRAME) {
         VP8LOG(LogLevel::Warning, "MediaRecorder lagging behind. Encoding keyframe.");
         flags |= VPX_EFLAG_FORCE_KF;
       }
       if (vpx_codec_encode(mVPXContext, mVPXImageWrapper, mEncodedTimestamp,
                            (unsigned long)chunk.GetDuration(), flags,
                            VPX_DL_REALTIME)) {
+        VP8LOG(LogLevel::Error, "vpx_codec_encode failed to encode the frame.");
         return NS_ERROR_FAILURE;
       }
       // Get the encoded data from VP8 encoder.
       rv = GetEncodedPartitions(aData);
       NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
     } else {
       // SKIP_FRAME
       // Extend the duration of the last encoded data in aData
--- a/dom/media/encoder/VP8TrackEncoder.h
+++ b/dom/media/encoder/VP8TrackEncoder.h
@@ -47,16 +47,27 @@ private:
   // Get the encoded data from encoder to aData.
   // Return value: false if the vpx_codec_get_cx_data returns null
   //               for EOS detection.
   nsresult GetEncodedPartitions(EncodedFrameContainer& aData);
 
   // Prepare the input data to the mVPXImageWrapper for encoding.
   nsresult PrepareRawFrame(VideoChunk &aChunk);
 
+  // Re-configures an existing encoder with a new frame size.
+  nsresult Reconfigure(int32_t aWidth, int32_t aHeight,
+                       int32_t aDisplayWidth, int32_t aDisplayHeight);
+
+  // Destroys the context and image wrapper. Does not de-allocate the structs.
+  void Destroy();
+
+  // Helper method to set the values on a VPX configuration.
+  nsresult SetConfigurationValues(int32_t aWidth, int32_t aHeight, int32_t aDisplayWidth,
+                                  int32_t aDisplayHeight, vpx_codec_enc_cfg_t& config);
+
   // Encoded timestamp.
   StreamTime mEncodedTimestamp;
 
   // Total duration in mTrackRate extracted by GetEncodedPartitions().
   CheckedInt64 mExtractedDuration;
 
   // Total duration in microseconds extracted by GetEncodedPartitions().
   CheckedInt64 mExtractedDurationUs;