Index: webrtc/modules/video_coding/codecs/vp8/screenshare_layers.cc |
diff --git a/webrtc/modules/video_coding/codecs/vp8/screenshare_layers.cc b/webrtc/modules/video_coding/codecs/vp8/screenshare_layers.cc |
index 63ef227812fc985195ee6fc701f4210e9c43cdab..ecaf3dd4a598fc84c5b291a3b09fae2e790d402f 100644 |
--- a/webrtc/modules/video_coding/codecs/vp8/screenshare_layers.cc |
+++ b/webrtc/modules/video_coding/codecs/vp8/screenshare_layers.cc |
@@ -11,32 +11,52 @@ |
#include <stdlib.h> |
+#include "webrtc/base/checks.h" |
#include "vpx/vpx_encoder.h" |
#include "vpx/vp8cx.h" |
#include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h" |
namespace webrtc { |
-enum { kOneSecond90Khz = 90000 }; |
+static const int kOneSecond90Khz = 90000; |
+static const int kMinTimeBetweenSyncs = kOneSecond90Khz * 5; |
+static const int kMaxTimeBetweenSyncs = kOneSecond90Khz * 10; |
+static const int kQpDeltaThresholdForSync = 8; |
const double ScreenshareLayers::kMaxTL0FpsReduction = 2.5; |
const double ScreenshareLayers::kAcceptableTargetOvershoot = 2.0; |
+// Since this is TL0 we only allow updating and predicting from the LAST |
+// reference frame. |
+const int ScreenshareLayers::kTl0Flags = |
+ VP8_EFLAG_NO_UPD_GF | VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_REF_GF | |
+ VP8_EFLAG_NO_REF_ARF; |
+ |
+// Allow predicting from both TL0 and TL1. |
+const int ScreenshareLayers::kTl1Flags = |
+ VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_UPD_LAST; |
+ |
+// Allow predicting from only TL0 to allow participants to switch to the high |
+// bitrate stream. This means predicting only from the LAST reference frame, but |
+// only updating GF to not corrupt TL0. |
+const int ScreenshareLayers::kTl1SyncFlags = |
+ VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_REF_GF | VP8_EFLAG_NO_UPD_ARF | |
+ VP8_EFLAG_NO_UPD_LAST; |
+ |
ScreenshareLayers::ScreenshareLayers(int num_temporal_layers, |
- uint8_t initial_tl0_pic_idx, |
- FrameDropper* tl0_frame_dropper, |
- FrameDropper* tl1_frame_dropper) |
- : tl0_frame_dropper_(tl0_frame_dropper), |
- tl1_frame_dropper_(tl1_frame_dropper), |
- number_of_temporal_layers_(num_temporal_layers), |
+ uint8_t initial_tl0_pic_idx) |
+ : number_of_temporal_layers_(num_temporal_layers), |
last_base_layer_sync_(false), |
tl0_pic_idx_(initial_tl0_pic_idx), |
- active_layer_(0), |
- framerate_(5), |
- last_sync_timestamp_(-1) { |
+ active_layer_(-1), |
+ last_timestamp_(-1), |
+ last_sync_timestamp_(-1), |
+ min_qp_(-1), |
+ max_qp_(-1), |
+ max_debt_bytes_(0), |
+ frame_rate_(-1) { |
assert(num_temporal_layers > 0); |
assert(num_temporal_layers <= 2); |
- assert(tl0_frame_dropper && tl1_frame_dropper); |
} |
int ScreenshareLayers::CurrentLayerId() const { |
@@ -49,84 +69,125 @@ int ScreenshareLayers::EncodeFlags(uint32_t timestamp) { |
// No flags needed for 1 layer screenshare. |
return 0; |
} |
- CalculateFramerate(timestamp); |
+ |
+ int64_t unwrapped_timestamp = time_wrap_handler_.Unwrap(timestamp); |
int flags = 0; |
- // Note that ARF on purpose isn't used in this scheme since it is allocated |
- // for the last key frame to make key frame caching possible. |
- if (tl0_frame_dropper_->DropFrame()) { |
- // Must drop TL0, encode TL1 instead. |
- if (tl1_frame_dropper_->DropFrame()) { |
- // Must drop both TL0 and TL1. |
- flags = -1; |
- } else { |
- active_layer_ = 1; |
- if (TimeToSync(timestamp)) { |
- last_sync_timestamp_ = timestamp; |
- // Allow predicting from only TL0 to allow participants to switch to the |
- // high bitrate stream. This means predicting only from the LAST |
- // reference frame, but only updating GF to not corrupt TL0. |
- flags = VP8_EFLAG_NO_REF_ARF; |
- flags |= VP8_EFLAG_NO_REF_GF; |
- flags |= VP8_EFLAG_NO_UPD_ARF; |
- flags |= VP8_EFLAG_NO_UPD_LAST; |
+ |
+ if (active_layer_ == -1 || |
+ layers_[active_layer_].state != TemporalLayer::State::kDropped) { |
+ if (layers_[0].debt_bytes_ > max_debt_bytes_) { |
+ // Must drop TL0, encode TL1 instead. |
+ if (layers_[1].debt_bytes_ > max_debt_bytes_) { |
+ // Must drop both TL0 and TL1. |
+ active_layer_ = -1; |
} else { |
- // Allow predicting from both TL0 and TL1. |
- flags = VP8_EFLAG_NO_REF_ARF; |
- flags |= VP8_EFLAG_NO_UPD_ARF; |
- flags |= VP8_EFLAG_NO_UPD_LAST; |
+ active_layer_ = 1; |
} |
+ } else { |
+ active_layer_ = 0; |
} |
- } else { |
- active_layer_ = 0; |
- // Since this is TL0 we only allow updating and predicting from the LAST |
- // reference frame. |
- flags = VP8_EFLAG_NO_UPD_GF; |
- flags |= VP8_EFLAG_NO_UPD_ARF; |
- flags |= VP8_EFLAG_NO_REF_GF; |
- flags |= VP8_EFLAG_NO_REF_ARF; |
} |
+ |
+ switch (active_layer_) { |
+ case 0: |
+ flags = kTl0Flags; |
+ break; |
+ case 1: |
+ if (TimeToSync(unwrapped_timestamp)) { |
+ last_sync_timestamp_ = unwrapped_timestamp; |
+ flags = kTl1SyncFlags; |
+ } else { |
+ flags = kTl1Flags; |
+ } |
+ break; |
+ case -1: |
+ flags = -1; |
+ break; |
+ default: |
+ flags = -1; |
+ RTC_NOTREACHED(); |
+ } |
+ |
// Make sure both frame droppers leak out bits. |
- tl0_frame_dropper_->Leak(framerate_); |
- tl1_frame_dropper_->Leak(framerate_); |
+ int64_t ts_diff; |
+ if (last_timestamp_ == -1) { |
+ ts_diff = kOneSecond90Khz / (frame_rate_ <= 0 ? 5 : frame_rate_); |
+ } else { |
+ ts_diff = unwrapped_timestamp - last_timestamp_; |
+ } |
+ |
+ layers_[0].UpdateDebt(ts_diff / 90); |
+ layers_[1].UpdateDebt(ts_diff / 90); |
+ last_timestamp_ = timestamp; |
return flags; |
} |
-bool ScreenshareLayers::ConfigureBitrates(int bitrate_kbit, |
- int max_bitrate_kbit, |
+bool ScreenshareLayers::ConfigureBitrates(int bitrate_kbps, |
+ int max_bitrate_kbps, |
int framerate, |
vpx_codec_enc_cfg_t* cfg) { |
- if (framerate > 0) |
- framerate_ = framerate; |
+ layers_[0].target_rate_kbps_ = bitrate_kbps; |
+ layers_[1].target_rate_kbps_ = max_bitrate_kbps; |
- tl0_frame_dropper_->SetRates(bitrate_kbit, framerate_); |
- tl1_frame_dropper_->SetRates(max_bitrate_kbit, framerate_); |
+ int target_bitrate_kbps = bitrate_kbps; |
if (cfg != nullptr) { |
// Calculate a codec target bitrate. This may be higher than TL0, gaining |
// quality at the expense of frame rate at TL0. Constraints: |
// - TL0 frame rate should not be less than framerate / kMaxTL0FpsReduction. |
// - Target rate * kAcceptableTargetOvershoot should not exceed TL1 rate. |
- double target_bitrate = |
- std::min(bitrate_kbit * kMaxTL0FpsReduction, |
- max_bitrate_kbit / kAcceptableTargetOvershoot); |
- cfg->rc_target_bitrate = |
- std::max(static_cast<unsigned int>(bitrate_kbit), |
- static_cast<unsigned int>(target_bitrate + 0.5)); |
+ target_bitrate_kbps = |
+ std::min(bitrate_kbps * kMaxTL0FpsReduction, |
+ max_bitrate_kbps / kAcceptableTargetOvershoot); |
+ |
+ // Don't reconfigure qp limits during quality boost frames. |
+ if (layers_[active_layer_].state != TemporalLayer::State::kQualityBoost) { |
+ min_qp_ = cfg->rc_min_quantizer; |
+ 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. |
+ // 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); |
+ } |
+ |
+ cfg->rc_target_bitrate = std::max(bitrate_kbps, target_bitrate_kbps); |
} |
+ int avg_frame_size = (target_bitrate_kbps * 1000) / (8 * framerate); |
+ max_debt_bytes_ = 4 * avg_frame_size; |
+ |
return true; |
} |
-void ScreenshareLayers::FrameEncoded(unsigned int size, uint32_t timestamp) { |
+void ScreenshareLayers::FrameEncoded(unsigned int size, |
+ uint32_t timestamp, |
+ int qp) { |
+ if (size == 0) { |
+ layers_[active_layer_].state = TemporalLayer::State::kDropped; |
+ return; |
+ } |
+ if (layers_[active_layer_].state == TemporalLayer::State::kDropped) { |
+ layers_[active_layer_].state = TemporalLayer::State::kQualityBoost; |
+ } |
+ |
+ if (qp != -1) |
+ layers_[active_layer_].last_qp = qp; |
+ |
if (active_layer_ == 0) { |
- tl0_frame_dropper_->Fill(size, true); |
+ layers_[0].debt_bytes_ += size; |
+ layers_[1].debt_bytes_ += size; |
+ } else if (active_layer_ == 1) { |
+ layers_[1].debt_bytes_ += size; |
} |
- tl1_frame_dropper_->Fill(size, true); |
} |
void ScreenshareLayers::PopulateCodecSpecific(bool base_layer_sync, |
CodecSpecificInfoVP8 *vp8_info, |
uint32_t timestamp) { |
+ int64_t unwrapped_timestamp = time_wrap_handler_.Unwrap(timestamp); |
if (number_of_temporal_layers_ == 1) { |
vp8_info->temporalIdx = kNoTemporalIdx; |
vp8_info->layerSync = false; |
@@ -135,13 +196,14 @@ void ScreenshareLayers::PopulateCodecSpecific(bool base_layer_sync, |
vp8_info->temporalIdx = active_layer_; |
if (base_layer_sync) { |
vp8_info->temporalIdx = 0; |
- last_sync_timestamp_ = timestamp; |
+ last_sync_timestamp_ = unwrapped_timestamp; |
} else if (last_base_layer_sync_ && vp8_info->temporalIdx != 0) { |
// Regardless of pattern the frame after a base layer sync will always |
// be a layer sync. |
- last_sync_timestamp_ = timestamp; |
+ last_sync_timestamp_ = unwrapped_timestamp; |
} |
- vp8_info->layerSync = (last_sync_timestamp_ == timestamp); |
+ vp8_info->layerSync = last_sync_timestamp_ != -1 && |
+ last_sync_timestamp_ == unwrapped_timestamp; |
if (vp8_info->temporalIdx == 0) { |
tl0_pic_idx_++; |
} |
@@ -150,27 +212,65 @@ void ScreenshareLayers::PopulateCodecSpecific(bool base_layer_sync, |
} |
} |
-bool ScreenshareLayers::TimeToSync(uint32_t timestamp) const { |
- const uint32_t timestamp_diff = timestamp - last_sync_timestamp_; |
- return last_sync_timestamp_ < 0 || timestamp_diff > kOneSecond90Khz; |
+bool ScreenshareLayers::TimeToSync(int64_t timestamp) const { |
+ if (active_layer_ != 1) { |
+ RTC_NOTREACHED(); |
+ return false; |
+ } |
+ DCHECK_NE(-1, layers_[0].last_qp); |
+ if (layers_[1].last_qp == -1) { |
+ // First frame in TL1 should only depend on TL0 since there are no |
+ // previous frames in TL1. |
+ return true; |
+ } |
+ |
+ DCHECK_NE(-1, last_sync_timestamp_); |
+ int64_t timestamp_diff = timestamp - last_sync_timestamp_; |
+ if (timestamp_diff > kMaxTimeBetweenSyncs) { |
+ // After a certain time, force a sync frame. |
+ return true; |
+ } else if (timestamp_diff < kMinTimeBetweenSyncs) { |
+ // If too soon from previous sync frame, don't issue a new one. |
+ return false; |
+ } |
+ // Issue a sync frame if difference in quality between TL0 and TL1 isn't too |
+ // large. |
+ if (layers_[0].last_qp - layers_[1].last_qp < kQpDeltaThresholdForSync) |
+ return true; |
+ return false; |
} |
-void ScreenshareLayers::CalculateFramerate(uint32_t timestamp) { |
- timestamp_list_.push_front(timestamp); |
- // Remove timestamps older than 1 second from the list. |
- uint32_t timestamp_diff = timestamp - timestamp_list_.back(); |
- while (timestamp_diff > kOneSecond90Khz) { |
- timestamp_list_.pop_back(); |
- timestamp_diff = timestamp - timestamp_list_.back(); |
+bool ScreenshareLayers::UpdateConfiguration(vpx_codec_enc_cfg_t* cfg) { |
+ if (max_qp_ == -1) |
+ return false; |
+ |
+ // If layer is in the quality boost state (following a dropped frame), update |
+ // the configuration with the adjusted (lower) qp and set the state back to |
+ // normal. |
+ unsigned int adjusted_max_qp; |
+ if (layers_[active_layer_].state == TemporalLayer::State::kQualityBoost && |
+ layers_[active_layer_].enhanced_max_qp != -1) { |
+ adjusted_max_qp = layers_[active_layer_].enhanced_max_qp; |
+ layers_[active_layer_].state = TemporalLayer::State::kNormal; |
+ } else { |
+ if (max_qp_ == -1) |
+ return false; |
+ adjusted_max_qp = max_qp_; // Set the normal max qp. |
} |
- // If we have encoded frames within the last second, that number of frames |
- // is a reasonable first estimate of the framerate. |
- framerate_ = timestamp_list_.size(); |
- if (timestamp_diff > 0) { |
- // Estimate the framerate by dividing the number of timestamp diffs with |
- // the sum of the timestamp diffs (with rounding). |
- framerate_ = (kOneSecond90Khz * (timestamp_list_.size() - 1) + |
- timestamp_diff / 2) / timestamp_diff; |
+ |
+ if (adjusted_max_qp == cfg->rc_max_quantizer) |
+ return false; |
+ |
+ cfg->rc_max_quantizer = adjusted_max_qp; |
+ return true; |
+} |
+ |
+void ScreenshareLayers::TemporalLayer::UpdateDebt(int64_t delta_ms) { |
+ uint32_t debt_reduction_bytes = target_rate_kbps_ * delta_ms / 8; |
+ if (debt_reduction_bytes >= debt_bytes_) { |
+ debt_bytes_ = 0; |
+ } else { |
+ debt_bytes_ -= debt_reduction_bytes; |
} |
} |