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