Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / media / video / capture / mac / video_capture_device_mac.mm
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.
4
5 #include "media/video/capture/mac/video_capture_device_mac.h"
6
7 #include "base/bind.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"
16
17 namespace media {
18
19 const int kMinFrameRate = 1;
20 const int kMaxFrameRate = 30;
21
22 // In QT device identifiers, the USB VID and PID are stored in 4 bytes each.
23 const size_t kVidPidSize = 4;
24
25 struct Resolution {
26   int width;
27   int height;
28 };
29
30 const Resolution kQVGA = { 320, 240 },
31                  kVGA = { 640, 480 },
32                  kHD = { 1280, 720 };
33
34 const Resolution* const kWellSupportedResolutions[] = {
35    &kQVGA,
36    &kVGA,
37    &kHD,
38 };
39
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;
45
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) {
57       min_diff = diff;
58       matched_width = kWellSupportedResolutions[i]->width;
59       matched_height = kWellSupportedResolutions[i]->height;
60     }
61   }
62   *width = matched_width;
63   *height = matched_height;
64 }
65
66 void VideoCaptureDevice::GetDeviceNames(Names* device_names) {
67   // Loop through all available devices and add to |device_names|.
68   device_names->clear();
69
70   NSDictionary* capture_devices;
71   if (AVFoundationGlue::IsAVFoundationSupported()) {
72     DVLOG(1) << "Enumerating video capture devices using AVFoundation";
73     capture_devices = [VideoCaptureDeviceAVFoundation deviceNames];
74   } else {
75     DVLOG(1) << "Enumerating video capture devices using QTKit";
76     capture_devices = [VideoCaptureDeviceQTKit deviceNames];
77   }
78   for (NSString* key in capture_devices) {
79     Name name([[capture_devices valueForKey:key] UTF8String],
80               [key UTF8String]);
81     device_names->push_back(name);
82   }
83 }
84
85 // static
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];
92   } else {
93     NOTIMPLEMENTED();
94   }
95 }
96
97 const std::string VideoCaptureDevice::Name::GetModel() const {
98   // Both PID and VID are 4 characters.
99   if (unique_id_.size() < 2 * kVidPidSize) {
100     return "";
101   }
102
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);
108
109   return id_vendor + ":" + id_product;
110 }
111
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;
119   }
120   return capture_device;
121 }
122
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),
129       weak_factory_(this),
130       weak_this_(weak_factory_.GetWeakPtr()),
131       capture_device_(nil) {
132 }
133
134 VideoCaptureDeviceMac::~VideoCaptureDeviceMac() {
135   DCHECK(task_runner_->BelongsToCurrentThread());
136   [capture_device_ release];
137 }
138
139 void VideoCaptureDeviceMac::AllocateAndStart(
140     const VideoCaptureParams& params,
141     scoped_ptr<VideoCaptureDevice::Client> client) {
142   DCHECK(task_runner_->BelongsToCurrentThread());
143   if (state_ != kIdle) {
144     return;
145   }
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;
149
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);
154
155   client_ = client.Pass();
156   NSString* deviceId =
157       [NSString stringWithUTF8String:device_name_.id().c_str()];
158
159   [capture_device_ setFrameReceiver:this];
160
161   if (![capture_device_ setCaptureDevice:deviceId]) {
162     SetErrorState("Could not open capture device.");
163     return;
164   }
165   if (frame_rate < kMinFrameRate)
166     frame_rate = kMinFrameRate;
167   else if (frame_rate > kMaxFrameRate)
168     frame_rate = kMaxFrameRate;
169
170   capture_format_.frame_size.SetSize(width, height);
171   capture_format_.frame_rate = frame_rate;
172   capture_format_.pixel_format = PIXEL_FORMAT_UYVY;
173
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())
178       return;
179   }
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.
182
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.
188
189   if (![capture_device_ startCapture]) {
190     SetErrorState("Could not start capture device.");
191     return;
192   }
193
194   state_ = kCapturing;
195 }
196
197 void VideoCaptureDeviceMac::StopAndDeAllocate() {
198   DCHECK(task_runner_->BelongsToCurrentThread());
199   DCHECK(state_ == kCapturing || state_ == kError) << state_;
200   [capture_device_ stopCapture];
201
202   [capture_device_ setCaptureDevice:nil];
203   [capture_device_ setFrameReceiver:nil];
204   client_.reset();
205   state_ = kIdle;
206   tried_to_square_pixels_ = false;
207 }
208
209 bool VideoCaptureDeviceMac::Init() {
210   DCHECK(task_runner_->BelongsToCurrentThread());
211   DCHECK_EQ(state_, kNotInitialized);
212
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.
216   Names device_names;
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())
221       break;
222   }
223   if (it == device_names.end())
224     return false;
225
226   if (AVFoundationGlue::IsAVFoundationSupported()) {
227     capture_device_ =
228         [[VideoCaptureDeviceAVFoundation alloc] initWithFrameReceiver:this];
229   } else {
230     capture_device_ =
231         [[VideoCaptureDeviceQTKit alloc] initWithFrameReceiver:this];
232   }
233
234   if (!capture_device_)
235     return false;
236
237   state_ = kIdle;
238   return true;
239 }
240
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.
249
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
255       // drop down to VGA.
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;
267       }
268
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;
274       }
275
276       if (change_to_vga) {
277         capture_format_.frame_size.SetSize(kVGA.width, kVGA.height);
278       }
279     }
280
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;
292       } else {
293         new_height = (new_height * aspect_denominator) / aspect_numerator;
294       }
295       capture_format_.frame_size.SetSize(new_width, new_height);
296       tried_to_square_pixels_ = true;
297     }
298
299     if (capture_format_.frame_size == frame_format.frame_size) {
300       sent_frame_info_ = true;
301     } else {
302       UpdateCaptureResolution();
303       // OnFrameInfo has not yet been called.  OnIncomingCapturedFrame must
304       // not be called until after OnFrameInfo, so we return early.
305       return;
306     }
307   }
308
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());
313
314   client_->OnIncomingCapturedFrame(video_frame,
315                                    video_frame_length,
316                                    base::TimeTicks::Now(),
317                                    0,
318                                    capture_format_);
319 }
320
321 void VideoCaptureDeviceMac::ReceiveError(const std::string& reason) {
322   task_runner_->PostTask(FROM_HERE,
323       base::Bind(&VideoCaptureDeviceMac::SetErrorState, weak_this_,
324           reason));
325 }
326
327 void VideoCaptureDeviceMac::SetErrorState(const std::string& reason) {
328   DCHECK(task_runner_->BelongsToCurrentThread());
329   DLOG(ERROR) << reason;
330   state_ = kError;
331   client_->OnError(reason);
332 }
333
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.");
339    return false;
340  }
341  return true;
342 }
343
344 } // namespace media