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_source.h"
11 #include "base/debug/trace_event.h"
12 #include "base/logging.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "content/child/child_process.h"
15 #include "content/renderer/media/media_stream_dependency_factory.h"
16 #include "content/renderer/media/media_stream_video_track.h"
17 #include "content/renderer/media/video_frame_deliverer.h"
18 #include "content/renderer/media/webrtc/webrtc_video_capturer_adapter.h"
22 // Constraint keys. Specified by draft-alvestrand-constraints-resolution-00b
23 const char MediaStreamVideoSource::kMinAspectRatio[] = "minAspectRatio";
24 const char MediaStreamVideoSource::kMaxAspectRatio[] = "maxAspectRatio";
25 const char MediaStreamVideoSource::kMaxWidth[] = "maxWidth";
26 const char MediaStreamVideoSource::kMinWidth[] = "minWidth";
27 const char MediaStreamVideoSource::kMaxHeight[] = "maxHeight";
28 const char MediaStreamVideoSource::kMinHeight[] = "minHeight";
29 const char MediaStreamVideoSource::kMaxFrameRate[] = "maxFrameRate";
30 const char MediaStreamVideoSource::kMinFrameRate[] = "minFrameRate";
32 const char* kSupportedConstraints[] = {
33 MediaStreamVideoSource::kMaxAspectRatio,
34 MediaStreamVideoSource::kMinAspectRatio,
35 MediaStreamVideoSource::kMaxWidth,
36 MediaStreamVideoSource::kMinWidth,
37 MediaStreamVideoSource::kMaxHeight,
38 MediaStreamVideoSource::kMinHeight,
39 MediaStreamVideoSource::kMaxFrameRate,
40 MediaStreamVideoSource::kMinFrameRate,
43 const int MediaStreamVideoSource::kDefaultWidth = 640;
44 const int MediaStreamVideoSource::kDefaultHeight = 480;
45 const int MediaStreamVideoSource::kDefaultFrameRate = 30;
48 // Constraints keys for http://dev.w3.org/2011/webrtc/editor/getusermedia.html
49 const char kSourceId[] = "sourceId";
51 // Google-specific key prefix. Constraints with this prefix are ignored if they
53 const char kGooglePrefix[] = "goog";
55 // MediaStreamVideoSource supports cropping of video frames but only up to
56 // kMaxCropFactor. Ie - if a constraint is set to maxHeight 360, an original
57 // input frame height of max 360 * kMaxCropFactor pixels is accepted.
58 const int kMaxCropFactor = 2;
60 // Returns true if |constraint| is fulfilled. |format| can be changed
61 // changed by a constraint. Ie - the frame rate can be changed by setting
63 bool UpdateFormatForConstraint(
64 const blink::WebMediaConstraint& constraint,
66 media::VideoCaptureFormat* format) {
67 DCHECK(format != NULL);
69 if (!format->IsValid())
72 std::string constraint_name = constraint.m_name.utf8();
73 std::string constraint_value = constraint.m_value.utf8();
75 if (constraint_name.find(kGooglePrefix) == 0) {
76 // These are actually options, not constraints, so they can be satisfied
77 // regardless of the format.
81 if (constraint_name == kSourceId) {
82 // This is a constraint that doesn't affect the format.
86 // Ignore Chrome specific Tab capture constraints.
87 if (constraint_name == kMediaStreamSource ||
88 constraint_name == kMediaStreamSourceId)
91 if (constraint_name == MediaStreamVideoSource::kMinAspectRatio ||
92 constraint_name == MediaStreamVideoSource::kMaxAspectRatio) {
93 double double_value = 0;
94 base::StringToDouble(constraint_value, &double_value);
96 // The aspect ratio in |constraint.m_value| has been converted to a string
97 // and back to a double, so it may have a rounding error.
98 // E.g if the value 1/3 is converted to a string, the string will not have
100 // We add a margin of 0.0005 which is high enough to detect the same aspect
101 // ratio but small enough to avoid matching wrong aspect ratios.
102 const double kRoundingTruncation = 0.0005;
103 double ratio = static_cast<double>(format->frame_size.width()) /
104 format->frame_size.height();
105 if (constraint_name == MediaStreamVideoSource::kMinAspectRatio)
106 return (double_value <= ratio + kRoundingTruncation);
107 // Subtract 0.0005 to avoid rounding problems. Same as above.
108 return (double_value >= ratio - kRoundingTruncation);
112 if (!base::StringToInt(constraint_value, &value)) {
113 DLOG(WARNING) << "Can't parse MediaStream constraint. Name:"
114 << constraint_name << " Value:" << constraint_value;
117 if (constraint_name == MediaStreamVideoSource::kMinWidth) {
118 return (value <= format->frame_size.width());
119 } else if (constraint_name == MediaStreamVideoSource::kMaxWidth) {
120 return (value * kMaxCropFactor >= format->frame_size.width());
121 } else if (constraint_name == MediaStreamVideoSource::kMinHeight) {
122 return (value <= format->frame_size.height());
123 } else if (constraint_name == MediaStreamVideoSource::kMaxHeight) {
124 return (value * kMaxCropFactor >= format->frame_size.height());
125 } else if (constraint_name == MediaStreamVideoSource::kMinFrameRate) {
126 return (value <= format->frame_rate);
127 } else if (constraint_name == MediaStreamVideoSource::kMaxFrameRate) {
129 // The frame rate is set by constraint.
130 // Don't allow 0 as frame rate if it is a mandatory constraint.
131 // Set the frame rate to 1 if it is not mandatory.
139 (format->frame_rate > value) ? value : format->frame_rate;
142 LOG(WARNING) << "Found unknown MediaStream constraint. Name:"
143 << constraint_name << " Value:" << constraint_value;
148 // Removes media::VideoCaptureFormats from |formats| that don't meet
150 void FilterFormatsByConstraint(
151 const blink::WebMediaConstraint& constraint,
153 media::VideoCaptureFormats* formats) {
154 DVLOG(3) << "FilterFormatsByConstraint("
155 << "{ constraint.m_name = " << constraint.m_name.utf8()
156 << " constraint.m_value = " << constraint.m_value.utf8()
157 << " mandatory = " << mandatory << "})";
158 media::VideoCaptureFormats::iterator format_it = formats->begin();
159 while (format_it != formats->end()) {
160 // Modify the format_it to fulfill the constraint if possible.
161 // Delete it otherwise.
162 if (!UpdateFormatForConstraint(constraint, mandatory, &(*format_it))) {
163 format_it = formats->erase(format_it);
170 // Returns the media::VideoCaptureFormats that matches |constraints|.
171 media::VideoCaptureFormats FilterFormats(
172 const blink::WebMediaConstraints& constraints,
173 const media::VideoCaptureFormats& supported_formats) {
174 if (constraints.isNull()) {
175 return supported_formats;
178 blink::WebVector<blink::WebMediaConstraint> mandatory;
179 blink::WebVector<blink::WebMediaConstraint> optional;
180 constraints.getMandatoryConstraints(mandatory);
181 constraints.getOptionalConstraints(optional);
183 media::VideoCaptureFormats candidates = supported_formats;
185 for (size_t i = 0; i < mandatory.size(); ++i)
186 FilterFormatsByConstraint(mandatory[i], true, &candidates);
188 if (candidates.empty())
191 // Ok - all mandatory checked and we still have candidates.
192 // Let's try filtering using the optional constraints. The optional
193 // constraints must be filtered in the order they occur in |optional|.
194 // But if a constraint produce zero candidates, the constraint is ignored and
195 // the next constraint is tested.
196 // http://dev.w3.org/2011/webrtc/editor/getusermedia.html#idl-def-Constraints
197 for (size_t i = 0; i < optional.size(); ++i) {
198 media::VideoCaptureFormats current_candidates = candidates;
199 FilterFormatsByConstraint(optional[i], false, ¤t_candidates);
200 if (!current_candidates.empty()) {
201 candidates = current_candidates;
205 // We have done as good as we can to filter the supported resolutions.
209 bool GetConstraintValue(const blink::WebMediaConstraints& constraints,
210 bool mandatory, const blink::WebString& name,
212 blink::WebString value_str;
213 bool ret = mandatory ?
214 constraints.getMandatoryConstraintValue(name, value_str) :
215 constraints.getOptionalConstraintValue(name, value_str);
217 base::StringToInt(value_str.utf8(), value);
221 // Returns true if |constraint| has mandatory constraints.
222 bool HasMandatoryConstraints(const blink::WebMediaConstraints& constraints) {
223 blink::WebVector<blink::WebMediaConstraint> mandatory_constraints;
224 constraints.getMandatoryConstraints(mandatory_constraints);
225 return !mandatory_constraints.isEmpty();
228 // Retrieve the desired max width and height from |constraints|.
229 void GetDesiredMaxWidthAndHeight(const blink::WebMediaConstraints& constraints,
230 int* desired_width, int* desired_height) {
231 bool mandatory = GetConstraintValue(constraints, true,
232 MediaStreamVideoSource::kMaxWidth,
234 mandatory |= GetConstraintValue(constraints, true,
235 MediaStreamVideoSource::kMaxHeight,
240 GetConstraintValue(constraints, false, MediaStreamVideoSource::kMaxWidth,
242 GetConstraintValue(constraints, false, MediaStreamVideoSource::kMaxHeight,
246 const media::VideoCaptureFormat& GetBestFormatBasedOnArea(
247 const media::VideoCaptureFormats& formats,
249 media::VideoCaptureFormats::const_iterator it = formats.begin();
250 media::VideoCaptureFormats::const_iterator best_it = formats.begin();
251 int best_diff = std::numeric_limits<int>::max();
252 for (; it != formats.end(); ++it) {
253 int diff = abs(area - it->frame_size.width() * it->frame_size.height());
254 if (diff < best_diff) {
262 // Find the format that best matches the default video size.
263 // This algorithm is chosen since a resolution must be picked even if no
264 // constraints are provided. We don't just select the maximum supported
265 // resolution since higher resolutions cost more in terms of complexity and
266 // many cameras have lower frame rate and have more noise in the image at
267 // their maximum supported resolution.
268 void GetBestCaptureFormat(
269 const media::VideoCaptureFormats& formats,
270 const blink::WebMediaConstraints& constraints,
271 media::VideoCaptureFormat* capture_format,
272 gfx::Size* max_frame_output_size) {
273 DCHECK(!formats.empty());
274 DCHECK(max_frame_output_size);
276 int max_width = std::numeric_limits<int>::max();
277 int max_height = std::numeric_limits<int>::max();;
278 GetDesiredMaxWidthAndHeight(constraints, &max_width, &max_height);
280 *capture_format = GetBestFormatBasedOnArea(
282 std::min(max_width, MediaStreamVideoSource::kDefaultWidth) *
283 std::min(max_height, MediaStreamVideoSource::kDefaultHeight));
285 max_frame_output_size->set_width(max_width);
286 max_frame_output_size->set_height(max_height);
289 // Empty method used for keeping a reference to the original media::VideoFrame
290 // in MediaStreamVideoSource::FrameDeliverer::DeliverFrameOnIO if cropping is
291 // needed. The reference to |frame| is kept in the closure that calls this
293 void ReleaseOriginalFrame(
294 const scoped_refptr<media::VideoFrame>& frame) {
297 } // anonymous namespace
299 // Helper class used for delivering video frames to all registered tracks
301 class MediaStreamVideoSource::FrameDeliverer : public VideoFrameDeliverer {
304 const scoped_refptr<base::MessageLoopProxy>& io_message_loop)
305 : VideoFrameDeliverer(io_message_loop) {
308 // Register |callback| to receive video frames of max size
309 // |max_frame_output_size| on the IO thread.
310 // TODO(perkj): Currently |max_frame_output_size| must be the same for all
312 void AddCallback(void* id,
313 const VideoCaptureDeliverFrameCB& callback,
314 const gfx::Size& max_frame_output_size) {
315 DCHECK(thread_checker().CalledOnValidThread());
316 io_message_loop()->PostTask(
319 &FrameDeliverer::AddCallbackWithResolutionOnIO,
320 this, id, callback, max_frame_output_size));
323 virtual void DeliverFrameOnIO(
324 const scoped_refptr<media::VideoFrame>& frame,
325 const media::VideoCaptureFormat& format) OVERRIDE {
326 DCHECK(io_message_loop()->BelongsToCurrentThread());
327 TRACE_EVENT0("video", "MediaStreamVideoSource::DeliverFrameOnIO");
328 if (max_output_size_.IsEmpty())
329 return; // Frame received before the output has been decided.
331 scoped_refptr<media::VideoFrame> video_frame(frame);
332 const gfx::Size& visible_size = frame->visible_rect().size();
333 if (visible_size.width() > max_output_size_.width() ||
334 visible_size.height() > max_output_size_.height()) {
335 // If |frame| is not the size that is expected, we need to crop it by
336 // providing a new |visible_rect|. The new visible rect must be within the
337 // original |visible_rect|.
338 gfx::Rect output_rect = frame->visible_rect();
339 output_rect.ClampToCenteredSize(max_output_size_);
340 // TODO(perkj): Allow cropping of textures once http://crbug/362521 is
342 if (frame->format() != media::VideoFrame::NATIVE_TEXTURE) {
343 video_frame = media::VideoFrame::WrapVideoFrame(
347 base::Bind(&ReleaseOriginalFrame, frame));
350 VideoFrameDeliverer::DeliverFrameOnIO(video_frame, format);
354 virtual ~FrameDeliverer() {
357 void AddCallbackWithResolutionOnIO(
359 const VideoCaptureDeliverFrameCB& callback,
360 const gfx::Size& max_frame_output_size) {
361 DCHECK(io_message_loop()->BelongsToCurrentThread());
362 // Currently we only support one frame output size.
363 DCHECK(!max_frame_output_size.IsEmpty() &&
364 (max_output_size_.IsEmpty() ||
365 max_output_size_ == max_frame_output_size));
366 max_output_size_ = max_frame_output_size;
367 VideoFrameDeliverer::AddCallbackOnIO(id, callback);
371 gfx::Size max_output_size_;
375 MediaStreamVideoSource* MediaStreamVideoSource::GetVideoSource(
376 const blink::WebMediaStreamSource& source) {
377 return static_cast<MediaStreamVideoSource*>(source.extraData());
381 bool MediaStreamVideoSource::IsConstraintSupported(const std::string& name) {
382 for (size_t i = 0; i < arraysize(kSupportedConstraints); ++i) {
383 if (kSupportedConstraints[i] == name)
389 MediaStreamVideoSource::MediaStreamVideoSource()
392 new MediaStreamVideoSource::FrameDeliverer(
393 ChildProcess::current()->io_message_loop_proxy())),
394 weak_factory_(this) {
397 MediaStreamVideoSource::~MediaStreamVideoSource() {
398 DVLOG(3) << "~MediaStreamVideoSource()";
401 void MediaStreamVideoSource::AddTrack(
402 MediaStreamVideoTrack* track,
403 const VideoCaptureDeliverFrameCB& frame_callback,
404 const blink::WebMediaConstraints& constraints,
405 const ConstraintsCallback& callback) {
406 DCHECK(CalledOnValidThread());
407 DCHECK(std::find(tracks_.begin(), tracks_.end(),
408 track) == tracks_.end());
409 tracks_.push_back(track);
411 requested_constraints_.push_back(
412 RequestedConstraints(track, frame_callback, constraints, callback));
416 // Tab capture and Screen capture needs the maximum requested height
417 // and width to decide on the resolution.
418 int max_requested_width = 0;
419 GetConstraintValue(constraints, true, kMaxWidth, &max_requested_width);
421 int max_requested_height = 0;
422 GetConstraintValue(constraints, true, kMaxHeight, &max_requested_height);
424 state_ = RETRIEVING_CAPABILITIES;
425 GetCurrentSupportedFormats(
427 max_requested_height,
428 base::Bind(&MediaStreamVideoSource::OnSupportedFormats,
429 weak_factory_.GetWeakPtr()));
434 case RETRIEVING_CAPABILITIES: {
435 // The |callback| will be triggered once the source has started or
436 // the capabilities have been retrieved.
441 // Currently, reconfiguring the source is not supported.
447 void MediaStreamVideoSource::RemoveTrack(MediaStreamVideoTrack* video_track) {
448 DCHECK(CalledOnValidThread());
449 std::vector<MediaStreamVideoTrack*>::iterator it =
450 std::find(tracks_.begin(), tracks_.end(), video_track);
451 DCHECK(it != tracks_.end());
453 // Call |RemoveCallback| here even if adding the track has failed and
454 // frame_deliverer_->AddCallback has not been called.
455 frame_deliverer_->RemoveCallback(video_track);
461 const scoped_refptr<base::MessageLoopProxy>&
462 MediaStreamVideoSource::io_message_loop() const {
463 return frame_deliverer_->io_message_loop();
466 void MediaStreamVideoSource::DoStopSource() {
467 DCHECK(CalledOnValidThread());
468 DVLOG(3) << "DoStopSource()";
473 SetReadyState(blink::WebMediaStreamSource::ReadyStateEnded);
476 void MediaStreamVideoSource::OnSupportedFormats(
477 const media::VideoCaptureFormats& formats) {
478 DCHECK(CalledOnValidThread());
479 DCHECK_EQ(RETRIEVING_CAPABILITIES, state_);
481 supported_formats_ = formats;
482 if (!FindBestFormatWithConstraints(supported_formats_,
484 &max_frame_output_size_,
485 ¤t_constraints_)) {
486 SetReadyState(blink::WebMediaStreamSource::ReadyStateEnded);
487 // This object can be deleted after calling FinalizeAddTrack. See comment
488 // in the header file.
494 DVLOG(3) << "Starting the capturer with"
495 << " width = " << current_format_.frame_size.width()
496 << " height = " << current_format_.frame_size.height()
497 << " frame rate = " << current_format_.frame_rate;
499 media::VideoCaptureParams params;
500 params.requested_format = current_format_;
503 base::Bind(&MediaStreamVideoSource::FrameDeliverer::DeliverFrameOnIO,
507 bool MediaStreamVideoSource::FindBestFormatWithConstraints(
508 const media::VideoCaptureFormats& formats,
509 media::VideoCaptureFormat* best_format,
510 gfx::Size* max_frame_output_size,
511 blink::WebMediaConstraints* resulting_constraints) {
512 // Find the first constraints that we can fulfill.
513 for (std::vector<RequestedConstraints>::iterator request_it =
514 requested_constraints_.begin();
515 request_it != requested_constraints_.end(); ++request_it) {
516 const blink::WebMediaConstraints& requested_constraints =
517 request_it->constraints;
519 // If the source doesn't support capability enumeration it is still ok if
520 // no mandatory constraints have been specified. That just means that
521 // we will start with whatever format is native to the source.
522 if (formats.empty() && !HasMandatoryConstraints(requested_constraints)) {
523 *best_format = media::VideoCaptureFormat();
524 *resulting_constraints = requested_constraints;
525 *max_frame_output_size = gfx::Size(std::numeric_limits<int>::max(),
526 std::numeric_limits<int>::max());
529 media::VideoCaptureFormats filtered_formats =
530 FilterFormats(requested_constraints, formats);
531 if (filtered_formats.size() > 0) {
532 // A request with constraints that can be fulfilled.
533 GetBestCaptureFormat(filtered_formats,
534 requested_constraints,
536 max_frame_output_size);
537 *resulting_constraints= requested_constraints;
544 void MediaStreamVideoSource::OnStartDone(bool success) {
545 DCHECK(CalledOnValidThread());
546 DVLOG(3) << "OnStartDone({success =" << success << "})";
548 DCHECK_EQ(STARTING, state_);
550 SetReadyState(blink::WebMediaStreamSource::ReadyStateLive);
553 SetReadyState(blink::WebMediaStreamSource::ReadyStateEnded);
557 // This object can be deleted after calling FinalizeAddTrack. See comment in
562 void MediaStreamVideoSource::FinalizeAddTrack() {
563 media::VideoCaptureFormats formats;
564 formats.push_back(current_format_);
566 std::vector<RequestedConstraints> callbacks;
567 callbacks.swap(requested_constraints_);
568 for (std::vector<RequestedConstraints>::iterator it = callbacks.begin();
569 it != callbacks.end(); ++it) {
570 // The track has been added successfully if the source has started and
571 // there are either no mandatory constraints and the source doesn't expose
572 // its format capabilities, or the constraints and the format match.
573 // For example, a remote source doesn't expose its format capabilities.
576 ((!current_format_.IsValid() && !HasMandatoryConstraints(
578 !FilterFormats(it->constraints, formats).empty());
580 frame_deliverer_->AddCallback(it->track, it->frame_callback,
581 max_frame_output_size_);
583 DVLOG(3) << "FinalizeAddTrack() success " << success;
584 if (!it->callback.is_null())
585 it->callback.Run(this, success);
589 void MediaStreamVideoSource::SetReadyState(
590 blink::WebMediaStreamSource::ReadyState state) {
591 if (!owner().isNull()) {
592 owner().setReadyState(state);
594 for (std::vector<MediaStreamVideoTrack*>::iterator it = tracks_.begin();
595 it != tracks_.end(); ++it) {
596 (*it)->OnReadyStateChanged(state);
600 MediaStreamVideoSource::RequestedConstraints::RequestedConstraints(
601 MediaStreamVideoTrack* track,
602 const VideoCaptureDeliverFrameCB& frame_callback,
603 const blink::WebMediaConstraints& constraints,
604 const ConstraintsCallback& callback)
606 frame_callback(frame_callback),
607 constraints(constraints),
611 MediaStreamVideoSource::RequestedConstraints::~RequestedConstraints() {
614 } // namespace content