1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/renderer/media/media_stream_video_capturer_source.h"
8 #include "base/callback_helpers.h"
9 #include "base/location.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "content/public/common/media_stream_request.h"
12 #include "content/renderer/media/media_stream_constraints_util.h"
13 #include "content/renderer/media/video_capture_impl_manager.h"
14 #include "content/renderer/render_thread_impl.h"
15 #include "media/base/bind_to_current_loop.h"
16 #include "media/base/limits.h"
17 #include "media/base/video_capturer_source.h"
18 #include "media/base/video_frame.h"
24 // Resolutions used if the source doesn't support capability enumeration.
28 } const kVideoResolutions
[] = {{1920, 1080},
36 // Frame rates for sources with no support for capability enumeration.
37 const int kVideoFrameRates
[] = {30, 60};
39 // Hard upper-bound frame rate for tab/desktop capture.
40 const double kMaxScreenCastFrameRate
= 120.0;
42 // Allows the user to Override default power line frequency.
43 const char kPowerLineFrequency
[] = "googPowerLineFrequency";
45 // Returns true if the value for width or height is reasonable.
46 bool DimensionValueIsValid(int x
) {
47 return x
> 0 && x
<= media::limits::kMaxDimension
;
50 // Returns true if the value for frame rate is reasonable.
51 bool FrameRateValueIsValid(double frame_rate
) {
52 return (frame_rate
> (1.0 / 60.0)) && // Lower-bound: One frame per minute.
53 (frame_rate
<= media::limits::kMaxFramesPerSecond
);
56 // Returns true if the aspect ratio of |a| and |b| are equivalent to two
57 // significant digits.
58 bool AreNearlyEquivalentInAspectRatio(const gfx::Size
& a
, const gfx::Size
& b
) {
61 const int aspect_ratio_a
= (100 * a
.width()) / a
.height();
62 const int aspect_ratio_b
= (100 * b
.width()) / b
.height();
63 return aspect_ratio_a
== aspect_ratio_b
;
66 // Checks if |device_info|s type is a generated content, e.g. Tab or Desktop.
67 bool IsContentVideoCaptureDevice(const StreamDeviceInfo
& device_info
) {
68 return device_info
.device
.type
== MEDIA_TAB_VIDEO_CAPTURE
||
69 device_info
.device
.type
== MEDIA_DESKTOP_VIDEO_CAPTURE
;
72 // Interprets the properties in |constraints| to override values in |params| and
73 // determine the resolution change policy.
74 void SetContentCaptureParamsFromConstraints(
75 const blink::WebMediaConstraints
& constraints
,
77 media::VideoCaptureParams
* params
) {
78 // The default resolution change policies for tab versus desktop capture are
79 // the way they are for legacy reasons.
80 if (type
== MEDIA_TAB_VIDEO_CAPTURE
) {
81 params
->resolution_change_policy
=
82 media::RESOLUTION_POLICY_FIXED_RESOLUTION
;
83 } else if (type
== MEDIA_DESKTOP_VIDEO_CAPTURE
) {
84 params
->resolution_change_policy
=
85 media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT
;
90 // If the maximum frame resolution was provided in the constraints, use it if
91 // either: 1) none has been set yet; or 2) the maximum specificed is smaller
92 // than the current setting.
95 gfx::Size desired_max_frame_size
;
96 if (GetConstraintValueAsInteger(constraints
,
97 MediaStreamVideoSource::kMaxWidth
,
99 GetConstraintValueAsInteger(constraints
,
100 MediaStreamVideoSource::kMaxHeight
,
102 DimensionValueIsValid(width
) &&
103 DimensionValueIsValid(height
)) {
104 desired_max_frame_size
.SetSize(width
, height
);
105 if (params
->requested_format
.frame_size
.IsEmpty() ||
106 desired_max_frame_size
.width() <
107 params
->requested_format
.frame_size
.width() ||
108 desired_max_frame_size
.height() <
109 params
->requested_format
.frame_size
.height()) {
110 params
->requested_format
.frame_size
= desired_max_frame_size
;
114 // Set the default frame resolution if none was provided.
115 if (params
->requested_format
.frame_size
.IsEmpty()) {
116 params
->requested_format
.frame_size
.SetSize(
117 MediaStreamVideoSource::kDefaultWidth
,
118 MediaStreamVideoSource::kDefaultHeight
);
121 // If the maximum frame rate was provided, use it if either: 1) none has been
122 // set yet; or 2) the maximum specificed is smaller than the current setting.
123 double frame_rate
= 0.0;
124 if (GetConstraintValueAsDouble(constraints
,
125 MediaStreamVideoSource::kMaxFrameRate
,
127 FrameRateValueIsValid(frame_rate
)) {
128 if (params
->requested_format
.frame_rate
<= 0.0f
||
129 frame_rate
< params
->requested_format
.frame_rate
) {
130 params
->requested_format
.frame_rate
= frame_rate
;
134 // Set the default frame rate if none was provided.
135 if (params
->requested_format
.frame_rate
<= 0.0f
) {
136 params
->requested_format
.frame_rate
=
137 MediaStreamVideoSource::kDefaultFrameRate
;
140 // If the minimum frame resolution was provided, compare it to the maximum
141 // frame resolution to determine the intended resolution change policy.
142 if (!desired_max_frame_size
.IsEmpty() &&
143 GetConstraintValueAsInteger(constraints
,
144 MediaStreamVideoSource::kMinWidth
,
146 GetConstraintValueAsInteger(constraints
,
147 MediaStreamVideoSource::kMinHeight
,
149 width
<= desired_max_frame_size
.width() &&
150 height
<= desired_max_frame_size
.height()) {
151 if (width
== desired_max_frame_size
.width() &&
152 height
== desired_max_frame_size
.height()) {
153 // Constraints explicitly require a single frame resolution.
154 params
->resolution_change_policy
=
155 media::RESOLUTION_POLICY_FIXED_RESOLUTION
;
156 } else if (DimensionValueIsValid(width
) &&
157 DimensionValueIsValid(height
) &&
158 AreNearlyEquivalentInAspectRatio(gfx::Size(width
, height
),
159 desired_max_frame_size
)) {
160 // Constraints only mention a single aspect ratio.
161 params
->resolution_change_policy
=
162 media::RESOLUTION_POLICY_FIXED_ASPECT_RATIO
;
164 // Constraints specify a minimum resolution that is smaller than the
165 // maximum resolution and has a different aspect ratio (possibly even
166 // 0x0). This indicates any frame resolution and aspect ratio is
168 params
->resolution_change_policy
=
169 media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT
;
173 DVLOG(1) << __FUNCTION__
<< " "
174 << media::VideoCaptureFormat::ToString(params
->requested_format
)
175 << " with resolution change policy "
176 << params
->resolution_change_policy
;
179 // Interprets the properties in |constraints| to override values in |params| and
180 // determine the power line frequency.
181 void SetPowerLineFrequencyParamFromConstraints(
182 const blink::WebMediaConstraints
& constraints
,
183 media::VideoCaptureParams
* params
) {
185 params
->power_line_frequency
= media::PowerLineFrequency::FREQUENCY_DEFAULT
;
186 if (!GetOptionalConstraintValueAsInteger(constraints
, kPowerLineFrequency
,
190 if (freq
== static_cast<int>(media::PowerLineFrequency::FREQUENCY_50HZ
))
191 params
->power_line_frequency
= media::PowerLineFrequency::FREQUENCY_50HZ
;
192 else if (freq
== static_cast<int>(media::PowerLineFrequency::FREQUENCY_60HZ
))
193 params
->power_line_frequency
= media::PowerLineFrequency::FREQUENCY_60HZ
;
196 // LocalVideoCapturerSource is a delegate used by MediaStreamVideoCapturerSource
197 // for local video capture. It uses the Render singleton VideoCaptureImplManager
198 // to start / stop and receive I420 frames from Chrome's video capture
199 // implementation. This is a main Render thread only object.
200 class LocalVideoCapturerSource final
: public media::VideoCapturerSource
{
202 explicit LocalVideoCapturerSource(const StreamDeviceInfo
& device_info
);
203 ~LocalVideoCapturerSource() override
;
205 // VideoCaptureDelegate Implementation.
206 void GetCurrentSupportedFormats(
207 int max_requested_width
,
208 int max_requested_height
,
209 double max_requested_frame_rate
,
210 const VideoCaptureDeviceFormatsCB
& callback
) override
;
211 void StartCapture(const media::VideoCaptureParams
& params
,
212 const VideoCaptureDeliverFrameCB
& new_frame_callback
,
213 const RunningCallback
& running_callback
) override
;
214 void StopCapture() override
;
217 void OnStateUpdate(VideoCaptureState state
);
218 void OnDeviceFormatsInUseReceived(const media::VideoCaptureFormats
& formats
);
219 void OnDeviceSupportedFormatsEnumerated(
220 const media::VideoCaptureFormats
& formats
);
222 // |session_id_| identifies the capture device used for this capture session.
223 const media::VideoCaptureSessionId session_id_
;
225 VideoCaptureImplManager
* const manager_
;
227 const base::Closure release_device_cb_
;
229 // Indicates if we are capturing generated content, e.g. Tab or Desktop.
230 const bool is_content_capture_
;
232 // These two are valid between StartCapture() and StopCapture().
233 base::Closure stop_capture_cb_
;
234 RunningCallback running_callback_
;
236 // Placeholder keeping the callback between asynchronous device enumeration
238 VideoCaptureDeviceFormatsCB formats_enumerated_callback_
;
240 // Bound to the main render thread.
241 base::ThreadChecker thread_checker_
;
243 base::WeakPtrFactory
<LocalVideoCapturerSource
> weak_factory_
;
245 DISALLOW_COPY_AND_ASSIGN(LocalVideoCapturerSource
);
250 LocalVideoCapturerSource::LocalVideoCapturerSource(
251 const StreamDeviceInfo
& device_info
)
252 : session_id_(device_info
.session_id
),
253 manager_(RenderThreadImpl::current()->video_capture_impl_manager()),
254 release_device_cb_(manager_
->UseDevice(session_id_
)),
255 is_content_capture_(IsContentVideoCaptureDevice(device_info
)),
256 weak_factory_(this) {
257 DCHECK(RenderThreadImpl::current());
260 LocalVideoCapturerSource::~LocalVideoCapturerSource() {
261 DCHECK(thread_checker_
.CalledOnValidThread());
262 release_device_cb_
.Run();
265 void LocalVideoCapturerSource::GetCurrentSupportedFormats(
266 int max_requested_width
,
267 int max_requested_height
,
268 double max_requested_frame_rate
,
269 const VideoCaptureDeviceFormatsCB
& callback
) {
270 DVLOG(3) << "GetCurrentSupportedFormats({ max_requested_height = "
271 << max_requested_height
<< "}) { max_requested_width = "
272 << max_requested_width
<< "}) { max_requested_frame_rate = "
273 << max_requested_frame_rate
<< "})";
274 DCHECK(thread_checker_
.CalledOnValidThread());
276 if (is_content_capture_
) {
277 const int width
= max_requested_width
?
278 max_requested_width
: MediaStreamVideoSource::kDefaultWidth
;
279 const int height
= max_requested_height
?
280 max_requested_height
: MediaStreamVideoSource::kDefaultHeight
;
281 callback
.Run(media::VideoCaptureFormats(
282 1, media::VideoCaptureFormat(
283 gfx::Size(width
, height
),
285 std::min(kMaxScreenCastFrameRate
, max_requested_frame_rate
)),
286 media::PIXEL_FORMAT_I420
)));
290 DCHECK(formats_enumerated_callback_
.is_null());
291 formats_enumerated_callback_
= callback
;
292 manager_
->GetDeviceFormatsInUse(
293 session_id_
, media::BindToCurrentLoop(base::Bind(
294 &LocalVideoCapturerSource::OnDeviceFormatsInUseReceived
,
295 weak_factory_
.GetWeakPtr())));
298 void LocalVideoCapturerSource::StartCapture(
299 const media::VideoCaptureParams
& params
,
300 const VideoCaptureDeliverFrameCB
& new_frame_callback
,
301 const RunningCallback
& running_callback
) {
302 DCHECK(params
.requested_format
.IsValid());
303 DCHECK(thread_checker_
.CalledOnValidThread());
304 running_callback_
= running_callback
;
306 stop_capture_cb_
= manager_
->StartCapture(
307 session_id_
, params
, media::BindToCurrentLoop(base::Bind(
308 &LocalVideoCapturerSource::OnStateUpdate
,
309 weak_factory_
.GetWeakPtr())),
313 void LocalVideoCapturerSource::StopCapture() {
314 DVLOG(3) << __FUNCTION__
;
315 DCHECK(thread_checker_
.CalledOnValidThread());
316 // Immediately make sure we don't provide more frames.
317 if (!stop_capture_cb_
.is_null())
318 base::ResetAndReturn(&stop_capture_cb_
).Run();
319 running_callback_
.Reset();
320 // Invalidate any potential format enumerations going on.
321 formats_enumerated_callback_
.Reset();
324 void LocalVideoCapturerSource::OnStateUpdate(VideoCaptureState state
) {
325 DVLOG(3) << __FUNCTION__
<< " state = " << state
;
326 DCHECK(thread_checker_
.CalledOnValidThread());
327 if (running_callback_
.is_null())
329 const bool is_started_ok
= state
== VIDEO_CAPTURE_STATE_STARTED
;
330 running_callback_
.Run(is_started_ok
);
332 running_callback_
.Reset();
335 void LocalVideoCapturerSource::OnDeviceFormatsInUseReceived(
336 const media::VideoCaptureFormats
& formats_in_use
) {
337 DVLOG(3) << __FUNCTION__
<< ", #formats received: " << formats_in_use
.size();
338 DCHECK(thread_checker_
.CalledOnValidThread());
339 // StopCapture() might have destroyed |formats_enumerated_callback_| before
341 if (formats_enumerated_callback_
.is_null())
343 if (formats_in_use
.size()) {
344 base::ResetAndReturn(&formats_enumerated_callback_
).Run(formats_in_use
);
348 // The device doesn't seem to have formats in use so try and retrieve the
349 // whole list of supported ones.
350 manager_
->GetDeviceSupportedFormats(
352 media::BindToCurrentLoop(
354 &LocalVideoCapturerSource::OnDeviceSupportedFormatsEnumerated
,
355 weak_factory_
.GetWeakPtr())));
358 void LocalVideoCapturerSource::OnDeviceSupportedFormatsEnumerated(
359 const media::VideoCaptureFormats
& formats
) {
360 DVLOG(3) << __FUNCTION__
<< ", #formats received: " << formats
.size();
361 DCHECK(thread_checker_
.CalledOnValidThread());
362 // StopCapture() might have destroyed |formats_enumerated_callback_| before
364 if (formats_enumerated_callback_
.is_null())
366 if (formats
.size()) {
367 base::ResetAndReturn(&formats_enumerated_callback_
).Run(formats
);
371 // The capture device doesn't seem to support capability enumeration, compose
372 // a fallback list of capabilities.
373 media::VideoCaptureFormats default_formats
;
374 for (const auto& resolution
: kVideoResolutions
) {
375 for (const auto frame_rate
: kVideoFrameRates
) {
376 default_formats
.push_back(media::VideoCaptureFormat(
377 gfx::Size(resolution
.width
, resolution
.height
), frame_rate
,
378 media::PIXEL_FORMAT_I420
));
381 base::ResetAndReturn(&formats_enumerated_callback_
).Run(default_formats
);
384 MediaStreamVideoCapturerSource::MediaStreamVideoCapturerSource(
385 const SourceStoppedCallback
& stop_callback
,
386 scoped_ptr
<media::VideoCapturerSource
> source
)
387 : source_(source
.Pass()) {
388 SetStopCallback(stop_callback
);
391 MediaStreamVideoCapturerSource::MediaStreamVideoCapturerSource(
392 const SourceStoppedCallback
& stop_callback
,
393 const StreamDeviceInfo
& device_info
)
394 : source_(new LocalVideoCapturerSource(device_info
)) {
395 SetStopCallback(stop_callback
);
396 SetDeviceInfo(device_info
);
399 MediaStreamVideoCapturerSource::~MediaStreamVideoCapturerSource() {
402 void MediaStreamVideoCapturerSource::GetCurrentSupportedFormats(
403 int max_requested_width
,
404 int max_requested_height
,
405 double max_requested_frame_rate
,
406 const VideoCaptureDeviceFormatsCB
& callback
) {
407 source_
->GetCurrentSupportedFormats(
409 max_requested_height
,
410 max_requested_frame_rate
,
414 void MediaStreamVideoCapturerSource::StartSourceImpl(
415 const media::VideoCaptureFormat
& format
,
416 const blink::WebMediaConstraints
& constraints
,
417 const VideoCaptureDeliverFrameCB
& frame_callback
) {
418 media::VideoCaptureParams new_params
;
419 new_params
.requested_format
= format
;
420 if (IsContentVideoCaptureDevice(device_info())) {
421 SetContentCaptureParamsFromConstraints(
422 constraints
, device_info().device
.type
, &new_params
);
423 } else if (device_info().device
.type
== MEDIA_DEVICE_VIDEO_CAPTURE
) {
424 SetPowerLineFrequencyParamFromConstraints(constraints
, &new_params
);
427 source_
->StartCapture(new_params
,
429 base::Bind(&MediaStreamVideoCapturerSource::OnStarted
,
430 base::Unretained(this)));
433 void MediaStreamVideoCapturerSource::StopSourceImpl() {
434 source_
->StopCapture();
437 void MediaStreamVideoCapturerSource::OnStarted(bool result
) {
438 OnStartDone(result
? MEDIA_DEVICE_OK
: MEDIA_DEVICE_TRACK_START_FAILURE
);
442 MediaStreamVideoCapturerSource::GetPowerLineFrequencyForTesting() const {
443 return kPowerLineFrequency
;
446 } // namespace content