1 // libjingle
2 // Copyright 2010 Google Inc.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are met:
6 //
7 //  1. Redistributions of source code must retain the above copyright notice,
8 //     this list of conditions and the following disclaimer.
9 //  2. Redistributions in binary form must reproduce the above copyright notice,
10 //     this list of conditions and the following disclaimer in the documentation
11 //     and/or other materials provided with the distribution.
12 //  3. The name of the author may not be used to endorse or promote products
13 //     derived from this software without specific prior written permission.
14 //
15 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
16 // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
17 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
18 // EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21 // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 
26 #include "talk/session/phone/videoadapter.h"
27 
28 #include <limits.h>
29 
30 #include "talk/base/logging.h"
31 #include "talk/base/timeutils.h"
32 #include "talk/session/phone/videoframe.h"
33 
34 namespace cricket {
35 
36 // TODO: Make downgrades settable
37 static const int kMaxCpuDowngrades = 2;  // Downgrade at most 2 times for CPU.
38 static const int kDefaultDowngradeWaitTimeMs = 2000;
39 
40 // Cpu system load thresholds relative to max cpus.
41 static const float kHighSystemThreshold = 0.95f;
42 static const float kLowSystemThreshold = 0.75f;
43 
44 // Cpu process load thresholds relative to current cpus.
45 static const float kMediumProcessThreshold = 0.50f;
46 
47 // TODO: Consider making scale factor table settable, to allow
48 // application to select quality vs performance tradeoff.
49 // List of scale factors that adapter will scale by.
50 #if defined(IOS) || defined(ANDROID)
51 // Mobile needs 1/4 scale for VGA (640x360) to QQVGA (160x90)
52 // or 1/4 scale for HVGA (480x270) to QQHVGA (120x67)
53 static const int kMinNumPixels = 120 * 67;
54 static float kScaleFactors[] = {
55   1.f, // full size
56   3.f/4.f, // 3/4 scale
57   1.f/2.f, // 1/2 scale
58   3.f/8.f, // 3/8 scale
59   1.f/4.f, // 1/4 scale
60 };
61 #else
62 // PC needs 1/8 scale for HD (1280x720) to QQVGA (160x90)
63 static const int kMinNumPixels = 160 * 100;
64 static float kScaleFactors[] = {
65   1.f, // full size
66   3.f/4.f, // 3/4 scale
67   1.f/2.f, // 1/2 scale
68   3.f/8.f, // 3/8 scale
69   1.f/4.f, // 1/4 scale
70   3.f/16.f, // 3/16 scale
71   1.f/8.f // 1/8 scale
72 };
73 #endif
74 
75 // Find scale factor that applied to width and height, is best match
76 // to num_pixels.
FindClosestScale(int width,int height,int target_num_pixels)77 float VideoAdapter::FindClosestScale(int width, int height,
78                                      int target_num_pixels) {
79   if (!target_num_pixels) {
80     return 0.f;
81   }
82   int best_distance = INT_MAX;
83   int best_index = 0;  // default to unscaled
84   for (size_t i = 0u; i < ARRAY_SIZE(kScaleFactors); ++i) {
85     int test_num_pixels = static_cast<int>(width * kScaleFactors[i] *
86                                            height * kScaleFactors[i]);
87     int diff = test_num_pixels - target_num_pixels;
88     if (diff < 0) {
89       diff = -diff;
90     }
91     if (diff < best_distance) {
92       best_distance = diff;
93       best_index = i;
94       if (!best_distance) { // Found exact match
95         break;
96       }
97     }
98   }
99   return kScaleFactors[best_index];
100 }
101 
102 // There are several frame sizes used by Adapter.  This explains them
103 // input_format - set once by server to frame size expected from the camera.
104 // output_format - size that output would like to be.  Includes framerate.
105 // output_num_pixels - size that output should be constrained to.  Used to
106 //   compute output_format from in_frame.
107 // in_frame - actual camera captured frame size, which is typically the same
108 //   as input_format.  This can also be rotated or cropped for aspect ratio.
109 // out_frame - actual frame output by adapter.  Should be a direct scale of
110 //   in_frame maintaining rotation and aspect ratio.
111 // OnOutputFormatRequest - server requests you send this resolution based on
112 //   view requests.
113 // OnEncoderResolutionRequest - encoder requests you send this resolution based
114 //   on bandwidth
115 // OnCpuLoadUpdated - cpu monitor requests you send this resolution based on
116 //   cpu load.
117 
118 ///////////////////////////////////////////////////////////////////////
119 // Implementation of VideoAdapter
VideoAdapter()120 VideoAdapter::VideoAdapter()
121     : output_num_pixels_(0),
122       black_output_(false),
123       is_black_(false),
124       drop_frame_count_(0) {
125 }
126 
~VideoAdapter()127 VideoAdapter::~VideoAdapter() {
128 }
129 
130 // TODO: Consider SetInputFormat and SetOutputFormat without
131 // VideoFormat.
SetInputFormat(const VideoFormat & format)132 void VideoAdapter::SetInputFormat(const VideoFormat& format) {
133   talk_base::CritScope cs(&critical_section_);
134   input_format_ = format;
135   output_format_.interval = talk_base::_max(
136       output_format_.interval, input_format_.interval);
137 }
138 
SetOutputFormat(const VideoFormat & format)139 void VideoAdapter::SetOutputFormat(const VideoFormat& format) {
140   talk_base::CritScope cs(&critical_section_);
141   output_format_ = format;
142   output_num_pixels_ = output_format_.width * output_format_.height;
143   output_format_.interval = talk_base::_max(
144       output_format_.interval, input_format_.interval);
145   drop_frame_count_ = 0;
146 }
147 
input_format()148 const VideoFormat& VideoAdapter::input_format() {
149   talk_base::CritScope cs(&critical_section_);
150   return input_format_;
151 }
152 
output_format()153 const VideoFormat& VideoAdapter::output_format() {
154   talk_base::CritScope cs(&critical_section_);
155   return output_format_;
156 }
157 
SetBlackOutput(bool black)158 void VideoAdapter::SetBlackOutput(bool black) {
159   talk_base::CritScope cs(&critical_section_);
160   black_output_ = black;
161 }
162 
163 // Constrain output resolution to this many pixels overall
SetOutputNumPixels(int num_pixels)164 void VideoAdapter::SetOutputNumPixels(int num_pixels) {
165   output_num_pixels_ = num_pixels;
166 }
167 
GetOutputNumPixels() const168 int VideoAdapter::GetOutputNumPixels() const {
169   return output_num_pixels_;
170 }
171 
AdaptFrame(const VideoFrame * in_frame,const VideoFrame ** out_frame)172 bool VideoAdapter::AdaptFrame(const VideoFrame* in_frame,
173                               const VideoFrame** out_frame) {
174   talk_base::CritScope cs(&critical_section_);
175 
176   if (!in_frame || !out_frame || input_format_.IsSize0x0()) {
177     return false;
178   }
179 
180   // Drop the input frame if necessary.
181   bool should_drop = false;
182   if (!output_num_pixels_) {
183     // Drop all frames as the output format is 0x0.
184     should_drop = true;
185   } else {
186     // Drop some frames based on the ratio of the input fps and the output fps.
187     // We assume that the output fps is a factor of the input fps. In other
188     // words, the output interval is divided by the input interval evenly.
189     should_drop = (drop_frame_count_ > 0);
190     if (input_format_.interval > 0 &&
191         output_format_.interval > input_format_.interval) {
192       ++drop_frame_count_;
193       drop_frame_count_ %= output_format_.interval / input_format_.interval;
194     }
195   }
196 
197   if (output_num_pixels_) {
198     float scale = VideoAdapter::FindClosestScale(in_frame->GetWidth(),
199                                                  in_frame->GetHeight(),
200                                                  output_num_pixels_);
201     output_format_.width = static_cast<int>(in_frame->GetWidth() * scale);
202     output_format_.height = static_cast<int>(in_frame->GetHeight() * scale);
203   }
204 
205   if (should_drop) {
206     *out_frame = NULL;
207     return true;
208   }
209 
210   if (!StretchToOutputFrame(in_frame)) {
211     return false;
212   }
213 
214   *out_frame = output_frame_.get();
215   return true;
216 }
217 
StretchToOutputFrame(const VideoFrame * in_frame)218 bool VideoAdapter::StretchToOutputFrame(const VideoFrame* in_frame) {
219   int output_width = output_format_.width;
220   int output_height = output_format_.height;
221 
222   // Create and stretch the output frame if it has not been created yet or its
223   // size is not same as the expected.
224   bool stretched = false;
225   if (!output_frame_.get() ||
226       output_frame_->GetWidth() != static_cast<size_t>(output_width) ||
227       output_frame_->GetHeight() != static_cast<size_t>(output_height)) {
228     output_frame_.reset(
229         in_frame->Stretch(output_width, output_height, true, true));
230     if (!output_frame_.get()) {
231       LOG(LS_WARNING) << "Adapter failed to stretch frame to "
232                       << output_width << "x" << output_height;
233       return false;
234     }
235     stretched = true;
236     is_black_ = false;
237   }
238 
239   if (!black_output_) {
240     if (!stretched) {
241       // The output frame does not need to be blacken and has not been stretched
242       // from the input frame yet, stretch the input frame. This is the most
243       // common case.
244       in_frame->StretchToFrame(output_frame_.get(), true, true);
245     }
246     is_black_ = false;
247   } else {
248     if (!is_black_) {
249       output_frame_->SetToBlack();
250       is_black_ = true;
251     }
252     output_frame_->SetElapsedTime(in_frame->GetElapsedTime());
253     output_frame_->SetTimeStamp(in_frame->GetTimeStamp());
254   }
255 
256   return true;
257 }
258 
259 ///////////////////////////////////////////////////////////////////////
260 // Implementation of CoordinatedVideoAdapter
CoordinatedVideoAdapter()261 CoordinatedVideoAdapter::CoordinatedVideoAdapter()
262     : cpu_adaptation_(false),
263       gd_adaptation_(true),
264       view_adaptation_(true),
265       cpu_downgrade_count_(0),
266       cpu_downgrade_wait_time_(0),
267       view_desired_num_pixels_(INT_MAX),
268       view_desired_interval_(0),
269       encoder_desired_num_pixels_(INT_MAX),
270       cpu_desired_num_pixels_(INT_MAX) {
271 }
272 
273 // Helper function to UPGRADE or DOWNGRADE a number of pixels
StepPixelCount(CoordinatedVideoAdapter::AdaptRequest request,int * num_pixels)274 void CoordinatedVideoAdapter::StepPixelCount(
275     CoordinatedVideoAdapter::AdaptRequest request,
276     int* num_pixels) {
277   switch (request) {
278     case CoordinatedVideoAdapter::DOWNGRADE:
279       *num_pixels /= 2;
280       break;
281 
282     case CoordinatedVideoAdapter::UPGRADE:
283       *num_pixels *= 2;
284       break;
285 
286     default:  // No change in pixel count
287       break;
288   }
289   return;
290 }
291 
292 // Find the adaptation request of the cpu based on the load. Return UPGRADE if
293 // the load is low, DOWNGRADE if the load is high, and KEEP otherwise.
FindCpuRequest(int current_cpus,int max_cpus,float process_load,float system_load)294 CoordinatedVideoAdapter::AdaptRequest CoordinatedVideoAdapter::FindCpuRequest(
295     int current_cpus, int max_cpus,
296     float process_load, float system_load) {
297   // Downgrade if system is high and plugin is at least more than midrange.
298   if (system_load >= kHighSystemThreshold * max_cpus &&
299       process_load >= kMediumProcessThreshold * current_cpus) {
300     return CoordinatedVideoAdapter::DOWNGRADE;
301   // Upgrade if system is low.
302   } else if (system_load < kLowSystemThreshold * max_cpus) {
303     return CoordinatedVideoAdapter::UPGRADE;
304   }
305   return CoordinatedVideoAdapter::KEEP;
306 }
307 
308 // A remote view request for a new resolution.
OnOutputFormatRequest(const VideoFormat & format)309 void CoordinatedVideoAdapter::OnOutputFormatRequest(const VideoFormat& format) {
310   talk_base::CritScope cs(&request_critical_section_);
311   if (!view_adaptation_) {
312     return;
313   }
314   // Set output for initial aspect ratio in mediachannel unittests.
315   int old_num_pixels = GetOutputNumPixels();
316   SetOutputFormat(format);
317   SetOutputNumPixels(old_num_pixels);
318   view_desired_num_pixels_ = format.width * format.height;
319   view_desired_interval_ = format.interval;
320   bool changed = AdaptToMinimumFormat();
321   LOG(LS_INFO) << "VAdapt View Request: "
322                << format.width << "x" << format.height
323                << " Pixels: " << view_desired_num_pixels_
324                << " Changed: " << (changed ? "true" : "false");
325 }
326 
327 // A Bandwidth GD request for new resolution
OnEncoderResolutionRequest(int width,int height,AdaptRequest request)328 void CoordinatedVideoAdapter::OnEncoderResolutionRequest(
329     int width, int height, AdaptRequest request) {
330   talk_base::CritScope cs(&request_critical_section_);
331   if (!gd_adaptation_) {
332     return;
333   }
334   if (KEEP != request) {
335     int new_encoder_desired_num_pixels = width * height;
336     int old_num_pixels = GetOutputNumPixels();
337     if (new_encoder_desired_num_pixels != old_num_pixels) {
338       LOG(LS_VERBOSE) << "VAdapt GD resolution stale.  Ignored";
339     } else {
340       // Update the encoder desired format based on the request.
341       encoder_desired_num_pixels_ = new_encoder_desired_num_pixels;
342       StepPixelCount(request, &encoder_desired_num_pixels_);
343     }
344   }
345   bool changed = AdaptToMinimumFormat();
346   LOG(LS_INFO) << "VAdapt GD Request: "
347                << (DOWNGRADE == request ? "down" :
348                    (UPGRADE == request ? "up" : "keep"))
349                << " From: " << width << "x" << height
350                << " Pixels: " << encoder_desired_num_pixels_
351                << " Changed: " << (changed ? "true" : "false");
352 }
353 
354 // A CPU request for new resolution
OnCpuLoadUpdated(int current_cpus,int max_cpus,float process_load,float system_load)355 void CoordinatedVideoAdapter::OnCpuLoadUpdated(
356     int current_cpus, int max_cpus, float process_load, float system_load) {
357   talk_base::CritScope cs(&request_critical_section_);
358   if (!cpu_adaptation_) {
359     return;
360   }
361   AdaptRequest request = FindCpuRequest(current_cpus, max_cpus,
362                                         process_load, system_load);
363   // Update how many times we have downgraded due to the cpu load.
364   switch (request) {
365     case DOWNGRADE:
366       if (cpu_downgrade_count_ < kMaxCpuDowngrades) {
367         // Ignore downgrades if we have downgraded the maximum times or we just
368         // downgraded in a short time.
369         if (cpu_downgrade_wait_time_ != 0 &&
370             talk_base::TimeIsLater(talk_base::Time(),
371                                    cpu_downgrade_wait_time_)) {
372           LOG(LS_VERBOSE) << "VAdapt CPU load high but do not downgrade until "
373                           << talk_base::TimeUntil(cpu_downgrade_wait_time_)
374                           << " ms.";
375           request = KEEP;
376         } else {
377           ++cpu_downgrade_count_;
378         }
379       } else {
380           LOG(LS_VERBOSE) << "VAdapt CPU load high but do not downgrade "
381                              "because maximum downgrades reached";
382       }
383       break;
384     case UPGRADE:
385       if (cpu_downgrade_count_ > 0) {
386         bool is_min = IsMinimumFormat(cpu_desired_num_pixels_);
387         if (is_min) {
388           --cpu_downgrade_count_;
389         } else {
390          LOG(LS_VERBOSE) << "VAdapt CPU load low but do not upgrade "
391                              "because cpu is not limiting resolution";
392         }
393       } else {
394           LOG(LS_VERBOSE) << "VAdapt CPU load low but do not upgrade "
395                              "because minimum downgrades reached";
396       }
397       break;
398     case KEEP:
399     default:
400       break;
401   }
402   if (KEEP != request) {
403     // TODO: compute stepping up/down from OutputNumPixels but
404     // clamp to inputpixels / 4 (2 steps)
405     cpu_desired_num_pixels_ = static_cast<int>(
406         input_format().width * input_format().height >> cpu_downgrade_count_);
407   }
408   bool changed = AdaptToMinimumFormat();
409   LOG(LS_INFO) << "VAdapt CPU Request: "
410                << (DOWNGRADE == request ? "down" :
411                    (UPGRADE == request ? "up" : "keep"))
412                << " Process: " << process_load
413                << " System: " << system_load
414                << " Steps: " << cpu_downgrade_count_
415                << " Changed: " << (changed ? "true" : "false");
416 }
417 
418 // Called by cpu adapter on up requests.
IsMinimumFormat(int pixels)419 bool CoordinatedVideoAdapter::IsMinimumFormat(int pixels) {
420   // Find closest scale factor that matches input resolution to min_num_pixels
421   // and set that for output resolution.  This is not needed for VideoAdapter,
422   // but provides feedback to unittests and users on expected resolution.
423   // Actual resolution is based on input frame.
424   VideoFormat new_output = output_format();
425   VideoFormat input = input_format();
426   if (input_format().IsSize0x0()) {
427     input = new_output;
428   }
429   float scale = 1.0f;
430   if (!input.IsSize0x0()) {
431     scale = FindClosestScale(input.width,
432                              input.height,
433                              pixels);
434   }
435   new_output.width = static_cast<int>(input.width * scale);
436   new_output.height = static_cast<int>(input.height * scale);
437   int new_pixels = new_output.width * new_output.height;
438   int num_pixels = GetOutputNumPixels();
439   return new_pixels <= num_pixels;
440 }
441 
442 // Called by all coordinators when there is a change.
AdaptToMinimumFormat()443 bool CoordinatedVideoAdapter::AdaptToMinimumFormat() {
444   int old_num_pixels = GetOutputNumPixels();
445   // Get the min of the formats that the server, encoder, and cpu wants.
446   int min_num_pixels = view_desired_num_pixels_;
447   if (encoder_desired_num_pixels_ &&
448       (encoder_desired_num_pixels_ < min_num_pixels)) {
449     min_num_pixels = encoder_desired_num_pixels_;
450   }
451   if (cpu_adaptation_ && cpu_desired_num_pixels_ &&
452       (cpu_desired_num_pixels_ < min_num_pixels)) {
453     min_num_pixels = cpu_desired_num_pixels_;
454     // Update the cpu_downgrade_wait_time_ if we are going to downgrade video.
455     cpu_downgrade_wait_time_ =
456       talk_base::TimeAfter(kDefaultDowngradeWaitTimeMs);
457   }
458   // prevent going below QQVGA
459   if (min_num_pixels > 0 && min_num_pixels < kMinNumPixels) {
460     min_num_pixels = kMinNumPixels;
461   }
462   SetOutputNumPixels(min_num_pixels);
463 
464   // Find closest scale factor that matches input resolution to min_num_pixels
465   // and set that for output resolution.  This is not needed for VideoAdapter,
466   // but provides feedback to unittests and users on expected resolution.
467   // Actual resolution is based on input frame.
468   VideoFormat new_output = output_format();
469   VideoFormat input = input_format();
470   if (input_format().IsSize0x0()) {
471     input = new_output;
472   }
473   float scale = 1.0f;
474   if (!input.IsSize0x0()) {
475     scale = FindClosestScale(input.width,
476                              input.height,
477                              min_num_pixels);
478   }
479   new_output.width = static_cast<int>(input.width * scale);
480   new_output.height = static_cast<int>(input.height * scale);
481   new_output.interval = view_desired_interval_;
482   SetOutputFormat(new_output);
483   int new_num_pixels = GetOutputNumPixels();
484   bool changed = new_num_pixels != old_num_pixels;
485 
486   LOG(LS_VERBOSE) << "VAdapt Status View: " << view_desired_num_pixels_
487                   << " GD: " << encoder_desired_num_pixels_
488                   << " CPU: " << cpu_desired_num_pixels_
489                   << " Pixels: " << min_num_pixels
490                   << " Scale: " << scale
491                   << " Resolution: " << new_output.width
492                   << "x" << new_output.height
493                   << " Changed: " << (changed ? "true" : "false");
494   return changed;
495 }
496 
497 }  // namespace cricket
498