diff --git a/webrtc/modules/video_coding/codecs/vp8/screenshare_layers.cc b/webrtc/modules/video_coding/codecs/vp8/screenshare_layers.cc index d535c617cd..ab1fea6416 100644 --- a/webrtc/modules/video_coding/codecs/vp8/screenshare_layers.cc +++ b/webrtc/modules/video_coding/codecs/vp8/screenshare_layers.cc @@ -26,6 +26,7 @@ static const int kOneSecond90Khz = 90000; static const int kMinTimeBetweenSyncs = kOneSecond90Khz * 5; static const int kMaxTimeBetweenSyncs = kOneSecond90Khz * 10; static const int kQpDeltaThresholdForSync = 8; +static const int kMinBitrateKbpsForQpBoost = 500; const double ScreenshareLayers::kMaxTL0FpsReduction = 2.5; const double ScreenshareLayers::kAcceptableTargetOvershoot = 2.0; @@ -33,8 +34,8 @@ const double ScreenshareLayers::kAcceptableTargetOvershoot = 2.0; constexpr int ScreenshareLayers::kMaxNumTemporalLayers; // Always emit a frame with certain interval, even if bitrate targets have -// been exceeded. -const int ScreenshareLayers::kMaxFrameIntervalMs = 2000; +// been exceeded. This prevents needless keyframe requests. +const int ScreenshareLayers::kMaxFrameIntervalMs = 3000; webrtc::TemporalLayers* ScreenshareTemporalLayersFactory::Create( int simulcast_id, @@ -95,7 +96,7 @@ TemporalLayers::FrameConfig ScreenshareLayers::UpdateLayerConfig( // TODO(pbos): Consider updating only last, and not all buffers. TemporalLayers::FrameConfig tl_config( kReferenceAndUpdate, kReferenceAndUpdate, kReferenceAndUpdate); - tl_config.pattern_idx = static_cast(kTl1); + tl_config.pattern_idx = static_cast(TemporalLayerState::kTl1); return tl_config; } @@ -110,7 +111,19 @@ TemporalLayers::FrameConfig ScreenshareLayers::UpdateLayerConfig( stats_.first_frame_time_ms_ = now_ms; int64_t unwrapped_timestamp = time_wrap_handler_.Unwrap(timestamp); - TemporalLayerState layer_state = kDrop; + int64_t ts_diff; + if (last_timestamp_ == -1) { + ts_diff = kOneSecond90Khz / capture_framerate_.value_or(*target_framerate_); + } else { + ts_diff = unwrapped_timestamp - last_timestamp_; + } + // Make sure both frame droppers leak out bits. + layers_[0].UpdateDebt(ts_diff / 90); + layers_[1].UpdateDebt(ts_diff / 90); + last_timestamp_ = timestamp; + + TemporalLayerState layer_state = TemporalLayerState::kDrop; + if (active_layer_ == -1 || layers_[active_layer_].state != TemporalLayer::State::kDropped) { if (last_emitted_tl0_timestamp_ != -1 && @@ -135,53 +148,43 @@ TemporalLayers::FrameConfig ScreenshareLayers::UpdateLayerConfig( switch (active_layer_) { case 0: - layer_state = kTl0; + layer_state = TemporalLayerState::kTl0; last_emitted_tl0_timestamp_ = unwrapped_timestamp; break; case 1: if (TimeToSync(unwrapped_timestamp)) { last_sync_timestamp_ = unwrapped_timestamp; - layer_state = kTl1Sync; + layer_state = TemporalLayerState::kTl1Sync; } else { - layer_state = kTl1; + layer_state = TemporalLayerState::kTl1; } break; case -1: - layer_state = kDrop; + layer_state = TemporalLayerState::kDrop; ++stats_.num_dropped_frames_; break; default: RTC_NOTREACHED(); } - int64_t ts_diff; - if (last_timestamp_ == -1) { - ts_diff = kOneSecond90Khz / capture_framerate_.value_or(*target_framerate_); - } else { - ts_diff = unwrapped_timestamp - last_timestamp_; - } - // Make sure both frame droppers leak out bits. - layers_[0].UpdateDebt(ts_diff / 90); - layers_[1].UpdateDebt(ts_diff / 90); - last_timestamp_ = timestamp; TemporalLayers::FrameConfig tl_config; // TODO(pbos): Consider referencing but not updating the 'alt' buffer for all // layers. switch (layer_state) { - case kDrop: + case TemporalLayerState::kDrop: tl_config = TemporalLayers::FrameConfig(kNone, kNone, kNone); break; - case kTl0: + case TemporalLayerState::kTl0: // TL0 only references and updates 'last'. tl_config = TemporalLayers::FrameConfig(kReferenceAndUpdate, kNone, kNone); break; - case kTl1: + case TemporalLayerState::kTl1: // TL1 references both 'last' and 'golden' but only updates 'golden'. tl_config = TemporalLayers::FrameConfig(kReference, kReferenceAndUpdate, kNone); break; - case kTl1Sync: + case TemporalLayerState::kTl1Sync: // Predict from only TL0 to allow participants to switch to the high // bitrate stream. Updates 'golden' so that TL1 can continue to refer to // and update 'golden' from this point on. @@ -275,14 +278,14 @@ void ScreenshareLayers::PopulateCodecSpecific( TemporalLayerState layer_state = static_cast(tl_config.pattern_idx); switch (layer_state) { - case kDrop: + case TemporalLayerState::kDrop: RTC_NOTREACHED(); break; - case kTl0: + case TemporalLayerState::kTl0: vp8_info->temporalIdx = 0; break; - case kTl1: - case kTl1Sync: + case TemporalLayerState::kTl1: + case TemporalLayerState::kTl1Sync: vp8_info->temporalIdx = 1; break; } @@ -358,17 +361,30 @@ bool ScreenshareLayers::UpdateConfiguration(vpx_codec_enc_cfg_t* cfg) { max_qp_ = cfg->rc_max_quantizer; // After a dropped frame, a frame with max qp will be encoded and the // quality will then ramp up from there. To boost the speed of recovery, - // encode the next frame with lower max qp. TL0 is the most important to - // improve since the errors in this layer will propagate to TL1. + // encode the next frame with lower max qp, if there is sufficient + // bandwidth to do so without causing excessive delay. + // TL0 is the most important to improve since the errors in this layer + // will propagate to TL1. // Currently, reduce max qp by 20% for TL0 and 15% for TL1. - layers_[0].enhanced_max_qp = min_qp_ + (((max_qp_ - min_qp_) * 80) / 100); - layers_[1].enhanced_max_qp = min_qp_ + (((max_qp_ - min_qp_) * 85) / 100); + if (layers_[1].target_rate_kbps_ >= kMinBitrateKbpsForQpBoost) { + layers_[0].enhanced_max_qp = + min_qp_ + (((max_qp_ - min_qp_) * 80) / 100); + layers_[1].enhanced_max_qp = + min_qp_ + (((max_qp_ - min_qp_) * 85) / 100); + } else { + layers_[0].enhanced_max_qp = -1; + layers_[1].enhanced_max_qp = -1; + } } if (capture_framerate_) { int avg_frame_size = (target_bitrate_kbps * 1000) / (8 * *capture_framerate_); - max_debt_bytes_ = 4 * avg_frame_size; + // Allow max debt to be the size of a single optimal frame. + // TODO(sprang): Determine if this needs to be adjusted by some factor. + // (Lower values may cause more frame drops, higher may lead to queuing + // delays.) + max_debt_bytes_ = avg_frame_size; } bitrate_updated_ = false; @@ -391,8 +407,6 @@ bool ScreenshareLayers::UpdateConfiguration(vpx_codec_enc_cfg_t* cfg) { adjusted_max_qp = layers_[active_layer_].enhanced_max_qp; layers_[active_layer_].state = TemporalLayer::State::kNormal; } else { - if (max_qp_ == -1) - return cfg_updated; adjusted_max_qp = max_qp_; // Set the normal max qp. } diff --git a/webrtc/modules/video_coding/codecs/vp8/screenshare_layers.h b/webrtc/modules/video_coding/codecs/vp8/screenshare_layers.h index a13f3c9aa7..873846e8b2 100644 --- a/webrtc/modules/video_coding/codecs/vp8/screenshare_layers.h +++ b/webrtc/modules/video_coding/codecs/vp8/screenshare_layers.h @@ -60,7 +60,7 @@ class ScreenshareLayers : public TemporalLayers { uint8_t Tl0PicIdx() const override; private: - enum TemporalLayerState { kDrop, kTl0, kTl1, kTl1Sync }; + enum class TemporalLayerState : int { kDrop, kTl0, kTl1, kTl1Sync }; bool TimeToSync(int64_t timestamp) const; uint32_t GetCodecTargetBitrateKbps() const; diff --git a/webrtc/modules/video_coding/codecs/vp8/screenshare_layers_unittest.cc b/webrtc/modules/video_coding/codecs/vp8/screenshare_layers_unittest.cc index f6b4beb379..9cac0b0a42 100644 --- a/webrtc/modules/video_coding/codecs/vp8/screenshare_layers_unittest.cc +++ b/webrtc/modules/video_coding/codecs/vp8/screenshare_layers_unittest.cc @@ -49,28 +49,44 @@ const int kTl1SyncFlags = VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_REF_GF | class ScreenshareLayerTest : public ::testing::Test { protected: ScreenshareLayerTest() - : min_qp_(2), max_qp_(kDefaultQp), frame_size_(-1), clock_(1) {} + : min_qp_(2), + max_qp_(kDefaultQp), + frame_size_(-1), + clock_(1), + timestamp_(90), + config_updated_(false) {} virtual ~ScreenshareLayerTest() {} - void SetUp() override { layers_.reset(new ScreenshareLayers(2, 0, &clock_)); } - - void EncodeFrame(uint32_t timestamp, - bool base_sync, - CodecSpecificInfoVP8* vp8_info, - int* flags) { - TemporalLayers::FrameConfig tl_config = - layers_->UpdateLayerConfig(timestamp); - if (tl_config.drop_frame) { - *flags = -1; - return; - } - *flags = VP8EncoderImpl::EncodeFlags(tl_config); - layers_->PopulateCodecSpecific(base_sync, tl_config, vp8_info, timestamp); - ASSERT_NE(-1, frame_size_); - layers_->FrameEncoded(frame_size_, kDefaultQp); + void SetUp() override { + layers_.reset(new ScreenshareLayers(2, 0, &clock_)); + cfg_ = ConfigureBitrates(); } - void ConfigureBitrates() { + int EncodeFrame(bool base_sync) { + int flags = ConfigureFrame(base_sync); + if (flags != -1) + layers_->FrameEncoded(frame_size_, kDefaultQp); + return flags; + } + + int ConfigureFrame(bool key_frame) { + tl_config_ = layers_->UpdateLayerConfig(timestamp_); + if (tl_config_.drop_frame) { + return -1; + } + config_updated_ = layers_->UpdateConfiguration(&cfg_); + int flags = VP8EncoderImpl::EncodeFlags(tl_config_); + layers_->PopulateCodecSpecific(key_frame, tl_config_, &vp8_info_, + timestamp_); + EXPECT_NE(-1, frame_size_); + return flags; + } + + int FrameSizeForBitrate(int bitrate_kbps) { + return ((bitrate_kbps * 1000) / 8) / kFrameRate; + } + + vpx_codec_enc_cfg_t ConfigureBitrates() { vpx_codec_enc_cfg_t vpx_cfg; memset(&vpx_cfg, 0, sizeof(vpx_codec_enc_cfg_t)); vpx_cfg.rc_min_quantizer = min_qp_; @@ -80,7 +96,8 @@ class ScreenshareLayerTest : public ::testing::Test { ElementsAre(kDefaultTl0BitrateKbps, kDefaultTl1BitrateKbps - kDefaultTl0BitrateKbps)); EXPECT_TRUE(layers_->UpdateConfiguration(&vpx_cfg)); - frame_size_ = ((vpx_cfg.rc_target_bitrate * 1000) / 8) / kFrameRate; + frame_size_ = FrameSizeForBitrate(vpx_cfg.rc_target_bitrate); + return vpx_cfg; } void WithQpLimits(int min_qp, int max_qp) { @@ -88,51 +105,39 @@ class ScreenshareLayerTest : public ::testing::Test { max_qp_ = max_qp; } - int RunGracePeriod() { - int flags = 0; - uint32_t timestamp = 0; - CodecSpecificInfoVP8 vp8_info; + // Runs a few initial frames and makes sure we have seen frames on both + // temporal layers. + bool RunGracePeriod() { bool got_tl0 = false; bool got_tl1 = false; for (int i = 0; i < 10; ++i) { - EncodeFrame(timestamp, false, &vp8_info, &flags); - timestamp += kTimestampDelta5Fps; - if (vp8_info.temporalIdx == 0) { + EXPECT_NE(-1, EncodeFrame(false)); + timestamp_ += kTimestampDelta5Fps; + if (vp8_info_.temporalIdx == 0) { got_tl0 = true; } else { got_tl1 = true; } if (got_tl0 && got_tl1) - return timestamp; + return true; } - ADD_FAILURE() << "Frames from both layers not received in time."; - return 0; + return false; } - int SkipUntilTl(int layer, int timestamp) { - CodecSpecificInfoVP8 vp8_info; + // Adds frames until we get one in the specified temporal layer. The last + // FrameEncoded() call will be omitted and needs to be done by the caller. + void SkipUntilTl(int layer) { for (int i = 0; i < 5; ++i) { - TemporalLayers::FrameConfig tl_config = - layers_->UpdateLayerConfig(timestamp); - VP8EncoderImpl::EncodeFlags(tl_config); - timestamp += kTimestampDelta5Fps; - layers_->PopulateCodecSpecific(false, tl_config, &vp8_info, timestamp); - if (vp8_info.temporalIdx != layer) { + ConfigureFrame(false); + timestamp_ += kTimestampDelta5Fps; + if (vp8_info_.temporalIdx != layer) { layers_->FrameEncoded(frame_size_, kDefaultQp); } else { - return timestamp; + // Found frame form sought layer. + return; } } ADD_FAILURE() << "Did not get a frame of TL" << layer << " in time."; - return 0; - } - - vpx_codec_enc_cfg_t GetConfig() { - vpx_codec_enc_cfg_t cfg; - memset(&cfg, 0, sizeof(cfg)); - cfg.rc_min_quantizer = 2; - cfg.rc_max_quantizer = kDefaultQp; - return cfg; } int min_qp_; @@ -140,99 +145,41 @@ class ScreenshareLayerTest : public ::testing::Test { int frame_size_; SimulatedClock clock_; std::unique_ptr layers_; + + uint32_t timestamp_; + TemporalLayers::FrameConfig tl_config_; + vpx_codec_enc_cfg_t cfg_; + bool config_updated_; + CodecSpecificInfoVP8 vp8_info_; }; TEST_F(ScreenshareLayerTest, 1Layer) { layers_.reset(new ScreenshareLayers(1, 0, &clock_)); ConfigureBitrates(); - int flags = 0; - uint32_t timestamp = 0; - CodecSpecificInfoVP8 vp8_info; // One layer screenshare should not use the frame dropper as all frames will // belong to the base layer. const int kSingleLayerFlags = 0; - TemporalLayers::FrameConfig tl_config; - tl_config = layers_->UpdateLayerConfig(timestamp); - flags = VP8EncoderImpl::EncodeFlags(tl_config); + int flags = EncodeFrame(false); + timestamp_ += kTimestampDelta5Fps; + EXPECT_EQ(static_cast(kNoTemporalIdx), vp8_info_.temporalIdx); + EXPECT_FALSE(vp8_info_.layerSync); + EXPECT_EQ(kNoTl0PicIdx, vp8_info_.tl0PicIdx); + + flags = EncodeFrame(false); EXPECT_EQ(kSingleLayerFlags, flags); - layers_->PopulateCodecSpecific(false, tl_config, &vp8_info, timestamp); - EXPECT_EQ(static_cast(kNoTemporalIdx), vp8_info.temporalIdx); - EXPECT_FALSE(vp8_info.layerSync); - EXPECT_EQ(kNoTl0PicIdx, vp8_info.tl0PicIdx); - layers_->FrameEncoded(frame_size_, kDefaultQp); - tl_config = layers_->UpdateLayerConfig(timestamp); - flags = VP8EncoderImpl::EncodeFlags(tl_config); - EXPECT_EQ(kSingleLayerFlags, flags); - timestamp += kTimestampDelta5Fps; - layers_->PopulateCodecSpecific(false, tl_config, &vp8_info, timestamp); - EXPECT_EQ(static_cast(kNoTemporalIdx), vp8_info.temporalIdx); - EXPECT_FALSE(vp8_info.layerSync); - EXPECT_EQ(kNoTl0PicIdx, vp8_info.tl0PicIdx); - layers_->FrameEncoded(frame_size_, kDefaultQp); -} - -TEST_F(ScreenshareLayerTest, 2Layer) { - ConfigureBitrates(); - int flags = 0; - uint32_t timestamp = 0; - uint8_t expected_tl0_idx = 0; - CodecSpecificInfoVP8 vp8_info; - EncodeFrame(timestamp, false, &vp8_info, &flags); - EXPECT_EQ(kTl0Flags, flags); - EXPECT_EQ(0, vp8_info.temporalIdx); - EXPECT_FALSE(vp8_info.layerSync); - ++expected_tl0_idx; - EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx); - - // Insert 5 frames, cover grace period. All should be in TL0. - for (int i = 0; i < 5; ++i) { - timestamp += kTimestampDelta5Fps; - EncodeFrame(timestamp, false, &vp8_info, &flags); - EXPECT_EQ(0, vp8_info.temporalIdx); - EXPECT_FALSE(vp8_info.layerSync); - ++expected_tl0_idx; - EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx); - } - - // First frame in TL0. - timestamp += kTimestampDelta5Fps; - EncodeFrame(timestamp, false, &vp8_info, &flags); - EXPECT_EQ(kTl0Flags, flags); - EXPECT_EQ(0, vp8_info.temporalIdx); - EXPECT_FALSE(vp8_info.layerSync); - ++expected_tl0_idx; - EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx); - - // Drop two frames from TL0, thus being coded in TL1. - timestamp += kTimestampDelta5Fps; - EncodeFrame(timestamp, false, &vp8_info, &flags); - // First frame is sync frame. - EXPECT_EQ(kTl1SyncFlags, flags); - EXPECT_EQ(1, vp8_info.temporalIdx); - EXPECT_TRUE(vp8_info.layerSync); - EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx); - - timestamp += kTimestampDelta5Fps; - EncodeFrame(timestamp, false, &vp8_info, &flags); - EXPECT_EQ(kTl1Flags, flags); - EXPECT_EQ(1, vp8_info.temporalIdx); - EXPECT_FALSE(vp8_info.layerSync); - EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx); + EXPECT_EQ(static_cast(kNoTemporalIdx), vp8_info_.temporalIdx); + EXPECT_FALSE(vp8_info_.layerSync); + EXPECT_EQ(kNoTl0PicIdx, vp8_info_.tl0PicIdx); } TEST_F(ScreenshareLayerTest, 2LayersPeriodicSync) { - ConfigureBitrates(); - int flags = 0; - uint32_t timestamp = 0; - CodecSpecificInfoVP8 vp8_info; std::vector sync_times; - const int kNumFrames = kSyncPeriodSeconds * kFrameRate * 2 - 1; for (int i = 0; i < kNumFrames; ++i) { - timestamp += kTimestampDelta5Fps; - EncodeFrame(timestamp, false, &vp8_info, &flags); - if (vp8_info.temporalIdx == 1 && vp8_info.layerSync) { - sync_times.push_back(timestamp); + EncodeFrame(false); + timestamp_ += kTimestampDelta5Fps; + if (vp8_info_.temporalIdx == 1 && vp8_info_.layerSync) { + sync_times.push_back(timestamp_); } } @@ -241,27 +188,24 @@ TEST_F(ScreenshareLayerTest, 2LayersPeriodicSync) { } TEST_F(ScreenshareLayerTest, 2LayersSyncAfterTimeout) { - ConfigureBitrates(); - uint32_t timestamp = 0; - CodecSpecificInfoVP8 vp8_info; std::vector sync_times; - const int kNumFrames = kMaxSyncPeriodSeconds * kFrameRate * 2 - 1; for (int i = 0; i < kNumFrames; ++i) { - timestamp += kTimestampDelta5Fps; - TemporalLayers::FrameConfig tl_config = - layers_->UpdateLayerConfig(timestamp); - layers_->PopulateCodecSpecific(false, tl_config, &vp8_info, timestamp); + tl_config_ = layers_->UpdateLayerConfig(timestamp_); + config_updated_ = layers_->UpdateConfiguration(&cfg_); + layers_->PopulateCodecSpecific(false, tl_config_, &vp8_info_, timestamp_); // Simulate TL1 being at least 8 qp steps better. - if (vp8_info.temporalIdx == 0) { + if (vp8_info_.temporalIdx == 0) { layers_->FrameEncoded(frame_size_, kDefaultQp); } else { layers_->FrameEncoded(frame_size_, kDefaultQp - 8); } - if (vp8_info.temporalIdx == 1 && vp8_info.layerSync) - sync_times.push_back(timestamp); + if (vp8_info_.temporalIdx == 1 && vp8_info_.layerSync) + sync_times.push_back(timestamp_); + + timestamp_ += kTimestampDelta5Fps; } ASSERT_EQ(2u, sync_times.size()); @@ -269,70 +213,59 @@ TEST_F(ScreenshareLayerTest, 2LayersSyncAfterTimeout) { } TEST_F(ScreenshareLayerTest, 2LayersSyncAfterSimilarQP) { - ConfigureBitrates(); - uint32_t timestamp = 0; - CodecSpecificInfoVP8 vp8_info; std::vector sync_times; const int kNumFrames = (kSyncPeriodSeconds + ((kMaxSyncPeriodSeconds - kSyncPeriodSeconds) / 2)) * kFrameRate; for (int i = 0; i < kNumFrames; ++i) { - timestamp += kTimestampDelta5Fps; - TemporalLayers::FrameConfig tl_config = - layers_->UpdateLayerConfig(timestamp); - layers_->PopulateCodecSpecific(false, tl_config, &vp8_info, timestamp); + ConfigureFrame(false); // Simulate TL1 being at least 8 qp steps better. - if (vp8_info.temporalIdx == 0) { + if (vp8_info_.temporalIdx == 0) { layers_->FrameEncoded(frame_size_, kDefaultQp); } else { layers_->FrameEncoded(frame_size_, kDefaultQp - 8); } - if (vp8_info.temporalIdx == 1 && vp8_info.layerSync) - sync_times.push_back(timestamp); + if (vp8_info_.temporalIdx == 1 && vp8_info_.layerSync) + sync_times.push_back(timestamp_); + + timestamp_ += kTimestampDelta5Fps; } ASSERT_EQ(1u, sync_times.size()); bool bumped_tl0_quality = false; for (int i = 0; i < 3; ++i) { - timestamp += kTimestampDelta5Fps; - TemporalLayers::FrameConfig tl_config = - layers_->UpdateLayerConfig(timestamp); - int flags = VP8EncoderImpl::EncodeFlags(tl_config); - layers_->PopulateCodecSpecific(false, tl_config, &vp8_info, timestamp); - - if (vp8_info.temporalIdx == 0) { + int flags = ConfigureFrame(false); + if (vp8_info_.temporalIdx == 0) { // Bump TL0 to same quality as TL1. layers_->FrameEncoded(frame_size_, kDefaultQp - 8); bumped_tl0_quality = true; } else { layers_->FrameEncoded(frame_size_, kDefaultQp - 8); if (bumped_tl0_quality) { - EXPECT_TRUE(vp8_info.layerSync); + EXPECT_TRUE(vp8_info_.layerSync); EXPECT_EQ(kTl1SyncFlags, flags); return; } } + timestamp_ += kTimestampDelta5Fps; } ADD_FAILURE() << "No TL1 frame arrived within time limit."; } TEST_F(ScreenshareLayerTest, 2LayersToggling) { - ConfigureBitrates(); - int flags = 0; - CodecSpecificInfoVP8 vp8_info; - uint32_t timestamp = RunGracePeriod(); + EXPECT_TRUE(RunGracePeriod()); // Insert 50 frames. 2/5 should be TL0. int tl0_frames = 0; int tl1_frames = 0; for (int i = 0; i < 50; ++i) { - timestamp += kTimestampDelta5Fps; - EncodeFrame(timestamp, false, &vp8_info, &flags); - switch (vp8_info.temporalIdx) { + EncodeFrame(false); + timestamp_ += kTimestampDelta5Fps; + switch (vp8_info_.temporalIdx) { case 0: ++tl0_frames; break; @@ -348,39 +281,31 @@ TEST_F(ScreenshareLayerTest, 2LayersToggling) { } TEST_F(ScreenshareLayerTest, AllFitsLayer0) { - ConfigureBitrates(); - frame_size_ = ((kDefaultTl0BitrateKbps * 1000) / 8) / kFrameRate; + frame_size_ = FrameSizeForBitrate(kDefaultTl0BitrateKbps); - int flags = 0; - uint32_t timestamp = 0; - CodecSpecificInfoVP8 vp8_info; // Insert 50 frames, small enough that all fits in TL0. for (int i = 0; i < 50; ++i) { - EncodeFrame(timestamp, false, &vp8_info, &flags); - timestamp += kTimestampDelta5Fps; + int flags = EncodeFrame(false); + timestamp_ += kTimestampDelta5Fps; EXPECT_EQ(kTl0Flags, flags); - EXPECT_EQ(0, vp8_info.temporalIdx); + EXPECT_EQ(0, vp8_info_.temporalIdx); } } TEST_F(ScreenshareLayerTest, TooHighBitrate) { - ConfigureBitrates(); - frame_size_ = 2 * ((kDefaultTl1BitrateKbps * 1000) / 8) / kFrameRate; - int flags = 0; - CodecSpecificInfoVP8 vp8_info; - uint32_t timestamp = RunGracePeriod(); + frame_size_ = 2 * FrameSizeForBitrate(kDefaultTl1BitrateKbps); // Insert 100 frames. Half should be dropped. int tl0_frames = 0; int tl1_frames = 0; int dropped_frames = 0; for (int i = 0; i < 100; ++i) { - timestamp += kTimestampDelta5Fps; - EncodeFrame(timestamp, false, &vp8_info, &flags); + int flags = EncodeFrame(false); + timestamp_ += kTimestampDelta5Fps; if (flags == -1) { ++dropped_frames; } else { - switch (vp8_info.temporalIdx) { + switch (vp8_info_.temporalIdx) { case 0: ++tl0_frames; break; @@ -388,107 +313,99 @@ TEST_F(ScreenshareLayerTest, TooHighBitrate) { ++tl1_frames; break; default: - abort(); + ADD_FAILURE() << "Unexpected temporal id"; } } } - EXPECT_EQ(50, tl0_frames + tl1_frames); - EXPECT_EQ(50, dropped_frames); + EXPECT_NEAR(50, tl0_frames + tl1_frames, 1); + EXPECT_NEAR(50, dropped_frames, 1); } TEST_F(ScreenshareLayerTest, TargetBitrateCappedByTL0) { - vpx_codec_enc_cfg_t cfg = GetConfig(); const int kTl0_kbps = 100; const int kTl1_kbps = 1000; layers_->OnRatesUpdated(kTl0_kbps, kTl1_kbps, 5); EXPECT_THAT(layers_->OnRatesUpdated(kTl0_kbps, kTl1_kbps, 5), ElementsAre(kTl0_kbps, kTl1_kbps - kTl0_kbps)); - EXPECT_TRUE(layers_->UpdateConfiguration(&cfg)); + EXPECT_TRUE(layers_->UpdateConfiguration(&cfg_)); EXPECT_EQ(static_cast( ScreenshareLayers::kMaxTL0FpsReduction * kTl0_kbps + 0.5), - cfg.rc_target_bitrate); + cfg_.rc_target_bitrate); } TEST_F(ScreenshareLayerTest, TargetBitrateCappedByTL1) { - vpx_codec_enc_cfg_t cfg = GetConfig(); const int kTl0_kbps = 100; const int kTl1_kbps = 450; EXPECT_THAT(layers_->OnRatesUpdated(kTl0_kbps, kTl1_kbps, 5), ElementsAre(kTl0_kbps, kTl1_kbps - kTl0_kbps)); - EXPECT_TRUE(layers_->UpdateConfiguration(&cfg)); + EXPECT_TRUE(layers_->UpdateConfiguration(&cfg_)); EXPECT_EQ(static_cast( kTl1_kbps / ScreenshareLayers::kAcceptableTargetOvershoot), - cfg.rc_target_bitrate); + cfg_.rc_target_bitrate); } TEST_F(ScreenshareLayerTest, TargetBitrateBelowTL0) { - vpx_codec_enc_cfg_t cfg = GetConfig(); const int kTl0_kbps = 100; const int kTl1_kbps = 100; EXPECT_THAT(layers_->OnRatesUpdated(kTl0_kbps, kTl1_kbps, 5), ElementsAre(kTl0_kbps)); - EXPECT_TRUE(layers_->UpdateConfiguration(&cfg)); + EXPECT_TRUE(layers_->UpdateConfiguration(&cfg_)); - EXPECT_EQ(static_cast(kTl1_kbps), cfg.rc_target_bitrate); + EXPECT_EQ(static_cast(kTl1_kbps), cfg_.rc_target_bitrate); } TEST_F(ScreenshareLayerTest, EncoderDrop) { - ConfigureBitrates(); - CodecSpecificInfoVP8 vp8_info; - vpx_codec_enc_cfg_t cfg = GetConfig(); - // Updates cfg with current target bitrate. - EXPECT_TRUE(layers_->UpdateConfiguration(&cfg)); - - uint32_t timestamp = RunGracePeriod(); - timestamp = SkipUntilTl(0, timestamp); + EXPECT_TRUE(RunGracePeriod()); + SkipUntilTl(0); // Size 0 indicates dropped frame. layers_->FrameEncoded(0, kDefaultQp); - timestamp += kTimestampDelta5Fps; - EXPECT_FALSE(layers_->UpdateConfiguration(&cfg)); - TemporalLayers::FrameConfig tl_config = layers_->UpdateLayerConfig(timestamp); - EXPECT_EQ(kTl0Flags, VP8EncoderImpl::EncodeFlags(tl_config)); - layers_->PopulateCodecSpecific(false, tl_config, &vp8_info, timestamp); - layers_->FrameEncoded(frame_size_, kDefaultQp); - timestamp = SkipUntilTl(0, timestamp); - EXPECT_TRUE(layers_->UpdateConfiguration(&cfg)); - EXPECT_LT(cfg.rc_max_quantizer, static_cast(kDefaultQp)); - layers_->FrameEncoded(frame_size_, kDefaultQp); + // Re-encode frame (so don't advance timestamp). + int flags = EncodeFrame(false); + timestamp_ += kTimestampDelta5Fps; + EXPECT_FALSE(config_updated_); + EXPECT_EQ(kTl0Flags, flags); - tl_config = layers_->UpdateLayerConfig(timestamp); - timestamp += kTimestampDelta5Fps; - EXPECT_TRUE(layers_->UpdateConfiguration(&cfg)); - layers_->PopulateCodecSpecific(false, tl_config, &vp8_info, timestamp); - EXPECT_EQ(cfg.rc_max_quantizer, static_cast(kDefaultQp)); + // Next frame should have boosted quality... + SkipUntilTl(0); + EXPECT_TRUE(config_updated_); + EXPECT_LT(cfg_.rc_max_quantizer, static_cast(kDefaultQp)); layers_->FrameEncoded(frame_size_, kDefaultQp); + timestamp_ += kTimestampDelta5Fps; + + // ...then back to standard setup. + SkipUntilTl(0); + layers_->FrameEncoded(frame_size_, kDefaultQp); + timestamp_ += kTimestampDelta5Fps; + EXPECT_EQ(cfg_.rc_max_quantizer, static_cast(kDefaultQp)); // Next drop in TL1. - - timestamp = SkipUntilTl(1, timestamp); + SkipUntilTl(1); layers_->FrameEncoded(0, kDefaultQp); - timestamp += kTimestampDelta5Fps; - EXPECT_FALSE(layers_->UpdateConfiguration(&cfg)); - tl_config = layers_->UpdateLayerConfig(timestamp); - EXPECT_EQ(kTl1Flags, VP8EncoderImpl::EncodeFlags(tl_config)); - layers_->PopulateCodecSpecific(false, tl_config, &vp8_info, timestamp); - layers_->FrameEncoded(frame_size_, kDefaultQp); - timestamp = SkipUntilTl(1, timestamp); - EXPECT_TRUE(layers_->UpdateConfiguration(&cfg)); - EXPECT_LT(cfg.rc_max_quantizer, static_cast(kDefaultQp)); - layers_->FrameEncoded(frame_size_, kDefaultQp); + // Re-encode frame (so don't advance timestamp). + flags = EncodeFrame(false); + timestamp_ += kTimestampDelta5Fps; + EXPECT_FALSE(config_updated_); + EXPECT_EQ(kTl1Flags, flags); - tl_config = layers_->UpdateLayerConfig(timestamp); - timestamp += kTimestampDelta5Fps; - EXPECT_TRUE(layers_->UpdateConfiguration(&cfg)); - layers_->PopulateCodecSpecific(false, tl_config, &vp8_info, timestamp); - EXPECT_EQ(cfg.rc_max_quantizer, static_cast(kDefaultQp)); + // Next frame should have boosted QP. + SkipUntilTl(1); + EXPECT_TRUE(config_updated_); + EXPECT_LT(cfg_.rc_max_quantizer, static_cast(kDefaultQp)); layers_->FrameEncoded(frame_size_, kDefaultQp); + timestamp_ += kTimestampDelta5Fps; + + // ...and back to normal. + SkipUntilTl(1); + EXPECT_EQ(cfg_.rc_max_quantizer, static_cast(kDefaultQp)); + layers_->FrameEncoded(frame_size_, kDefaultQp); + timestamp_ += kTimestampDelta5Fps; } TEST_F(ScreenshareLayerTest, RespectsMaxIntervalBetweenFrames) { @@ -496,9 +413,8 @@ TEST_F(ScreenshareLayerTest, RespectsMaxIntervalBetweenFrames) { const int kLargeFrameSizeBytes = 100000; const uint32_t kStartTimestamp = 1234; - vpx_codec_enc_cfg_t cfg = GetConfig(); layers_->OnRatesUpdated(kLowBitrateKbps, kLowBitrateKbps, 5); - layers_->UpdateConfiguration(&cfg); + layers_->UpdateConfiguration(&cfg_); EXPECT_EQ(kTl0Flags, VP8EncoderImpl::EncodeFlags( layers_->UpdateLayerConfig(kStartTimestamp))); @@ -520,8 +436,6 @@ TEST_F(ScreenshareLayerTest, RespectsMaxIntervalBetweenFrames) { TEST_F(ScreenshareLayerTest, UpdatesHistograms) { metrics::Reset(); - ConfigureBitrates(); - vpx_codec_enc_cfg_t cfg = GetConfig(); bool trigger_drop = false; bool dropped_frame = false; bool overshoot = false; @@ -530,15 +444,14 @@ TEST_F(ScreenshareLayerTest, UpdatesHistograms) { for (int64_t timestamp = 0; timestamp < kTimestampDelta5Fps * 5 * metrics::kMinRunTimeInSeconds; timestamp += kTimestampDelta5Fps) { - TemporalLayers::FrameConfig tl_config = - layers_->UpdateLayerConfig(timestamp); - if (tl_config.drop_frame) { + tl_config_ = layers_->UpdateLayerConfig(timestamp); + if (tl_config_.drop_frame) { dropped_frame = true; continue; } - int flags = VP8EncoderImpl::EncodeFlags(tl_config); + int flags = VP8EncoderImpl::EncodeFlags(tl_config_); if (flags != -1) - layers_->UpdateConfiguration(&cfg); + layers_->UpdateConfiguration(&cfg_); if (timestamp >= kTimestampDelta5Fps * 5 && !overshoot && flags != -1) { // Simulate one overshoot. @@ -551,7 +464,7 @@ TEST_F(ScreenshareLayerTest, UpdatesHistograms) { if (flags == kTl0Flags) { if (timestamp >= kTimestampDelta5Fps * 20 && !trigger_drop) { // Simulate a too large frame, to cause frame drop. - layers_->FrameEncoded(frame_size_ * 5, kTl0Qp); + layers_->FrameEncoded(frame_size_ * 10, kTl0Qp); trigger_drop = true; } else { layers_->FrameEncoded(frame_size_, kTl0Qp); @@ -603,13 +516,13 @@ TEST_F(ScreenshareLayerTest, UpdatesHistograms) { } TEST_F(ScreenshareLayerTest, AllowsUpdateConfigBeforeSetRates) { - vpx_codec_enc_cfg_t cfg = GetConfig(); - EXPECT_FALSE(layers_->UpdateConfiguration(&cfg)); + layers_.reset(new ScreenshareLayers(2, 0, &clock_)); + // New layer instance, OnRatesUpdated() never called. + // UpdateConfiguration() call should not cause crash. + layers_->UpdateConfiguration(&cfg_); } TEST_F(ScreenshareLayerTest, RespectsConfiguredFramerate) { - ConfigureBitrates(); - int64_t kTestSpanMs = 2000; int64_t kFrameIntervalsMs = 1000 / kFrameRate;