File: webrtc_frame_scheduler_simple.cc

package info (click to toggle)
chromium-browser 57.0.2987.98-1~deb8u1
  • links: PTS, VCS
  • area: main
  • in suites: jessie
  • size: 2,637,852 kB
  • ctags: 2,544,394
  • sloc: cpp: 12,815,961; ansic: 3,676,222; python: 1,147,112; asm: 526,608; java: 523,212; xml: 286,794; perl: 92,654; sh: 86,408; objc: 73,271; makefile: 27,698; cs: 18,487; yacc: 13,031; tcl: 12,957; pascal: 4,875; ml: 4,716; lex: 3,904; sql: 3,862; ruby: 1,982; lisp: 1,508; php: 1,368; exp: 404; awk: 325; csh: 117; jsp: 39; sed: 37
file content (305 lines) | stat: -rw-r--r-- 10,775 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "remoting/protocol/webrtc_frame_scheduler_simple.h"

#include <algorithm>

#include "remoting/protocol/frame_stats.h"
#include "remoting/protocol/webrtc_dummy_video_encoder.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"

namespace remoting {
namespace protocol {

namespace {

// Number of samples used to estimate processing time for the next frame.
const int kStatsWindow = 5;

const int kTargetFrameRate = 30;
constexpr base::TimeDelta kTargetFrameInterval =
    base::TimeDelta::FromMilliseconds(1000 / kTargetFrameRate);

// Target quantizer at which stop the encoding top-off.
const int kTargetQuantizerForVp8TopOff = 30;

const int64_t kPixelsPerMegapixel = 1000000;

// Minimum target bitrate per megapixel. The value is chosen experimentally such
// that when screen is not changing the codec converges to the target quantizer
// above in less than 10 frames.
const int kVp8MinimumTargetBitrateKbpsPerMegapixel = 2500;

// Threshold in number of updated pixels used to detect "big" frames. These
// frames update significant portion of the screen compared to the preceding
// frames. For these frames min quantizer may need to be adjusted in order to
// ensure that they get delivered to the client as soon as possible, in exchange
// for lower-quality image.
const int kBigFrameThresholdPixels = 300000;

// Estimated size (in bytes per megapixel) of encoded frame at target quantizer
// value (see kTargetQuantizerForVp8TopOff). Compression ratio varies depending
// on the image, so this is just a rough estimate. It's used to predict when
// encoded "big" frame may be too large to be delivered to the client quickly.
const int kEstimatedBytesPerMegapixel = 100000;

// Interval over which the bandwidth estimates is averaged to set target encoder
// bitrate.
constexpr base::TimeDelta kBandwidthAveragingInterval =
    base::TimeDelta::FromSeconds(1);

// Only update encoder bitrate when bandwidth changes by more than 33%. This
// value is chosen such that the codec is notified about significant changes in
// bandwidth, while ignoring bandwidth estimate noise. This is necessary because
// the encoder drops quality every time it's being reconfigured. When using VP8
// encoder in realtime mode encoded frame size correlates very poorly with the
// target bitrate, so it's not necessary to set target bitrate to match
// bandwidth exactly. Send bitrate is controlled more precisely by adjusting
// time intervals between frames (i.e. FPS).
const int kEncoderBitrateChangePercentage = 33;

int64_t GetRegionArea(const webrtc::DesktopRegion& region) {
  int64_t result = 0;
  for (webrtc::DesktopRegion::Iterator r(region); !r.IsAtEnd(); r.Advance()) {
    result += r.rect().width() * r.rect().height();
  }
  return result;
}

}  // namespace

WebrtcFrameSchedulerSimple::EncoderBitrateFilter::EncoderBitrateFilter() {}
WebrtcFrameSchedulerSimple::EncoderBitrateFilter::~EncoderBitrateFilter() {}

void WebrtcFrameSchedulerSimple::EncoderBitrateFilter::SetBandwidthEstimate(
    int bandwidth_kbps,
    base::TimeTicks now) {
  while (!bandwidth_samples_.empty() &&
         now - bandwidth_samples_.front().first > kBandwidthAveragingInterval) {
    bandwidth_samples_sum_ -= bandwidth_samples_.front().second;
    bandwidth_samples_.pop();
  }

  bandwidth_samples_.push(std::make_pair(now, bandwidth_kbps));
  bandwidth_samples_sum_ += bandwidth_kbps;

  UpdateTargetBitrate();
}

void WebrtcFrameSchedulerSimple::EncoderBitrateFilter::SetFrameSize(
    webrtc::DesktopSize size) {
  // TODO(sergeyu): This logic is applicable only to VP8. Reconsider it for VP9.
  minimum_bitrate_ =
      static_cast<int64_t>(kVp8MinimumTargetBitrateKbpsPerMegapixel) *
      size.width() * size.height() / 1000000LL;

  UpdateTargetBitrate();
}

int WebrtcFrameSchedulerSimple::EncoderBitrateFilter::GetTargetBitrateKbps()
    const {
  DCHECK_GT(current_target_bitrate_, 0);
  return current_target_bitrate_;
}

void WebrtcFrameSchedulerSimple::EncoderBitrateFilter::UpdateTargetBitrate() {
  if (bandwidth_samples_.empty()) {
    return;
  }

  int bandwidth_estimate = bandwidth_samples_sum_ / bandwidth_samples_.size();
  int target_bitrate = std::max(minimum_bitrate_, bandwidth_estimate);

  // Update encoder bitrate only when it changes by more than 30%. This is
  // necessary because the encoder resets internal state when it's reconfigured
  // and this causes visible drop in quality.
  if (current_target_bitrate_ == 0 ||
      std::abs(target_bitrate - current_target_bitrate_) >
          current_target_bitrate_ * kEncoderBitrateChangePercentage / 100) {
    current_target_bitrate_ = target_bitrate;
  }
}

WebrtcFrameSchedulerSimple::WebrtcFrameSchedulerSimple()
    : pacing_bucket_(LeakyBucket::kUnlimitedDepth, 0),
      frame_processing_delay_us_(kStatsWindow),
      updated_region_area_(kStatsWindow),
      weak_factory_(this) {}

WebrtcFrameSchedulerSimple::~WebrtcFrameSchedulerSimple() {}

void WebrtcFrameSchedulerSimple::OnKeyFrameRequested() {
  DCHECK(thread_checker_.CalledOnValidThread());
  key_frame_request_ = true;
  ScheduleNextFrame(base::TimeTicks::Now());
}

void WebrtcFrameSchedulerSimple::OnChannelParameters(int packet_loss,
                                                     base::TimeDelta rtt) {
  DCHECK(thread_checker_.CalledOnValidThread());

  rtt_estimate_ = rtt;
}

void WebrtcFrameSchedulerSimple::OnTargetBitrateChanged(int bandwidth_kbps) {
  DCHECK(thread_checker_.CalledOnValidThread());
  base::TimeTicks now = base::TimeTicks::Now();
  pacing_bucket_.UpdateRate(bandwidth_kbps * 1000 / 8, now);
  encoder_bitrate_.SetBandwidthEstimate(bandwidth_kbps, now);
  ScheduleNextFrame(now);
}

void WebrtcFrameSchedulerSimple::Start(
    WebrtcDummyVideoEncoderFactory* video_encoder_factory,
    const base::Closure& capture_callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  capture_callback_ = capture_callback;
  video_encoder_factory->SetVideoChannelStateObserver(
      weak_factory_.GetWeakPtr());
}

void WebrtcFrameSchedulerSimple::Pause(bool pause) {
  DCHECK(thread_checker_.CalledOnValidThread());

  paused_ = pause;
  if (paused_) {
    capture_timer_.Stop();
  } else {
    ScheduleNextFrame(base::TimeTicks::Now());
  }
}

bool WebrtcFrameSchedulerSimple::OnFrameCaptured(
    const webrtc::DesktopFrame* frame,
    WebrtcVideoEncoder::FrameParams* params_out) {
  DCHECK(thread_checker_.CalledOnValidThread());

  base::TimeTicks now = base::TimeTicks::Now();

  if ((!frame || frame->updated_region().is_empty()) && !top_off_is_active_ &&
      !key_frame_request_) {
    frame_pending_ = false;
    ScheduleNextFrame(now);
    return false;
  }

  if (frame) {
    encoder_bitrate_.SetFrameSize(frame->size());
  }

  params_out->bitrate_kbps = encoder_bitrate_.GetTargetBitrateKbps();
  params_out->duration = kTargetFrameInterval;
  params_out->key_frame = key_frame_request_;
  key_frame_request_ = false;

  params_out->vpx_min_quantizer = 10;

  int64_t updated_area = 0;
  if (frame) {
    updated_area = params_out->key_frame
                       ? frame->size().width() * frame->size().height()
                       : GetRegionArea(frame->updated_region());
  }

  // If bandwidth is being underutilized then libvpx is likely to choose the
  // minimum allowed quantizer value, which means that encoded frame size may
  // be significantly bigger than the bandwidth allows. Detect this case and set
  // vpx_min_quantizer to 60. The quality will be topped off later.
  if (updated_area - updated_region_area_.Max() > kBigFrameThresholdPixels) {
    int expected_frame_size =
        updated_area * kEstimatedBytesPerMegapixel / kPixelsPerMegapixel;
    base::TimeDelta expected_send_delay = base::TimeDelta::FromMicroseconds(
        base::Time::kMicrosecondsPerSecond * expected_frame_size /
        pacing_bucket_.rate());
    if (expected_send_delay > kTargetFrameInterval) {
      params_out->vpx_min_quantizer = 60;
    }
  }

  updated_region_area_.Record(updated_area);

  params_out->vpx_max_quantizer = 63;

  params_out->clear_active_map = !top_off_is_active_;

  return true;
}

void WebrtcFrameSchedulerSimple::OnFrameEncoded(
    const WebrtcVideoEncoder::EncodedFrame* encoded_frame,
    HostFrameStats* frame_stats) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(frame_pending_);
  frame_pending_ = false;

  base::TimeTicks now = base::TimeTicks::Now();

  if (frame_stats) {
    // Calculate |send_pending_delay| before refilling |pacing_bucket_|.
    frame_stats->send_pending_delay =
        std::max(base::TimeDelta(), pacing_bucket_.GetEmptyTime() - now);
  }

  if (!encoded_frame || encoded_frame->data.empty()) {
    top_off_is_active_ = false;
  } else {
    pacing_bucket_.RefillOrSpill(encoded_frame->data.size(), now);

    frame_processing_delay_us_.Record(
        (now - last_capture_started_time_).InMicroseconds());

    // Top-off until the target quantizer value is reached.
    top_off_is_active_ =
        encoded_frame->quantizer > kTargetQuantizerForVp8TopOff;
  }

  ScheduleNextFrame(now);

  if (frame_stats) {
    frame_stats->rtt_estimate = rtt_estimate_;
    frame_stats->bandwidth_estimate_kbps = pacing_bucket_.rate() * 8 / 1000;
  }
}

void WebrtcFrameSchedulerSimple::ScheduleNextFrame(base::TimeTicks now) {
  DCHECK(thread_checker_.CalledOnValidThread());

  if (paused_ || pacing_bucket_.rate() == 0 || capture_callback_.is_null() ||
      frame_pending_) {
    return;
  }

  // If this is not the first frame then capture next frame after the previous
  // one has finished sending.
  base::TimeDelta expected_processing_time =
      base::TimeDelta::FromMicroseconds(frame_processing_delay_us_.Max());
  base::TimeTicks target_capture_time =
      pacing_bucket_.GetEmptyTime() - expected_processing_time;

  // Cap interval between frames to kTargetFrameInterval.
  if (!last_capture_started_time_.is_null()) {
    target_capture_time = std::max(
        target_capture_time, last_capture_started_time_ + kTargetFrameInterval);
  }

  if (target_capture_time < now) {
    target_capture_time = now;
  }

  capture_timer_.Start(FROM_HERE, target_capture_time - now,
                       base::Bind(&WebrtcFrameSchedulerSimple::CaptureNextFrame,
                                  base::Unretained(this)));
}

void WebrtcFrameSchedulerSimple::CaptureNextFrame() {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(!frame_pending_);
  last_capture_started_time_ = base::TimeTicks::Now();
  frame_pending_ = true;
  capture_callback_.Run();
}

}  // namespace protocol
}  // namespace remoting