• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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