Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / content / renderer / media / media_stream_video_source.cc
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.
4
5 #include "content/renderer/media/media_stream_video_source.h"
6
7 #include <algorithm>
8 #include <limits>
9 #include <string>
10
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"
19
20 namespace content {
21
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";
31
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,
41 };
42
43 const int MediaStreamVideoSource::kDefaultWidth = 640;
44 const int MediaStreamVideoSource::kDefaultHeight = 480;
45 const int MediaStreamVideoSource::kDefaultFrameRate = 30;
46
47 namespace {
48 // Constraints keys for http://dev.w3.org/2011/webrtc/editor/getusermedia.html
49 const char kSourceId[] = "sourceId";
50
51 // Google-specific key prefix. Constraints with this prefix are ignored if they
52 // are unknown.
53 const char kGooglePrefix[] = "goog";
54
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;
59
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
62 // maxFrameRate.
63 bool UpdateFormatForConstraint(
64     const blink::WebMediaConstraint& constraint,
65     bool mandatory,
66     media::VideoCaptureFormat* format) {
67   DCHECK(format != NULL);
68
69   if (!format->IsValid())
70     return false;
71
72   std::string constraint_name = constraint.m_name.utf8();
73   std::string constraint_value = constraint.m_value.utf8();
74
75   if (constraint_name.find(kGooglePrefix) == 0) {
76     // These are actually options, not constraints, so they can be satisfied
77     // regardless of the format.
78     return true;
79   }
80
81   if (constraint_name == kSourceId) {
82     // This is a constraint that doesn't affect the format.
83     return true;
84   }
85
86   // Ignore Chrome specific Tab capture constraints.
87   if (constraint_name == kMediaStreamSource ||
88       constraint_name == kMediaStreamSourceId)
89     return true;
90
91   if (constraint_name == MediaStreamVideoSource::kMinAspectRatio ||
92       constraint_name == MediaStreamVideoSource::kMaxAspectRatio) {
93     double double_value = 0;
94     base::StringToDouble(constraint_value, &double_value);
95
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
99     // infinite length.
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);
109   }
110
111   int value;
112   if (!base::StringToInt(constraint_value, &value)) {
113     DLOG(WARNING) << "Can't parse MediaStream constraint. Name:"
114                   <<  constraint_name << " Value:" << constraint_value;
115     return false;
116   }
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) {
128     if (value == 0) {
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.
132       if (mandatory) {
133         return false;
134       } else {
135         value = 1;
136       }
137     }
138     format->frame_rate =
139         (format->frame_rate > value) ? value : format->frame_rate;
140     return true;
141   } else {
142     LOG(WARNING) << "Found unknown MediaStream constraint. Name:"
143                  <<  constraint_name << " Value:" << constraint_value;
144     return false;
145   }
146 }
147
148 // Removes media::VideoCaptureFormats from |formats| that don't meet
149 // |constraint|.
150 void FilterFormatsByConstraint(
151     const blink::WebMediaConstraint& constraint,
152     bool mandatory,
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);
164     } else {
165       ++format_it;
166     }
167   }
168 }
169
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;
176   }
177
178   blink::WebVector<blink::WebMediaConstraint> mandatory;
179   blink::WebVector<blink::WebMediaConstraint> optional;
180   constraints.getMandatoryConstraints(mandatory);
181   constraints.getOptionalConstraints(optional);
182
183   media::VideoCaptureFormats candidates = supported_formats;
184
185   for (size_t i = 0; i < mandatory.size(); ++i)
186     FilterFormatsByConstraint(mandatory[i], true, &candidates);
187
188   if (candidates.empty())
189     return candidates;
190
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, &current_candidates);
200     if (!current_candidates.empty()) {
201       candidates = current_candidates;
202     }
203   }
204
205   // We have done as good as we can to filter the supported resolutions.
206   return candidates;
207 }
208
209 bool GetConstraintValue(const blink::WebMediaConstraints& constraints,
210                         bool mandatory, const blink::WebString& name,
211                         int* value) {
212   blink::WebString value_str;
213   bool ret = mandatory ?
214       constraints.getMandatoryConstraintValue(name, value_str) :
215       constraints.getOptionalConstraintValue(name, value_str);
216   if (ret)
217     base::StringToInt(value_str.utf8(), value);
218   return ret;
219 }
220
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();
226 }
227
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,
233                                       desired_width);
234   mandatory |= GetConstraintValue(constraints, true,
235                                   MediaStreamVideoSource::kMaxHeight,
236                                   desired_height);
237   if (mandatory)
238     return;
239
240   GetConstraintValue(constraints, false, MediaStreamVideoSource::kMaxWidth,
241                      desired_width);
242   GetConstraintValue(constraints, false, MediaStreamVideoSource::kMaxHeight,
243                      desired_height);
244 }
245
246 const media::VideoCaptureFormat& GetBestFormatBasedOnArea(
247     const media::VideoCaptureFormats& formats,
248     int area) {
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) {
255       best_diff = diff;
256       best_it = it;
257     }
258   }
259   return *best_it;
260 }
261
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);
275
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);
279
280   *capture_format = GetBestFormatBasedOnArea(
281       formats,
282       std::min(max_width, MediaStreamVideoSource::kDefaultWidth) *
283       std::min(max_height, MediaStreamVideoSource::kDefaultHeight));
284
285   max_frame_output_size->set_width(max_width);
286   max_frame_output_size->set_height(max_height);
287 }
288
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
292 // method.
293 void ReleaseOriginalFrame(
294     const scoped_refptr<media::VideoFrame>& frame) {
295 }
296
297 }  // anonymous namespace
298
299 // Helper class used for delivering video frames to all registered tracks
300 // on the IO-thread.
301 class MediaStreamVideoSource::FrameDeliverer : public VideoFrameDeliverer {
302  public:
303   FrameDeliverer(
304       const scoped_refptr<base::MessageLoopProxy>& io_message_loop)
305       : VideoFrameDeliverer(io_message_loop)  {
306   }
307
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
311   // |callbacks|.
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(
317         FROM_HERE,
318         base::Bind(
319             &FrameDeliverer::AddCallbackWithResolutionOnIO,
320             this, id, callback, max_frame_output_size));
321   }
322
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.
330
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
341       // fixed.
342       if (frame->format() != media::VideoFrame::NATIVE_TEXTURE) {
343         video_frame = media::VideoFrame::WrapVideoFrame(
344             frame,
345             output_rect,
346             output_rect.size(),
347             base::Bind(&ReleaseOriginalFrame, frame));
348       }
349     }
350     VideoFrameDeliverer::DeliverFrameOnIO(video_frame, format);
351   }
352
353  protected:
354   virtual ~FrameDeliverer() {
355   }
356
357   void AddCallbackWithResolutionOnIO(
358       void* id,
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);
368   }
369
370  private:
371   gfx::Size max_output_size_;
372 };
373
374 // static
375 MediaStreamVideoSource* MediaStreamVideoSource::GetVideoSource(
376     const blink::WebMediaStreamSource& source) {
377   return static_cast<MediaStreamVideoSource*>(source.extraData());
378 }
379
380 // static
381 bool MediaStreamVideoSource::IsConstraintSupported(const std::string& name) {
382   for (size_t i = 0; i < arraysize(kSupportedConstraints); ++i) {
383     if (kSupportedConstraints[i] == name)
384       return true;
385   }
386   return false;
387 }
388
389 MediaStreamVideoSource::MediaStreamVideoSource()
390     : state_(NEW),
391       frame_deliverer_(
392           new MediaStreamVideoSource::FrameDeliverer(
393               ChildProcess::current()->io_message_loop_proxy())),
394       weak_factory_(this) {
395 }
396
397 MediaStreamVideoSource::~MediaStreamVideoSource() {
398   DVLOG(3) << "~MediaStreamVideoSource()";
399 }
400
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);
410
411   requested_constraints_.push_back(
412       RequestedConstraints(track, frame_callback, constraints, callback));
413
414   switch (state_) {
415     case NEW: {
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);
420
421       int max_requested_height = 0;
422       GetConstraintValue(constraints, true, kMaxHeight, &max_requested_height);
423
424       state_ = RETRIEVING_CAPABILITIES;
425       GetCurrentSupportedFormats(
426           max_requested_width,
427           max_requested_height,
428           base::Bind(&MediaStreamVideoSource::OnSupportedFormats,
429                      weak_factory_.GetWeakPtr()));
430
431       break;
432     }
433     case STARTING:
434     case RETRIEVING_CAPABILITIES: {
435       // The |callback| will be triggered once the source has started or
436       // the capabilities have been retrieved.
437       break;
438     }
439     case ENDED:
440     case STARTED: {
441       // Currently, reconfiguring the source is not supported.
442       FinalizeAddTrack();
443     }
444   }
445 }
446
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());
452   tracks_.erase(it);
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);
456
457   if (tracks_.empty())
458     StopSource();
459 }
460
461 const scoped_refptr<base::MessageLoopProxy>&
462 MediaStreamVideoSource::io_message_loop() const {
463   return frame_deliverer_->io_message_loop();
464 }
465
466 void MediaStreamVideoSource::DoStopSource() {
467   DCHECK(CalledOnValidThread());
468   DVLOG(3) << "DoStopSource()";
469   if (state_ == ENDED)
470     return;
471   StopSourceImpl();
472   state_ = ENDED;
473   SetReadyState(blink::WebMediaStreamSource::ReadyStateEnded);
474 }
475
476 void MediaStreamVideoSource::OnSupportedFormats(
477     const media::VideoCaptureFormats& formats) {
478   DCHECK(CalledOnValidThread());
479   DCHECK_EQ(RETRIEVING_CAPABILITIES, state_);
480
481   supported_formats_ = formats;
482   if (!FindBestFormatWithConstraints(supported_formats_,
483                                      &current_format_,
484                                      &max_frame_output_size_,
485                                      &current_constraints_)) {
486     SetReadyState(blink::WebMediaStreamSource::ReadyStateEnded);
487     // This object can be deleted after calling FinalizeAddTrack. See comment
488     // in the header file.
489     FinalizeAddTrack();
490     return;
491   }
492
493   state_ = STARTING;
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;
498
499   media::VideoCaptureParams params;
500   params.requested_format = current_format_;
501   StartSourceImpl(
502       params,
503       base::Bind(&MediaStreamVideoSource::FrameDeliverer::DeliverFrameOnIO,
504                  frame_deliverer_));
505 }
506
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;
518
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());
527       return true;
528     }
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,
535                            best_format,
536                            max_frame_output_size);
537       *resulting_constraints= requested_constraints;
538       return true;
539     }
540   }
541   return false;
542 }
543
544 void MediaStreamVideoSource::OnStartDone(bool success) {
545   DCHECK(CalledOnValidThread());
546   DVLOG(3) << "OnStartDone({success =" << success << "})";
547   if (success) {
548     DCHECK_EQ(STARTING, state_);
549     state_ = STARTED;
550     SetReadyState(blink::WebMediaStreamSource::ReadyStateLive);
551   } else {
552     state_ = ENDED;
553     SetReadyState(blink::WebMediaStreamSource::ReadyStateEnded);
554     StopSourceImpl();
555   }
556
557   // This object can be deleted after calling FinalizeAddTrack. See comment in
558   // the header file.
559   FinalizeAddTrack();
560 }
561
562 void MediaStreamVideoSource::FinalizeAddTrack() {
563   media::VideoCaptureFormats formats;
564   formats.push_back(current_format_);
565
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.
574     bool success =
575         state_ == STARTED &&
576         ((!current_format_.IsValid() && !HasMandatoryConstraints(
577             it->constraints)) ||
578          !FilterFormats(it->constraints, formats).empty());
579     if (success) {
580       frame_deliverer_->AddCallback(it->track, it->frame_callback,
581                                     max_frame_output_size_);
582     }
583     DVLOG(3) << "FinalizeAddTrack() success " << success;
584     if (!it->callback.is_null())
585       it->callback.Run(this, success);
586   }
587 }
588
589 void MediaStreamVideoSource::SetReadyState(
590     blink::WebMediaStreamSource::ReadyState state) {
591   if (!owner().isNull()) {
592     owner().setReadyState(state);
593   }
594   for (std::vector<MediaStreamVideoTrack*>::iterator it = tracks_.begin();
595        it != tracks_.end(); ++it) {
596     (*it)->OnReadyStateChanged(state);
597   }
598 }
599
600 MediaStreamVideoSource::RequestedConstraints::RequestedConstraints(
601     MediaStreamVideoTrack* track,
602     const VideoCaptureDeliverFrameCB& frame_callback,
603     const blink::WebMediaConstraints& constraints,
604     const ConstraintsCallback& callback)
605     : track(track),
606       frame_callback(frame_callback),
607       constraints(constraints),
608       callback(callback) {
609 }
610
611 MediaStreamVideoSource::RequestedConstraints::~RequestedConstraints() {
612 }
613
614 }  // namespace content