blob: 653e7934a390a890b5ec6da7c314e66fb520d900 [file] [log] [blame]
jlmiller@webrtc.org5f93d0a2015-01-20 21:36:13 +00001/*
2 * libjingle
3 * Copyright 2010 Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
henrike@webrtc.org28e20752013-07-10 00:45:36 +000028// Implementation file of class VideoCapturer.
29
30#include "talk/media/base/videocapturer.h"
31
32#include <algorithm>
33
henrike@webrtc.org28e20752013-07-10 00:45:36 +000034#include "libyuv/scale_argb.h"
buildbot@webrtc.orga09a9992014-08-13 17:26:08 +000035#include "talk/media/base/videoframefactory.h"
36#include "talk/media/base/videoprocessor.h"
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +000037#include "webrtc/base/common.h"
38#include "webrtc/base/logging.h"
39#include "webrtc/base/systeminfo.h"
henrike@webrtc.org28e20752013-07-10 00:45:36 +000040
41#if defined(HAVE_WEBRTC_VIDEO)
42#include "talk/media/webrtc/webrtcvideoframe.h"
buildbot@webrtc.org4f0d4012014-08-07 04:47:36 +000043#include "talk/media/webrtc/webrtcvideoframefactory.h"
henrike@webrtc.org28e20752013-07-10 00:45:36 +000044#endif // HAVE_WEBRTC_VIDEO
45
henrike@webrtc.org28e20752013-07-10 00:45:36 +000046namespace cricket {
47
48namespace {
49
50// TODO(thorcarpenter): This is a BIG hack to flush the system with black
51// frames. Frontends should coordinate to update the video state of a muted
52// user. When all frontends to this consider removing the black frame business.
53const int kNumBlackFramesOnMute = 30;
54
55// MessageHandler constants.
56enum {
57 MSG_DO_PAUSE = 0,
58 MSG_DO_UNPAUSE,
59 MSG_STATE_CHANGE
60};
61
62static const int64 kMaxDistance = ~(static_cast<int64>(1) << 63);
henrike@webrtc.orgf5bebd42014-04-04 18:39:07 +000063#ifdef LINUX
henrike@webrtc.org28e20752013-07-10 00:45:36 +000064static const int kYU12Penalty = 16; // Needs to be higher than MJPG index.
henrike@webrtc.orgf5bebd42014-04-04 18:39:07 +000065#endif
henrike@webrtc.org28e20752013-07-10 00:45:36 +000066static const int kDefaultScreencastFps = 5;
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +000067typedef rtc::TypedMessageData<CaptureState> StateChangeParams;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000068
henrike@webrtc.org704bf9e2014-02-27 17:52:04 +000069// Limit stats data collections to ~20 seconds of 30fps data before dropping
70// old data in case stats aren't reset for long periods of time.
71static const size_t kMaxAccumulatorSize = 600;
72
henrike@webrtc.org28e20752013-07-10 00:45:36 +000073} // namespace
74
75/////////////////////////////////////////////////////////////////////
76// Implementation of struct CapturedFrame
77/////////////////////////////////////////////////////////////////////
78CapturedFrame::CapturedFrame()
79 : width(0),
80 height(0),
81 fourcc(0),
82 pixel_width(0),
83 pixel_height(0),
84 elapsed_time(0),
85 time_stamp(0),
86 data_size(0),
87 rotation(0),
88 data(NULL) {}
89
90// TODO(fbarchard): Remove this function once lmimediaengine stops using it.
91bool CapturedFrame::GetDataSize(uint32* size) const {
92 if (!size || data_size == CapturedFrame::kUnknownDataSize) {
93 return false;
94 }
95 *size = data_size;
96 return true;
97}
98
guoweis@webrtc.org6c930c72015-02-09 01:28:12 +000099webrtc::VideoRotation CapturedFrame::GetRotation() const {
100 ASSERT(rotation == 0 || rotation == 90 || rotation == 180 || rotation == 270);
101 return static_cast<webrtc::VideoRotation>(rotation);
102}
103
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000104/////////////////////////////////////////////////////////////////////
105// Implementation of class VideoCapturer
106/////////////////////////////////////////////////////////////////////
henrike@webrtc.org704bf9e2014-02-27 17:52:04 +0000107VideoCapturer::VideoCapturer()
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000108 : thread_(rtc::Thread::Current()),
henrike@webrtc.org704bf9e2014-02-27 17:52:04 +0000109 adapt_frame_drops_data_(kMaxAccumulatorSize),
110 effect_frame_drops_data_(kMaxAccumulatorSize),
guoweis@webrtc.org1226e922015-02-11 18:37:54 +0000111 frame_time_data_(kMaxAccumulatorSize),
112 apply_rotation_(true) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000113 Construct();
114}
115
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000116VideoCapturer::VideoCapturer(rtc::Thread* thread)
henrike@webrtc.org704bf9e2014-02-27 17:52:04 +0000117 : thread_(thread),
118 adapt_frame_drops_data_(kMaxAccumulatorSize),
119 effect_frame_drops_data_(kMaxAccumulatorSize),
guoweis@webrtc.org1226e922015-02-11 18:37:54 +0000120 frame_time_data_(kMaxAccumulatorSize),
121 apply_rotation_(true) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000122 Construct();
123}
124
125void VideoCapturer::Construct() {
126 ClearAspectRatio();
127 enable_camera_list_ = false;
mallinath@webrtc.org1b15f422013-09-06 22:56:28 +0000128 square_pixel_aspect_ratio_ = false;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000129 capture_state_ = CS_STOPPED;
130 SignalFrameCaptured.connect(this, &VideoCapturer::OnFrameCaptured);
131 scaled_width_ = 0;
132 scaled_height_ = 0;
wu@webrtc.orgcadf9042013-08-30 21:24:16 +0000133 screencast_max_pixels_ = 0;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000134 muted_ = false;
135 black_frame_count_down_ = kNumBlackFramesOnMute;
henrike@webrtc.orga7b98182014-02-21 15:51:43 +0000136 enable_video_adapter_ = true;
henrike@webrtc.org704bf9e2014-02-27 17:52:04 +0000137 adapt_frame_drops_ = 0;
138 effect_frame_drops_ = 0;
139 previous_frame_time_ = 0.0;
buildbot@webrtc.orgc3df61e2014-08-13 23:57:23 +0000140#ifdef HAVE_WEBRTC_VIDEO
141 // There are lots of video capturers out there that don't call
142 // set_frame_factory. We can either go change all of them, or we
143 // can set this default.
144 // TODO(pthatcher): Remove this hack and require the frame factory
145 // to be passed in the constructor.
146 set_frame_factory(new WebRtcVideoFrameFactory());
147#endif
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000148}
149
150const std::vector<VideoFormat>* VideoCapturer::GetSupportedFormats() const {
151 return &filtered_supported_formats_;
152}
153
154bool VideoCapturer::StartCapturing(const VideoFormat& capture_format) {
henrike@webrtc.org704bf9e2014-02-27 17:52:04 +0000155 previous_frame_time_ = frame_length_time_reporter_.TimerNow();
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000156 CaptureState result = Start(capture_format);
157 const bool success = (result == CS_RUNNING) || (result == CS_STARTING);
158 if (!success) {
159 return false;
160 }
161 if (result == CS_RUNNING) {
162 SetCaptureState(result);
163 }
164 return true;
165}
166
167void VideoCapturer::UpdateAspectRatio(int ratio_w, int ratio_h) {
168 if (ratio_w == 0 || ratio_h == 0) {
169 LOG(LS_WARNING) << "UpdateAspectRatio ignored invalid ratio: "
170 << ratio_w << "x" << ratio_h;
171 return;
172 }
173 ratio_w_ = ratio_w;
174 ratio_h_ = ratio_h;
175}
176
177void VideoCapturer::ClearAspectRatio() {
178 ratio_w_ = 0;
179 ratio_h_ = 0;
180}
181
182// Override this to have more control of how your device is started/stopped.
183bool VideoCapturer::Pause(bool pause) {
184 if (pause) {
185 if (capture_state() == CS_PAUSED) {
186 return true;
187 }
188 bool is_running = capture_state() == CS_STARTING ||
189 capture_state() == CS_RUNNING;
190 if (!is_running) {
191 LOG(LS_ERROR) << "Cannot pause a stopped camera.";
192 return false;
193 }
194 LOG(LS_INFO) << "Pausing a camera.";
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000195 rtc::scoped_ptr<VideoFormat> capture_format_when_paused(
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000196 capture_format_ ? new VideoFormat(*capture_format_) : NULL);
197 Stop();
198 SetCaptureState(CS_PAUSED);
199 // If you override this function be sure to restore the capture format
200 // after calling Stop().
201 SetCaptureFormat(capture_format_when_paused.get());
202 } else { // Unpause.
203 if (capture_state() != CS_PAUSED) {
204 LOG(LS_WARNING) << "Cannot unpause a camera that hasn't been paused.";
205 return false;
206 }
207 if (!capture_format_) {
208 LOG(LS_ERROR) << "Missing capture_format_, cannot unpause a camera.";
209 return false;
210 }
211 if (muted_) {
212 LOG(LS_WARNING) << "Camera cannot be unpaused while muted.";
213 return false;
214 }
215 LOG(LS_INFO) << "Unpausing a camera.";
216 if (!Start(*capture_format_)) {
217 LOG(LS_ERROR) << "Camera failed to start when unpausing.";
218 return false;
219 }
220 }
221 return true;
222}
223
224bool VideoCapturer::Restart(const VideoFormat& capture_format) {
225 if (!IsRunning()) {
226 return StartCapturing(capture_format);
227 }
228
229 if (GetCaptureFormat() != NULL && *GetCaptureFormat() == capture_format) {
230 // The reqested format is the same; nothing to do.
231 return true;
232 }
233
234 Stop();
235 return StartCapturing(capture_format);
236}
237
238bool VideoCapturer::MuteToBlackThenPause(bool muted) {
239 if (muted == IsMuted()) {
240 return true;
241 }
242
243 LOG(LS_INFO) << (muted ? "Muting" : "Unmuting") << " this video capturer.";
244 muted_ = muted; // Do this before calling Pause().
245 if (muted) {
246 // Reset black frame count down.
247 black_frame_count_down_ = kNumBlackFramesOnMute;
248 // Following frames will be overritten with black, then the camera will be
249 // paused.
250 return true;
251 }
252 // Start the camera.
253 thread_->Clear(this, MSG_DO_PAUSE);
254 return Pause(false);
255}
256
Guo-wei Shieh64c1e8c2015-04-01 15:33:06 -0700257// Note that the last caller decides whether rotation should be applied if there
258// are multiple send streams using the same camera.
guoweis@webrtc.org1226e922015-02-11 18:37:54 +0000259bool VideoCapturer::SetApplyRotation(bool enable) {
260 apply_rotation_ = enable;
261 if (frame_factory_) {
262 frame_factory_->SetApplyRotation(apply_rotation_);
263 }
264 return true;
265}
266
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000267void VideoCapturer::SetSupportedFormats(
268 const std::vector<VideoFormat>& formats) {
269 supported_formats_ = formats;
270 UpdateFilteredSupportedFormats();
271}
272
273bool VideoCapturer::GetBestCaptureFormat(const VideoFormat& format,
274 VideoFormat* best_format) {
275 // TODO(fbarchard): Directly support max_format.
276 UpdateFilteredSupportedFormats();
277 const std::vector<VideoFormat>* supported_formats = GetSupportedFormats();
278
279 if (supported_formats->empty()) {
280 return false;
281 }
282 LOG(LS_INFO) << " Capture Requested " << format.ToString();
283 int64 best_distance = kMaxDistance;
284 std::vector<VideoFormat>::const_iterator best = supported_formats->end();
285 std::vector<VideoFormat>::const_iterator i;
286 for (i = supported_formats->begin(); i != supported_formats->end(); ++i) {
287 int64 distance = GetFormatDistance(format, *i);
288 // TODO(fbarchard): Reduce to LS_VERBOSE if/when camera capture is
289 // relatively bug free.
290 LOG(LS_INFO) << " Supported " << i->ToString() << " distance " << distance;
291 if (distance < best_distance) {
292 best_distance = distance;
293 best = i;
294 }
295 }
296 if (supported_formats->end() == best) {
297 LOG(LS_ERROR) << " No acceptable camera format found";
298 return false;
299 }
300
301 if (best_format) {
302 best_format->width = best->width;
303 best_format->height = best->height;
304 best_format->fourcc = best->fourcc;
sergeyu@chromium.org4b26e2e2014-01-15 23:15:54 +0000305 best_format->interval = best->interval;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000306 LOG(LS_INFO) << " Best " << best_format->ToString() << " Interval "
307 << best_format->interval << " distance " << best_distance;
308 }
309 return true;
310}
311
312void VideoCapturer::AddVideoProcessor(VideoProcessor* video_processor) {
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000313 rtc::CritScope cs(&crit_);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000314 ASSERT(std::find(video_processors_.begin(), video_processors_.end(),
315 video_processor) == video_processors_.end());
316 video_processors_.push_back(video_processor);
317}
318
319bool VideoCapturer::RemoveVideoProcessor(VideoProcessor* video_processor) {
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000320 rtc::CritScope cs(&crit_);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000321 VideoProcessors::iterator found = std::find(
322 video_processors_.begin(), video_processors_.end(), video_processor);
323 if (found == video_processors_.end()) {
324 return false;
325 }
326 video_processors_.erase(found);
327 return true;
328}
329
330void VideoCapturer::ConstrainSupportedFormats(const VideoFormat& max_format) {
331 max_format_.reset(new VideoFormat(max_format));
332 LOG(LS_VERBOSE) << " ConstrainSupportedFormats " << max_format.ToString();
333 UpdateFilteredSupportedFormats();
334}
335
336std::string VideoCapturer::ToString(const CapturedFrame* captured_frame) const {
337 std::string fourcc_name = GetFourccName(captured_frame->fourcc) + " ";
338 for (std::string::const_iterator i = fourcc_name.begin();
339 i < fourcc_name.end(); ++i) {
340 // Test character is printable; Avoid isprint() which asserts on negatives.
341 if (*i < 32 || *i >= 127) {
342 fourcc_name = "";
343 break;
344 }
345 }
346
347 std::ostringstream ss;
348 ss << fourcc_name << captured_frame->width << "x" << captured_frame->height
sergeyu@chromium.org4b26e2e2014-01-15 23:15:54 +0000349 << "x" << VideoFormat::IntervalToFpsFloat(captured_frame->elapsed_time);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000350 return ss.str();
351}
352
guoweis@webrtc.org1226e922015-02-11 18:37:54 +0000353void VideoCapturer::set_frame_factory(VideoFrameFactory* frame_factory) {
354 frame_factory_.reset(frame_factory);
355 if (frame_factory) {
356 frame_factory->SetApplyRotation(apply_rotation_);
357 }
358}
359
henrike@webrtc.org704bf9e2014-02-27 17:52:04 +0000360void VideoCapturer::GetStats(VariableInfo<int>* adapt_drops_stats,
361 VariableInfo<int>* effect_drops_stats,
buildbot@webrtc.org0b53bd22014-05-06 17:12:36 +0000362 VariableInfo<double>* frame_time_stats,
363 VideoFormat* last_captured_frame_format) {
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000364 rtc::CritScope cs(&frame_stats_crit_);
henrike@webrtc.org704bf9e2014-02-27 17:52:04 +0000365 GetVariableSnapshot(adapt_frame_drops_data_, adapt_drops_stats);
366 GetVariableSnapshot(effect_frame_drops_data_, effect_drops_stats);
367 GetVariableSnapshot(frame_time_data_, frame_time_stats);
buildbot@webrtc.org0b53bd22014-05-06 17:12:36 +0000368 *last_captured_frame_format = last_captured_frame_format_;
henrike@webrtc.org704bf9e2014-02-27 17:52:04 +0000369
370 adapt_frame_drops_data_.Reset();
371 effect_frame_drops_data_.Reset();
372 frame_time_data_.Reset();
373}
374
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000375void VideoCapturer::OnFrameCaptured(VideoCapturer*,
376 const CapturedFrame* captured_frame) {
377 if (muted_) {
378 if (black_frame_count_down_ == 0) {
379 thread_->Post(this, MSG_DO_PAUSE, NULL);
380 } else {
381 --black_frame_count_down_;
382 }
383 }
384
385 if (SignalVideoFrame.is_empty()) {
386 return;
387 }
braveyao@webrtc.org086c8d52014-12-22 05:46:42 +0000388
389 // Use a temporary buffer to scale
390 rtc::scoped_ptr<uint8[]> scale_buffer;
391
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000392 if (IsScreencast()) {
393 int scaled_width, scaled_height;
wu@webrtc.orgcadf9042013-08-30 21:24:16 +0000394 if (screencast_max_pixels_ > 0) {
395 ComputeScaleMaxPixels(captured_frame->width, captured_frame->height,
396 screencast_max_pixels_, &scaled_width, &scaled_height);
397 } else {
398 int desired_screencast_fps = capture_format_.get() ?
399 VideoFormat::IntervalToFps(capture_format_->interval) :
400 kDefaultScreencastFps;
401 ComputeScale(captured_frame->width, captured_frame->height,
402 desired_screencast_fps, &scaled_width, &scaled_height);
403 }
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000404
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000405 if (FOURCC_ARGB == captured_frame->fourcc &&
sergeyu@chromium.org0be6aa02013-08-23 23:21:25 +0000406 (scaled_width != captured_frame->width ||
mallinath@webrtc.org1b15f422013-09-06 22:56:28 +0000407 scaled_height != captured_frame->height)) {
408 if (scaled_width != scaled_width_ || scaled_height != scaled_height_) {
409 LOG(LS_INFO) << "Scaling Screencast from "
410 << captured_frame->width << "x"
411 << captured_frame->height << " to "
412 << scaled_width << "x" << scaled_height;
413 scaled_width_ = scaled_width;
414 scaled_height_ = scaled_height;
415 }
416 CapturedFrame* modified_frame =
417 const_cast<CapturedFrame*>(captured_frame);
braveyao@webrtc.org086c8d52014-12-22 05:46:42 +0000418 const int modified_frame_size = scaled_width * scaled_height * 4;
419 scale_buffer.reset(new uint8[modified_frame_size]);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000420 // Compute new width such that width * height is less than maximum but
421 // maintains original captured frame aspect ratio.
422 // Round down width to multiple of 4 so odd width won't round up beyond
423 // maximum, and so chroma channel is even width to simplify spatial
424 // resampling.
425 libyuv::ARGBScale(reinterpret_cast<const uint8*>(captured_frame->data),
426 captured_frame->width * 4, captured_frame->width,
427 captured_frame->height,
braveyao@webrtc.org086c8d52014-12-22 05:46:42 +0000428 scale_buffer.get(),
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000429 scaled_width * 4, scaled_width, scaled_height,
430 libyuv::kFilterBilinear);
mallinath@webrtc.org1b15f422013-09-06 22:56:28 +0000431 modified_frame->width = scaled_width;
432 modified_frame->height = scaled_height;
433 modified_frame->data_size = scaled_width * 4 * scaled_height;
braveyao@webrtc.org086c8d52014-12-22 05:46:42 +0000434 modified_frame->data = scale_buffer.get();
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000435 }
436 }
mallinath@webrtc.org1b15f422013-09-06 22:56:28 +0000437
438 const int kYuy2Bpp = 2;
439 const int kArgbBpp = 4;
440 // TODO(fbarchard): Make a helper function to adjust pixels to square.
441 // TODO(fbarchard): Hook up experiment to scaling.
442 // TODO(fbarchard): Avoid scale and convert if muted.
443 // Temporary buffer is scoped here so it will persist until i420_frame.Init()
444 // makes a copy of the frame, converting to I420.
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000445 rtc::scoped_ptr<uint8[]> temp_buffer;
mallinath@webrtc.org1b15f422013-09-06 22:56:28 +0000446 // YUY2 can be scaled vertically using an ARGB scaler. Aspect ratio is only
447 // a problem on OSX. OSX always converts webcams to YUY2 or UYVY.
448 bool can_scale =
449 FOURCC_YUY2 == CanonicalFourCC(captured_frame->fourcc) ||
450 FOURCC_UYVY == CanonicalFourCC(captured_frame->fourcc);
451
452 // If pixels are not square, optionally use vertical scaling to make them
453 // square. Square pixels simplify the rest of the pipeline, including
454 // effects and rendering.
455 if (can_scale && square_pixel_aspect_ratio_ &&
456 captured_frame->pixel_width != captured_frame->pixel_height) {
457 int scaled_width, scaled_height;
458 // modified_frame points to the captured_frame but with const casted away
459 // so it can be modified.
460 CapturedFrame* modified_frame = const_cast<CapturedFrame*>(captured_frame);
461 // Compute the frame size that makes pixels square pixel aspect ratio.
462 ComputeScaleToSquarePixels(captured_frame->width, captured_frame->height,
463 captured_frame->pixel_width,
464 captured_frame->pixel_height,
465 &scaled_width, &scaled_height);
466
467 if (scaled_width != scaled_width_ || scaled_height != scaled_height_) {
468 LOG(LS_INFO) << "Scaling WebCam from "
469 << captured_frame->width << "x"
470 << captured_frame->height << " to "
471 << scaled_width << "x" << scaled_height
472 << " for PAR "
473 << captured_frame->pixel_width << "x"
474 << captured_frame->pixel_height;
475 scaled_width_ = scaled_width;
476 scaled_height_ = scaled_height;
477 }
478 const int modified_frame_size = scaled_width * scaled_height * kYuy2Bpp;
479 uint8* temp_buffer_data;
480 // Pixels are wide and short; Increasing height. Requires temporary buffer.
481 if (scaled_height > captured_frame->height) {
482 temp_buffer.reset(new uint8[modified_frame_size]);
483 temp_buffer_data = temp_buffer.get();
484 } else {
485 // Pixels are narrow and tall; Decreasing height. Scale will be done
486 // in place.
487 temp_buffer_data = reinterpret_cast<uint8*>(captured_frame->data);
488 }
489
490 // Use ARGBScaler to vertically scale the YUY2 image, adjusting for 16 bpp.
491 libyuv::ARGBScale(reinterpret_cast<const uint8*>(captured_frame->data),
492 captured_frame->width * kYuy2Bpp, // Stride for YUY2.
493 captured_frame->width * kYuy2Bpp / kArgbBpp, // Width.
494 abs(captured_frame->height), // Height.
495 temp_buffer_data,
496 scaled_width * kYuy2Bpp, // Stride for YUY2.
497 scaled_width * kYuy2Bpp / kArgbBpp, // Width.
498 abs(scaled_height), // New height.
499 libyuv::kFilterBilinear);
500 modified_frame->width = scaled_width;
501 modified_frame->height = scaled_height;
502 modified_frame->pixel_width = 1;
503 modified_frame->pixel_height = 1;
504 modified_frame->data_size = modified_frame_size;
505 modified_frame->data = temp_buffer_data;
506 }
mallinath@webrtc.org1b15f422013-09-06 22:56:28 +0000507
508 // Size to crop captured frame to. This adjusts the captured frames
509 // aspect ratio to match the final view aspect ratio, considering pixel
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000510 // aspect ratio and rotation. The final size may be scaled down by video
511 // adapter to better match ratio_w_ x ratio_h_.
512 // Note that abs() of frame height is passed in, because source may be
513 // inverted, but output will be positive.
magjed@webrtc.orgf58b4552014-11-19 18:09:14 +0000514 int cropped_width = captured_frame->width;
515 int cropped_height = captured_frame->height;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000516
517 // TODO(fbarchard): Improve logic to pad or crop.
518 // MJPG can crop vertically, but not horizontally. This logic disables crop.
519 // Alternatively we could pad the image with black, or implement a 2 step
520 // crop.
521 bool can_crop = true;
522 if (captured_frame->fourcc == FOURCC_MJPG) {
523 float cam_aspect = static_cast<float>(captured_frame->width) /
524 static_cast<float>(captured_frame->height);
525 float view_aspect = static_cast<float>(ratio_w_) /
526 static_cast<float>(ratio_h_);
527 can_crop = cam_aspect <= view_aspect;
528 }
529 if (can_crop && !IsScreencast()) {
530 // TODO(ronghuawu): The capturer should always produce the native
531 // resolution and the cropping should be done in downstream code.
532 ComputeCrop(ratio_w_, ratio_h_, captured_frame->width,
533 abs(captured_frame->height), captured_frame->pixel_width,
534 captured_frame->pixel_height, captured_frame->rotation,
magjed@webrtc.orgf58b4552014-11-19 18:09:14 +0000535 &cropped_width, &cropped_height);
536 }
537
538 int adapted_width = cropped_width;
539 int adapted_height = cropped_height;
540 if (enable_video_adapter_ && !IsScreencast()) {
541 const VideoFormat adapted_format =
542 video_adapter_.AdaptFrameResolution(cropped_width, cropped_height);
543 if (adapted_format.IsSize0x0()) {
544 // VideoAdapter dropped the frame.
545 ++adapt_frame_drops_;
546 return;
547 }
548 adapted_width = adapted_format.width;
549 adapted_height = adapted_format.height;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000550 }
551
buildbot@webrtc.org4f0d4012014-08-07 04:47:36 +0000552 if (!frame_factory_) {
553 LOG(LS_ERROR) << "No video frame factory.";
554 return;
555 }
556
magjed@webrtc.orgf58b4552014-11-19 18:09:14 +0000557 rtc::scoped_ptr<VideoFrame> adapted_frame(
558 frame_factory_->CreateAliasedFrame(captured_frame,
559 cropped_width, cropped_height,
560 adapted_width, adapted_height));
561
562 if (!adapted_frame) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000563 // TODO(fbarchard): LOG more information about captured frame attributes.
564 LOG(LS_ERROR) << "Couldn't convert to I420! "
565 << "From " << ToString(captured_frame) << " To "
magjed@webrtc.orgf58b4552014-11-19 18:09:14 +0000566 << cropped_width << " x " << cropped_height;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000567 return;
568 }
mallinath@webrtc.org67ee6b92014-02-03 16:57:16 +0000569
magjed@webrtc.orgf58b4552014-11-19 18:09:14 +0000570 if (!muted_ && !ApplyProcessors(adapted_frame.get())) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000571 // Processor dropped the frame.
henrike@webrtc.org704bf9e2014-02-27 17:52:04 +0000572 ++effect_frame_drops_;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000573 return;
574 }
pbos@webrtc.orgea894952015-02-27 08:56:14 +0000575 if (muted_) {
buildbot@webrtc.org4f0d4012014-08-07 04:47:36 +0000576 // TODO(pthatcher): Use frame_factory_->CreateBlackFrame() instead.
mallinath@webrtc.org67ee6b92014-02-03 16:57:16 +0000577 adapted_frame->SetToBlack();
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000578 }
magjed@webrtc.orgf58b4552014-11-19 18:09:14 +0000579 SignalVideoFrame(this, adapted_frame.get());
henrike@webrtc.org704bf9e2014-02-27 17:52:04 +0000580
buildbot@webrtc.org0b53bd22014-05-06 17:12:36 +0000581 UpdateStats(captured_frame);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000582}
583
584void VideoCapturer::SetCaptureState(CaptureState state) {
585 if (state == capture_state_) {
586 // Don't trigger a state changed callback if the state hasn't changed.
587 return;
588 }
589 StateChangeParams* state_params = new StateChangeParams(state);
590 capture_state_ = state;
591 thread_->Post(this, MSG_STATE_CHANGE, state_params);
592}
593
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000594void VideoCapturer::OnMessage(rtc::Message* message) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000595 switch (message->message_id) {
596 case MSG_STATE_CHANGE: {
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000597 rtc::scoped_ptr<StateChangeParams> p(
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000598 static_cast<StateChangeParams*>(message->pdata));
599 SignalStateChange(this, p->data());
600 break;
601 }
602 case MSG_DO_PAUSE: {
603 Pause(true);
604 break;
605 }
606 case MSG_DO_UNPAUSE: {
607 Pause(false);
608 break;
609 }
610 default: {
611 ASSERT(false);
612 }
613 }
614}
615
616// Get the distance between the supported and desired formats.
617// Prioritization is done according to this algorithm:
618// 1) Width closeness. If not same, we prefer wider.
619// 2) Height closeness. If not same, we prefer higher.
620// 3) Framerate closeness. If not same, we prefer faster.
621// 4) Compression. If desired format has a specific fourcc, we need exact match;
622// otherwise, we use preference.
623int64 VideoCapturer::GetFormatDistance(const VideoFormat& desired,
624 const VideoFormat& supported) {
625 int64 distance = kMaxDistance;
626
627 // Check fourcc.
628 uint32 supported_fourcc = CanonicalFourCC(supported.fourcc);
629 int64 delta_fourcc = kMaxDistance;
630 if (FOURCC_ANY == desired.fourcc) {
631 // Any fourcc is OK for the desired. Use preference to find best fourcc.
632 std::vector<uint32> preferred_fourccs;
633 if (!GetPreferredFourccs(&preferred_fourccs)) {
634 return distance;
635 }
636
637 for (size_t i = 0; i < preferred_fourccs.size(); ++i) {
638 if (supported_fourcc == CanonicalFourCC(preferred_fourccs[i])) {
639 delta_fourcc = i;
640#ifdef LINUX
641 // For HD avoid YU12 which is a software conversion and has 2 bugs
642 // b/7326348 b/6960899. Reenable when fixed.
643 if (supported.height >= 720 && (supported_fourcc == FOURCC_YU12 ||
644 supported_fourcc == FOURCC_YV12)) {
645 delta_fourcc += kYU12Penalty;
646 }
647#endif
648 break;
649 }
650 }
651 } else if (supported_fourcc == CanonicalFourCC(desired.fourcc)) {
652 delta_fourcc = 0; // Need exact match.
653 }
654
655 if (kMaxDistance == delta_fourcc) {
656 // Failed to match fourcc.
657 return distance;
658 }
659
660 // Check resolution and fps.
661 int desired_width = desired.width;
662 int desired_height = desired.height;
663 int64 delta_w = supported.width - desired_width;
sergeyu@chromium.org4b26e2e2014-01-15 23:15:54 +0000664 float supported_fps = VideoFormat::IntervalToFpsFloat(supported.interval);
665 float delta_fps =
666 supported_fps - VideoFormat::IntervalToFpsFloat(desired.interval);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000667 // Check height of supported height compared to height we would like it to be.
668 int64 aspect_h =
669 desired_width ? supported.width * desired_height / desired_width
670 : desired_height;
671 int64 delta_h = supported.height - aspect_h;
672
673 distance = 0;
674 // Set high penalty if the supported format is lower than the desired format.
675 // 3x means we would prefer down to down to 3/4, than up to double.
676 // But we'd prefer up to double than down to 1/2. This is conservative,
677 // strongly avoiding going down in resolution, similar to
678 // the old method, but not completely ruling it out in extreme situations.
679 // It also ignores framerate, which is often very low at high resolutions.
680 // TODO(fbarchard): Improve logic to use weighted factors.
681 static const int kDownPenalty = -3;
682 if (delta_w < 0) {
683 delta_w = delta_w * kDownPenalty;
684 }
685 if (delta_h < 0) {
686 delta_h = delta_h * kDownPenalty;
687 }
688 // Require camera fps to be at least 80% of what is requested if resolution
689 // matches.
690 // Require camera fps to be at least 96% of what is requested, or higher,
691 // if resolution differs. 96% allows for slight variations in fps. e.g. 29.97
692 if (delta_fps < 0) {
sergeyu@chromium.org4b26e2e2014-01-15 23:15:54 +0000693 float min_desirable_fps = delta_w ?
694 VideoFormat::IntervalToFpsFloat(desired.interval) * 28.f / 30.f :
695 VideoFormat::IntervalToFpsFloat(desired.interval) * 23.f / 30.f;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000696 delta_fps = -delta_fps;
697 if (supported_fps < min_desirable_fps) {
698 distance |= static_cast<int64>(1) << 62;
699 } else {
700 distance |= static_cast<int64>(1) << 15;
701 }
702 }
sergeyu@chromium.org4b26e2e2014-01-15 23:15:54 +0000703 int64 idelta_fps = static_cast<int>(delta_fps);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000704
705 // 12 bits for width and height and 8 bits for fps and fourcc.
706 distance |=
sergeyu@chromium.org4b26e2e2014-01-15 23:15:54 +0000707 (delta_w << 28) | (delta_h << 16) | (idelta_fps << 8) | delta_fourcc;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000708
709 return distance;
710}
711
712bool VideoCapturer::ApplyProcessors(VideoFrame* video_frame) {
713 bool drop_frame = false;
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000714 rtc::CritScope cs(&crit_);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000715 for (VideoProcessors::iterator iter = video_processors_.begin();
716 iter != video_processors_.end(); ++iter) {
717 (*iter)->OnFrame(kDummyVideoSsrc, video_frame, &drop_frame);
718 if (drop_frame) {
719 return false;
720 }
721 }
722 return true;
723}
724
725void VideoCapturer::UpdateFilteredSupportedFormats() {
726 filtered_supported_formats_.clear();
727 filtered_supported_formats_ = supported_formats_;
728 if (!max_format_) {
729 return;
730 }
731 std::vector<VideoFormat>::iterator iter = filtered_supported_formats_.begin();
732 while (iter != filtered_supported_formats_.end()) {
733 if (ShouldFilterFormat(*iter)) {
734 iter = filtered_supported_formats_.erase(iter);
735 } else {
736 ++iter;
737 }
738 }
739 if (filtered_supported_formats_.empty()) {
740 // The device only captures at resolutions higher than |max_format_| this
741 // indicates that |max_format_| should be ignored as it is better to capture
742 // at too high a resolution than to not capture at all.
743 filtered_supported_formats_ = supported_formats_;
744 }
745}
746
747bool VideoCapturer::ShouldFilterFormat(const VideoFormat& format) const {
748 if (!enable_camera_list_) {
749 return false;
750 }
751 return format.width > max_format_->width ||
752 format.height > max_format_->height;
753}
754
buildbot@webrtc.org0b53bd22014-05-06 17:12:36 +0000755void VideoCapturer::UpdateStats(const CapturedFrame* captured_frame) {
756 // Update stats protected from fetches from different thread.
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000757 rtc::CritScope cs(&frame_stats_crit_);
buildbot@webrtc.org0b53bd22014-05-06 17:12:36 +0000758
759 last_captured_frame_format_.width = captured_frame->width;
760 last_captured_frame_format_.height = captured_frame->height;
761 // TODO(ronghuawu): Useful to report interval as well?
762 last_captured_frame_format_.interval = 0;
763 last_captured_frame_format_.fourcc = captured_frame->fourcc;
764
765 double time_now = frame_length_time_reporter_.TimerNow();
766 if (previous_frame_time_ != 0.0) {
767 adapt_frame_drops_data_.AddSample(adapt_frame_drops_);
768 effect_frame_drops_data_.AddSample(effect_frame_drops_);
769 frame_time_data_.AddSample(time_now - previous_frame_time_);
770 }
771 previous_frame_time_ = time_now;
772 effect_frame_drops_ = 0;
773 adapt_frame_drops_ = 0;
774}
775
henrike@webrtc.org704bf9e2014-02-27 17:52:04 +0000776template<class T>
777void VideoCapturer::GetVariableSnapshot(
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000778 const rtc::RollingAccumulator<T>& data,
henrike@webrtc.org704bf9e2014-02-27 17:52:04 +0000779 VariableInfo<T>* stats) {
780 stats->max_val = data.ComputeMax();
781 stats->mean = data.ComputeMean();
782 stats->min_val = data.ComputeMin();
783 stats->variance = data.ComputeVariance();
784}
785
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000786} // namespace cricket