~ubuntu-branches/debian/experimental/kopete/experimental

« back to all changes in this revision

Viewing changes to protocols/jabber/libjingle/talk/session/phone/videoadapter.cc

  • Committer: Package Import Robot
  • Author(s): Maximiliano Curia
  • Date: 2015-02-24 11:32:57 UTC
  • mfrom: (1.1.41 vivid)
  • Revision ID: package-import@ubuntu.com-20150224113257-gnupg4v7lzz18ij0
Tags: 4:14.12.2-1
* New upstream release (14.12.2).
* Bump Standards-Version to 3.9.6, no changes needed.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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.
 
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
 
120
VideoAdapter::VideoAdapter()
 
121
    : output_num_pixels_(0),
 
122
      black_output_(false),
 
123
      is_black_(false),
 
124
      drop_frame_count_(0) {
 
125
}
 
126
 
 
127
VideoAdapter::~VideoAdapter() {
 
128
}
 
129
 
 
130
// TODO: Consider SetInputFormat and SetOutputFormat without
 
131
// VideoFormat.
 
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
 
 
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
 
 
148
const VideoFormat& VideoAdapter::input_format() {
 
149
  talk_base::CritScope cs(&critical_section_);
 
150
  return input_format_;
 
151
}
 
152
 
 
153
const VideoFormat& VideoAdapter::output_format() {
 
154
  talk_base::CritScope cs(&critical_section_);
 
155
  return output_format_;
 
156
}
 
157
 
 
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
 
164
void VideoAdapter::SetOutputNumPixels(int num_pixels) {
 
165
  output_num_pixels_ = num_pixels;
 
166
}
 
167
 
 
168
int VideoAdapter::GetOutputNumPixels() const {
 
169
  return output_num_pixels_;
 
170
}
 
171
 
 
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
 
 
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
 
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
 
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.
 
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.
 
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
 
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
 
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.
 
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.
 
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