blob: 8b589c383b449935859cf0941909ca5f333caaa9 [file] [log] [blame]
Henrik Boströmefbec9a2020-03-06 10:41:25 +01001/*
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 "video/adaptation/video_stream_adapter.h"
12
13#include <algorithm>
14#include <limits>
15
16#include "absl/types/optional.h"
Henrik Boströmb0f2e0c2020-03-06 13:32:03 +010017#include "api/video_codecs/video_encoder.h"
Henrik Boströmefbec9a2020-03-06 10:41:25 +010018#include "rtc_base/constructor_magic.h"
19#include "rtc_base/logging.h"
20#include "rtc_base/numerics/safe_conversions.h"
21
22namespace webrtc {
23
Henrik Boströmb0f2e0c2020-03-06 13:32:03 +010024namespace {
25
26const int kMinFramerateFps = 2;
27
28// Generate suggested higher and lower frame rates and resolutions, to be
29// applied to the VideoSourceRestrictor. These are used in "maintain-resolution"
30// and "maintain-framerate". The "balanced" degradation preference also makes
31// use of BalancedDegradationPreference when generating suggestions. The
32// VideoSourceRestrictor decidedes whether or not a proposed adaptation is
33// valid.
34
35// For frame rate, the steps we take are 2/3 (down) and 3/2 (up).
36int 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?
41int GetHigherFrameRateThan(int fps) {
42 return fps != std::numeric_limits<int>::max()
43 ? (fps * 3) / 2
44 : std::numeric_limits<int>::max();
45}
46
47// For resolution, the steps we take are 3/5 (down) and 5/3 (up).
48// Notice the asymmetry of which restriction property is set depending on if
49// we are adapting up or down:
50// - VideoSourceRestrictor::DecreaseResolution() sets the max_pixels_per_frame()
51// to the desired target and target_pixels_per_frame() to null.
52// - VideoSourceRestrictor::IncreaseResolutionTo() sets the
53// target_pixels_per_frame() to the desired target, and max_pixels_per_frame()
54// is set according to VideoSourceRestrictor::GetIncreasedMaxPixelsWanted().
55int GetLowerResolutionThan(int pixel_count) {
56 RTC_DCHECK(pixel_count != std::numeric_limits<int>::max());
57 return (pixel_count * 3) / 5;
58}
59// TODO(hbos): Use absl::optional<> instead?
60int GetHigherResolutionThan(int pixel_count) {
61 return pixel_count != std::numeric_limits<int>::max()
62 ? (pixel_count * 5) / 3
63 : std::numeric_limits<int>::max();
64}
65
66// One of the conditions used in VideoStreamAdapter::GetAdaptUpTarget().
67// TODO(hbos): Whether or not we can adapt up due to encoder settings and
68// bitrate should be expressed as a bandwidth-related Resource.
69bool CanAdaptUpResolution(
70 const absl::optional<EncoderSettings>& encoder_settings,
71 absl::optional<uint32_t> encoder_target_bitrate_bps,
72 int input_pixels) {
73 uint32_t bitrate_bps = encoder_target_bitrate_bps.value_or(0);
74 absl::optional<VideoEncoder::ResolutionBitrateLimits> bitrate_limits =
75 encoder_settings.has_value()
76 ? encoder_settings->encoder_info()
77 .GetEncoderBitrateLimitsForResolution(
78 GetHigherResolutionThan(input_pixels))
79 : absl::nullopt;
80 if (!bitrate_limits.has_value() || bitrate_bps == 0) {
81 return true; // No limit configured or bitrate provided.
82 }
83 RTC_DCHECK_GE(bitrate_limits->frame_size_pixels, input_pixels);
84 return bitrate_bps >=
85 static_cast<uint32_t>(bitrate_limits->min_start_bitrate_bps);
86}
87
88} // namespace
89
90VideoStreamAdapter::AdaptationTarget::AdaptationTarget(AdaptationAction action,
91 int value)
92 : action(action), value(value) {}
93
Henrik Boströmefbec9a2020-03-06 10:41:25 +010094// VideoSourceRestrictor is responsible for keeping track of current
Henrik Boströmb0f2e0c2020-03-06 13:32:03 +010095// VideoSourceRestrictions.
Henrik Boströmefbec9a2020-03-06 10:41:25 +010096class VideoStreamAdapter::VideoSourceRestrictor {
97 public:
Henrik Boströmefbec9a2020-03-06 10:41:25 +010098 VideoSourceRestrictor() {}
99
100 VideoSourceRestrictions source_restrictions() const {
101 return source_restrictions_;
102 }
103 const AdaptationCounters& adaptation_counters() const { return adaptations_; }
104 void ClearRestrictions() {
105 source_restrictions_ = VideoSourceRestrictions();
106 adaptations_ = AdaptationCounters();
107 }
108
109 bool CanDecreaseResolutionTo(int target_pixels, int min_pixels_per_frame) {
110 int max_pixels_per_frame = rtc::dchecked_cast<int>(
111 source_restrictions_.max_pixels_per_frame().value_or(
112 std::numeric_limits<int>::max()));
113 return target_pixels < max_pixels_per_frame &&
114 target_pixels >= min_pixels_per_frame;
115 }
116 void DecreaseResolutionTo(int target_pixels, int min_pixels_per_frame) {
117 RTC_DCHECK(CanDecreaseResolutionTo(target_pixels, min_pixels_per_frame));
118 RTC_LOG(LS_INFO) << "Scaling down resolution, max pixels: "
119 << target_pixels;
120 source_restrictions_.set_max_pixels_per_frame(
121 target_pixels != std::numeric_limits<int>::max()
122 ? absl::optional<size_t>(target_pixels)
123 : absl::nullopt);
124 source_restrictions_.set_target_pixels_per_frame(absl::nullopt);
125 ++adaptations_.resolution_adaptations;
126 }
127
128 bool CanIncreaseResolutionTo(int target_pixels) {
129 int max_pixels_wanted = GetIncreasedMaxPixelsWanted(target_pixels);
130 int max_pixels_per_frame = rtc::dchecked_cast<int>(
131 source_restrictions_.max_pixels_per_frame().value_or(
132 std::numeric_limits<int>::max()));
133 return max_pixels_wanted > max_pixels_per_frame;
134 }
135 void IncreaseResolutionTo(int target_pixels) {
136 RTC_DCHECK(CanIncreaseResolutionTo(target_pixels));
137 int max_pixels_wanted = GetIncreasedMaxPixelsWanted(target_pixels);
138 RTC_LOG(LS_INFO) << "Scaling up resolution, max pixels: "
139 << max_pixels_wanted;
140 source_restrictions_.set_max_pixels_per_frame(
141 max_pixels_wanted != std::numeric_limits<int>::max()
142 ? absl::optional<size_t>(max_pixels_wanted)
143 : absl::nullopt);
144 source_restrictions_.set_target_pixels_per_frame(
145 max_pixels_wanted != std::numeric_limits<int>::max()
146 ? absl::optional<size_t>(target_pixels)
147 : absl::nullopt);
148 --adaptations_.resolution_adaptations;
149 RTC_DCHECK_GE(adaptations_.resolution_adaptations, 0);
150 }
151
152 bool CanDecreaseFrameRateTo(int max_frame_rate) {
153 const int fps_wanted = std::max(kMinFramerateFps, max_frame_rate);
154 return fps_wanted < rtc::dchecked_cast<int>(
155 source_restrictions_.max_frame_rate().value_or(
156 std::numeric_limits<int>::max()));
157 }
158 void DecreaseFrameRateTo(int max_frame_rate) {
159 RTC_DCHECK(CanDecreaseFrameRateTo(max_frame_rate));
160 max_frame_rate = std::max(kMinFramerateFps, max_frame_rate);
161 RTC_LOG(LS_INFO) << "Scaling down framerate: " << max_frame_rate;
162 source_restrictions_.set_max_frame_rate(
163 max_frame_rate != std::numeric_limits<int>::max()
164 ? absl::optional<double>(max_frame_rate)
165 : absl::nullopt);
166 ++adaptations_.fps_adaptations;
167 }
168
169 bool CanIncreaseFrameRateTo(int max_frame_rate) {
170 return max_frame_rate > rtc::dchecked_cast<int>(
171 source_restrictions_.max_frame_rate().value_or(
172 std::numeric_limits<int>::max()));
173 }
174 void IncreaseFrameRateTo(int max_frame_rate) {
175 RTC_DCHECK(CanIncreaseFrameRateTo(max_frame_rate));
176 RTC_LOG(LS_INFO) << "Scaling up framerate: " << max_frame_rate;
177 source_restrictions_.set_max_frame_rate(
178 max_frame_rate != std::numeric_limits<int>::max()
179 ? absl::optional<double>(max_frame_rate)
180 : absl::nullopt);
181 --adaptations_.fps_adaptations;
182 RTC_DCHECK_GE(adaptations_.fps_adaptations, 0);
183 }
184
185 private:
186 static int GetIncreasedMaxPixelsWanted(int target_pixels) {
187 if (target_pixels == std::numeric_limits<int>::max())
188 return std::numeric_limits<int>::max();
189 // When we decrease resolution, we go down to at most 3/5 of current pixels.
190 // Thus to increase resolution, we need 3/5 to get back to where we started.
191 // When going up, the desired max_pixels_per_frame() has to be significantly
192 // higher than the target because the source's native resolutions might not
193 // match the target. We pick 12/5 of the target.
194 //
195 // (This value was historically 4 times the old target, which is (3/5)*4 of
196 // the new target - or 12/5 - assuming the target is adjusted according to
197 // the above steps.)
198 RTC_DCHECK(target_pixels != std::numeric_limits<int>::max());
199 return (target_pixels * 12) / 5;
200 }
201
202 VideoSourceRestrictions source_restrictions_;
203 AdaptationCounters adaptations_;
204
205 RTC_DISALLOW_COPY_AND_ASSIGN(VideoSourceRestrictor);
206};
207
Henrik Boströmefbec9a2020-03-06 10:41:25 +0100208// static
Henrik Boströmb0f2e0c2020-03-06 13:32:03 +0100209VideoStreamAdapter::AdaptationRequest::Mode
210VideoStreamAdapter::AdaptationRequest::GetModeFromAdaptationAction(
211 VideoStreamAdapter::AdaptationAction action) {
212 switch (action) {
213 case AdaptationAction::kIncreaseResolution:
214 return AdaptationRequest::Mode::kAdaptUp;
215 case AdaptationAction::kDecreaseResolution:
216 return AdaptationRequest::Mode::kAdaptDown;
217 case AdaptationAction::kIncreaseFrameRate:
218 return AdaptationRequest::Mode::kAdaptUp;
219 case AdaptationAction::kDecreaseFrameRate:
220 return AdaptationRequest::Mode::kAdaptDown;
221 }
Henrik Boströmefbec9a2020-03-06 10:41:25 +0100222}
223
224VideoStreamAdapter::VideoStreamAdapter()
Henrik Boströmb0f2e0c2020-03-06 13:32:03 +0100225 : source_restrictor_(std::make_unique<VideoSourceRestrictor>()),
226 balanced_settings_(),
227 degradation_preference_(DegradationPreference::DISABLED),
228 last_adaptation_request_(absl::nullopt) {}
Henrik Boströmefbec9a2020-03-06 10:41:25 +0100229
230VideoStreamAdapter::~VideoStreamAdapter() {}
231
232VideoSourceRestrictions VideoStreamAdapter::source_restrictions() const {
233 return source_restrictor_->source_restrictions();
234}
235
236const AdaptationCounters& VideoStreamAdapter::adaptation_counters() const {
237 return source_restrictor_->adaptation_counters();
238}
239
Henrik Boströmb0f2e0c2020-03-06 13:32:03 +0100240const BalancedDegradationSettings& VideoStreamAdapter::balanced_settings()
241 const {
242 return balanced_settings_;
243}
244
Henrik Boströmefbec9a2020-03-06 10:41:25 +0100245void VideoStreamAdapter::ClearRestrictions() {
246 source_restrictor_->ClearRestrictions();
Henrik Boströmb0f2e0c2020-03-06 13:32:03 +0100247 last_adaptation_request_.reset();
Henrik Boströmefbec9a2020-03-06 10:41:25 +0100248}
249
Henrik Boströmb0f2e0c2020-03-06 13:32:03 +0100250VideoStreamAdapter::SetDegradationPreferenceResult
251VideoStreamAdapter::SetDegradationPreference(
252 DegradationPreference degradation_preference) {
253 bool did_clear = false;
254 if (degradation_preference_ != degradation_preference) {
255 if (degradation_preference == DegradationPreference::BALANCED ||
256 degradation_preference_ == DegradationPreference::BALANCED) {
257 ClearRestrictions();
258 did_clear = true;
259 }
260 }
261 degradation_preference_ = degradation_preference;
262 return did_clear ? SetDegradationPreferenceResult::kRestrictionsCleared
263 : SetDegradationPreferenceResult::kRestrictionsNotCleared;
Henrik Boströmefbec9a2020-03-06 10:41:25 +0100264}
265
Henrik Boströmb0f2e0c2020-03-06 13:32:03 +0100266DegradationPreference VideoStreamAdapter::EffectiveDegradationPreference(
267 VideoInputMode input_mode) const {
268 // Balanced mode for screenshare works via automatic animation detection:
269 // Resolution is capped for fullscreen animated content.
270 // Adapatation is done only via framerate downgrade.
271 // Thus effective degradation preference is MAINTAIN_RESOLUTION.
272 // TODO(hbos): Don't do this. This is not what "balanced" means. If the
273 // application wants to maintain resolution it should set that degradation
274 // preference rather than depend on non-standard behaviors.
275 return (input_mode == VideoInputMode::kScreenshareVideo &&
276 degradation_preference_ == DegradationPreference::BALANCED)
277 ? DegradationPreference::MAINTAIN_RESOLUTION
278 : degradation_preference_;
Henrik Boströmefbec9a2020-03-06 10:41:25 +0100279}
280
Henrik Boströmb0f2e0c2020-03-06 13:32:03 +0100281absl::optional<VideoStreamAdapter::AdaptationTarget>
282VideoStreamAdapter::GetAdaptUpTarget(
283 const absl::optional<EncoderSettings>& encoder_settings,
284 absl::optional<uint32_t> encoder_target_bitrate_bps,
285 VideoInputMode input_mode,
286 int input_pixels,
287 int input_fps,
288 AdaptationObserverInterface::AdaptReason reason) const {
289 // Preconditions for being able to adapt up:
290 if (input_mode == VideoInputMode::kNoVideo)
291 return absl::nullopt;
292 // 1. We shouldn't adapt up if we're currently waiting for a previous upgrade
293 // to have an effect.
294 // TODO(hbos): What about in the case of other degradation preferences?
295 bool last_adaptation_was_up =
296 last_adaptation_request_ &&
297 last_adaptation_request_->mode_ == AdaptationRequest::Mode::kAdaptUp;
298 if (last_adaptation_was_up &&
299 degradation_preference_ == DegradationPreference::MAINTAIN_FRAMERATE &&
300 input_pixels <= last_adaptation_request_->input_pixel_count_) {
301 return absl::nullopt;
302 }
303 // 2. We shouldn't adapt up if BalancedSettings doesn't allow it, which is
304 // only applicable if reason is kQuality and preference is BALANCED.
305 if (reason == AdaptationObserverInterface::AdaptReason::kQuality &&
306 EffectiveDegradationPreference(input_mode) ==
307 DegradationPreference::BALANCED &&
308 !balanced_settings_.CanAdaptUp(
309 GetVideoCodecTypeOrGeneric(encoder_settings), input_pixels,
310 encoder_target_bitrate_bps.value_or(0))) {
311 return absl::nullopt;
312 }
313
314 // Attempt to find an allowed adaptation target.
315 switch (EffectiveDegradationPreference(input_mode)) {
316 case DegradationPreference::BALANCED: {
317 // Attempt to increase target frame rate.
318 int target_fps = balanced_settings_.MaxFps(
319 GetVideoCodecTypeOrGeneric(encoder_settings), input_pixels);
320 if (source_restrictor_->CanIncreaseFrameRateTo(target_fps)) {
321 return AdaptationTarget(AdaptationAction::kIncreaseFrameRate,
322 target_fps);
323 }
324 // Fall-through to maybe-adapting resolution, unless |balanced_settings_|
325 // forbids it based on bitrate.
326 if (reason == AdaptationObserverInterface::AdaptReason::kQuality &&
327 !balanced_settings_.CanAdaptUpResolution(
328 GetVideoCodecTypeOrGeneric(encoder_settings), input_pixels,
329 encoder_target_bitrate_bps.value_or(0))) {
330 return absl::nullopt;
331 }
332 // Scale up resolution.
333 ABSL_FALLTHROUGH_INTENDED;
334 }
335 case DegradationPreference::MAINTAIN_FRAMERATE: {
336 // Don't adapt resolution if CanAdaptUpResolution() forbids it based on
337 // bitrate and limits specified by encoder capabilities.
338 if (reason == AdaptationObserverInterface::AdaptReason::kQuality &&
339 !CanAdaptUpResolution(encoder_settings, encoder_target_bitrate_bps,
340 input_pixels)) {
341 return absl::nullopt;
342 }
343 // Attempt to increase pixel count.
344 int target_pixels = input_pixels;
345 if (source_restrictor_->adaptation_counters().resolution_adaptations ==
346 1) {
347 RTC_LOG(LS_INFO) << "Removing resolution down-scaling setting.";
348 target_pixels = std::numeric_limits<int>::max();
349 }
350 target_pixels = GetHigherResolutionThan(target_pixels);
351 if (!source_restrictor_->CanIncreaseResolutionTo(target_pixels))
352 return absl::nullopt;
353 return AdaptationTarget(AdaptationAction::kIncreaseResolution,
354 target_pixels);
355 }
356 case DegradationPreference::MAINTAIN_RESOLUTION: {
357 // Scale up framerate.
358 int target_fps = input_fps;
359 if (source_restrictor_->adaptation_counters().fps_adaptations == 1) {
360 RTC_LOG(LS_INFO) << "Removing framerate down-scaling setting.";
361 target_fps = std::numeric_limits<int>::max();
362 }
363 target_fps = GetHigherFrameRateThan(target_fps);
364 if (!source_restrictor_->CanIncreaseFrameRateTo(target_fps))
365 return absl::nullopt;
366 return AdaptationTarget(AdaptationAction::kIncreaseFrameRate, target_fps);
367 }
368 case DegradationPreference::DISABLED:
369 return absl::nullopt;
370 }
Henrik Boströmefbec9a2020-03-06 10:41:25 +0100371}
372
Henrik Boströmb0f2e0c2020-03-06 13:32:03 +0100373absl::optional<VideoStreamAdapter::AdaptationTarget>
374VideoStreamAdapter::GetAdaptDownTarget(
375 const absl::optional<EncoderSettings>& encoder_settings,
376 VideoInputMode input_mode,
377 int input_pixels,
378 int input_fps,
379 int min_pixels_per_frame,
380 VideoStreamEncoderObserver* encoder_stats_observer) const {
381 // Preconditions for being able to adapt down:
382 if (input_mode == VideoInputMode::kNoVideo)
383 return absl::nullopt;
384 // 1. We are not disabled.
385 // TODO(hbos): Don't support DISABLED, it doesn't exist in the spec and it
386 // causes scaling due to bandwidth constraints (QualityScalerResource) to be
387 // ignored, not just CPU signals. This is not a use case we want to support
388 // long-term; remove this enum value.
389 if (degradation_preference_ == DegradationPreference::DISABLED)
390 return absl::nullopt;
391 bool last_adaptation_was_down =
392 last_adaptation_request_ &&
393 last_adaptation_request_->mode_ == AdaptationRequest::Mode::kAdaptDown;
394 // 2. We shouldn't adapt down if our frame rate is below the minimum or if its
395 // currently unknown.
396 if (EffectiveDegradationPreference(input_mode) ==
397 DegradationPreference::MAINTAIN_RESOLUTION) {
398 // TODO(hbos): This usage of |last_adaptation_was_down| looks like a mistake
399 // - delete it.
400 if (input_fps <= 0 ||
401 (last_adaptation_was_down && input_fps < kMinFramerateFps)) {
402 return absl::nullopt;
403 }
404 }
405 // 3. We shouldn't adapt down if we're currently waiting for a previous
406 // downgrade to have an effect.
407 // TODO(hbos): What about in the case of other degradation preferences?
408 if (last_adaptation_was_down &&
409 degradation_preference_ == DegradationPreference::MAINTAIN_FRAMERATE &&
410 input_pixels >= last_adaptation_request_->input_pixel_count_) {
411 return absl::nullopt;
412 }
413
414 // Attempt to find an allowed adaptation target.
415 switch (EffectiveDegradationPreference(input_mode)) {
416 case DegradationPreference::BALANCED: {
417 // Try scale down framerate, if lower.
418 int target_fps = balanced_settings_.MinFps(
419 GetVideoCodecTypeOrGeneric(encoder_settings), input_pixels);
420 if (source_restrictor_->CanDecreaseFrameRateTo(target_fps)) {
421 return AdaptationTarget(AdaptationAction::kDecreaseFrameRate,
422 target_fps);
423 }
424 // Scale down resolution.
425 ABSL_FALLTHROUGH_INTENDED;
426 }
427 case DegradationPreference::MAINTAIN_FRAMERATE: {
428 // Scale down resolution.
429 int target_pixels = GetLowerResolutionThan(input_pixels);
430 // TODO(https://crbug.com/webrtc/11393): Move this logic to
431 // ApplyAdaptationTarget() or elsewhere - simply checking which adaptation
432 // target is available should not have side-effects.
433 if (target_pixels < min_pixels_per_frame)
434 encoder_stats_observer->OnMinPixelLimitReached();
435 if (!source_restrictor_->CanDecreaseResolutionTo(target_pixels,
436 min_pixels_per_frame)) {
437 return absl::nullopt;
438 }
439 return AdaptationTarget(AdaptationAction::kDecreaseResolution,
440 target_pixels);
441 }
442 case DegradationPreference::MAINTAIN_RESOLUTION: {
443 int target_fps = GetLowerFrameRateThan(input_fps);
444 if (!source_restrictor_->CanDecreaseFrameRateTo(target_fps))
445 return absl::nullopt;
446 return AdaptationTarget(AdaptationAction::kDecreaseFrameRate, target_fps);
447 }
448 case DegradationPreference::DISABLED:
449 RTC_NOTREACHED();
450 return absl::nullopt;
451 }
Henrik Boströmefbec9a2020-03-06 10:41:25 +0100452}
453
Henrik Boströmb0f2e0c2020-03-06 13:32:03 +0100454void VideoStreamAdapter::ApplyAdaptationTarget(const AdaptationTarget& target,
455 VideoInputMode input_mode,
456 int input_pixels,
457 int input_fps,
458 int min_pixels_per_frame) {
459 // Remember the input pixels and fps of this adaptation. Used to avoid
460 // adapting again before this adaptation has had an effect.
461 last_adaptation_request_.emplace(AdaptationRequest{
462 input_pixels, input_fps,
463 AdaptationRequest::GetModeFromAdaptationAction(target.action)});
464 switch (target.action) {
465 case AdaptationAction::kIncreaseResolution:
466 source_restrictor_->IncreaseResolutionTo(target.value);
467 return;
468 case AdaptationAction::kDecreaseResolution:
469 source_restrictor_->DecreaseResolutionTo(target.value,
470 min_pixels_per_frame);
471 return;
472 case AdaptationAction::kIncreaseFrameRate:
473 source_restrictor_->IncreaseFrameRateTo(target.value);
474 // TODO(https://crbug.com/webrtc/11222): Don't adapt in two steps.
475 // GetAdaptUpTarget() should tell us the correct value, but BALANCED logic
476 // in DecrementFramerate() makes it hard to predict whether this will be
477 // the last step. Remove the dependency on GetConstAdaptCounter().
478 if (EffectiveDegradationPreference(input_mode) ==
479 DegradationPreference::BALANCED &&
480 source_restrictor_->adaptation_counters().fps_adaptations == 0 &&
481 target.value != std::numeric_limits<int>::max()) {
482 RTC_LOG(LS_INFO) << "Removing framerate down-scaling setting.";
483 source_restrictor_->IncreaseFrameRateTo(
484 std::numeric_limits<int>::max());
485 }
486 return;
487 case AdaptationAction::kDecreaseFrameRate:
488 source_restrictor_->DecreaseFrameRateTo(target.value);
489 return;
490 }
Henrik Boströmefbec9a2020-03-06 10:41:25 +0100491}
492
493} // namespace webrtc