Don't boost QP after drop unless there is sufficient bandwidth

If a frame is dropped and re-encoded because it exceeded the target
bitrate by a large factor, the next frame will be encoded at max qp
(worst quality) in order to get a frame through in a timely manner. The
next frame after this will still have lower quality since the rate
controller essentially gets reset. In order to mitigate that we boost
the qp for that next frame, which brings the stream back to a good
quality quicker.

However, if the network conditions are _really_ bad, this boosted qp
may be too large, causing the frame again to be dropped an re-encoded.

This CL set's a minimum bitrate available in order to enabling the
boosting in the first place.
It also adjusts a timeout (max time between frames in TL0), since a
too small value and very difficult frames in conjunction with the
mentioned bad network could actually cause bad network over-utilization
in turn leading to packet loss and bad follow-on effects to that.

There was also some slop in the rate keeping for the two layers.
This has been tightened up and affected test cases have been fixed.

BUG=webrtc:7694

Review-Url: https://codereview.webrtc.org/2897983002
Cr-Commit-Position: refs/heads/master@{#18236}
This commit is contained in:
sprang 2017-05-23 07:47:55 -07:00 committed by Commit bot
parent 7855fff5bf
commit 916170ae46
3 changed files with 204 additions and 277 deletions

View File

@ -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<int>(kTl1);
tl_config.pattern_idx = static_cast<int>(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<TemporalLayerState>(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.
}

View File

@ -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;

View File

@ -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<ScreenshareLayers> 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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<int> 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<int> 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<int> 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<unsigned int>(
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<unsigned int>(
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<uint32_t>(kTl1_kbps), cfg.rc_target_bitrate);
EXPECT_EQ(static_cast<uint32_t>(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<unsigned int>(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<unsigned int>(kDefaultQp));
// Next frame should have boosted quality...
SkipUntilTl(0);
EXPECT_TRUE(config_updated_);
EXPECT_LT(cfg_.rc_max_quantizer, static_cast<unsigned int>(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<unsigned int>(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<unsigned int>(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<unsigned int>(kDefaultQp));
// Next frame should have boosted QP.
SkipUntilTl(1);
EXPECT_TRUE(config_updated_);
EXPECT_LT(cfg_.rc_max_quantizer, static_cast<unsigned int>(kDefaultQp));
layers_->FrameEncoded(frame_size_, kDefaultQp);
timestamp_ += kTimestampDelta5Fps;
// ...and back to normal.
SkipUntilTl(1);
EXPECT_EQ(cfg_.rc_max_quantizer, static_cast<unsigned int>(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;