Index: webrtc/video/vie_encoder.cc |
diff --git a/webrtc/video/vie_encoder.cc b/webrtc/video/vie_encoder.cc |
index c30752b6dfb048e96d40a5f2e2e25853fe4b529e..ad1c144d6682f3c532d849f40901eb87fdb1a8d0 100644 |
--- a/webrtc/video/vie_encoder.cc |
+++ b/webrtc/video/vie_encoder.cc |
@@ -12,6 +12,7 @@ |
#include <algorithm> |
#include <limits> |
+#include <numeric> |
#include <utility> |
#include "webrtc/base/arraysize.h" |
@@ -42,6 +43,7 @@ const int64_t kFrameLogIntervalMs = 60000; |
// on MediaCodec and fallback implementations are in place. |
// See https://bugs.chromium.org/p/webrtc/issues/detail?id=7206 |
const int kMinPixelsPerFrame = 320 * 180; |
+const int kMinFramerateFps = 2; |
// The maximum number of frames to drop at beginning of stream |
// to try and achieve desired bitrate. |
@@ -150,7 +152,7 @@ class ViEEncoder::VideoSourceProxy { |
public: |
explicit VideoSourceProxy(ViEEncoder* vie_encoder) |
: vie_encoder_(vie_encoder), |
- degradation_preference_(DegradationPreference::kMaintainResolution), |
+ degradation_preference_(DegradationPreference::kDegradationDisabled), |
source_(nullptr) {} |
void SetSource(rtc::VideoSourceInterface<VideoFrame>* source, |
@@ -161,10 +163,10 @@ class ViEEncoder::VideoSourceProxy { |
rtc::VideoSinkWants wants; |
{ |
rtc::CritScope lock(&crit_); |
+ degradation_preference_ = degradation_preference; |
old_source = source_; |
source_ = source; |
- degradation_preference_ = degradation_preference; |
- wants = current_wants(); |
+ wants = GetActiveSinkWants(); |
} |
if (old_source != source && old_source != nullptr) { |
@@ -181,10 +183,30 @@ class ViEEncoder::VideoSourceProxy { |
void SetWantsRotationApplied(bool rotation_applied) { |
rtc::CritScope lock(&crit_); |
sink_wants_.rotation_applied = rotation_applied; |
- disabled_scaling_sink_wants_.rotation_applied = rotation_applied; |
- if (source_) { |
- source_->AddOrUpdateSink(vie_encoder_, current_wants()); |
+ if (source_) |
+ source_->AddOrUpdateSink(vie_encoder_, sink_wants_); |
+ } |
+ |
+ rtc::VideoSinkWants GetActiveSinkWants() EXCLUSIVE_LOCKS_REQUIRED(&crit_) { |
+ rtc::VideoSinkWants wants = sink_wants_; |
+ // Clear any constraints from the current sink wants that don't apply to |
+ // the used degradation_preference. |
+ switch (degradation_preference_) { |
+ case DegradationPreference::kBalanced: |
+ FALLTHROUGH(); |
+ case DegradationPreference::kMaintainFramerate: |
+ wants.max_framerate_fps = std::numeric_limits<int>::max(); |
+ break; |
+ case DegradationPreference::kMaintainResolution: |
+ wants.max_pixel_count = std::numeric_limits<int>::max(); |
+ wants.target_pixel_count.reset(); |
+ break; |
+ case DegradationPreference::kDegradationDisabled: |
+ wants.max_pixel_count = std::numeric_limits<int>::max(); |
+ wants.target_pixel_count.reset(); |
+ wants.max_framerate_fps = std::numeric_limits<int>::max(); |
} |
+ return wants; |
} |
void RequestResolutionLowerThan(int pixel_count) { |
@@ -202,10 +224,28 @@ class ViEEncoder::VideoSourceProxy { |
const int pixels_wanted = (pixel_count * 3) / 5; |
if (pixels_wanted < kMinPixelsPerFrame) |
return; |
- sink_wants_.max_pixel_count = rtc::Optional<int>(pixels_wanted); |
+ sink_wants_.max_pixel_count = pixels_wanted; |
sink_wants_.target_pixel_count = rtc::Optional<int>(); |
if (source_) |
- source_->AddOrUpdateSink(vie_encoder_, sink_wants_); |
+ source_->AddOrUpdateSink(vie_encoder_, GetActiveSinkWants()); |
+ } |
+ |
+ void RequestFramerateLowerThan(int framerate_fps) { |
+ // Called on the encoder task queue. |
+ rtc::CritScope lock(&crit_); |
+ if (!IsFramerateScalingEnabledLocked()) { |
+ // This can happen since |degradation_preference_| is set on |
+ // libjingle's worker thread but the adaptation is done on the encoder |
+ // task queue. |
+ return; |
+ } |
+ // The input video frame rate will be scaled down to 2/3 of input fps, |
+ // rounding down. |
+ const int framerate_wanted = |
+ std::max(kMinFramerateFps, (framerate_fps * 2) / 3); |
+ sink_wants_.max_framerate_fps = framerate_wanted; |
+ if (source_) |
+ source_->AddOrUpdateSink(vie_encoder_, GetActiveSinkWants()); |
} |
void RequestHigherResolutionThan(int pixel_count) { |
@@ -216,16 +256,46 @@ class ViEEncoder::VideoSourceProxy { |
// task queue. |
return; |
} |
- // On step down we request at most 3/5 the pixel count of the previous |
- // resolution, so in order to take "one step up" we request a resolution as |
- // close as possible to 5/3 of the current resolution. The actual pixel |
- // count selected depends on the capabilities of the source. In order to not |
- // take a too large step up, we cap the requested pixel count to be at most |
- // four time the current number of pixels. |
- sink_wants_.target_pixel_count = rtc::Optional<int>((pixel_count * 5) / 3); |
- sink_wants_.max_pixel_count = rtc::Optional<int>(pixel_count * 4); |
+ |
+ if (pixel_count == std::numeric_limits<int>::max()) { |
+ // Remove any constraints. |
+ sink_wants_.target_pixel_count.reset(); |
+ sink_wants_.max_pixel_count = std::numeric_limits<int>::max(); |
+ } else { |
+ // On step down we request at most 3/5 the pixel count of the previous |
+ // resolution, so in order to take "one step up" we request a resolution |
+ // as close as possible to 5/3 of the current resolution. The actual pixel |
+ // count selected depends on the capabilities of the source. In order to |
+ // not take a too large step up, we cap the requested pixel count to be at |
+ // most four time the current number of pixels. |
+ sink_wants_.target_pixel_count = |
+ rtc::Optional<int>((pixel_count * 5) / 3); |
+ sink_wants_.max_pixel_count = pixel_count * 4; |
+ } |
if (source_) |
- source_->AddOrUpdateSink(vie_encoder_, sink_wants_); |
+ source_->AddOrUpdateSink(vie_encoder_, GetActiveSinkWants()); |
+ } |
+ |
+ void RequestHigherFramerateThan(int framerate_fps) { |
+ // Called on the encoder task queue. |
+ rtc::CritScope lock(&crit_); |
+ if (!IsFramerateScalingEnabledLocked()) { |
+ // This can happen since |degradation_preference_| is set on |
+ // libjingle's worker thread but the adaptation is done on the encoder |
+ // task queue. |
+ return; |
+ } |
+ if (framerate_fps == std::numeric_limits<int>::max()) { |
+ // Remove any restrains. |
+ sink_wants_.max_framerate_fps = std::numeric_limits<int>::max(); |
+ } else { |
+ // The input video frame rate will be scaled up to the last step, with |
+ // rounding. |
+ const int framerate_wanted = (framerate_fps * 3) / 2; |
+ sink_wants_.max_framerate_fps = framerate_wanted; |
+ } |
+ if (source_) |
+ source_->AddOrUpdateSink(vie_encoder_, GetActiveSinkWants()); |
} |
private: |
@@ -235,17 +305,16 @@ class ViEEncoder::VideoSourceProxy { |
DegradationPreference::kMaintainResolution; |
} |
- const rtc::VideoSinkWants& current_wants() const |
+ bool IsFramerateScalingEnabledLocked() const |
EXCLUSIVE_LOCKS_REQUIRED(&crit_) { |
- return IsResolutionScalingEnabledLocked() ? sink_wants_ |
- : disabled_scaling_sink_wants_; |
+ return degradation_preference_ == |
+ DegradationPreference::kMaintainResolution; |
} |
rtc::CriticalSection crit_; |
rtc::SequencedTaskChecker main_checker_; |
ViEEncoder* const vie_encoder_; |
rtc::VideoSinkWants sink_wants_ GUARDED_BY(&crit_); |
- rtc::VideoSinkWants disabled_scaling_sink_wants_ GUARDED_BY(&crit_); |
DegradationPreference degradation_preference_ GUARDED_BY(&crit_); |
rtc::VideoSourceInterface<VideoFrame>* source_ GUARDED_BY(&crit_); |
@@ -284,8 +353,7 @@ ViEEncoder::ViEEncoder(uint32_t number_of_cores, |
has_received_rpsi_(false), |
picture_id_rpsi_(0), |
clock_(Clock::GetRealTimeClock()), |
- scale_counter_(kScaleReasonSize, 0), |
- degradation_preference_(DegradationPreference::kMaintainResolution), |
+ degradation_preference_(DegradationPreference::kDegradationDisabled), |
last_captured_timestamp_(0), |
delta_ntp_internal_ms_(clock_->CurrentNtpInMilliseconds() - |
clock_->TimeInMilliseconds()), |
@@ -356,7 +424,11 @@ void ViEEncoder::SetSource( |
source_proxy_->SetSource(source, degradation_preference); |
encoder_queue_.PostTask([this, degradation_preference] { |
RTC_DCHECK_RUN_ON(&encoder_queue_); |
- |
+ if (degradation_preference_ != degradation_preference) { |
+ // Reset adaptation state, so that we're not tricked into thinking there's |
+ // an already pending request of the same type. |
+ last_adaptation_request_.reset(); |
+ } |
degradation_preference_ = degradation_preference; |
initial_rampup_ = |
degradation_preference_ != DegradationPreference::kMaintainResolution |
@@ -475,9 +547,10 @@ void ViEEncoder::ConfigureQualityScaler() { |
quality_scaler_.reset(nullptr); |
initial_rampup_ = kMaxInitialFramedrop; |
} |
+ const std::vector<int>& scale_counters = GetScaleCounters(); |
stats_proxy_->SetResolutionRestrictionStats( |
- degradation_preference_allows_scaling, scale_counter_[kCpu] > 0, |
- scale_counter_[kQuality]); |
+ degradation_preference_allows_scaling, scale_counters[kCpu] > 0, |
+ scale_counters[kQuality]); |
} |
void ViEEncoder::OnFrame(const VideoFrame& video_frame) { |
@@ -740,79 +813,191 @@ void ViEEncoder::OnBitrateUpdated(uint32_t bitrate_bps, |
void ViEEncoder::AdaptDown(AdaptReason reason) { |
RTC_DCHECK_RUN_ON(&encoder_queue_); |
- if (degradation_preference_ != DegradationPreference::kBalanced) |
- return; |
- RTC_DCHECK(static_cast<bool>(last_frame_info_)); |
- int current_pixel_count = last_frame_info_->pixel_count(); |
- if (last_adaptation_request_ && |
- last_adaptation_request_->mode_ == AdaptationRequest::Mode::kAdaptDown && |
- current_pixel_count >= last_adaptation_request_->input_pixel_count_) { |
- // Don't request lower resolution if the current resolution is not lower |
- // than the last time we asked for the resolution to be lowered. |
- return; |
+ AdaptationRequest adaptation_request = { |
+ last_frame_info_->pixel_count(), |
+ stats_proxy_->GetStats().input_frame_rate, |
+ AdaptationRequest::Mode::kAdaptDown}; |
+ bool downgrade_requested = |
+ last_adaptation_request_ && |
+ last_adaptation_request_->mode_ == AdaptationRequest::Mode::kAdaptDown; |
+ |
+ int max_downgrades = 0; |
+ switch (degradation_preference_) { |
+ case DegradationPreference::kBalanced: |
+ FALLTHROUGH(); |
+ case DegradationPreference::kMaintainFramerate: |
+ max_downgrades = kMaxCpuResolutionDowngrades; |
+ if (downgrade_requested && |
+ adaptation_request.input_pixel_count_ >= |
+ last_adaptation_request_->input_pixel_count_) { |
+ // Don't request lower resolution if the current resolution is not |
+ // lower than the last time we asked for the resolution to be lowered. |
+ return; |
+ } |
+ break; |
+ case DegradationPreference::kMaintainResolution: |
+ max_downgrades = kMaxCpuFramerateDowngrades; |
+ if (adaptation_request.framerate_fps_ <= 0 || |
+ (downgrade_requested && |
+ adaptation_request.framerate_fps_ < kMinFramerateFps)) { |
+ // If no input fps estimate available, can't determine how to scale down |
+ // framerate. Otherwise, don't request lower framerate if we don't have |
+ // a valid frame rate. Since framerate, unlike resolution, is a measure |
+ // we have to estimate, and can fluctuate naturally over time, don't |
+ // make the same kind of limitations as for resolution, but trust the |
+ // overuse detector to not trigger too often. |
+ return; |
+ } |
+ break; |
+ case DegradationPreference::kDegradationDisabled: |
+ return; |
} |
- last_adaptation_request_.emplace(AdaptationRequest{ |
- current_pixel_count, AdaptationRequest::Mode::kAdaptDown}); |
+ |
+ last_adaptation_request_.emplace(adaptation_request); |
+ const std::vector<int>& scale_counter = GetScaleCounters(); |
switch (reason) { |
case kQuality: |
- stats_proxy_->OnQualityRestrictedResolutionChanged( |
- scale_counter_[reason] + 1); |
+ stats_proxy_->OnQualityRestrictedResolutionChanged(scale_counter[reason] + |
+ 1); |
break; |
case kCpu: |
- if (scale_counter_[reason] >= kMaxCpuDowngrades) |
+ if (scale_counter[reason] >= max_downgrades) |
return; |
// Update stats accordingly. |
stats_proxy_->OnCpuRestrictedResolutionChanged(true); |
break; |
} |
- ++scale_counter_[reason]; |
- source_proxy_->RequestResolutionLowerThan(current_pixel_count); |
- LOG(LS_INFO) << "Scaling down resolution."; |
+ |
+ IncrementScaleCounter(reason, 1); |
+ |
+ switch (degradation_preference_) { |
+ case DegradationPreference::kBalanced: |
+ FALLTHROUGH(); |
+ case DegradationPreference::kMaintainFramerate: |
+ source_proxy_->RequestResolutionLowerThan( |
+ adaptation_request.input_pixel_count_); |
+ LOG(LS_INFO) << "Scaling down resolution."; |
+ break; |
+ case DegradationPreference::kMaintainResolution: |
+ source_proxy_->RequestFramerateLowerThan( |
+ adaptation_request.framerate_fps_); |
+ LOG(LS_INFO) << "Scaling down framerate."; |
+ break; |
+ case DegradationPreference::kDegradationDisabled: |
+ RTC_NOTREACHED(); |
+ } |
+ |
for (size_t i = 0; i < kScaleReasonSize; ++i) { |
- LOG(LS_INFO) << "Scaled " << scale_counter_[i] |
+ LOG(LS_INFO) << "Scaled " << GetScaleCounters()[i] |
<< " times for reason: " << (i ? "cpu" : "quality"); |
} |
} |
void ViEEncoder::AdaptUp(AdaptReason reason) { |
RTC_DCHECK_RUN_ON(&encoder_queue_); |
- if (scale_counter_[reason] == 0 || |
- degradation_preference_ != DegradationPreference::kBalanced) { |
- return; |
- } |
- // Only scale if resolution is higher than last time we requested higher |
- // resolution. |
- RTC_DCHECK(static_cast<bool>(last_frame_info_)); |
- int current_pixel_count = last_frame_info_->pixel_count(); |
- if (last_adaptation_request_ && |
- last_adaptation_request_->mode_ == AdaptationRequest::Mode::kAdaptUp && |
- current_pixel_count <= last_adaptation_request_->input_pixel_count_) { |
- // Don't request higher resolution if the current resolution is not higher |
- // than the last time we asked for the resolution to be higher. |
+ int scale_counter = GetScaleCounters()[reason]; |
+ if (scale_counter == 0) |
return; |
+ RTC_DCHECK_GT(scale_counter, 0); |
+ AdaptationRequest adaptation_request = { |
+ last_frame_info_->pixel_count(), |
+ stats_proxy_->GetStats().input_frame_rate, |
+ AdaptationRequest::Mode::kAdaptUp}; |
+ |
+ bool adapt_up_requested = |
+ last_adaptation_request_ && |
+ last_adaptation_request_->mode_ == AdaptationRequest::Mode::kAdaptUp; |
+ switch (degradation_preference_) { |
+ case DegradationPreference::kBalanced: |
+ FALLTHROUGH(); |
+ case DegradationPreference::kMaintainFramerate: |
+ if (adapt_up_requested && |
+ adaptation_request.input_pixel_count_ <= |
+ last_adaptation_request_->input_pixel_count_) { |
+ // Don't request higher resolution if the current resolution is not |
+ // higher than the last time we asked for the resolution to be higher. |
+ return; |
+ } |
+ break; |
+ case DegradationPreference::kMaintainResolution: |
+ // TODO(sprang): Don't request higher framerate if we are already at |
+ // max requested fps? |
+ break; |
+ case DegradationPreference::kDegradationDisabled: |
+ return; |
} |
- last_adaptation_request_.emplace(AdaptationRequest{ |
- current_pixel_count, AdaptationRequest::Mode::kAdaptUp}); |
switch (reason) { |
case kQuality: |
- stats_proxy_->OnQualityRestrictedResolutionChanged( |
- scale_counter_[reason] - 1); |
+ stats_proxy_->OnQualityRestrictedResolutionChanged(scale_counter - 1); |
break; |
case kCpu: |
// Update stats accordingly. |
- stats_proxy_->OnCpuRestrictedResolutionChanged(scale_counter_[reason] > |
- 1); |
+ stats_proxy_->OnCpuRestrictedResolutionChanged(scale_counter > 1); |
+ break; |
+ } |
+ |
+ // Decrease counter of how many times we have scaled down, for this |
+ // degradation preference mode and reason. |
+ IncrementScaleCounter(reason, -1); |
+ |
+ // Get a sum of how many times have scaled down, in total, for this |
+ // degradation preference mode. If it is 0, remove any restraints. |
+ const std::vector<int>& current_scale_counters = GetScaleCounters(); |
+ const int scale_sum = std::accumulate(current_scale_counters.begin(), |
+ current_scale_counters.end(), 0); |
+ switch (degradation_preference_) { |
+ case DegradationPreference::kBalanced: |
+ FALLTHROUGH(); |
+ case DegradationPreference::kMaintainFramerate: |
+ if (scale_sum == 0) { |
+ LOG(LS_INFO) << "Removing resolution down-scaling setting."; |
+ source_proxy_->RequestHigherResolutionThan( |
+ std::numeric_limits<int>::max()); |
+ } else { |
+ source_proxy_->RequestHigherResolutionThan( |
+ adaptation_request.input_pixel_count_); |
+ LOG(LS_INFO) << "Scaling up resolution."; |
+ } |
break; |
+ case DegradationPreference::kMaintainResolution: |
+ if (scale_sum == 0) { |
+ LOG(LS_INFO) << "Removing framerate down-scaling setting."; |
+ source_proxy_->RequestHigherFramerateThan( |
+ std::numeric_limits<int>::max()); |
+ } else { |
+ source_proxy_->RequestHigherFramerateThan( |
+ adaptation_request.framerate_fps_); |
+ LOG(LS_INFO) << "Scaling up framerate."; |
+ } |
+ break; |
+ case DegradationPreference::kDegradationDisabled: |
+ RTC_NOTREACHED(); |
} |
- --scale_counter_[reason]; |
- source_proxy_->RequestHigherResolutionThan(current_pixel_count); |
- LOG(LS_INFO) << "Scaling up resolution."; |
+ |
for (size_t i = 0; i < kScaleReasonSize; ++i) { |
- LOG(LS_INFO) << "Scaled " << scale_counter_[i] |
+ LOG(LS_INFO) << "Scaled " << current_scale_counters[i] |
<< " times for reason: " << (i ? "cpu" : "quality"); |
} |
} |
+const std::vector<int>& ViEEncoder::GetScaleCounters() { |
+ auto it = scale_counters_.find(degradation_preference_); |
+ if (it == scale_counters_.end()) { |
+ scale_counters_[degradation_preference_].resize(kScaleReasonSize); |
+ return scale_counters_[degradation_preference_]; |
+ } |
+ return it->second; |
+} |
+ |
+void ViEEncoder::IncrementScaleCounter(int reason, int delta) { |
+ // Get the counters and validate. This may also lazily initialize the state. |
+ const std::vector<int>& counter = GetScaleCounters(); |
+ if (delta < 0) { |
+ RTC_DCHECK_GE(counter[reason], delta); |
+ } |
+ scale_counters_[degradation_preference_][reason] += delta; |
+} |
+ |
} // namespace webrtc |