1 // Copyright (c) 2012 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 "media/video/capture/mac/video_capture_device_mac.h"
8 #include "base/location.h"
9 #include "base/logging.h"
10 #include "base/message_loop/message_loop_proxy.h"
11 #include "base/time/time.h"
12 #import "media/video/capture/mac/avfoundation_glue.h"
13 #import "media/video/capture/mac/platform_video_capturing_mac.h"
14 #import "media/video/capture/mac/video_capture_device_avfoundation_mac.h"
15 #import "media/video/capture/mac/video_capture_device_qtkit_mac.h"
19 const int kMinFrameRate = 1;
20 const int kMaxFrameRate = 30;
22 // In QT device identifiers, the USB VID and PID are stored in 4 bytes each.
23 const size_t kVidPidSize = 4;
30 const Resolution kQVGA = { 320, 240 },
34 const Resolution* const kWellSupportedResolutions[] = {
40 // Rescaling the image to fix the pixel aspect ratio runs the risk of making
41 // the aspect ratio worse, if QTKit selects a new source mode with a different
42 // shape. This constant ensures that we don't take this risk if the current
43 // aspect ratio is tolerable.
44 const float kMaxPixelAspectRatio = 1.15;
46 // TODO(ronghuawu): Replace this with CapabilityList::GetBestMatchedCapability.
47 void GetBestMatchSupportedResolution(int* width, int* height) {
48 int min_diff = kint32max;
49 int matched_width = *width;
50 int matched_height = *height;
51 int desired_res_area = *width * *height;
52 for (size_t i = 0; i < arraysize(kWellSupportedResolutions); ++i) {
53 int area = kWellSupportedResolutions[i]->width *
54 kWellSupportedResolutions[i]->height;
55 int diff = std::abs(desired_res_area - area);
56 if (diff < min_diff) {
58 matched_width = kWellSupportedResolutions[i]->width;
59 matched_height = kWellSupportedResolutions[i]->height;
62 *width = matched_width;
63 *height = matched_height;
66 void VideoCaptureDevice::GetDeviceNames(Names* device_names) {
67 // Loop through all available devices and add to |device_names|.
68 device_names->clear();
70 NSDictionary* capture_devices;
71 if (AVFoundationGlue::IsAVFoundationSupported()) {
72 DVLOG(1) << "Enumerating video capture devices using AVFoundation";
73 capture_devices = [VideoCaptureDeviceAVFoundation deviceNames];
75 DVLOG(1) << "Enumerating video capture devices using QTKit";
76 capture_devices = [VideoCaptureDeviceQTKit deviceNames];
78 for (NSString* key in capture_devices) {
79 Name name([[capture_devices valueForKey:key] UTF8String],
81 device_names->push_back(name);
86 void VideoCaptureDevice::GetDeviceSupportedFormats(const Name& device,
87 VideoCaptureFormats* formats) {
88 if (AVFoundationGlue::IsAVFoundationSupported()) {
89 DVLOG(1) << "Enumerating video capture capabilities, AVFoundation";
90 [VideoCaptureDeviceAVFoundation getDevice:device
91 supportedFormats:formats];
97 const std::string VideoCaptureDevice::Name::GetModel() const {
98 // Both PID and VID are 4 characters.
99 if (unique_id_.size() < 2 * kVidPidSize) {
103 // The last characters of device id is a concatenation of VID and then PID.
104 const size_t vid_location = unique_id_.size() - 2 * kVidPidSize;
105 std::string id_vendor = unique_id_.substr(vid_location, kVidPidSize);
106 const size_t pid_location = unique_id_.size() - kVidPidSize;
107 std::string id_product = unique_id_.substr(pid_location, kVidPidSize);
109 return id_vendor + ":" + id_product;
112 VideoCaptureDevice* VideoCaptureDevice::Create(const Name& device_name) {
113 VideoCaptureDeviceMac* capture_device =
114 new VideoCaptureDeviceMac(device_name);
115 if (!capture_device->Init()) {
116 LOG(ERROR) << "Could not initialize VideoCaptureDevice.";
117 delete capture_device;
118 capture_device = NULL;
120 return capture_device;
123 VideoCaptureDeviceMac::VideoCaptureDeviceMac(const Name& device_name)
124 : device_name_(device_name),
125 sent_frame_info_(false),
126 tried_to_square_pixels_(false),
127 task_runner_(base::MessageLoopProxy::current()),
128 state_(kNotInitialized),
130 weak_this_(weak_factory_.GetWeakPtr()),
131 capture_device_(nil) {
134 VideoCaptureDeviceMac::~VideoCaptureDeviceMac() {
135 DCHECK(task_runner_->BelongsToCurrentThread());
136 [capture_device_ release];
139 void VideoCaptureDeviceMac::AllocateAndStart(
140 const VideoCaptureParams& params,
141 scoped_ptr<VideoCaptureDevice::Client> client) {
142 DCHECK(task_runner_->BelongsToCurrentThread());
143 if (state_ != kIdle) {
146 int width = params.requested_format.frame_size.width();
147 int height = params.requested_format.frame_size.height();
148 int frame_rate = params.requested_format.frame_rate;
150 // The OS API can scale captured frame to any size requested, which would lead
151 // to undesired aspect ratio change. Try to open the camera with a natively
152 // supported format and let the client crop/pad the captured frames.
153 GetBestMatchSupportedResolution(&width, &height);
155 client_ = client.Pass();
157 [NSString stringWithUTF8String:device_name_.id().c_str()];
159 [capture_device_ setFrameReceiver:this];
161 if (![capture_device_ setCaptureDevice:deviceId]) {
162 SetErrorState("Could not open capture device.");
165 if (frame_rate < kMinFrameRate)
166 frame_rate = kMinFrameRate;
167 else if (frame_rate > kMaxFrameRate)
168 frame_rate = kMaxFrameRate;
170 capture_format_.frame_size.SetSize(width, height);
171 capture_format_.frame_rate = frame_rate;
172 capture_format_.pixel_format = PIXEL_FORMAT_UYVY;
174 if (width <= kVGA.width || height <= kVGA.height) {
175 // If the resolution is VGA or QVGA, set the capture resolution to the
176 // target size. Essentially all supported cameras offer at least VGA.
177 if (!UpdateCaptureResolution())
180 // For higher resolutions, we first open at the default resolution to find
181 // out if the request is larger than the camera's native resolution.
183 // If the resolution is HD, start capturing without setting a resolution.
184 // QTKit/AVFoundation will produce frames at the native resolution, allowing
185 // us to identify cameras whose native resolution is too low for HD. This
186 // additional information comes at a cost in startup latency, because the
187 // webcam will need to be reopened if its default resolution is not HD or VGA.
189 if (![capture_device_ startCapture]) {
190 SetErrorState("Could not start capture device.");
197 void VideoCaptureDeviceMac::StopAndDeAllocate() {
198 DCHECK(task_runner_->BelongsToCurrentThread());
199 DCHECK(state_ == kCapturing || state_ == kError) << state_;
200 [capture_device_ stopCapture];
202 [capture_device_ setCaptureDevice:nil];
203 [capture_device_ setFrameReceiver:nil];
206 tried_to_square_pixels_ = false;
209 bool VideoCaptureDeviceMac::Init() {
210 DCHECK(task_runner_->BelongsToCurrentThread());
211 DCHECK_EQ(state_, kNotInitialized);
213 // TODO(mcasas): The following check might not be necessary; if the device has
214 // disappeared after enumeration and before coming here, opening would just
215 // fail but not necessarily produce a crash.
217 GetDeviceNames(&device_names);
218 Names::iterator it = device_names.begin();
219 for (; it != device_names.end(); ++it) {
220 if (it->id() == device_name_.id())
223 if (it == device_names.end())
226 if (AVFoundationGlue::IsAVFoundationSupported()) {
228 [[VideoCaptureDeviceAVFoundation alloc] initWithFrameReceiver:this];
231 [[VideoCaptureDeviceQTKit alloc] initWithFrameReceiver:this];
234 if (!capture_device_)
241 void VideoCaptureDeviceMac::ReceiveFrame(
242 const uint8* video_frame,
243 int video_frame_length,
244 const VideoCaptureFormat& frame_format,
245 int aspect_numerator,
246 int aspect_denominator) {
247 // This method is safe to call from a device capture thread,
248 // i.e. any thread controlled by QTKit/AVFoundation.
250 if (!sent_frame_info_) {
251 // Final resolution has not yet been selected.
252 if (capture_format_.frame_size.width() > kVGA.width ||
253 capture_format_.frame_size.height() > kVGA.height) {
254 // We are requesting HD. Make sure that the picture is good, otherwise
256 bool change_to_vga = false;
257 if (frame_format.frame_size.width() <
258 capture_format_.frame_size.width() ||
259 frame_format.frame_size.height() <
260 capture_format_.frame_size.height()) {
261 // These are the default capture settings, not yet configured to match
262 // |capture_format_|.
263 DCHECK(frame_format.frame_rate == 0);
264 DVLOG(1) << "Switching to VGA because the default resolution is " <<
265 frame_format.frame_size.ToString();
266 change_to_vga = true;
269 if (capture_format_.frame_size == frame_format.frame_size &&
270 aspect_numerator != aspect_denominator) {
271 DVLOG(1) << "Switching to VGA because HD has nonsquare pixel " <<
272 "aspect ratio " << aspect_numerator << ":" << aspect_denominator;
273 change_to_vga = true;
277 capture_format_.frame_size.SetSize(kVGA.width, kVGA.height);
281 if (capture_format_.frame_size == frame_format.frame_size &&
282 !tried_to_square_pixels_ &&
283 (aspect_numerator > kMaxPixelAspectRatio * aspect_denominator ||
284 aspect_denominator > kMaxPixelAspectRatio * aspect_numerator)) {
285 // The requested size results in non-square PAR.
286 // Shrink the frame to 1:1 PAR (assuming QTKit selects the same input
287 // mode, which is not guaranteed).
288 int new_width = capture_format_.frame_size.width();
289 int new_height = capture_format_.frame_size.height();
290 if (aspect_numerator < aspect_denominator) {
291 new_width = (new_width * aspect_numerator) / aspect_denominator;
293 new_height = (new_height * aspect_denominator) / aspect_numerator;
295 capture_format_.frame_size.SetSize(new_width, new_height);
296 tried_to_square_pixels_ = true;
299 if (capture_format_.frame_size == frame_format.frame_size) {
300 sent_frame_info_ = true;
302 UpdateCaptureResolution();
303 // OnFrameInfo has not yet been called. OnIncomingCapturedFrame must
304 // not be called until after OnFrameInfo, so we return early.
309 DCHECK_EQ(capture_format_.frame_size.width(),
310 frame_format.frame_size.width());
311 DCHECK_EQ(capture_format_.frame_size.height(),
312 frame_format.frame_size.height());
314 client_->OnIncomingCapturedFrame(video_frame,
316 base::TimeTicks::Now(),
321 void VideoCaptureDeviceMac::ReceiveError(const std::string& reason) {
322 task_runner_->PostTask(FROM_HERE,
323 base::Bind(&VideoCaptureDeviceMac::SetErrorState, weak_this_,
327 void VideoCaptureDeviceMac::SetErrorState(const std::string& reason) {
328 DCHECK(task_runner_->BelongsToCurrentThread());
329 DLOG(ERROR) << reason;
331 client_->OnError(reason);
334 bool VideoCaptureDeviceMac::UpdateCaptureResolution() {
335 if (![capture_device_ setCaptureHeight:capture_format_.frame_size.height()
336 width:capture_format_.frame_size.width()
337 frameRate:capture_format_.frame_rate]) {
338 ReceiveError("Could not configure capture device.");