Index: remoting/codec/webrtc_video_encoder_vpx.cc |
diff --git a/remoting/codec/video_encoder_vpx.cc b/remoting/codec/webrtc_video_encoder_vpx.cc |
similarity index 77% |
copy from remoting/codec/video_encoder_vpx.cc |
copy to remoting/codec/webrtc_video_encoder_vpx.cc |
index e281ef58efbc9c9e092c3b2efadf04b528dbebdf..5071a783edff468e64a1a466a3709e39e0c1f6c9 100644 |
--- a/remoting/codec/video_encoder_vpx.cc |
+++ b/remoting/codec/webrtc_video_encoder_vpx.cc |
@@ -1,8 +1,8 @@ |
-// Copyright 2013 The Chromium Authors. All rights reserved. |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
// Use of this source code is governed by a BSD-style license that can be |
// found in the LICENSE file. |
-#include "remoting/codec/video_encoder_vpx.h" |
+#include "remoting/codec/webrtc_video_encoder_vpx.h" |
#include <utility> |
@@ -42,11 +42,13 @@ const int kVp9I444ProfileNumber = 1; |
const int kVp9AqModeNone = 0; |
const int kVp9AqModeCyclicRefresh = 3; |
+const int kDefaultTargetBitrateKbps = 1000; |
+ |
void SetCommonCodecParameters(vpx_codec_enc_cfg_t* config, |
const webrtc::DesktopSize& size) { |
// Use millisecond granularity time base. |
config->g_timebase.num = 1; |
- config->g_timebase.den = 1000; |
+ config->g_timebase.den = base::Time::kMicrosecondsPerSecond; |
config->g_w = size.width(); |
config->g_h = size.height(); |
@@ -61,19 +63,20 @@ void SetCommonCodecParameters(vpx_codec_enc_cfg_t* config, |
config->kf_min_dist = 10000; |
config->kf_max_dist = 10000; |
- // Using 2 threads gives a great boost in performance for most systems with |
- // adequate processing power. NB: Going to multiple threads on low end |
- // windows systems can really hurt performance. |
- // http://crbug.com/99179 |
- config->g_threads = (base::SysInfo::NumberOfProcessors() > 2) ? 2 : 1; |
+ // Allow multiple cores on a system to be used for encoding for |
+ // performance while at the same time ensuring we do not saturate. |
+ config->g_threads = (base::SysInfo::NumberOfProcessors() + 1) / 2; |
+ |
+ // Do not drop any frames at encoder. |
+ config->rc_dropframe_thresh = 0; |
+ // We do not want variations in bandwidth. |
+ config->rc_end_usage = VPX_CBR; |
+ config->rc_undershoot_pct = 100; |
+ config->rc_overshoot_pct = 15; |
} |
void SetVp8CodecParameters(vpx_codec_enc_cfg_t* config, |
const webrtc::DesktopSize& size) { |
- // Adjust default target bit-rate to account for actual desktop size. |
- config->rc_target_bitrate = size.width() * size.height() * |
- config->rc_target_bitrate / config->g_w / config->g_h; |
- |
SetCommonCodecParameters(config, size); |
// Value of 2 means using the real time profile. This is basically a |
@@ -81,9 +84,11 @@ void SetVp8CodecParameters(vpx_codec_enc_cfg_t* config, |
// encoding. |
config->g_profile = 2; |
- // Clamping the quantizer constrains the worst-case quality and CPU usage. |
+ // To enable remoting to be highly interactive and allow the target bitrate |
+ // to be met, we relax the max quantizer. The quality will get topped-off |
+ // in subsequent frames. |
config->rc_min_quantizer = 20; |
- config->rc_max_quantizer = 30; |
+ config->rc_max_quantizer = 63; |
} |
void SetVp9CodecParameters(vpx_codec_enc_cfg_t* config, |
@@ -139,8 +144,7 @@ void SetVp9CodecOptions(vpx_codec_ctx_t* codec, bool lossless_encode) { |
DCHECK_EQ(VPX_CODEC_OK, ret) << "Failed to set noise sensitivity"; |
// Configure the codec to tune it for screen media. |
- ret = vpx_codec_control( |
- codec, VP9E_SET_TUNE_CONTENT, VP9E_CONTENT_SCREEN); |
+ ret = vpx_codec_control(codec, VP9E_SET_TUNE_CONTENT, VP9E_CONTENT_SCREEN); |
DCHECK_EQ(VPX_CODEC_OK, ret) << "Failed to set screen content mode"; |
// Set cyclic refresh (aka "top-off") only for lossy encoding. |
@@ -205,11 +209,12 @@ void CreateImage(bool use_i444, |
// Assuming macroblocks are 16x16, aligning the planes' strides above also |
// macroblock aligned them. |
static_assert(kMacroBlockSize == 16, "macroblock_size_not_16"); |
- const int y_rows = ((image->h - 1) & ~(kMacroBlockSize-1)) + kMacroBlockSize; |
+ const int y_rows = |
+ ((image->h - 1) & ~(kMacroBlockSize - 1)) + kMacroBlockSize; |
const int uv_rows = y_rows >> image->y_chroma_shift; |
// Allocate a YUV buffer large enough for the aligned data & padding. |
- const int buffer_size = y_stride * y_rows + 2*uv_stride * uv_rows; |
+ const int buffer_size = y_stride * y_rows + 2 * uv_stride * uv_rows; |
std::unique_ptr<uint8_t[]> image_buffer(new uint8_t[buffer_size]); |
// Reset image value to 128 so we just need to fill in the y plane. |
@@ -232,22 +237,22 @@ void CreateImage(bool use_i444, |
} // namespace |
// static |
-std::unique_ptr<VideoEncoderVpx> VideoEncoderVpx::CreateForVP8() { |
- return base::WrapUnique(new VideoEncoderVpx(false)); |
+std::unique_ptr<WebRtcVideoEncoderVpx> WebRtcVideoEncoderVpx::CreateForVP8() { |
+ return base::WrapUnique(new WebRtcVideoEncoderVpx(false)); |
} |
// static |
-std::unique_ptr<VideoEncoderVpx> VideoEncoderVpx::CreateForVP9() { |
- return base::WrapUnique(new VideoEncoderVpx(true)); |
+std::unique_ptr<WebRtcVideoEncoderVpx> WebRtcVideoEncoderVpx::CreateForVP9() { |
+ return base::WrapUnique(new WebRtcVideoEncoderVpx(true)); |
} |
-VideoEncoderVpx::~VideoEncoderVpx() {} |
+WebRtcVideoEncoderVpx::~WebRtcVideoEncoderVpx() {} |
-void VideoEncoderVpx::SetTickClockForTests(base::TickClock* tick_clock) { |
+void WebRtcVideoEncoderVpx::SetTickClockForTests(base::TickClock* tick_clock) { |
clock_ = tick_clock; |
} |
-void VideoEncoderVpx::SetLosslessEncode(bool want_lossless) { |
+void WebRtcVideoEncoderVpx::SetLosslessEncode(bool want_lossless) { |
if (use_vp9_ && (want_lossless != lossless_encode_)) { |
lossless_encode_ = want_lossless; |
if (codec_) |
@@ -256,7 +261,7 @@ void VideoEncoderVpx::SetLosslessEncode(bool want_lossless) { |
} |
} |
-void VideoEncoderVpx::SetLosslessColor(bool want_lossless) { |
+void WebRtcVideoEncoderVpx::SetLosslessColor(bool want_lossless) { |
if (use_vp9_ && (want_lossless != lossless_color_)) { |
lossless_color_ = want_lossless; |
// TODO(wez): Switch to ConfigureCodec() path once libvpx supports it. |
@@ -268,14 +273,33 @@ void VideoEncoderVpx::SetLosslessColor(bool want_lossless) { |
} |
} |
-std::unique_ptr<VideoPacket> VideoEncoderVpx::Encode( |
+void WebRtcVideoEncoderVpx::UpdateTargetBitrate(uint32_t new_bitrate) { |
+ target_bitrate_kbps_ = new_bitrate; |
+ // Configuration not initialized. |
+ if (config_.g_timebase.den == 0) |
+ return; |
+ |
+ if (config_.rc_target_bitrate == new_bitrate) |
+ return; |
+ config_.rc_target_bitrate = new_bitrate; |
+ |
+ // Update encoder context. |
+ if (vpx_codec_enc_config_set(codec_.get(), &config_)) |
+ NOTREACHED() << "Unable to set encoder config"; |
+ |
+ VLOG(1) << "New rc_target_bitrate: " << new_bitrate << " kbps"; |
+} |
+ |
+std::unique_ptr<VideoPacket> WebRtcVideoEncoderVpx::Encode( |
const webrtc::DesktopFrame& frame, |
uint32_t flags) { |
DCHECK_LE(32, frame.size().width()); |
DCHECK_LE(32, frame.size().height()); |
- // If there is nothing to encode, and nothing to top-off, then return nothing. |
- if (frame.updated_region().is_empty() && !encode_unchanged_frame_) |
+ // VP8: Encode top-off is controlled at the call site. |
+ // VP9: Based on information fetching active map, we return here if there is |
+ // nothing to top-off. |
+ if (use_vp9_ && frame.updated_region().is_empty() && !encode_unchanged_frame_) |
return nullptr; |
// Create or reconfigure the codec to match the size of |frame|. |
@@ -285,29 +309,31 @@ std::unique_ptr<VideoPacket> VideoEncoderVpx::Encode( |
Configure(frame.size()); |
} |
- // Convert the updated capture data ready for encode. |
- webrtc::DesktopRegion updated_region; |
- PrepareImage(frame, &updated_region); |
- |
- // Update active map based on updated region. |
- SetActiveMapFromRegion(updated_region); |
- |
- // Apply active map to the encoder. |
vpx_active_map_t act_map; |
act_map.rows = active_map_size_.height(); |
act_map.cols = active_map_size_.width(); |
act_map.active_map = active_map_.get(); |
- if (vpx_codec_control(codec_.get(), VP8E_SET_ACTIVEMAP, &act_map)) { |
- LOG(ERROR) << "Unable to apply active map"; |
- } |
- if (flags & REQUEST_KEY_FRAME) |
- vpx_codec_control(codec_.get(), VP8E_SET_FRAME_FLAGS, VPX_EFLAG_FORCE_KF); |
+ webrtc::DesktopRegion updated_region; |
+ if (!frame.updated_region().is_empty()) { |
+ // Convert the updated capture data ready for encode. |
+ PrepareImage(frame, &updated_region); |
+ |
+ // Update active map based on updated region. |
+ SetActiveMapFromRegion(updated_region); |
- // Do the actual encoding. |
- int timestamp = (clock_->NowTicks() - timestamp_base_).InMilliseconds(); |
+ // Apply active map to the encoder. |
+ |
+ if (vpx_codec_control(codec_.get(), VP8E_SET_ACTIVEMAP, &act_map)) { |
+ LOG(ERROR) << "Unable to apply active map"; |
+ } |
+ } |
+ |
+ // Frame rate is adapted based on how well the encoder meets the target |
+ // bandwidth requiremetn. We specify a target rate of 1 / 15 fps here. |
Sergey Ulanov
2016/05/10 00:00:58
typo: requiremetn
Sergey Ulanov
2016/05/10 00:00:58
why is it 1/15 fps instead of 30 fps?
Irfan
2016/05/10 16:30:45
I added a TODO here. Basically, we need to further
|
vpx_codec_err_t ret = vpx_codec_encode( |
- codec_.get(), image_.get(), timestamp, 1, 0, VPX_DL_REALTIME); |
+ codec_.get(), image_.get(), 0, 66666, |
+ (flags & REQUEST_KEY_FRAME) ? VPX_EFLAG_FORCE_KF : 0, VPX_DL_REALTIME); |
DCHECK_EQ(ret, VPX_CODEC_OK) |
<< "Encoding error: " << vpx_codec_err_to_string(ret) << "\n" |
<< "Details: " << vpx_codec_error(codec_.get()) << "\n" |
@@ -316,8 +342,8 @@ std::unique_ptr<VideoPacket> VideoEncoderVpx::Encode( |
if (use_vp9_ && !lossless_encode_) { |
ret = vpx_codec_control(codec_.get(), VP9E_GET_ACTIVEMAP, &act_map); |
DCHECK_EQ(ret, VPX_CODEC_OK) |
- << "Failed to fetch active map: " |
- << vpx_codec_err_to_string(ret) << "\n"; |
+ << "Failed to fetch active map: " << vpx_codec_err_to_string(ret) |
+ << "\n"; |
UpdateRegionFromActiveMap(&updated_region); |
// If the encoder output no changes then there's nothing left to top-off. |
@@ -340,11 +366,16 @@ std::unique_ptr<VideoPacket> VideoEncoderVpx::Encode( |
if (!vpx_packet) |
continue; |
+ int quantizer = -1; |
switch (vpx_packet->kind) { |
case VPX_CODEC_CX_FRAME_PKT: |
got_data = true; |
packet->set_data(vpx_packet->data.frame.buf, vpx_packet->data.frame.sz); |
packet->set_key_frame(vpx_packet->data.frame.flags & VPX_FRAME_IS_KEY); |
+ CHECK_EQ(vpx_codec_control(codec_.get(), VP8E_GET_LAST_QUANTIZER_64, |
+ &quantizer), |
+ VPX_CODEC_OK); |
+ packet->set_quantizer(quantizer); |
break; |
default: |
break; |
@@ -354,12 +385,16 @@ std::unique_ptr<VideoPacket> VideoEncoderVpx::Encode( |
return packet; |
} |
-VideoEncoderVpx::VideoEncoderVpx(bool use_vp9) |
+WebRtcVideoEncoderVpx::WebRtcVideoEncoderVpx(bool use_vp9) |
: use_vp9_(use_vp9), |
+ target_bitrate_kbps_(kDefaultTargetBitrateKbps), |
encode_unchanged_frame_(false), |
- clock_(&default_tick_clock_) {} |
+ clock_(&default_tick_clock_) { |
+ // Indicates config is still uninitialized. |
+ config_.g_timebase.den = 0; |
+} |
-void VideoEncoderVpx::Configure(const webrtc::DesktopSize& size) { |
+void WebRtcVideoEncoderVpx::Configure(const webrtc::DesktopSize& size) { |
DCHECK(use_vp9_ || !lossless_color_); |
DCHECK(use_vp9_ || !lossless_encode_); |
@@ -385,32 +420,27 @@ void VideoEncoderVpx::Configure(const webrtc::DesktopSize& size) { |
} |
} |
- // (Re)Set the base for frame timestamps if the codec is being (re)created. |
- if (!codec_) { |
- timestamp_base_ = clock_->NowTicks(); |
- } |
- |
// Fetch a default configuration for the desired codec. |
const vpx_codec_iface_t* interface = |
use_vp9_ ? vpx_codec_vp9_cx() : vpx_codec_vp8_cx(); |
- vpx_codec_enc_cfg_t config; |
- vpx_codec_err_t ret = vpx_codec_enc_config_default(interface, &config, 0); |
+ vpx_codec_err_t ret = vpx_codec_enc_config_default(interface, &config_, 0); |
DCHECK_EQ(VPX_CODEC_OK, ret) << "Failed to fetch default configuration"; |
// Customize the default configuration to our needs. |
if (use_vp9_) { |
- SetVp9CodecParameters(&config, size, lossless_color_, lossless_encode_); |
+ SetVp9CodecParameters(&config_, size, lossless_color_, lossless_encode_); |
} else { |
- SetVp8CodecParameters(&config, size); |
+ SetVp8CodecParameters(&config_, size); |
} |
+ config_.rc_target_bitrate = target_bitrate_kbps_; |
// Initialize or re-configure the codec with the custom configuration. |
if (!codec_) { |
codec_.reset(new vpx_codec_ctx_t); |
- ret = vpx_codec_enc_init(codec_.get(), interface, &config, 0); |
+ ret = vpx_codec_enc_init(codec_.get(), interface, &config_, 0); |
CHECK_EQ(VPX_CODEC_OK, ret) << "Failed to initialize codec"; |
} else { |
- ret = vpx_codec_enc_config_set(codec_.get(), &config); |
+ ret = vpx_codec_enc_config_set(codec_.get(), &config_); |
CHECK_EQ(VPX_CODEC_OK, ret) << "Failed to reconfigure codec"; |
} |
@@ -422,8 +452,9 @@ void VideoEncoderVpx::Configure(const webrtc::DesktopSize& size) { |
} |
} |
-void VideoEncoderVpx::PrepareImage(const webrtc::DesktopFrame& frame, |
- webrtc::DesktopRegion* updated_region) { |
+void WebRtcVideoEncoderVpx::PrepareImage( |
+ const webrtc::DesktopFrame& frame, |
+ webrtc::DesktopRegion* updated_region) { |
if (frame.updated_region().is_empty()) { |
updated_region->Clear(); |
return; |
@@ -474,13 +505,12 @@ void VideoEncoderVpx::PrepareImage(const webrtc::DesktopFrame& frame, |
for (webrtc::DesktopRegion::Iterator r(*updated_region); !r.IsAtEnd(); |
r.Advance()) { |
const webrtc::DesktopRect& rect = r.rect(); |
- int rgb_offset = rgb_stride * rect.top() + |
- rect.left() * kBytesPerRgbPixel; |
+ int rgb_offset = |
+ rgb_stride * rect.top() + rect.left() * kBytesPerRgbPixel; |
int yuv_offset = uv_stride * rect.top() + rect.left(); |
libyuv::ARGBToI444(rgb_data + rgb_offset, rgb_stride, |
- y_data + yuv_offset, y_stride, |
- u_data + yuv_offset, uv_stride, |
- v_data + yuv_offset, uv_stride, |
+ y_data + yuv_offset, y_stride, u_data + yuv_offset, |
+ uv_stride, v_data + yuv_offset, uv_stride, |
rect.width(), rect.height()); |
} |
break; |
@@ -488,15 +518,14 @@ void VideoEncoderVpx::PrepareImage(const webrtc::DesktopFrame& frame, |
for (webrtc::DesktopRegion::Iterator r(*updated_region); !r.IsAtEnd(); |
r.Advance()) { |
const webrtc::DesktopRect& rect = r.rect(); |
- int rgb_offset = rgb_stride * rect.top() + |
- rect.left() * kBytesPerRgbPixel; |
+ int rgb_offset = |
+ rgb_stride * rect.top() + rect.left() * kBytesPerRgbPixel; |
int y_offset = y_stride * rect.top() + rect.left(); |
int uv_offset = uv_stride * rect.top() / 2 + rect.left() / 2; |
- libyuv::ARGBToI420(rgb_data + rgb_offset, rgb_stride, |
- y_data + y_offset, y_stride, |
- u_data + uv_offset, uv_stride, |
- v_data + uv_offset, uv_stride, |
- rect.width(), rect.height()); |
+ libyuv::ARGBToI420(rgb_data + rgb_offset, rgb_stride, y_data + y_offset, |
+ y_stride, u_data + uv_offset, uv_stride, |
+ v_data + uv_offset, uv_stride, rect.width(), |
+ rect.height()); |
} |
break; |
default: |
@@ -505,7 +534,7 @@ void VideoEncoderVpx::PrepareImage(const webrtc::DesktopFrame& frame, |
} |
} |
-void VideoEncoderVpx::SetActiveMapFromRegion( |
+void WebRtcVideoEncoderVpx::SetActiveMapFromRegion( |
const webrtc::DesktopRegion& updated_region) { |
// Clear active map first. |
memset(active_map_.get(), 0, |
@@ -531,7 +560,7 @@ void VideoEncoderVpx::SetActiveMapFromRegion( |
} |
} |
-void VideoEncoderVpx::UpdateRegionFromActiveMap( |
+void WebRtcVideoEncoderVpx::UpdateRegionFromActiveMap( |
webrtc::DesktopRegion* updated_region) { |
const uint8_t* map = active_map_.get(); |
for (int y = 0; y < active_map_size_.height(); ++y) { |