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/resource_adaptation_processor.h"
12
13 #include <algorithm>
14 #include <string>
15 #include <utility>
16
17 #include "absl/algorithm/container.h"
18 #include "absl/strings/string_view.h"
19 #include "api/sequence_checker.h"
20 #include "api/video/video_adaptation_counters.h"
21 #include "call/adaptation/video_stream_adapter.h"
22 #include "rtc_base/logging.h"
23 #include "rtc_base/strings/string_builder.h"
24
25 namespace webrtc {
26
ResourceListenerDelegate(ResourceAdaptationProcessor * processor)27 ResourceAdaptationProcessor::ResourceListenerDelegate::ResourceListenerDelegate(
28 ResourceAdaptationProcessor* processor)
29 : task_queue_(TaskQueueBase::Current()), processor_(processor) {
30 RTC_DCHECK(task_queue_);
31 }
32
33 void ResourceAdaptationProcessor::ResourceListenerDelegate::
OnProcessorDestroyed()34 OnProcessorDestroyed() {
35 RTC_DCHECK_RUN_ON(task_queue_);
36 processor_ = nullptr;
37 }
38
39 void ResourceAdaptationProcessor::ResourceListenerDelegate::
OnResourceUsageStateMeasured(rtc::scoped_refptr<Resource> resource,ResourceUsageState usage_state)40 OnResourceUsageStateMeasured(rtc::scoped_refptr<Resource> resource,
41 ResourceUsageState usage_state) {
42 if (!task_queue_->IsCurrent()) {
43 task_queue_->PostTask(
44 [this_ref = rtc::scoped_refptr<ResourceListenerDelegate>(this),
45 resource, usage_state] {
46 this_ref->OnResourceUsageStateMeasured(resource, usage_state);
47 });
48 return;
49 }
50 RTC_DCHECK_RUN_ON(task_queue_);
51 if (processor_) {
52 processor_->OnResourceUsageStateMeasured(resource, usage_state);
53 }
54 }
55
56 ResourceAdaptationProcessor::MitigationResultAndLogMessage::
MitigationResultAndLogMessage()57 MitigationResultAndLogMessage()
58 : result(MitigationResult::kAdaptationApplied), message() {}
59
60 ResourceAdaptationProcessor::MitigationResultAndLogMessage::
MitigationResultAndLogMessage(MitigationResult result,absl::string_view message)61 MitigationResultAndLogMessage(MitigationResult result,
62 absl::string_view message)
63 : result(result), message(message) {}
64
ResourceAdaptationProcessor(VideoStreamAdapter * stream_adapter)65 ResourceAdaptationProcessor::ResourceAdaptationProcessor(
66 VideoStreamAdapter* stream_adapter)
67 : task_queue_(TaskQueueBase::Current()),
68 resource_listener_delegate_(
69 rtc::make_ref_counted<ResourceListenerDelegate>(this)),
70 resources_(),
71 stream_adapter_(stream_adapter),
72 last_reported_source_restrictions_(),
73 previous_mitigation_results_() {
74 RTC_DCHECK(task_queue_);
75 stream_adapter_->AddRestrictionsListener(this);
76 }
77
~ResourceAdaptationProcessor()78 ResourceAdaptationProcessor::~ResourceAdaptationProcessor() {
79 RTC_DCHECK_RUN_ON(task_queue_);
80 RTC_DCHECK(resources_.empty())
81 << "There are resource(s) attached to a ResourceAdaptationProcessor "
82 << "being destroyed.";
83 stream_adapter_->RemoveRestrictionsListener(this);
84 resource_listener_delegate_->OnProcessorDestroyed();
85 }
86
AddResourceLimitationsListener(ResourceLimitationsListener * limitations_listener)87 void ResourceAdaptationProcessor::AddResourceLimitationsListener(
88 ResourceLimitationsListener* limitations_listener) {
89 RTC_DCHECK_RUN_ON(task_queue_);
90 RTC_DCHECK(std::find(resource_limitations_listeners_.begin(),
91 resource_limitations_listeners_.end(),
92 limitations_listener) ==
93 resource_limitations_listeners_.end());
94 resource_limitations_listeners_.push_back(limitations_listener);
95 }
96
RemoveResourceLimitationsListener(ResourceLimitationsListener * limitations_listener)97 void ResourceAdaptationProcessor::RemoveResourceLimitationsListener(
98 ResourceLimitationsListener* limitations_listener) {
99 RTC_DCHECK_RUN_ON(task_queue_);
100 auto it =
101 std::find(resource_limitations_listeners_.begin(),
102 resource_limitations_listeners_.end(), limitations_listener);
103 RTC_DCHECK(it != resource_limitations_listeners_.end());
104 resource_limitations_listeners_.erase(it);
105 }
106
AddResource(rtc::scoped_refptr<Resource> resource)107 void ResourceAdaptationProcessor::AddResource(
108 rtc::scoped_refptr<Resource> resource) {
109 RTC_DCHECK(resource);
110 {
111 MutexLock crit(&resources_lock_);
112 RTC_DCHECK(absl::c_find(resources_, resource) == resources_.end())
113 << "Resource \"" << resource->Name() << "\" was already registered.";
114 resources_.push_back(resource);
115 }
116 resource->SetResourceListener(resource_listener_delegate_.get());
117 RTC_LOG(LS_INFO) << "Registered resource \"" << resource->Name() << "\".";
118 }
119
120 std::vector<rtc::scoped_refptr<Resource>>
GetResources() const121 ResourceAdaptationProcessor::GetResources() const {
122 MutexLock crit(&resources_lock_);
123 return resources_;
124 }
125
RemoveResource(rtc::scoped_refptr<Resource> resource)126 void ResourceAdaptationProcessor::RemoveResource(
127 rtc::scoped_refptr<Resource> resource) {
128 RTC_DCHECK(resource);
129 RTC_LOG(LS_INFO) << "Removing resource \"" << resource->Name() << "\".";
130 resource->SetResourceListener(nullptr);
131 {
132 MutexLock crit(&resources_lock_);
133 auto it = absl::c_find(resources_, resource);
134 RTC_DCHECK(it != resources_.end()) << "Resource \"" << resource->Name()
135 << "\" was not a registered resource.";
136 resources_.erase(it);
137 }
138 RemoveLimitationsImposedByResource(std::move(resource));
139 }
140
RemoveLimitationsImposedByResource(rtc::scoped_refptr<Resource> resource)141 void ResourceAdaptationProcessor::RemoveLimitationsImposedByResource(
142 rtc::scoped_refptr<Resource> resource) {
143 if (!task_queue_->IsCurrent()) {
144 task_queue_->PostTask(
145 [this, resource]() { RemoveLimitationsImposedByResource(resource); });
146 return;
147 }
148 RTC_DCHECK_RUN_ON(task_queue_);
149 auto resource_adaptation_limits =
150 adaptation_limits_by_resources_.find(resource);
151 if (resource_adaptation_limits != adaptation_limits_by_resources_.end()) {
152 VideoStreamAdapter::RestrictionsWithCounters adaptation_limits =
153 resource_adaptation_limits->second;
154 adaptation_limits_by_resources_.erase(resource_adaptation_limits);
155 if (adaptation_limits_by_resources_.empty()) {
156 // Only the resource being removed was adapted so clear restrictions.
157 stream_adapter_->ClearRestrictions();
158 return;
159 }
160
161 VideoStreamAdapter::RestrictionsWithCounters most_limited =
162 FindMostLimitedResources().second;
163
164 if (adaptation_limits.counters.Total() <= most_limited.counters.Total()) {
165 // The removed limitations were less limited than the most limited
166 // resource. Don't change the current restrictions.
167 return;
168 }
169
170 // Apply the new most limited resource as the next restrictions.
171 Adaptation adapt_to = stream_adapter_->GetAdaptationTo(
172 most_limited.counters, most_limited.restrictions);
173 RTC_DCHECK_EQ(adapt_to.status(), Adaptation::Status::kValid);
174 stream_adapter_->ApplyAdaptation(adapt_to, nullptr);
175
176 RTC_LOG(LS_INFO)
177 << "Most limited resource removed. Restoring restrictions to "
178 "next most limited restrictions: "
179 << most_limited.restrictions.ToString() << " with counters "
180 << most_limited.counters.ToString();
181 }
182 }
183
OnResourceUsageStateMeasured(rtc::scoped_refptr<Resource> resource,ResourceUsageState usage_state)184 void ResourceAdaptationProcessor::OnResourceUsageStateMeasured(
185 rtc::scoped_refptr<Resource> resource,
186 ResourceUsageState usage_state) {
187 RTC_DCHECK_RUN_ON(task_queue_);
188 RTC_DCHECK(resource);
189 // `resource` could have been removed after signalling.
190 {
191 MutexLock crit(&resources_lock_);
192 if (absl::c_find(resources_, resource) == resources_.end()) {
193 RTC_LOG(LS_INFO) << "Ignoring signal from removed resource \""
194 << resource->Name() << "\".";
195 return;
196 }
197 }
198 MitigationResultAndLogMessage result_and_message;
199 switch (usage_state) {
200 case ResourceUsageState::kOveruse:
201 result_and_message = OnResourceOveruse(resource);
202 break;
203 case ResourceUsageState::kUnderuse:
204 result_and_message = OnResourceUnderuse(resource);
205 break;
206 }
207 // Maybe log the result of the operation.
208 auto it = previous_mitigation_results_.find(resource.get());
209 if (it != previous_mitigation_results_.end() &&
210 it->second == result_and_message.result) {
211 // This resource has previously reported the same result and we haven't
212 // successfully adapted since - don't log to avoid spam.
213 return;
214 }
215 RTC_LOG(LS_INFO) << "Resource \"" << resource->Name() << "\" signalled "
216 << ResourceUsageStateToString(usage_state) << ". "
217 << result_and_message.message;
218 if (result_and_message.result == MitigationResult::kAdaptationApplied) {
219 previous_mitigation_results_.clear();
220 } else {
221 previous_mitigation_results_.insert(
222 std::make_pair(resource.get(), result_and_message.result));
223 }
224 }
225
226 ResourceAdaptationProcessor::MitigationResultAndLogMessage
OnResourceUnderuse(rtc::scoped_refptr<Resource> reason_resource)227 ResourceAdaptationProcessor::OnResourceUnderuse(
228 rtc::scoped_refptr<Resource> reason_resource) {
229 RTC_DCHECK_RUN_ON(task_queue_);
230 // How can this stream be adapted up?
231 Adaptation adaptation = stream_adapter_->GetAdaptationUp();
232 if (adaptation.status() != Adaptation::Status::kValid) {
233 rtc::StringBuilder message;
234 message << "Not adapting up because VideoStreamAdapter returned "
235 << Adaptation::StatusToString(adaptation.status());
236 return MitigationResultAndLogMessage(MitigationResult::kRejectedByAdapter,
237 message.Release());
238 }
239 // Check that resource is most limited.
240 std::vector<rtc::scoped_refptr<Resource>> most_limited_resources;
241 VideoStreamAdapter::RestrictionsWithCounters most_limited_restrictions;
242 std::tie(most_limited_resources, most_limited_restrictions) =
243 FindMostLimitedResources();
244
245 // If the most restricted resource is less limited than current restrictions
246 // then proceed with adapting up.
247 if (!most_limited_resources.empty() &&
248 most_limited_restrictions.counters.Total() >=
249 stream_adapter_->adaptation_counters().Total()) {
250 // If `reason_resource` is not one of the most limiting resources then abort
251 // adaptation.
252 if (absl::c_find(most_limited_resources, reason_resource) ==
253 most_limited_resources.end()) {
254 rtc::StringBuilder message;
255 message << "Resource \"" << reason_resource->Name()
256 << "\" was not the most limited resource.";
257 return MitigationResultAndLogMessage(
258 MitigationResult::kNotMostLimitedResource, message.Release());
259 }
260
261 if (most_limited_resources.size() > 1) {
262 // If there are multiple most limited resources, all must signal underuse
263 // before the adaptation is applied.
264 UpdateResourceLimitations(reason_resource, adaptation.restrictions(),
265 adaptation.counters());
266 rtc::StringBuilder message;
267 message << "Resource \"" << reason_resource->Name()
268 << "\" was not the only most limited resource.";
269 return MitigationResultAndLogMessage(
270 MitigationResult::kSharedMostLimitedResource, message.Release());
271 }
272 }
273 // Apply adaptation.
274 stream_adapter_->ApplyAdaptation(adaptation, reason_resource);
275 rtc::StringBuilder message;
276 message << "Adapted up successfully. Unfiltered adaptations: "
277 << stream_adapter_->adaptation_counters().ToString();
278 return MitigationResultAndLogMessage(MitigationResult::kAdaptationApplied,
279 message.Release());
280 }
281
282 ResourceAdaptationProcessor::MitigationResultAndLogMessage
OnResourceOveruse(rtc::scoped_refptr<Resource> reason_resource)283 ResourceAdaptationProcessor::OnResourceOveruse(
284 rtc::scoped_refptr<Resource> reason_resource) {
285 RTC_DCHECK_RUN_ON(task_queue_);
286 // How can this stream be adapted up?
287 Adaptation adaptation = stream_adapter_->GetAdaptationDown();
288 if (adaptation.status() == Adaptation::Status::kLimitReached) {
289 // Add resource as most limited.
290 VideoStreamAdapter::RestrictionsWithCounters restrictions;
291 std::tie(std::ignore, restrictions) = FindMostLimitedResources();
292 UpdateResourceLimitations(reason_resource, restrictions.restrictions,
293 restrictions.counters);
294 }
295 if (adaptation.status() != Adaptation::Status::kValid) {
296 rtc::StringBuilder message;
297 message << "Not adapting down because VideoStreamAdapter returned "
298 << Adaptation::StatusToString(adaptation.status());
299 return MitigationResultAndLogMessage(MitigationResult::kRejectedByAdapter,
300 message.Release());
301 }
302 // Apply adaptation.
303 UpdateResourceLimitations(reason_resource, adaptation.restrictions(),
304 adaptation.counters());
305 stream_adapter_->ApplyAdaptation(adaptation, reason_resource);
306 rtc::StringBuilder message;
307 message << "Adapted down successfully. Unfiltered adaptations: "
308 << stream_adapter_->adaptation_counters().ToString();
309 return MitigationResultAndLogMessage(MitigationResult::kAdaptationApplied,
310 message.Release());
311 }
312
313 std::pair<std::vector<rtc::scoped_refptr<Resource>>,
314 VideoStreamAdapter::RestrictionsWithCounters>
FindMostLimitedResources() const315 ResourceAdaptationProcessor::FindMostLimitedResources() const {
316 std::vector<rtc::scoped_refptr<Resource>> most_limited_resources;
317 VideoStreamAdapter::RestrictionsWithCounters most_limited_restrictions{
318 VideoSourceRestrictions(), VideoAdaptationCounters()};
319
320 for (const auto& resource_and_adaptation_limit_ :
321 adaptation_limits_by_resources_) {
322 const auto& restrictions_with_counters =
323 resource_and_adaptation_limit_.second;
324 if (restrictions_with_counters.counters.Total() >
325 most_limited_restrictions.counters.Total()) {
326 most_limited_restrictions = restrictions_with_counters;
327 most_limited_resources.clear();
328 most_limited_resources.push_back(resource_and_adaptation_limit_.first);
329 } else if (most_limited_restrictions.counters ==
330 restrictions_with_counters.counters) {
331 most_limited_resources.push_back(resource_and_adaptation_limit_.first);
332 }
333 }
334 return std::make_pair(std::move(most_limited_resources),
335 most_limited_restrictions);
336 }
337
UpdateResourceLimitations(rtc::scoped_refptr<Resource> reason_resource,const VideoSourceRestrictions & restrictions,const VideoAdaptationCounters & counters)338 void ResourceAdaptationProcessor::UpdateResourceLimitations(
339 rtc::scoped_refptr<Resource> reason_resource,
340 const VideoSourceRestrictions& restrictions,
341 const VideoAdaptationCounters& counters) {
342 auto& adaptation_limits = adaptation_limits_by_resources_[reason_resource];
343 if (adaptation_limits.restrictions == restrictions &&
344 adaptation_limits.counters == counters) {
345 return;
346 }
347 adaptation_limits = {restrictions, counters};
348
349 std::map<rtc::scoped_refptr<Resource>, VideoAdaptationCounters> limitations;
350 for (const auto& p : adaptation_limits_by_resources_) {
351 limitations.insert(std::make_pair(p.first, p.second.counters));
352 }
353 for (auto limitations_listener : resource_limitations_listeners_) {
354 limitations_listener->OnResourceLimitationChanged(reason_resource,
355 limitations);
356 }
357 }
358
OnVideoSourceRestrictionsUpdated(VideoSourceRestrictions restrictions,const VideoAdaptationCounters & adaptation_counters,rtc::scoped_refptr<Resource> reason,const VideoSourceRestrictions & unfiltered_restrictions)359 void ResourceAdaptationProcessor::OnVideoSourceRestrictionsUpdated(
360 VideoSourceRestrictions restrictions,
361 const VideoAdaptationCounters& adaptation_counters,
362 rtc::scoped_refptr<Resource> reason,
363 const VideoSourceRestrictions& unfiltered_restrictions) {
364 RTC_DCHECK_RUN_ON(task_queue_);
365 if (reason) {
366 UpdateResourceLimitations(reason, unfiltered_restrictions,
367 adaptation_counters);
368 } else if (adaptation_counters.Total() == 0) {
369 // Adaptations are cleared.
370 adaptation_limits_by_resources_.clear();
371 previous_mitigation_results_.clear();
372 for (auto limitations_listener : resource_limitations_listeners_) {
373 limitations_listener->OnResourceLimitationChanged(nullptr, {});
374 }
375 }
376 }
377
378 } // namespace webrtc
379