OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright 2012 The WebRTC project authors. All Rights Reserved. | |
3 * | |
4 * Use of this source code is governed by a BSD-style license | |
5 * that can be found in the LICENSE file in the root of the source | |
6 * tree. An additional intellectual property rights grant can be found | |
7 * in the file PATENTS. All contributing project authors may | |
8 * be found in the AUTHORS file in the root of the source tree. | |
9 */ | |
10 | |
11 #include "webrtc/api/videocapturertracksource.h" | |
12 | |
13 #include <cstdlib> | |
14 #include <string> | |
15 #include <vector> | |
16 | |
17 #include "webrtc/api/mediaconstraintsinterface.h" | |
18 #include "webrtc/base/arraysize.h" | |
19 #include "webrtc/base/checks.h" | |
20 | |
21 using cricket::CaptureState; | |
22 using webrtc::MediaConstraintsInterface; | |
23 using webrtc::MediaSourceInterface; | |
24 | |
25 namespace { | |
26 | |
27 const double kRoundingTruncation = 0.0005; | |
28 | |
29 // Default resolution. If no constraint is specified, this is the resolution we | |
30 // will use. | |
31 static const cricket::VideoFormatPod kDefaultFormat = { | |
32 640, 480, FPS_TO_INTERVAL(30), cricket::FOURCC_ANY}; | |
33 | |
34 // List of formats used if the camera doesn't support capability enumeration. | |
35 static const cricket::VideoFormatPod kVideoFormats[] = { | |
36 {1920, 1080, FPS_TO_INTERVAL(30), cricket::FOURCC_ANY}, | |
37 {1280, 720, FPS_TO_INTERVAL(30), cricket::FOURCC_ANY}, | |
38 {960, 720, FPS_TO_INTERVAL(30), cricket::FOURCC_ANY}, | |
39 {640, 360, FPS_TO_INTERVAL(30), cricket::FOURCC_ANY}, | |
40 {640, 480, FPS_TO_INTERVAL(30), cricket::FOURCC_ANY}, | |
41 {320, 240, FPS_TO_INTERVAL(30), cricket::FOURCC_ANY}, | |
42 {320, 180, FPS_TO_INTERVAL(30), cricket::FOURCC_ANY}}; | |
43 | |
44 MediaSourceInterface::SourceState GetReadyState(cricket::CaptureState state) { | |
45 switch (state) { | |
46 case cricket::CS_STARTING: | |
47 return MediaSourceInterface::kInitializing; | |
48 case cricket::CS_RUNNING: | |
49 return MediaSourceInterface::kLive; | |
50 case cricket::CS_FAILED: | |
51 case cricket::CS_STOPPED: | |
52 return MediaSourceInterface::kEnded; | |
53 default: | |
54 RTC_NOTREACHED() << "GetReadyState unknown state"; | |
55 } | |
56 return MediaSourceInterface::kEnded; | |
57 } | |
58 | |
59 void SetUpperLimit(int new_limit, int* original_limit) { | |
60 if (*original_limit < 0 || new_limit < *original_limit) | |
61 *original_limit = new_limit; | |
62 } | |
63 | |
64 // Updates |format_upper_limit| from |constraint|. | |
65 // If constraint.maxFoo is smaller than format_upper_limit.foo, | |
66 // set format_upper_limit.foo to constraint.maxFoo. | |
67 void SetUpperLimitFromConstraint( | |
68 const MediaConstraintsInterface::Constraint& constraint, | |
69 cricket::VideoFormat* format_upper_limit) { | |
70 if (constraint.key == MediaConstraintsInterface::kMaxWidth) { | |
71 int value = rtc::FromString<int>(constraint.value); | |
72 SetUpperLimit(value, &(format_upper_limit->width)); | |
73 } else if (constraint.key == MediaConstraintsInterface::kMaxHeight) { | |
74 int value = rtc::FromString<int>(constraint.value); | |
75 SetUpperLimit(value, &(format_upper_limit->height)); | |
76 } | |
77 } | |
78 | |
79 // Fills |format_out| with the max width and height allowed by |constraints|. | |
80 void FromConstraintsForScreencast( | |
81 const MediaConstraintsInterface::Constraints& constraints, | |
82 cricket::VideoFormat* format_out) { | |
83 typedef MediaConstraintsInterface::Constraints::const_iterator | |
84 ConstraintsIterator; | |
85 | |
86 cricket::VideoFormat upper_limit(-1, -1, 0, 0); | |
87 for (ConstraintsIterator constraints_it = constraints.begin(); | |
88 constraints_it != constraints.end(); ++constraints_it) | |
89 SetUpperLimitFromConstraint(*constraints_it, &upper_limit); | |
90 | |
91 if (upper_limit.width >= 0) | |
92 format_out->width = upper_limit.width; | |
93 if (upper_limit.height >= 0) | |
94 format_out->height = upper_limit.height; | |
95 } | |
96 | |
97 // Returns true if |constraint| is fulfilled. |format_out| can differ from | |
98 // |format_in| if the format is changed by the constraint. Ie - the frame rate | |
99 // can be changed by setting maxFrameRate. | |
100 bool NewFormatWithConstraints( | |
101 const MediaConstraintsInterface::Constraint& constraint, | |
102 const cricket::VideoFormat& format_in, | |
103 bool mandatory, | |
104 cricket::VideoFormat* format_out) { | |
105 RTC_DCHECK(format_out != NULL); | |
106 *format_out = format_in; | |
107 | |
108 if (constraint.key == MediaConstraintsInterface::kMinWidth) { | |
109 int value = rtc::FromString<int>(constraint.value); | |
110 return (value <= format_in.width); | |
111 } else if (constraint.key == MediaConstraintsInterface::kMaxWidth) { | |
112 int value = rtc::FromString<int>(constraint.value); | |
113 return (value >= format_in.width); | |
114 } else if (constraint.key == MediaConstraintsInterface::kMinHeight) { | |
115 int value = rtc::FromString<int>(constraint.value); | |
116 return (value <= format_in.height); | |
117 } else if (constraint.key == MediaConstraintsInterface::kMaxHeight) { | |
118 int value = rtc::FromString<int>(constraint.value); | |
119 return (value >= format_in.height); | |
120 } else if (constraint.key == MediaConstraintsInterface::kMinFrameRate) { | |
121 int value = rtc::FromString<int>(constraint.value); | |
122 return (value <= cricket::VideoFormat::IntervalToFps(format_in.interval)); | |
123 } else if (constraint.key == MediaConstraintsInterface::kMaxFrameRate) { | |
124 int value = rtc::FromString<int>(constraint.value); | |
125 if (value == 0) { | |
126 if (mandatory) { | |
127 // TODO(ronghuawu): Convert the constraint value to float when sub-1fps | |
128 // is supported by the capturer. | |
129 return false; | |
130 } else { | |
131 value = 1; | |
132 } | |
133 } | |
134 if (value <= cricket::VideoFormat::IntervalToFps(format_in.interval)) | |
135 format_out->interval = cricket::VideoFormat::FpsToInterval(value); | |
136 return true; | |
137 } else if (constraint.key == MediaConstraintsInterface::kMinAspectRatio) { | |
138 double value = rtc::FromString<double>(constraint.value); | |
139 // The aspect ratio in |constraint.value| has been converted to a string and | |
140 // back to a double, so it may have a rounding error. | |
141 // E.g if the value 1/3 is converted to a string, the string will not have | |
142 // infinite length. | |
143 // We add a margin of 0.0005 which is high enough to detect the same aspect | |
144 // ratio but small enough to avoid matching wrong aspect ratios. | |
145 double ratio = static_cast<double>(format_in.width) / format_in.height; | |
146 return (value <= ratio + kRoundingTruncation); | |
147 } else if (constraint.key == MediaConstraintsInterface::kMaxAspectRatio) { | |
148 double value = rtc::FromString<double>(constraint.value); | |
149 double ratio = static_cast<double>(format_in.width) / format_in.height; | |
150 // Subtract 0.0005 to avoid rounding problems. Same as above. | |
151 const double kRoundingTruncation = 0.0005; | |
152 return (value >= ratio - kRoundingTruncation); | |
153 } else if (constraint.key == MediaConstraintsInterface::kNoiseReduction) { | |
154 // These are actually options, not constraints, so they can be satisfied | |
155 // regardless of the format. | |
156 return true; | |
157 } | |
158 LOG(LS_WARNING) << "Found unknown MediaStream constraint. Name:" | |
159 << constraint.key << " Value:" << constraint.value; | |
160 return false; | |
161 } | |
162 | |
163 // Removes cricket::VideoFormats from |formats| that don't meet |constraint|. | |
164 void FilterFormatsByConstraint( | |
165 const MediaConstraintsInterface::Constraint& constraint, | |
166 bool mandatory, | |
167 std::vector<cricket::VideoFormat>* formats) { | |
168 std::vector<cricket::VideoFormat>::iterator format_it = formats->begin(); | |
169 while (format_it != formats->end()) { | |
170 // Modify the format_it to fulfill the constraint if possible. | |
171 // Delete it otherwise. | |
172 if (!NewFormatWithConstraints(constraint, (*format_it), mandatory, | |
173 &(*format_it))) { | |
174 format_it = formats->erase(format_it); | |
175 } else { | |
176 ++format_it; | |
177 } | |
178 } | |
179 } | |
180 | |
181 // Returns a vector of cricket::VideoFormat that best match |constraints|. | |
182 std::vector<cricket::VideoFormat> FilterFormats( | |
183 const MediaConstraintsInterface::Constraints& mandatory, | |
184 const MediaConstraintsInterface::Constraints& optional, | |
185 const std::vector<cricket::VideoFormat>& supported_formats) { | |
186 typedef MediaConstraintsInterface::Constraints::const_iterator | |
187 ConstraintsIterator; | |
188 std::vector<cricket::VideoFormat> candidates = supported_formats; | |
189 | |
190 for (ConstraintsIterator constraints_it = mandatory.begin(); | |
191 constraints_it != mandatory.end(); ++constraints_it) | |
192 FilterFormatsByConstraint(*constraints_it, true, &candidates); | |
193 | |
194 if (candidates.size() == 0) | |
195 return candidates; | |
196 | |
197 // Ok - all mandatory checked and we still have a candidate. | |
198 // Let's try filtering using the optional constraints. | |
199 for (ConstraintsIterator constraints_it = optional.begin(); | |
200 constraints_it != optional.end(); ++constraints_it) { | |
201 std::vector<cricket::VideoFormat> current_candidates = candidates; | |
202 FilterFormatsByConstraint(*constraints_it, false, ¤t_candidates); | |
203 if (current_candidates.size() > 0) { | |
204 candidates = current_candidates; | |
205 } | |
206 } | |
207 | |
208 // We have done as good as we can to filter the supported resolutions. | |
209 return candidates; | |
210 } | |
211 | |
212 // Find the format that best matches the default video size. | |
213 // Constraints are optional and since the performance of a video call | |
214 // might be bad due to bitrate limitations, CPU, and camera performance, | |
215 // it is better to select a resolution that is as close as possible to our | |
216 // default and still meets the contraints. | |
217 const cricket::VideoFormat& GetBestCaptureFormat( | |
218 const std::vector<cricket::VideoFormat>& formats) { | |
219 RTC_DCHECK(formats.size() > 0); | |
220 | |
221 int default_area = kDefaultFormat.width * kDefaultFormat.height; | |
222 | |
223 std::vector<cricket::VideoFormat>::const_iterator it = formats.begin(); | |
224 std::vector<cricket::VideoFormat>::const_iterator best_it = formats.begin(); | |
225 int best_diff_area = std::abs(default_area - it->width * it->height); | |
226 int64_t best_diff_interval = kDefaultFormat.interval; | |
227 for (; it != formats.end(); ++it) { | |
228 int diff_area = std::abs(default_area - it->width * it->height); | |
229 int64_t diff_interval = std::abs(kDefaultFormat.interval - it->interval); | |
230 if (diff_area < best_diff_area || | |
231 (diff_area == best_diff_area && diff_interval < best_diff_interval)) { | |
232 best_diff_area = diff_area; | |
233 best_diff_interval = diff_interval; | |
234 best_it = it; | |
235 } | |
236 } | |
237 return *best_it; | |
238 } | |
239 | |
240 // Set |option| to the highest-priority value of |key| in the constraints. | |
241 // Return false if the key is mandatory, and the value is invalid. | |
242 bool ExtractOption(const MediaConstraintsInterface* all_constraints, | |
243 const std::string& key, | |
244 rtc::Optional<bool>* option) { | |
245 size_t mandatory = 0; | |
246 bool value; | |
247 if (FindConstraint(all_constraints, key, &value, &mandatory)) { | |
248 *option = rtc::Optional<bool>(value); | |
249 return true; | |
250 } | |
251 | |
252 return mandatory == 0; | |
253 } | |
254 | |
255 } // anonymous namespace | |
256 | |
257 namespace webrtc { | |
258 | |
259 rtc::scoped_refptr<VideoTrackSourceInterface> VideoCapturerTrackSource::Create( | |
260 rtc::Thread* worker_thread, | |
261 cricket::VideoCapturer* capturer, | |
262 const webrtc::MediaConstraintsInterface* constraints, | |
263 bool remote) { | |
264 RTC_DCHECK(worker_thread != NULL); | |
265 RTC_DCHECK(capturer != NULL); | |
266 rtc::scoped_refptr<VideoCapturerTrackSource> source( | |
267 new rtc::RefCountedObject<VideoCapturerTrackSource>(worker_thread, | |
268 capturer, remote)); | |
269 source->Initialize(constraints); | |
270 return source; | |
271 } | |
272 | |
273 rtc::scoped_refptr<VideoTrackSourceInterface> VideoCapturerTrackSource::Create( | |
274 rtc::Thread* worker_thread, | |
275 cricket::VideoCapturer* capturer, | |
276 bool remote) { | |
277 RTC_DCHECK(worker_thread != NULL); | |
278 RTC_DCHECK(capturer != NULL); | |
279 rtc::scoped_refptr<VideoCapturerTrackSource> source( | |
280 new rtc::RefCountedObject<VideoCapturerTrackSource>(worker_thread, | |
281 capturer, remote)); | |
282 source->Initialize(nullptr); | |
283 return source; | |
284 } | |
285 | |
286 VideoCapturerTrackSource::VideoCapturerTrackSource( | |
287 rtc::Thread* worker_thread, | |
288 cricket::VideoCapturer* capturer, | |
289 bool remote) | |
290 : VideoTrackSource(capturer, remote), | |
291 signaling_thread_(rtc::Thread::Current()), | |
292 worker_thread_(worker_thread), | |
293 video_capturer_(capturer), | |
294 started_(false) { | |
295 video_capturer_->SignalStateChange.connect( | |
296 this, &VideoCapturerTrackSource::OnStateChange); | |
297 } | |
298 | |
299 VideoCapturerTrackSource::~VideoCapturerTrackSource() { | |
300 video_capturer_->SignalStateChange.disconnect(this); | |
301 Stop(); | |
302 } | |
303 | |
304 void VideoCapturerTrackSource::Initialize( | |
305 const webrtc::MediaConstraintsInterface* constraints) { | |
306 std::vector<cricket::VideoFormat> formats = | |
307 *video_capturer_->GetSupportedFormats(); | |
308 if (formats.empty()) { | |
309 if (video_capturer_->IsScreencast()) { | |
310 // The screen capturer can accept any resolution and we will derive the | |
311 // format from the constraints if any. | |
312 // Note that this only affects tab capturing, not desktop capturing, | |
313 // since the desktop capturer does not respect the VideoFormat passed in. | |
314 formats.push_back(cricket::VideoFormat(kDefaultFormat)); | |
315 } else { | |
316 // The VideoCapturer implementation doesn't support capability | |
317 // enumeration. We need to guess what the camera supports. | |
318 for (uint32_t i = 0; i < arraysize(kVideoFormats); ++i) { | |
319 formats.push_back(cricket::VideoFormat(kVideoFormats[i])); | |
320 } | |
321 } | |
322 } | |
323 | |
324 if (constraints) { | |
325 MediaConstraintsInterface::Constraints mandatory_constraints = | |
326 constraints->GetMandatory(); | |
327 MediaConstraintsInterface::Constraints optional_constraints; | |
328 optional_constraints = constraints->GetOptional(); | |
329 | |
330 if (video_capturer_->IsScreencast()) { | |
331 // Use the maxWidth and maxHeight allowed by constraints for screencast. | |
332 FromConstraintsForScreencast(mandatory_constraints, &(formats[0])); | |
333 } | |
334 | |
335 formats = | |
336 FilterFormats(mandatory_constraints, optional_constraints, formats); | |
337 } | |
338 | |
339 if (formats.size() == 0) { | |
340 LOG(LS_WARNING) << "Failed to find a suitable video format."; | |
341 SetState(kEnded); | |
342 return; | |
343 } | |
344 | |
345 if (!ExtractOption(constraints, MediaConstraintsInterface::kNoiseReduction, | |
346 &needs_denoising_)) { | |
347 LOG(LS_WARNING) << "Invalid mandatory value for" | |
348 << MediaConstraintsInterface::kNoiseReduction; | |
349 SetState(kEnded); | |
350 return; | |
351 } | |
352 | |
353 format_ = GetBestCaptureFormat(formats); | |
354 // Start the camera with our best guess. | |
355 if (!worker_thread_->Invoke<bool>( | |
356 RTC_FROM_HERE, rtc::Bind(&cricket::VideoCapturer::StartCapturing, | |
357 video_capturer_.get(), format_))) { | |
358 SetState(kEnded); | |
359 return; | |
360 } | |
361 started_ = true; | |
362 // Initialize hasn't succeeded until a successful state change has occurred. | |
363 } | |
364 | |
365 bool VideoCapturerTrackSource::GetStats(Stats* stats) { | |
366 return video_capturer_->GetInputSize(&stats->input_width, | |
367 &stats->input_height); | |
368 } | |
369 | |
370 void VideoCapturerTrackSource::Stop() { | |
371 if (!started_) { | |
372 return; | |
373 } | |
374 started_ = false; | |
375 worker_thread_->Invoke<void>( | |
376 RTC_FROM_HERE, | |
377 rtc::Bind(&cricket::VideoCapturer::Stop, video_capturer_.get())); | |
378 } | |
379 | |
380 // OnStateChange listens to the cricket::VideoCapturer::SignalStateChange. | |
381 void VideoCapturerTrackSource::OnStateChange( | |
382 cricket::VideoCapturer* capturer, | |
383 cricket::CaptureState capture_state) { | |
384 if (rtc::Thread::Current() != signaling_thread_) { | |
385 invoker_.AsyncInvoke<void>( | |
386 RTC_FROM_HERE, signaling_thread_, | |
387 rtc::Bind(&VideoCapturerTrackSource::OnStateChange, this, capturer, | |
388 capture_state)); | |
389 return; | |
390 } | |
391 | |
392 if (capturer == video_capturer_.get()) { | |
393 SetState(GetReadyState(capture_state)); | |
394 } | |
395 } | |
396 | |
397 } // namespace webrtc | |
OLD | NEW |