summaryrefslogtreecommitdiffstats
path: root/content/renderer/media/media_stream_video_capturer_source.cc
blob: ea2fb04af8da96c8b2105bdff99eae860a8a92bd (plain)
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
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
// Copyright 2014 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 "content/renderer/media/media_stream_video_capturer_source.h"

#include <utility>

#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "content/public/common/media_stream_request.h"
#include "content/renderer/media/media_stream_constraints_util.h"
#include "content/renderer/media/video_capture_impl_manager.h"
#include "content/renderer/render_thread_impl.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/limits.h"
#include "media/base/video_capturer_source.h"
#include "media/base/video_frame.h"

namespace content {

namespace {

// Resolutions used if the source doesn't support capability enumeration.
struct {
  int width;
  int height;
} const kVideoResolutions[] = {{1920, 1080},
                               {1280, 720},
                               {960, 720},
                               {640, 480},
                               {640, 360},
                               {320, 240},
                               {320, 180}};

// Frame rates for sources with no support for capability enumeration.
const int kVideoFrameRates[] = {30, 60};

// Hard upper-bound frame rate for tab/desktop capture.
const double kMaxScreenCastFrameRate = 120.0;

// Allows the user to Override default power line frequency.
const char kPowerLineFrequency[] = "googPowerLineFrequency";

// Returns true if the value for width or height is reasonable.
bool DimensionValueIsValid(int x) {
  return x > 0 && x <= media::limits::kMaxDimension;
}

// Returns true if the value for frame rate is reasonable.
bool FrameRateValueIsValid(double frame_rate) {
  return (frame_rate > (1.0 / 60.0)) &&  // Lower-bound: One frame per minute.
      (frame_rate <= media::limits::kMaxFramesPerSecond);
}

// Returns true if the aspect ratio of |a| and |b| are equivalent to two
// significant digits.
bool AreNearlyEquivalentInAspectRatio(const gfx::Size& a, const gfx::Size& b) {
  DCHECK(!a.IsEmpty());
  DCHECK(!b.IsEmpty());
  const int aspect_ratio_a = (100 * a.width()) / a.height();
  const int aspect_ratio_b = (100 * b.width()) / b.height();
  return aspect_ratio_a == aspect_ratio_b;
}

// Checks if |device_info|s type is a generated content, e.g. Tab or Desktop.
bool IsContentVideoCaptureDevice(const StreamDeviceInfo& device_info) {
  return device_info.device.type == MEDIA_TAB_VIDEO_CAPTURE ||
         device_info.device.type == MEDIA_DESKTOP_VIDEO_CAPTURE;
}

// Interprets the properties in |constraints| to override values in |params| and
// determine the resolution change policy.
void SetContentCaptureParamsFromConstraints(
    const blink::WebMediaConstraints& constraints,
    MediaStreamType type,
    media::VideoCaptureParams* params) {
  // The default resolution change policies for tab versus desktop capture are
  // the way they are for legacy reasons.
  if (type == MEDIA_TAB_VIDEO_CAPTURE) {
    params->resolution_change_policy =
        media::RESOLUTION_POLICY_FIXED_RESOLUTION;
  } else if (type == MEDIA_DESKTOP_VIDEO_CAPTURE) {
    params->resolution_change_policy =
        media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT;
  } else {
    NOTREACHED();
  }

  // If the maximum frame resolution was provided in the constraints, use it if
  // either: 1) none has been set yet; or 2) the maximum specificed is smaller
  // than the current setting.
  int width = 0;
  int height = 0;
  gfx::Size desired_max_frame_size;
  if (GetConstraintMaxAsInteger(
          constraints, &blink::WebMediaTrackConstraintSet::width, &width) &&
      GetConstraintMaxAsInteger(
          constraints, &blink::WebMediaTrackConstraintSet::height, &height) &&
      DimensionValueIsValid(width) && DimensionValueIsValid(height)) {
    desired_max_frame_size.SetSize(width, height);
    if (params->requested_format.frame_size.IsEmpty() ||
        desired_max_frame_size.width() <
            params->requested_format.frame_size.width() ||
        desired_max_frame_size.height() <
            params->requested_format.frame_size.height()) {
      params->requested_format.frame_size = desired_max_frame_size;
    }
  }

  // Set the default frame resolution if none was provided.
  if (params->requested_format.frame_size.IsEmpty()) {
    params->requested_format.frame_size.SetSize(
        MediaStreamVideoSource::kDefaultWidth,
        MediaStreamVideoSource::kDefaultHeight);
  }

  // If the maximum frame rate was provided, use it if either: 1) none has been
  // set yet; or 2) the maximum specificed is smaller than the current setting.
  double frame_rate = 0.0;
  if (GetConstraintMaxAsDouble(constraints,
                               &blink::WebMediaTrackConstraintSet::frameRate,
                               &frame_rate) &&
      FrameRateValueIsValid(frame_rate)) {
    if (params->requested_format.frame_rate <= 0.0f ||
        frame_rate < params->requested_format.frame_rate) {
      params->requested_format.frame_rate = frame_rate;
    }
  }

  // Set the default frame rate if none was provided.
  if (params->requested_format.frame_rate <= 0.0f) {
    params->requested_format.frame_rate =
        MediaStreamVideoSource::kDefaultFrameRate;
  }

  // If the minimum frame resolution was provided, compare it to the maximum
  // frame resolution to determine the intended resolution change policy.
  if (!desired_max_frame_size.IsEmpty() &&
      GetConstraintMinAsInteger(
          constraints, &blink::WebMediaTrackConstraintSet::width, &width) &&
      GetConstraintMinAsInteger(
          constraints, &blink::WebMediaTrackConstraintSet::height, &height) &&
      width <= desired_max_frame_size.width() &&
      height <= desired_max_frame_size.height()) {
    if (width == desired_max_frame_size.width() &&
        height == desired_max_frame_size.height()) {
      // Constraints explicitly require a single frame resolution.
      params->resolution_change_policy =
          media::RESOLUTION_POLICY_FIXED_RESOLUTION;
    } else if (DimensionValueIsValid(width) &&
               DimensionValueIsValid(height) &&
               AreNearlyEquivalentInAspectRatio(gfx::Size(width, height),
                                                desired_max_frame_size)) {
      // Constraints only mention a single aspect ratio.
      params->resolution_change_policy =
          media::RESOLUTION_POLICY_FIXED_ASPECT_RATIO;
    } else {
      // Constraints specify a minimum resolution that is smaller than the
      // maximum resolution and has a different aspect ratio (possibly even
      // 0x0). This indicates any frame resolution and aspect ratio is
      // acceptable.
      params->resolution_change_policy =
          media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT;
    }
  }

  DVLOG(1) << __FUNCTION__ << " "
           << media::VideoCaptureFormat::ToString(params->requested_format)
           << " with resolution change policy "
           << params->resolution_change_policy;
}

// Interprets the properties in |constraints| to override values in |params| and
// determine the power line frequency.
void SetPowerLineFrequencyParamFromConstraints(
    const blink::WebMediaConstraints& constraints,
    media::VideoCaptureParams* params) {
  int freq;
  params->power_line_frequency = media::PowerLineFrequency::FREQUENCY_DEFAULT;
  if (!GetConstraintValueAsInteger(
          constraints,
          &blink::WebMediaTrackConstraintSet::googPowerLineFrequency, &freq)) {
    return;
  }
  if (freq == static_cast<int>(media::PowerLineFrequency::FREQUENCY_50HZ))
    params->power_line_frequency = media::PowerLineFrequency::FREQUENCY_50HZ;
  else if (freq == static_cast<int>(media::PowerLineFrequency::FREQUENCY_60HZ))
    params->power_line_frequency = media::PowerLineFrequency::FREQUENCY_60HZ;
}

// LocalVideoCapturerSource is a delegate used by MediaStreamVideoCapturerSource
// for local video capture. It uses the Render singleton VideoCaptureImplManager
// to start / stop and receive I420 frames from Chrome's video capture
// implementation. This is a main Render thread only object.
class LocalVideoCapturerSource final : public media::VideoCapturerSource {
 public:
  explicit LocalVideoCapturerSource(const StreamDeviceInfo& device_info);
  ~LocalVideoCapturerSource() override;

  // VideoCaptureDelegate Implementation.
  void GetCurrentSupportedFormats(
      int max_requested_width,
      int max_requested_height,
      double max_requested_frame_rate,
      const VideoCaptureDeviceFormatsCB& callback) override;
  void StartCapture(const media::VideoCaptureParams& params,
                    const VideoCaptureDeliverFrameCB& new_frame_callback,
                    const RunningCallback& running_callback) override;
  void StopCapture() override;

 private:
  void OnStateUpdate(VideoCaptureState state);
  void OnDeviceFormatsInUseReceived(const media::VideoCaptureFormats& formats);
  void OnDeviceSupportedFormatsEnumerated(
      const media::VideoCaptureFormats& formats);

  // |session_id_| identifies the capture device used for this capture session.
  const media::VideoCaptureSessionId session_id_;

  VideoCaptureImplManager* const manager_;

  const base::Closure release_device_cb_;

  // Indicates if we are capturing generated content, e.g. Tab or Desktop.
  const bool is_content_capture_;

  // These two are valid between StartCapture() and StopCapture().
  base::Closure stop_capture_cb_;
  RunningCallback running_callback_;

  // Placeholder keeping the callback between asynchronous device enumeration
  // calls.
  VideoCaptureDeviceFormatsCB formats_enumerated_callback_;

  // Bound to the main render thread.
  base::ThreadChecker thread_checker_;

  base::WeakPtrFactory<LocalVideoCapturerSource> weak_factory_;

  DISALLOW_COPY_AND_ASSIGN(LocalVideoCapturerSource);
};

}  // namespace

LocalVideoCapturerSource::LocalVideoCapturerSource(
    const StreamDeviceInfo& device_info)
    : session_id_(device_info.session_id),
      manager_(RenderThreadImpl::current()->video_capture_impl_manager()),
      release_device_cb_(manager_->UseDevice(session_id_)),
      is_content_capture_(IsContentVideoCaptureDevice(device_info)),
      weak_factory_(this) {
  DCHECK(RenderThreadImpl::current());
}

LocalVideoCapturerSource::~LocalVideoCapturerSource() {
  DCHECK(thread_checker_.CalledOnValidThread());
  release_device_cb_.Run();
}

void LocalVideoCapturerSource::GetCurrentSupportedFormats(
    int max_requested_width,
    int max_requested_height,
    double max_requested_frame_rate,
    const VideoCaptureDeviceFormatsCB& callback) {
  DVLOG(3) << "GetCurrentSupportedFormats({ max_requested_height = "
           << max_requested_height << "}) { max_requested_width = "
           << max_requested_width << "}) { max_requested_frame_rate = "
           << max_requested_frame_rate << "})";
  DCHECK(thread_checker_.CalledOnValidThread());

  if (is_content_capture_) {
    const int width = max_requested_width ?
        max_requested_width : MediaStreamVideoSource::kDefaultWidth;
    const int height = max_requested_height ?
        max_requested_height : MediaStreamVideoSource::kDefaultHeight;
    callback.Run(media::VideoCaptureFormats(
        1, media::VideoCaptureFormat(
               gfx::Size(width, height),
               static_cast<float>(
                   std::min(kMaxScreenCastFrameRate, max_requested_frame_rate)),
               media::PIXEL_FORMAT_I420)));
    return;
  }

  DCHECK(formats_enumerated_callback_.is_null());
  formats_enumerated_callback_ = callback;
  manager_->GetDeviceFormatsInUse(
      session_id_, media::BindToCurrentLoop(base::Bind(
                       &LocalVideoCapturerSource::OnDeviceFormatsInUseReceived,
                       weak_factory_.GetWeakPtr())));
}

void LocalVideoCapturerSource::StartCapture(
    const media::VideoCaptureParams& params,
    const VideoCaptureDeliverFrameCB& new_frame_callback,
    const RunningCallback& running_callback) {
  DCHECK(params.requested_format.IsValid());
  DCHECK(thread_checker_.CalledOnValidThread());
  running_callback_ = running_callback;

  stop_capture_cb_ = manager_->StartCapture(
      session_id_, params, media::BindToCurrentLoop(base::Bind(
                               &LocalVideoCapturerSource::OnStateUpdate,
                               weak_factory_.GetWeakPtr())),
      new_frame_callback);
}

void LocalVideoCapturerSource::StopCapture() {
  DVLOG(3) << __FUNCTION__;
  DCHECK(thread_checker_.CalledOnValidThread());
  // Immediately make sure we don't provide more frames.
  if (!stop_capture_cb_.is_null())
    base::ResetAndReturn(&stop_capture_cb_).Run();
  running_callback_.Reset();
  // Invalidate any potential format enumerations going on.
  formats_enumerated_callback_.Reset();
}

void LocalVideoCapturerSource::OnStateUpdate(VideoCaptureState state) {
  DVLOG(3) << __FUNCTION__ << " state = " << state;
  DCHECK(thread_checker_.CalledOnValidThread());
  if (running_callback_.is_null())
    return;
  const bool is_started_ok = state == VIDEO_CAPTURE_STATE_STARTED;
  running_callback_.Run(is_started_ok);
  if (!is_started_ok)
    running_callback_.Reset();
}

void LocalVideoCapturerSource::OnDeviceFormatsInUseReceived(
    const media::VideoCaptureFormats& formats_in_use) {
  DVLOG(3) << __FUNCTION__ << ", #formats received: " << formats_in_use.size();
  DCHECK(thread_checker_.CalledOnValidThread());
  // StopCapture() might have destroyed |formats_enumerated_callback_| before
  // arriving here.
  if (formats_enumerated_callback_.is_null())
    return;
  if (formats_in_use.size()) {
    base::ResetAndReturn(&formats_enumerated_callback_).Run(formats_in_use);
    return;
  }

  // The device doesn't seem to have formats in use so try and retrieve the
  // whole list of supported ones.
  manager_->GetDeviceSupportedFormats(
      session_id_,
      media::BindToCurrentLoop(
          base::Bind(
              &LocalVideoCapturerSource::OnDeviceSupportedFormatsEnumerated,
              weak_factory_.GetWeakPtr())));
}

void LocalVideoCapturerSource::OnDeviceSupportedFormatsEnumerated(
    const media::VideoCaptureFormats& formats) {
  DVLOG(3) << __FUNCTION__ << ", #formats received: " << formats.size();
  DCHECK(thread_checker_.CalledOnValidThread());
  // StopCapture() might have destroyed |formats_enumerated_callback_| before
  // arriving here.
  if (formats_enumerated_callback_.is_null())
    return;
  if (formats.size()) {
    base::ResetAndReturn(&formats_enumerated_callback_).Run(formats);
    return;
  }

  // The capture device doesn't seem to support capability enumeration, compose
  // a fallback list of capabilities.
  media::VideoCaptureFormats default_formats;
  for (const auto& resolution : kVideoResolutions) {
    for (const auto frame_rate : kVideoFrameRates) {
      default_formats.push_back(media::VideoCaptureFormat(
          gfx::Size(resolution.width, resolution.height), frame_rate,
          media::PIXEL_FORMAT_I420));
    }
  }
  base::ResetAndReturn(&formats_enumerated_callback_).Run(default_formats);
}

MediaStreamVideoCapturerSource::MediaStreamVideoCapturerSource(
    const SourceStoppedCallback& stop_callback,
    scoped_ptr<media::VideoCapturerSource> source)
    : source_(std::move(source)) {
  SetStopCallback(stop_callback);
}

MediaStreamVideoCapturerSource::MediaStreamVideoCapturerSource(
    const SourceStoppedCallback& stop_callback,
    const StreamDeviceInfo& device_info)
    : source_(new LocalVideoCapturerSource(device_info)) {
  SetStopCallback(stop_callback);
  SetDeviceInfo(device_info);
}

MediaStreamVideoCapturerSource::~MediaStreamVideoCapturerSource() {
}

void MediaStreamVideoCapturerSource::GetCurrentSupportedFormats(
    int max_requested_width,
    int max_requested_height,
    double max_requested_frame_rate,
    const VideoCaptureDeviceFormatsCB& callback) {
  source_->GetCurrentSupportedFormats(
      max_requested_width,
      max_requested_height,
      max_requested_frame_rate,
      callback);
}

void MediaStreamVideoCapturerSource::StartSourceImpl(
    const media::VideoCaptureFormat& format,
    const blink::WebMediaConstraints& constraints,
    const VideoCaptureDeliverFrameCB& frame_callback) {
  media::VideoCaptureParams new_params;
  new_params.requested_format = format;
  if (IsContentVideoCaptureDevice(device_info())) {
    SetContentCaptureParamsFromConstraints(
        constraints, device_info().device.type, &new_params);
  } else if (device_info().device.type == MEDIA_DEVICE_VIDEO_CAPTURE) {
    SetPowerLineFrequencyParamFromConstraints(constraints, &new_params);
  }

  source_->StartCapture(new_params,
                          frame_callback,
                          base::Bind(&MediaStreamVideoCapturerSource::OnStarted,
                                     base::Unretained(this)));
}

void MediaStreamVideoCapturerSource::StopSourceImpl() {
  source_->StopCapture();
}

void MediaStreamVideoCapturerSource::OnStarted(bool result) {
  OnStartDone(result ? MEDIA_DEVICE_OK : MEDIA_DEVICE_TRACK_START_FAILURE);
}

const char*
MediaStreamVideoCapturerSource::GetPowerLineFrequencyForTesting() const {
  return kPowerLineFrequency;
}

}  // namespace content