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 "content/public/common/media_stream_request.h"
11 #include "content/renderer/media/media_stream_constraints_util.h"
12 #include "content/renderer/media/video_capture_impl_manager.h"
13 #include "content/renderer/render_thread_impl.h"
14 #include "media/base/bind_to_current_loop.h"
15 #include "media/base/limits.h"
16 #include "media/base/video_frame.h"
22 // Resolutions used if the source doesn't support capability enumeration.
26 } const kVideoResolutions
[] = {{1920, 1080},
34 // Frame rates for sources with no support for capability enumeration.
35 const int kVideoFrameRates
[] = {30, 60};
37 // Hard upper-bound frame rate for tab/desktop capture.
38 const double kMaxScreenCastFrameRate
= 120.0;
40 // Returns true if the value for width or height is reasonable.
41 bool DimensionValueIsValid(int x
) {
42 return x
> 0 && x
<= media::limits::kMaxDimension
;
45 // Returns true if the value for frame rate is reasonable.
46 bool FrameRateValueIsValid(double frame_rate
) {
47 return (frame_rate
> (1.0 / 60.0)) && // Lower-bound: One frame per minute.
48 (frame_rate
<= media::limits::kMaxFramesPerSecond
);
51 // Returns true if the aspect ratio of |a| and |b| are equivalent to two
52 // significant digits.
53 bool AreNearlyEquivalentInAspectRatio(const gfx::Size
& a
, const gfx::Size
& b
) {
56 const int aspect_ratio_a
= (100 * a
.width()) / a
.height();
57 const int aspect_ratio_b
= (100 * b
.width()) / b
.height();
58 return aspect_ratio_a
== aspect_ratio_b
;
61 // Interprets the properties in |constraints| to override values in |params| and
62 // determine the resolution change policy.
63 void SetScreenCastParamsFromConstraints(
64 const blink::WebMediaConstraints
& constraints
,
66 media::VideoCaptureParams
* params
) {
67 // The default resolution change policies for tab versus desktop capture are
68 // the way they are for legacy reasons.
69 if (type
== MEDIA_TAB_VIDEO_CAPTURE
) {
70 params
->resolution_change_policy
=
71 media::RESOLUTION_POLICY_FIXED_RESOLUTION
;
72 } else if (type
== MEDIA_DESKTOP_VIDEO_CAPTURE
) {
73 params
->resolution_change_policy
=
74 media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT
;
79 // If the maximum frame resolution was provided in the constraints, use it if
80 // either: 1) none has been set yet; or 2) the maximum specificed is smaller
81 // than the current setting.
84 gfx::Size desired_max_frame_size
;
85 if (GetConstraintValueAsInteger(constraints
,
86 MediaStreamVideoSource::kMaxWidth
,
88 GetConstraintValueAsInteger(constraints
,
89 MediaStreamVideoSource::kMaxHeight
,
91 DimensionValueIsValid(width
) &&
92 DimensionValueIsValid(height
)) {
93 desired_max_frame_size
.SetSize(width
, height
);
94 if (params
->requested_format
.frame_size
.IsEmpty() ||
95 desired_max_frame_size
.width() <
96 params
->requested_format
.frame_size
.width() ||
97 desired_max_frame_size
.height() <
98 params
->requested_format
.frame_size
.height()) {
99 params
->requested_format
.frame_size
= desired_max_frame_size
;
103 // Set the default frame resolution if none was provided.
104 if (params
->requested_format
.frame_size
.IsEmpty()) {
105 params
->requested_format
.frame_size
.SetSize(
106 MediaStreamVideoSource::kDefaultWidth
,
107 MediaStreamVideoSource::kDefaultHeight
);
110 // If the maximum frame rate was provided, use it if either: 1) none has been
111 // set yet; or 2) the maximum specificed is smaller than the current setting.
112 double frame_rate
= 0.0;
113 if (GetConstraintValueAsDouble(constraints
,
114 MediaStreamVideoSource::kMaxFrameRate
,
116 FrameRateValueIsValid(frame_rate
)) {
117 if (params
->requested_format
.frame_rate
<= 0.0f
||
118 frame_rate
< params
->requested_format
.frame_rate
) {
119 params
->requested_format
.frame_rate
= frame_rate
;
123 // Set the default frame rate if none was provided.
124 if (params
->requested_format
.frame_rate
<= 0.0f
) {
125 params
->requested_format
.frame_rate
=
126 MediaStreamVideoSource::kDefaultFrameRate
;
129 // If the minimum frame resolution was provided, compare it to the maximum
130 // frame resolution to determine the intended resolution change policy.
131 if (!desired_max_frame_size
.IsEmpty() &&
132 GetConstraintValueAsInteger(constraints
,
133 MediaStreamVideoSource::kMinWidth
,
135 GetConstraintValueAsInteger(constraints
,
136 MediaStreamVideoSource::kMinHeight
,
138 width
<= desired_max_frame_size
.width() &&
139 height
<= desired_max_frame_size
.height()) {
140 if (width
== desired_max_frame_size
.width() &&
141 height
== desired_max_frame_size
.height()) {
142 // Constraints explicitly require a single frame resolution.
143 params
->resolution_change_policy
=
144 media::RESOLUTION_POLICY_FIXED_RESOLUTION
;
145 } else if (DimensionValueIsValid(width
) &&
146 DimensionValueIsValid(height
) &&
147 AreNearlyEquivalentInAspectRatio(gfx::Size(width
, height
),
148 desired_max_frame_size
)) {
149 // Constraints only mention a single aspect ratio.
150 params
->resolution_change_policy
=
151 media::RESOLUTION_POLICY_FIXED_ASPECT_RATIO
;
153 // Constraints specify a minimum resolution that is smaller than the
154 // maximum resolution and has a different aspect ratio (possibly even
155 // 0x0). This indicates any frame resolution and aspect ratio is
157 params
->resolution_change_policy
=
158 media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT
;
162 DVLOG(1) << "SetScreenCastParamsFromConstraints: "
163 << media::VideoCaptureFormat::ToString(params
->requested_format
)
164 << " with resolution change policy "
165 << params
->resolution_change_policy
;
170 VideoCapturerDelegate::VideoCapturerDelegate(
171 const StreamDeviceInfo
& device_info
)
172 : session_id_(device_info
.session_id
),
173 is_screen_cast_(device_info
.device
.type
== MEDIA_TAB_VIDEO_CAPTURE
||
174 device_info
.device
.type
== MEDIA_DESKTOP_VIDEO_CAPTURE
),
175 weak_factory_(this) {
176 DVLOG(3) << "VideoCapturerDelegate::ctor";
178 // NULL in unit test.
179 if (RenderThreadImpl::current()) {
180 VideoCaptureImplManager
* manager
=
181 RenderThreadImpl::current()->video_capture_impl_manager();
183 release_device_cb_
= manager
->UseDevice(session_id_
);
187 VideoCapturerDelegate::~VideoCapturerDelegate() {
188 DCHECK(thread_checker_
.CalledOnValidThread());
189 DVLOG(3) << "VideoCapturerDelegate::dtor";
190 if (!release_device_cb_
.is_null())
191 release_device_cb_
.Run();
194 void VideoCapturerDelegate::GetCurrentSupportedFormats(
195 int max_requested_width
,
196 int max_requested_height
,
197 double max_requested_frame_rate
,
198 const VideoCaptureDeviceFormatsCB
& callback
) {
200 << "GetCurrentSupportedFormats("
201 << " { max_requested_height = " << max_requested_height
<< "})"
202 << " { max_requested_width = " << max_requested_width
<< "})"
203 << " { max_requested_frame_rate = " << max_requested_frame_rate
<< "})";
205 if (is_screen_cast_
) {
206 const int width
= max_requested_width
?
207 max_requested_width
: MediaStreamVideoSource::kDefaultWidth
;
208 const int height
= max_requested_height
?
209 max_requested_height
: MediaStreamVideoSource::kDefaultHeight
;
210 callback
.Run(media::VideoCaptureFormats(
211 1, media::VideoCaptureFormat(
212 gfx::Size(width
, height
),
214 std::min(kMaxScreenCastFrameRate
, max_requested_frame_rate
)),
215 media::VIDEO_CAPTURE_PIXEL_FORMAT_I420
)));
219 // NULL in unit test.
220 if (!RenderThreadImpl::current())
222 VideoCaptureImplManager
* const manager
=
223 RenderThreadImpl::current()->video_capture_impl_manager();
226 DCHECK(source_formats_callback_
.is_null());
227 source_formats_callback_
= callback
;
228 manager
->GetDeviceFormatsInUse(
230 media::BindToCurrentLoop(
232 &VideoCapturerDelegate::OnDeviceFormatsInUseReceived
,
233 weak_factory_
.GetWeakPtr())));
236 void VideoCapturerDelegate::StartCapture(
237 const media::VideoCaptureParams
& params
,
238 const VideoCaptureDeliverFrameCB
& new_frame_callback
,
239 scoped_refptr
<base::SingleThreadTaskRunner
> frame_callback_task_runner
,
240 const RunningCallback
& running_callback
) {
241 DCHECK(params
.requested_format
.IsValid());
242 DCHECK(thread_checker_
.CalledOnValidThread());
243 running_callback_
= running_callback
;
245 // NULL in unit test.
246 if (!RenderThreadImpl::current())
248 VideoCaptureImplManager
* const manager
=
249 RenderThreadImpl::current()->video_capture_impl_manager();
252 if (frame_callback_task_runner
!=
253 RenderThreadImpl::current()->GetIOMessageLoopProxy()) {
254 DCHECK(false) << "Only IO thread supported right now.";
255 running_callback
.Run(false);
260 manager
->StartCapture(
263 media::BindToCurrentLoop(base::Bind(
264 &VideoCapturerDelegate::OnStateUpdate
,
265 weak_factory_
.GetWeakPtr())),
269 void VideoCapturerDelegate::StopCapture() {
270 // Immediately make sure we don't provide more frames.
271 DVLOG(3) << "VideoCapturerDelegate::StopCapture()";
272 DCHECK(thread_checker_
.CalledOnValidThread());
273 if (!stop_capture_cb_
.is_null()) {
274 base::ResetAndReturn(&stop_capture_cb_
).Run();
276 running_callback_
.Reset();
277 source_formats_callback_
.Reset();
280 void VideoCapturerDelegate::OnStateUpdate(
281 VideoCaptureState state
) {
282 DCHECK(thread_checker_
.CalledOnValidThread());
283 DVLOG(3) << "OnStateUpdate state = " << state
;
284 if (state
== VIDEO_CAPTURE_STATE_STARTED
&& !running_callback_
.is_null()) {
285 running_callback_
.Run(true);
288 if (state
> VIDEO_CAPTURE_STATE_STARTED
&& !running_callback_
.is_null()) {
289 base::ResetAndReturn(&running_callback_
).Run(false);
293 void VideoCapturerDelegate::OnDeviceFormatsInUseReceived(
294 const media::VideoCaptureFormats
& formats_in_use
) {
295 DVLOG(3) << "OnDeviceFormatsInUseReceived: " << formats_in_use
.size();
296 DCHECK(thread_checker_
.CalledOnValidThread());
297 // StopCapture() might have destroyed |source_formats_callback_| before
299 if (source_formats_callback_
.is_null())
301 // If there are no formats in use, try to retrieve the whole list of
303 if (!formats_in_use
.empty()) {
304 source_formats_callback_
.Run(formats_in_use
);
305 source_formats_callback_
.Reset();
309 // NULL in unit test.
310 if (!RenderThreadImpl::current())
312 VideoCaptureImplManager
* const manager
=
313 RenderThreadImpl::current()->video_capture_impl_manager();
317 manager
->GetDeviceSupportedFormats(
319 media::BindToCurrentLoop(
321 &VideoCapturerDelegate::OnDeviceSupportedFormatsEnumerated
,
322 weak_factory_
.GetWeakPtr())));
325 void VideoCapturerDelegate::OnDeviceSupportedFormatsEnumerated(
326 const media::VideoCaptureFormats
& formats
) {
327 DVLOG(3) << "OnDeviceSupportedFormatsEnumerated: " << formats
.size()
329 DCHECK(thread_checker_
.CalledOnValidThread());
330 // StopCapture() might have destroyed |source_formats_callback_| before
332 if (source_formats_callback_
.is_null())
334 if (formats
.size()) {
335 base::ResetAndReturn(&source_formats_callback_
).Run(formats
);
337 // The capture device doesn't seem to support capability enumeration,
338 // compose a fallback list of capabilities.
339 media::VideoCaptureFormats default_formats
;
340 for (const auto& resolution
: kVideoResolutions
) {
341 for (const auto frame_rate
: kVideoFrameRates
) {
342 default_formats
.push_back(media::VideoCaptureFormat(
343 gfx::Size(resolution
.width
, resolution
.height
), frame_rate
,
344 media::VIDEO_CAPTURE_PIXEL_FORMAT_I420
));
347 base::ResetAndReturn(&source_formats_callback_
).Run(default_formats
);
351 MediaStreamVideoCapturerSource::MediaStreamVideoCapturerSource(
352 const SourceStoppedCallback
& stop_callback
,
353 scoped_ptr
<media::VideoCapturerSource
> delegate
)
354 : delegate_(delegate
.Pass()) {
355 SetStopCallback(stop_callback
);
358 MediaStreamVideoCapturerSource::~MediaStreamVideoCapturerSource() {
361 void MediaStreamVideoCapturerSource::SetDeviceInfo(
362 const StreamDeviceInfo
& device_info
) {
363 MediaStreamVideoSource::SetDeviceInfo(device_info
);
366 void MediaStreamVideoCapturerSource::GetCurrentSupportedFormats(
367 int max_requested_width
,
368 int max_requested_height
,
369 double max_requested_frame_rate
,
370 const VideoCaptureDeviceFormatsCB
& callback
) {
371 delegate_
->GetCurrentSupportedFormats(
373 max_requested_height
,
374 max_requested_frame_rate
,
378 void MediaStreamVideoCapturerSource::StartSourceImpl(
379 const media::VideoCaptureFormat
& format
,
380 const blink::WebMediaConstraints
& constraints
,
381 const VideoCaptureDeliverFrameCB
& frame_callback
) {
382 media::VideoCaptureParams new_params
;
383 new_params
.requested_format
= format
;
384 if (device_info().device
.type
== MEDIA_TAB_VIDEO_CAPTURE
||
385 device_info().device
.type
== MEDIA_DESKTOP_VIDEO_CAPTURE
) {
386 SetScreenCastParamsFromConstraints(
387 constraints
, device_info().device
.type
, &new_params
);
389 delegate_
->StartCapture(
392 RenderThreadImpl::current() ?
393 RenderThreadImpl::current()->GetIOMessageLoopProxy() :
395 base::Bind(&MediaStreamVideoCapturerSource::OnStarted
,
396 base::Unretained(this)));
399 void MediaStreamVideoCapturerSource::StopSourceImpl() {
400 delegate_
->StopCapture();
403 void MediaStreamVideoCapturerSource::OnStarted(bool result
) {
404 OnStartDone(result
? MEDIA_DEVICE_OK
: MEDIA_DEVICE_TRACK_START_FAILURE
);
407 } // namespace content