Chromium Code Reviews| 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) { |