1 /*
2 * Copyright 2020 The WebRTC Project Authors. All rights reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include "call/adaptation/video_stream_adapter.h"
12
13 #include <algorithm>
14 #include <limits>
15 #include <utility>
16
17 #include "absl/types/optional.h"
18 #include "absl/types/variant.h"
19 #include "api/video/video_adaptation_counters.h"
20 #include "api/video/video_adaptation_reason.h"
21 #include "api/video_codecs/video_encoder.h"
22 #include "call/adaptation/video_source_restrictions.h"
23 #include "call/adaptation/video_stream_input_state.h"
24 #include "rtc_base/checks.h"
25 #include "rtc_base/constructor_magic.h"
26 #include "rtc_base/logging.h"
27 #include "rtc_base/numerics/safe_conversions.h"
28 #include "rtc_base/synchronization/sequence_checker.h"
29
30 namespace webrtc {
31
32 const int kMinFrameRateFps = 2;
33
34 namespace {
35
36 // For frame rate, the steps we take are 2/3 (down) and 3/2 (up).
GetLowerFrameRateThan(int fps)37 int GetLowerFrameRateThan(int fps) {
38 RTC_DCHECK(fps != std::numeric_limits<int>::max());
39 return (fps * 2) / 3;
40 }
41 // TODO(hbos): Use absl::optional<> instead?
GetHigherFrameRateThan(int fps)42 int GetHigherFrameRateThan(int fps) {
43 return fps != std::numeric_limits<int>::max()
44 ? (fps * 3) / 2
45 : std::numeric_limits<int>::max();
46 }
47
48 // For resolution, the steps we take are 3/5 (down) and 5/3 (up).
49 // Notice the asymmetry of which restriction property is set depending on if
50 // we are adapting up or down:
51 // - VideoSourceRestrictor::DecreaseResolution() sets the max_pixels_per_frame()
52 // to the desired target and target_pixels_per_frame() to null.
53 // - VideoSourceRestrictor::IncreaseResolutionTo() sets the
54 // target_pixels_per_frame() to the desired target, and max_pixels_per_frame()
55 // is set according to VideoSourceRestrictor::GetIncreasedMaxPixelsWanted().
GetLowerResolutionThan(int pixel_count)56 int GetLowerResolutionThan(int pixel_count) {
57 RTC_DCHECK(pixel_count != std::numeric_limits<int>::max());
58 return (pixel_count * 3) / 5;
59 }
60
GetIncreasedMaxPixelsWanted(int target_pixels)61 int GetIncreasedMaxPixelsWanted(int target_pixels) {
62 if (target_pixels == std::numeric_limits<int>::max())
63 return std::numeric_limits<int>::max();
64 // When we decrease resolution, we go down to at most 3/5 of current pixels.
65 // Thus to increase resolution, we need 3/5 to get back to where we started.
66 // When going up, the desired max_pixels_per_frame() has to be significantly
67 // higher than the target because the source's native resolutions might not
68 // match the target. We pick 12/5 of the target.
69 //
70 // (This value was historically 4 times the old target, which is (3/5)*4 of
71 // the new target - or 12/5 - assuming the target is adjusted according to
72 // the above steps.)
73 RTC_DCHECK(target_pixels != std::numeric_limits<int>::max());
74 return (target_pixels * 12) / 5;
75 }
76
CanDecreaseResolutionTo(int target_pixels,const VideoStreamInputState & input_state,const VideoSourceRestrictions & restrictions)77 bool CanDecreaseResolutionTo(int target_pixels,
78 const VideoStreamInputState& input_state,
79 const VideoSourceRestrictions& restrictions) {
80 int max_pixels_per_frame =
81 rtc::dchecked_cast<int>(restrictions.max_pixels_per_frame().value_or(
82 std::numeric_limits<int>::max()));
83 return target_pixels < max_pixels_per_frame &&
84 target_pixels >= input_state.min_pixels_per_frame();
85 }
86
CanIncreaseResolutionTo(int target_pixels,const VideoSourceRestrictions & restrictions)87 bool CanIncreaseResolutionTo(int target_pixels,
88 const VideoSourceRestrictions& restrictions) {
89 int max_pixels_wanted = GetIncreasedMaxPixelsWanted(target_pixels);
90 int max_pixels_per_frame =
91 rtc::dchecked_cast<int>(restrictions.max_pixels_per_frame().value_or(
92 std::numeric_limits<int>::max()));
93 return max_pixels_wanted > max_pixels_per_frame;
94 }
95
CanDecreaseFrameRateTo(int max_frame_rate,const VideoSourceRestrictions & restrictions)96 bool CanDecreaseFrameRateTo(int max_frame_rate,
97 const VideoSourceRestrictions& restrictions) {
98 const int fps_wanted = std::max(kMinFrameRateFps, max_frame_rate);
99 return fps_wanted <
100 rtc::dchecked_cast<int>(restrictions.max_frame_rate().value_or(
101 std::numeric_limits<int>::max()));
102 }
103
CanIncreaseFrameRateTo(int max_frame_rate,const VideoSourceRestrictions & restrictions)104 bool CanIncreaseFrameRateTo(int max_frame_rate,
105 const VideoSourceRestrictions& restrictions) {
106 return max_frame_rate >
107 rtc::dchecked_cast<int>(restrictions.max_frame_rate().value_or(
108 std::numeric_limits<int>::max()));
109 }
110
111 } // namespace
112
113 VideoSourceRestrictionsListener::~VideoSourceRestrictionsListener() = default;
114
FilterRestrictionsByDegradationPreference(VideoSourceRestrictions source_restrictions,DegradationPreference degradation_preference)115 VideoSourceRestrictions FilterRestrictionsByDegradationPreference(
116 VideoSourceRestrictions source_restrictions,
117 DegradationPreference degradation_preference) {
118 switch (degradation_preference) {
119 case DegradationPreference::BALANCED:
120 break;
121 case DegradationPreference::MAINTAIN_FRAMERATE:
122 source_restrictions.set_max_frame_rate(absl::nullopt);
123 break;
124 case DegradationPreference::MAINTAIN_RESOLUTION:
125 source_restrictions.set_max_pixels_per_frame(absl::nullopt);
126 source_restrictions.set_target_pixels_per_frame(absl::nullopt);
127 break;
128 case DegradationPreference::DISABLED:
129 source_restrictions.set_max_pixels_per_frame(absl::nullopt);
130 source_restrictions.set_target_pixels_per_frame(absl::nullopt);
131 source_restrictions.set_max_frame_rate(absl::nullopt);
132 }
133 return source_restrictions;
134 }
135
136 // TODO(hbos): Use absl::optional<> instead?
GetHigherResolutionThan(int pixel_count)137 int GetHigherResolutionThan(int pixel_count) {
138 return pixel_count != std::numeric_limits<int>::max()
139 ? (pixel_count * 5) / 3
140 : std::numeric_limits<int>::max();
141 }
142
143 // static
StatusToString(Adaptation::Status status)144 const char* Adaptation::StatusToString(Adaptation::Status status) {
145 switch (status) {
146 case Adaptation::Status::kValid:
147 return "kValid";
148 case Adaptation::Status::kLimitReached:
149 return "kLimitReached";
150 case Adaptation::Status::kAwaitingPreviousAdaptation:
151 return "kAwaitingPreviousAdaptation";
152 case Status::kInsufficientInput:
153 return "kInsufficientInput";
154 case Status::kAdaptationDisabled:
155 return "kAdaptationDisabled";
156 case Status::kRejectedByConstraint:
157 return "kRejectedByConstraint";
158 }
159 }
160
Adaptation(int validation_id,VideoSourceRestrictions restrictions,VideoAdaptationCounters counters,VideoStreamInputState input_state,bool min_pixel_limit_reached)161 Adaptation::Adaptation(int validation_id,
162 VideoSourceRestrictions restrictions,
163 VideoAdaptationCounters counters,
164 VideoStreamInputState input_state,
165 bool min_pixel_limit_reached)
166 : validation_id_(validation_id),
167 status_(Status::kValid),
168 min_pixel_limit_reached_(min_pixel_limit_reached),
169 input_state_(std::move(input_state)),
170 restrictions_(std::move(restrictions)),
171 counters_(std::move(counters)) {}
172
Adaptation(int validation_id,Status invalid_status,VideoStreamInputState input_state,bool min_pixel_limit_reached)173 Adaptation::Adaptation(int validation_id,
174 Status invalid_status,
175 VideoStreamInputState input_state,
176 bool min_pixel_limit_reached)
177 : validation_id_(validation_id),
178 status_(invalid_status),
179 min_pixel_limit_reached_(min_pixel_limit_reached),
180 input_state_(std::move(input_state)) {
181 RTC_DCHECK_NE(status_, Status::kValid);
182 }
183
status() const184 Adaptation::Status Adaptation::status() const {
185 return status_;
186 }
187
min_pixel_limit_reached() const188 bool Adaptation::min_pixel_limit_reached() const {
189 return min_pixel_limit_reached_;
190 }
191
input_state() const192 const VideoStreamInputState& Adaptation::input_state() const {
193 return input_state_;
194 }
195
restrictions() const196 const VideoSourceRestrictions& Adaptation::restrictions() const {
197 return restrictions_;
198 }
199
counters() const200 const VideoAdaptationCounters& Adaptation::counters() const {
201 return counters_;
202 }
203
VideoStreamAdapter(VideoStreamInputStateProvider * input_state_provider)204 VideoStreamAdapter::VideoStreamAdapter(
205 VideoStreamInputStateProvider* input_state_provider)
206 : input_state_provider_(input_state_provider),
207 balanced_settings_(),
208 adaptation_validation_id_(0),
209 degradation_preference_(DegradationPreference::DISABLED),
210 awaiting_frame_size_change_(absl::nullopt),
211 last_video_source_restrictions_() {
212 sequence_checker_.Detach();
213 }
214
~VideoStreamAdapter()215 VideoStreamAdapter::~VideoStreamAdapter() {
216 RTC_DCHECK(adaptation_listeners_.empty())
217 << "There are listener(s) attached to a VideoStreamAdapter being "
218 "destroyed.";
219 RTC_DCHECK(adaptation_constraints_.empty())
220 << "There are constaint(s) attached to a VideoStreamAdapter being "
221 "destroyed.";
222 }
223
source_restrictions() const224 VideoSourceRestrictions VideoStreamAdapter::source_restrictions() const {
225 RTC_DCHECK_RUN_ON(&sequence_checker_);
226 return current_restrictions_.restrictions;
227 }
228
adaptation_counters() const229 const VideoAdaptationCounters& VideoStreamAdapter::adaptation_counters() const {
230 RTC_DCHECK_RUN_ON(&sequence_checker_);
231 return current_restrictions_.counters;
232 }
233
ClearRestrictions()234 void VideoStreamAdapter::ClearRestrictions() {
235 RTC_DCHECK_RUN_ON(&sequence_checker_);
236 // Invalidate any previously returned Adaptation.
237 RTC_LOG(INFO) << "Resetting restrictions";
238 ++adaptation_validation_id_;
239 current_restrictions_ = {VideoSourceRestrictions(),
240 VideoAdaptationCounters()};
241 awaiting_frame_size_change_ = absl::nullopt;
242 BroadcastVideoRestrictionsUpdate(input_state_provider_->InputState(),
243 nullptr);
244 }
245
AddRestrictionsListener(VideoSourceRestrictionsListener * restrictions_listener)246 void VideoStreamAdapter::AddRestrictionsListener(
247 VideoSourceRestrictionsListener* restrictions_listener) {
248 RTC_DCHECK_RUN_ON(&sequence_checker_);
249 RTC_DCHECK(std::find(restrictions_listeners_.begin(),
250 restrictions_listeners_.end(),
251 restrictions_listener) == restrictions_listeners_.end());
252 restrictions_listeners_.push_back(restrictions_listener);
253 }
254
RemoveRestrictionsListener(VideoSourceRestrictionsListener * restrictions_listener)255 void VideoStreamAdapter::RemoveRestrictionsListener(
256 VideoSourceRestrictionsListener* restrictions_listener) {
257 RTC_DCHECK_RUN_ON(&sequence_checker_);
258 auto it = std::find(restrictions_listeners_.begin(),
259 restrictions_listeners_.end(), restrictions_listener);
260 RTC_DCHECK(it != restrictions_listeners_.end());
261 restrictions_listeners_.erase(it);
262 }
263
AddAdaptationListener(AdaptationListener * adaptation_listener)264 void VideoStreamAdapter::AddAdaptationListener(
265 AdaptationListener* adaptation_listener) {
266 RTC_DCHECK_RUN_ON(&sequence_checker_);
267 RTC_DCHECK(std::find(adaptation_listeners_.begin(),
268 adaptation_listeners_.end(),
269 adaptation_listener) == adaptation_listeners_.end());
270 adaptation_listeners_.push_back(adaptation_listener);
271 }
272
RemoveAdaptationListener(AdaptationListener * adaptation_listener)273 void VideoStreamAdapter::RemoveAdaptationListener(
274 AdaptationListener* adaptation_listener) {
275 RTC_DCHECK_RUN_ON(&sequence_checker_);
276 auto it = std::find(adaptation_listeners_.begin(),
277 adaptation_listeners_.end(), adaptation_listener);
278 RTC_DCHECK(it != adaptation_listeners_.end());
279 adaptation_listeners_.erase(it);
280 }
281
AddAdaptationConstraint(AdaptationConstraint * adaptation_constraint)282 void VideoStreamAdapter::AddAdaptationConstraint(
283 AdaptationConstraint* adaptation_constraint) {
284 RTC_DCHECK_RUN_ON(&sequence_checker_);
285 RTC_DCHECK(std::find(adaptation_constraints_.begin(),
286 adaptation_constraints_.end(),
287 adaptation_constraint) == adaptation_constraints_.end());
288 adaptation_constraints_.push_back(adaptation_constraint);
289 }
290
RemoveAdaptationConstraint(AdaptationConstraint * adaptation_constraint)291 void VideoStreamAdapter::RemoveAdaptationConstraint(
292 AdaptationConstraint* adaptation_constraint) {
293 RTC_DCHECK_RUN_ON(&sequence_checker_);
294 auto it = std::find(adaptation_constraints_.begin(),
295 adaptation_constraints_.end(), adaptation_constraint);
296 RTC_DCHECK(it != adaptation_constraints_.end());
297 adaptation_constraints_.erase(it);
298 }
299
SetDegradationPreference(DegradationPreference degradation_preference)300 void VideoStreamAdapter::SetDegradationPreference(
301 DegradationPreference degradation_preference) {
302 RTC_DCHECK_RUN_ON(&sequence_checker_);
303 if (degradation_preference_ == degradation_preference)
304 return;
305 // Invalidate any previously returned Adaptation.
306 ++adaptation_validation_id_;
307 bool balanced_switch =
308 degradation_preference == DegradationPreference::BALANCED ||
309 degradation_preference_ == DegradationPreference::BALANCED;
310 degradation_preference_ = degradation_preference;
311 if (balanced_switch) {
312 // ClearRestrictions() calls BroadcastVideoRestrictionsUpdate(nullptr).
313 ClearRestrictions();
314 } else {
315 BroadcastVideoRestrictionsUpdate(input_state_provider_->InputState(),
316 nullptr);
317 }
318 }
319
320 struct VideoStreamAdapter::RestrictionsOrStateVisitor {
operator ()webrtc::VideoStreamAdapter::RestrictionsOrStateVisitor321 Adaptation operator()(const RestrictionsWithCounters& r) const {
322 return Adaptation(adaptation_validation_id, r.restrictions, r.counters,
323 input_state, min_pixel_limit_reached());
324 }
operator ()webrtc::VideoStreamAdapter::RestrictionsOrStateVisitor325 Adaptation operator()(const Adaptation::Status& status) const {
326 RTC_DCHECK_NE(status, Adaptation::Status::kValid);
327 return Adaptation(adaptation_validation_id, status, input_state,
328 min_pixel_limit_reached());
329 }
min_pixel_limit_reachedwebrtc::VideoStreamAdapter::RestrictionsOrStateVisitor330 bool min_pixel_limit_reached() const {
331 return input_state.frame_size_pixels().has_value() &&
332 GetLowerResolutionThan(input_state.frame_size_pixels().value()) <
333 input_state.min_pixels_per_frame();
334 }
335
336 const int adaptation_validation_id;
337 const VideoStreamInputState& input_state;
338 };
339
RestrictionsOrStateToAdaptation(VideoStreamAdapter::RestrictionsOrState step_or_state,const VideoStreamInputState & input_state) const340 Adaptation VideoStreamAdapter::RestrictionsOrStateToAdaptation(
341 VideoStreamAdapter::RestrictionsOrState step_or_state,
342 const VideoStreamInputState& input_state) const {
343 RTC_DCHECK(!step_or_state.valueless_by_exception());
344 return absl::visit(
345 RestrictionsOrStateVisitor{adaptation_validation_id_, input_state},
346 step_or_state);
347 }
348
GetAdaptationUp(const VideoStreamInputState & input_state,rtc::scoped_refptr<Resource> resource) const349 Adaptation VideoStreamAdapter::GetAdaptationUp(
350 const VideoStreamInputState& input_state,
351 rtc::scoped_refptr<Resource> resource) const {
352 RestrictionsOrState step = GetAdaptationUpStep(input_state);
353 // If an adaptation proposed, check with the constraints that it is ok.
354 if (absl::holds_alternative<RestrictionsWithCounters>(step)) {
355 RestrictionsWithCounters restrictions =
356 absl::get<RestrictionsWithCounters>(step);
357 for (const auto* constraint : adaptation_constraints_) {
358 if (!constraint->IsAdaptationUpAllowed(
359 input_state, current_restrictions_.restrictions,
360 restrictions.restrictions, resource)) {
361 RTC_LOG(INFO) << "Not adapting up because constraint \""
362 << constraint->Name() << "\" disallowed it";
363 step = Adaptation::Status::kRejectedByConstraint;
364 }
365 }
366 }
367 return RestrictionsOrStateToAdaptation(step, input_state);
368 }
369
GetAdaptationUp(rtc::scoped_refptr<Resource> resource)370 Adaptation VideoStreamAdapter::GetAdaptationUp(
371 rtc::scoped_refptr<Resource> resource) {
372 RTC_DCHECK_RUN_ON(&sequence_checker_);
373 RTC_DCHECK(resource);
374 VideoStreamInputState input_state = input_state_provider_->InputState();
375 ++adaptation_validation_id_;
376 Adaptation adaptation = GetAdaptationUp(input_state, resource);
377 return adaptation;
378 }
379
GetAdaptationUpStep(const VideoStreamInputState & input_state) const380 VideoStreamAdapter::RestrictionsOrState VideoStreamAdapter::GetAdaptationUpStep(
381 const VideoStreamInputState& input_state) const {
382 if (!HasSufficientInputForAdaptation(input_state)) {
383 return Adaptation::Status::kInsufficientInput;
384 }
385 // Don't adapt if we're awaiting a previous adaptation to have an effect.
386 if (awaiting_frame_size_change_ &&
387 awaiting_frame_size_change_->pixels_increased &&
388 degradation_preference_ == DegradationPreference::MAINTAIN_FRAMERATE &&
389 input_state.frame_size_pixels().value() <=
390 awaiting_frame_size_change_->frame_size_pixels) {
391 return Adaptation::Status::kAwaitingPreviousAdaptation;
392 }
393
394 // Maybe propose targets based on degradation preference.
395 switch (degradation_preference_) {
396 case DegradationPreference::BALANCED: {
397 // Attempt to increase target frame rate.
398 RestrictionsOrState increase_frame_rate =
399 IncreaseFramerate(input_state, current_restrictions_);
400 if (absl::holds_alternative<RestrictionsWithCounters>(
401 increase_frame_rate)) {
402 return increase_frame_rate;
403 }
404 // else, increase resolution.
405 ABSL_FALLTHROUGH_INTENDED;
406 }
407 case DegradationPreference::MAINTAIN_FRAMERATE: {
408 // Attempt to increase pixel count.
409 return IncreaseResolution(input_state, current_restrictions_);
410 }
411 case DegradationPreference::MAINTAIN_RESOLUTION: {
412 // Scale up framerate.
413 return IncreaseFramerate(input_state, current_restrictions_);
414 }
415 case DegradationPreference::DISABLED:
416 return Adaptation::Status::kAdaptationDisabled;
417 }
418 }
419
GetAdaptationDown()420 Adaptation VideoStreamAdapter::GetAdaptationDown() {
421 RTC_DCHECK_RUN_ON(&sequence_checker_);
422 VideoStreamInputState input_state = input_state_provider_->InputState();
423 ++adaptation_validation_id_;
424 return RestrictionsOrStateToAdaptation(GetAdaptationDownStep(input_state),
425 input_state);
426 }
427
428 VideoStreamAdapter::RestrictionsOrState
GetAdaptationDownStep(const VideoStreamInputState & input_state) const429 VideoStreamAdapter::GetAdaptationDownStep(
430 const VideoStreamInputState& input_state) const {
431 if (!HasSufficientInputForAdaptation(input_state)) {
432 return Adaptation::Status::kInsufficientInput;
433 }
434 // Don't adapt if we're awaiting a previous adaptation to have an effect or
435 // if we switched degradation preference.
436 if (awaiting_frame_size_change_ &&
437 !awaiting_frame_size_change_->pixels_increased &&
438 degradation_preference_ == DegradationPreference::MAINTAIN_FRAMERATE &&
439 input_state.frame_size_pixels().value() >=
440 awaiting_frame_size_change_->frame_size_pixels) {
441 return Adaptation::Status::kAwaitingPreviousAdaptation;
442 }
443 // Maybe propose targets based on degradation preference.
444 switch (degradation_preference_) {
445 case DegradationPreference::BALANCED: {
446 // Try scale down framerate, if lower.
447 RestrictionsOrState decrease_frame_rate =
448 DecreaseFramerate(input_state, current_restrictions_);
449 if (absl::holds_alternative<RestrictionsWithCounters>(
450 decrease_frame_rate)) {
451 return decrease_frame_rate;
452 }
453 // else, decrease resolution.
454 ABSL_FALLTHROUGH_INTENDED;
455 }
456 case DegradationPreference::MAINTAIN_FRAMERATE: {
457 return DecreaseResolution(input_state, current_restrictions_);
458 }
459 case DegradationPreference::MAINTAIN_RESOLUTION: {
460 return DecreaseFramerate(input_state, current_restrictions_);
461 }
462 case DegradationPreference::DISABLED:
463 return Adaptation::Status::kAdaptationDisabled;
464 }
465 }
466
DecreaseResolution(const VideoStreamInputState & input_state,const RestrictionsWithCounters & current_restrictions)467 VideoStreamAdapter::RestrictionsOrState VideoStreamAdapter::DecreaseResolution(
468 const VideoStreamInputState& input_state,
469 const RestrictionsWithCounters& current_restrictions) {
470 int target_pixels =
471 GetLowerResolutionThan(input_state.frame_size_pixels().value());
472 if (!CanDecreaseResolutionTo(target_pixels, input_state,
473 current_restrictions.restrictions)) {
474 return Adaptation::Status::kLimitReached;
475 }
476 RestrictionsWithCounters new_restrictions = current_restrictions;
477 RTC_LOG(LS_INFO) << "Scaling down resolution, max pixels: " << target_pixels;
478 new_restrictions.restrictions.set_max_pixels_per_frame(
479 target_pixels != std::numeric_limits<int>::max()
480 ? absl::optional<size_t>(target_pixels)
481 : absl::nullopt);
482 new_restrictions.restrictions.set_target_pixels_per_frame(absl::nullopt);
483 ++new_restrictions.counters.resolution_adaptations;
484 return new_restrictions;
485 }
486
DecreaseFramerate(const VideoStreamInputState & input_state,const RestrictionsWithCounters & current_restrictions) const487 VideoStreamAdapter::RestrictionsOrState VideoStreamAdapter::DecreaseFramerate(
488 const VideoStreamInputState& input_state,
489 const RestrictionsWithCounters& current_restrictions) const {
490 int max_frame_rate;
491 if (degradation_preference_ == DegradationPreference::MAINTAIN_RESOLUTION) {
492 max_frame_rate = GetLowerFrameRateThan(input_state.frames_per_second());
493 } else if (degradation_preference_ == DegradationPreference::BALANCED) {
494 max_frame_rate =
495 balanced_settings_.MinFps(input_state.video_codec_type(),
496 input_state.frame_size_pixels().value());
497 } else {
498 RTC_NOTREACHED();
499 max_frame_rate = GetLowerFrameRateThan(input_state.frames_per_second());
500 }
501 if (!CanDecreaseFrameRateTo(max_frame_rate,
502 current_restrictions.restrictions)) {
503 return Adaptation::Status::kLimitReached;
504 }
505 RestrictionsWithCounters new_restrictions = current_restrictions;
506 max_frame_rate = std::max(kMinFrameRateFps, max_frame_rate);
507 RTC_LOG(LS_INFO) << "Scaling down framerate: " << max_frame_rate;
508 new_restrictions.restrictions.set_max_frame_rate(
509 max_frame_rate != std::numeric_limits<int>::max()
510 ? absl::optional<double>(max_frame_rate)
511 : absl::nullopt);
512 ++new_restrictions.counters.fps_adaptations;
513 return new_restrictions;
514 }
515
IncreaseResolution(const VideoStreamInputState & input_state,const RestrictionsWithCounters & current_restrictions)516 VideoStreamAdapter::RestrictionsOrState VideoStreamAdapter::IncreaseResolution(
517 const VideoStreamInputState& input_state,
518 const RestrictionsWithCounters& current_restrictions) {
519 int target_pixels = input_state.frame_size_pixels().value();
520 if (current_restrictions.counters.resolution_adaptations == 1) {
521 RTC_LOG(LS_INFO) << "Removing resolution down-scaling setting.";
522 target_pixels = std::numeric_limits<int>::max();
523 }
524 target_pixels = GetHigherResolutionThan(target_pixels);
525 if (!CanIncreaseResolutionTo(target_pixels,
526 current_restrictions.restrictions)) {
527 return Adaptation::Status::kLimitReached;
528 }
529 int max_pixels_wanted = GetIncreasedMaxPixelsWanted(target_pixels);
530 RestrictionsWithCounters new_restrictions = current_restrictions;
531 RTC_LOG(LS_INFO) << "Scaling up resolution, max pixels: "
532 << max_pixels_wanted;
533 new_restrictions.restrictions.set_max_pixels_per_frame(
534 max_pixels_wanted != std::numeric_limits<int>::max()
535 ? absl::optional<size_t>(max_pixels_wanted)
536 : absl::nullopt);
537 new_restrictions.restrictions.set_target_pixels_per_frame(
538 max_pixels_wanted != std::numeric_limits<int>::max()
539 ? absl::optional<size_t>(target_pixels)
540 : absl::nullopt);
541 --new_restrictions.counters.resolution_adaptations;
542 RTC_DCHECK_GE(new_restrictions.counters.resolution_adaptations, 0);
543 return new_restrictions;
544 }
545
IncreaseFramerate(const VideoStreamInputState & input_state,const RestrictionsWithCounters & current_restrictions) const546 VideoStreamAdapter::RestrictionsOrState VideoStreamAdapter::IncreaseFramerate(
547 const VideoStreamInputState& input_state,
548 const RestrictionsWithCounters& current_restrictions) const {
549 int max_frame_rate;
550 if (degradation_preference_ == DegradationPreference::MAINTAIN_RESOLUTION) {
551 max_frame_rate = GetHigherFrameRateThan(input_state.frames_per_second());
552 } else if (degradation_preference_ == DegradationPreference::BALANCED) {
553 max_frame_rate =
554 balanced_settings_.MaxFps(input_state.video_codec_type(),
555 input_state.frame_size_pixels().value());
556 // In BALANCED, the max_frame_rate must be checked before proceeding. This
557 // is because the MaxFps might be the current Fps and so the balanced
558 // settings may want to scale up the resolution.=
559 if (!CanIncreaseFrameRateTo(max_frame_rate,
560 current_restrictions.restrictions)) {
561 return Adaptation::Status::kLimitReached;
562 }
563 } else {
564 RTC_NOTREACHED();
565 max_frame_rate = GetHigherFrameRateThan(input_state.frames_per_second());
566 }
567 if (current_restrictions.counters.fps_adaptations == 1) {
568 RTC_LOG(LS_INFO) << "Removing framerate down-scaling setting.";
569 max_frame_rate = std::numeric_limits<int>::max();
570 }
571 if (!CanIncreaseFrameRateTo(max_frame_rate,
572 current_restrictions.restrictions)) {
573 return Adaptation::Status::kLimitReached;
574 }
575 RTC_LOG(LS_INFO) << "Scaling up framerate: " << max_frame_rate;
576 RestrictionsWithCounters new_restrictions = current_restrictions;
577 new_restrictions.restrictions.set_max_frame_rate(
578 max_frame_rate != std::numeric_limits<int>::max()
579 ? absl::optional<double>(max_frame_rate)
580 : absl::nullopt);
581 --new_restrictions.counters.fps_adaptations;
582 RTC_DCHECK_GE(new_restrictions.counters.fps_adaptations, 0);
583 return new_restrictions;
584 }
585
GetAdaptDownResolution()586 Adaptation VideoStreamAdapter::GetAdaptDownResolution() {
587 RTC_DCHECK_RUN_ON(&sequence_checker_);
588 VideoStreamInputState input_state = input_state_provider_->InputState();
589 switch (degradation_preference_) {
590 case DegradationPreference::DISABLED:
591 return RestrictionsOrStateToAdaptation(
592 Adaptation::Status::kAdaptationDisabled, input_state);
593 case DegradationPreference::MAINTAIN_RESOLUTION:
594 return RestrictionsOrStateToAdaptation(Adaptation::Status::kLimitReached,
595 input_state);
596 case DegradationPreference::MAINTAIN_FRAMERATE:
597 return GetAdaptationDown();
598 case DegradationPreference::BALANCED: {
599 return RestrictionsOrStateToAdaptation(
600 GetAdaptDownResolutionStepForBalanced(input_state), input_state);
601 }
602 default:
603 RTC_NOTREACHED();
604 }
605 }
606
607 VideoStreamAdapter::RestrictionsOrState
GetAdaptDownResolutionStepForBalanced(const VideoStreamInputState & input_state) const608 VideoStreamAdapter::GetAdaptDownResolutionStepForBalanced(
609 const VideoStreamInputState& input_state) const {
610 // Adapt twice if the first adaptation did not decrease resolution.
611 auto first_step = GetAdaptationDownStep(input_state);
612 if (!absl::holds_alternative<RestrictionsWithCounters>(first_step)) {
613 return first_step;
614 }
615 auto first_restrictions = absl::get<RestrictionsWithCounters>(first_step);
616 if (first_restrictions.counters.resolution_adaptations >
617 current_restrictions_.counters.resolution_adaptations) {
618 return first_step;
619 }
620 // We didn't decrease resolution so force it; amend a resolution resuction
621 // to the existing framerate reduction in |first_restrictions|.
622 auto second_step = DecreaseResolution(input_state, first_restrictions);
623 if (absl::holds_alternative<RestrictionsWithCounters>(second_step)) {
624 return second_step;
625 }
626 // If the second step was not successful then settle for the first one.
627 return first_step;
628 }
629
ApplyAdaptation(const Adaptation & adaptation,rtc::scoped_refptr<Resource> resource)630 void VideoStreamAdapter::ApplyAdaptation(
631 const Adaptation& adaptation,
632 rtc::scoped_refptr<Resource> resource) {
633 RTC_DCHECK_RUN_ON(&sequence_checker_);
634 RTC_DCHECK_EQ(adaptation.validation_id_, adaptation_validation_id_);
635 if (adaptation.status() != Adaptation::Status::kValid)
636 return;
637 // Remember the input pixels and fps of this adaptation. Used to avoid
638 // adapting again before this adaptation has had an effect.
639 if (DidIncreaseResolution(current_restrictions_.restrictions,
640 adaptation.restrictions())) {
641 awaiting_frame_size_change_.emplace(
642 true, adaptation.input_state().frame_size_pixels().value());
643 } else if (DidDecreaseResolution(current_restrictions_.restrictions,
644 adaptation.restrictions())) {
645 awaiting_frame_size_change_.emplace(
646 false, adaptation.input_state().frame_size_pixels().value());
647 } else {
648 awaiting_frame_size_change_ = absl::nullopt;
649 }
650 current_restrictions_ = {adaptation.restrictions(), adaptation.counters()};
651 BroadcastVideoRestrictionsUpdate(adaptation.input_state(), resource);
652 }
653
GetAdaptationTo(const VideoAdaptationCounters & counters,const VideoSourceRestrictions & restrictions)654 Adaptation VideoStreamAdapter::GetAdaptationTo(
655 const VideoAdaptationCounters& counters,
656 const VideoSourceRestrictions& restrictions) {
657 // Adapts up/down from the current levels so counters are equal.
658 RTC_DCHECK_RUN_ON(&sequence_checker_);
659 VideoStreamInputState input_state = input_state_provider_->InputState();
660 return Adaptation(adaptation_validation_id_, restrictions, counters,
661 input_state, false);
662 }
663
BroadcastVideoRestrictionsUpdate(const VideoStreamInputState & input_state,const rtc::scoped_refptr<Resource> & resource)664 void VideoStreamAdapter::BroadcastVideoRestrictionsUpdate(
665 const VideoStreamInputState& input_state,
666 const rtc::scoped_refptr<Resource>& resource) {
667 RTC_DCHECK_RUN_ON(&sequence_checker_);
668 VideoSourceRestrictions filtered = FilterRestrictionsByDegradationPreference(
669 source_restrictions(), degradation_preference_);
670 if (last_filtered_restrictions_ == filtered) {
671 return;
672 }
673 for (auto* restrictions_listener : restrictions_listeners_) {
674 restrictions_listener->OnVideoSourceRestrictionsUpdated(
675 filtered, current_restrictions_.counters, resource,
676 source_restrictions());
677 }
678 for (auto* adaptation_listener : adaptation_listeners_) {
679 adaptation_listener->OnAdaptationApplied(
680 input_state, last_video_source_restrictions_,
681 current_restrictions_.restrictions, resource);
682 }
683 last_video_source_restrictions_ = current_restrictions_.restrictions;
684 last_filtered_restrictions_ = filtered;
685 }
686
HasSufficientInputForAdaptation(const VideoStreamInputState & input_state) const687 bool VideoStreamAdapter::HasSufficientInputForAdaptation(
688 const VideoStreamInputState& input_state) const {
689 return input_state.HasInputFrameSizeAndFramesPerSecond() &&
690 (degradation_preference_ !=
691 DegradationPreference::MAINTAIN_RESOLUTION ||
692 input_state.frames_per_second() >= kMinFrameRateFps);
693 }
694
AwaitingFrameSizeChange(bool pixels_increased,int frame_size_pixels)695 VideoStreamAdapter::AwaitingFrameSizeChange::AwaitingFrameSizeChange(
696 bool pixels_increased,
697 int frame_size_pixels)
698 : pixels_increased(pixels_increased),
699 frame_size_pixels(frame_size_pixels) {}
700
701 } // namespace webrtc
702