- add sources.
[platform/framework/web/crosswalk.git] / src / media / video / capture / mac / video_capture_device_qtkit_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 #import "media/video/capture/mac/video_capture_device_qtkit_mac.h"
6
7 #import <QTKit/QTKit.h>
8
9 #include "base/debug/crash_logging.h"
10 #include "base/logging.h"
11 #include "base/mac/scoped_nsexception_enabler.h"
12 #include "media/video/capture/mac/video_capture_device_mac.h"
13 #include "media/video/capture/video_capture_device.h"
14 #include "media/video/capture/video_capture_types.h"
15
16 @implementation VideoCaptureDeviceQTKit
17
18 #pragma mark Class methods
19
20 + (void)getDeviceNames:(NSMutableDictionary*)deviceNames {
21   // Third-party drivers often throw exceptions, which are fatal in
22   // Chromium (see comments in scoped_nsexception_enabler.h).  The
23   // following catches any exceptions and continues in an orderly
24   // fashion with no devices detected.
25   NSArray* captureDevices =
26       base::mac::RunBlockIgnoringExceptions(^{
27           return [QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeVideo];
28       });
29
30   for (QTCaptureDevice* device in captureDevices) {
31     if (![[device attributeForKey:QTCaptureDeviceSuspendedAttribute] boolValue])
32       [deviceNames setObject:[device localizedDisplayName]
33                       forKey:[device uniqueID]];
34   }
35 }
36
37 + (NSDictionary*)deviceNames {
38   NSMutableDictionary* deviceNames =
39       [[[NSMutableDictionary alloc] init] autorelease];
40
41   // TODO(shess): Post to the main thread to see if that helps
42   // http://crbug.com/139164
43   [self performSelectorOnMainThread:@selector(getDeviceNames:)
44                          withObject:deviceNames
45                       waitUntilDone:YES];
46   return deviceNames;
47 }
48
49 #pragma mark Public methods
50
51 - (id)initWithFrameReceiver:(media::VideoCaptureDeviceMac *)frameReceiver {
52   self = [super init];
53   if (self) {
54     frameReceiver_ = frameReceiver;
55     lock_ = [[NSLock alloc] init];
56   }
57   return self;
58 }
59
60 - (void)dealloc {
61   [captureSession_ release];
62   [captureDeviceInput_ release];
63   [super dealloc];
64 }
65
66 - (void)setFrameReceiver:(media::VideoCaptureDeviceMac *)frameReceiver {
67   [lock_ lock];
68   frameReceiver_ = frameReceiver;
69   [lock_ unlock];
70 }
71
72 - (BOOL)setCaptureDevice:(NSString *)deviceId {
73   if (deviceId) {
74     // Set the capture device.
75     if (captureDeviceInput_) {
76       DLOG(ERROR) << "Video capture device already set.";
77       return NO;
78     }
79
80     NSArray *captureDevices =
81         [QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeVideo];
82     NSArray *captureDevicesNames =
83         [captureDevices valueForKey:@"uniqueID"];
84     NSUInteger index = [captureDevicesNames indexOfObject:deviceId];
85     if (index == NSNotFound) {
86       DLOG(ERROR) << "Video capture device not found.";
87       return NO;
88     }
89     QTCaptureDevice *device = [captureDevices objectAtIndex:index];
90     if ([[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
91             boolValue]) {
92       DLOG(ERROR) << "Cannot open suspended video capture device.";
93       return NO;
94     }
95     NSError *error;
96     if (![device open:&error]) {
97       DLOG(ERROR) << "Could not open video capture device."
98                   << [[error localizedDescription] UTF8String];
99       return NO;
100     }
101     captureDeviceInput_ = [[QTCaptureDeviceInput alloc] initWithDevice:device];
102     captureSession_ = [[QTCaptureSession alloc] init];
103
104     QTCaptureDecompressedVideoOutput *captureDecompressedOutput =
105         [[[QTCaptureDecompressedVideoOutput alloc] init] autorelease];
106     [captureDecompressedOutput setDelegate:self];
107     if (![captureSession_ addOutput:captureDecompressedOutput error:&error]) {
108       DLOG(ERROR) << "Could not connect video capture output."
109                   << [[error localizedDescription] UTF8String];
110       return NO;
111     }
112
113     // This key can be used to check if video capture code was related to a
114     // particular crash.
115     base::debug::SetCrashKeyValue("VideoCaptureDeviceQTKit", "OpenedDevice");
116
117     // Set the video pixel format to 2VUY (a.k.a UYVY, packed 4:2:2).
118     NSDictionary *captureDictionary = [NSDictionary
119         dictionaryWithObject:
120             [NSNumber numberWithUnsignedInt:kCVPixelFormatType_422YpCbCr8]
121                       forKey:(id)kCVPixelBufferPixelFormatTypeKey];
122     [captureDecompressedOutput setPixelBufferAttributes:captureDictionary];
123
124     return YES;
125   } else {
126     // Remove the previously set capture device.
127     if (!captureDeviceInput_) {
128       DLOG(ERROR) << "No video capture device set.";
129       return YES;
130     }
131     if ([[captureSession_ inputs] count] > 0) {
132       // The device is still running.
133       [self stopCapture];
134     }
135     if ([[captureSession_ outputs] count] > 0) {
136       // Only one output is set for |captureSession_|.
137       DCHECK_EQ([[captureSession_ outputs] count], 1u);
138       id output = [[captureSession_ outputs] objectAtIndex:0];
139       [output setDelegate:nil];
140
141       // TODO(shess): QTKit achieves thread safety by posting messages
142       // to the main thread.  As part of -addOutput:, it posts a
143       // message to the main thread which in turn posts a notification
144       // which will run in a future spin after the original method
145       // returns.  -removeOutput: can post a main-thread message in
146       // between while holding a lock which the notification handler
147       // will need.  Posting either -addOutput: or -removeOutput: to
148       // the main thread should fix it, remove is likely safer.
149       // http://crbug.com/152757
150       [captureSession_ performSelectorOnMainThread:@selector(removeOutput:)
151                                         withObject:output
152                                      waitUntilDone:YES];
153     }
154     [captureSession_ release];
155     captureSession_ = nil;
156     [captureDeviceInput_ release];
157     captureDeviceInput_ = nil;
158     return YES;
159   }
160 }
161
162 - (BOOL)setCaptureHeight:(int)height width:(int)width frameRate:(int)frameRate {
163   if (!captureDeviceInput_) {
164     DLOG(ERROR) << "No video capture device set.";
165     return NO;
166   }
167   if ([[captureSession_ outputs] count] != 1) {
168     DLOG(ERROR) << "Video capture capabilities already set.";
169     return NO;
170   }
171   if (frameRate <= 0) {
172     DLOG(ERROR) << "Wrong frame rate.";
173     return NO;
174   }
175
176   frameRate_ = frameRate;
177
178   QTCaptureDecompressedVideoOutput *output =
179       [[captureSession_ outputs] objectAtIndex:0];
180
181   // The old capture dictionary is used to retrieve the initial pixel
182   // format, which must be maintained.
183   NSDictionary *oldCaptureDictionary = [output pixelBufferAttributes];
184
185   // Set up desired output properties.
186   NSDictionary *captureDictionary =
187       [NSDictionary dictionaryWithObjectsAndKeys:
188           [NSNumber numberWithDouble:width],
189           (id)kCVPixelBufferWidthKey,
190           [NSNumber numberWithDouble:height],
191           (id)kCVPixelBufferHeightKey,
192           [oldCaptureDictionary
193               valueForKey:(id)kCVPixelBufferPixelFormatTypeKey],
194           (id)kCVPixelBufferPixelFormatTypeKey,
195           nil];
196   [output setPixelBufferAttributes:captureDictionary];
197
198   [output setMinimumVideoFrameInterval:(NSTimeInterval)1/(float)frameRate];
199   return YES;
200 }
201
202 - (BOOL)startCapture {
203   if ([[captureSession_ outputs] count] == 0) {
204     // Capture properties not set.
205     DLOG(ERROR) << "Video capture device not initialized.";
206     return NO;
207   }
208   if ([[captureSession_ inputs] count] == 0) {
209     NSError *error;
210     if (![captureSession_ addInput:captureDeviceInput_ error:&error]) {
211       DLOG(ERROR) << "Could not connect video capture device."
212                   << [[error localizedDescription] UTF8String];
213       return NO;
214     }
215     NSNotificationCenter * notificationCenter =
216         [NSNotificationCenter defaultCenter];
217     [notificationCenter addObserver:self
218                            selector:@selector(handleNotification:)
219                                name:QTCaptureSessionRuntimeErrorNotification
220                              object:captureSession_];
221     [captureSession_ startRunning];
222   }
223   return YES;
224 }
225
226 - (void)stopCapture {
227   if ([[captureSession_ inputs] count] == 1) {
228     [captureSession_ removeInput:captureDeviceInput_];
229     [captureSession_ stopRunning];
230   }
231
232   [[NSNotificationCenter defaultCenter] removeObserver:self];
233 }
234
235 // |captureOutput| is called by the capture device to deliver a new frame.
236 - (void)captureOutput:(QTCaptureOutput *)captureOutput
237   didOutputVideoFrame:(CVImageBufferRef)videoFrame
238      withSampleBuffer:(QTSampleBuffer *)sampleBuffer
239        fromConnection:(QTCaptureConnection *)connection {
240   [lock_ lock];
241   if(!frameReceiver_) {
242     [lock_ unlock];
243     return;
244   }
245
246   // Lock the frame and calculate frame size.
247   const int kLockFlags = 0;
248   if (CVPixelBufferLockBaseAddress(videoFrame, kLockFlags)
249       == kCVReturnSuccess) {
250     void *baseAddress = CVPixelBufferGetBaseAddress(videoFrame);
251     size_t bytesPerRow = CVPixelBufferGetBytesPerRow(videoFrame);
252     size_t frameWidth = CVPixelBufferGetWidth(videoFrame);
253     size_t frameHeight = CVPixelBufferGetHeight(videoFrame);
254     size_t frameSize = bytesPerRow * frameHeight;
255
256     // TODO(shess): bytesPerRow may not correspond to frameWidth_*2,
257     // but VideoCaptureController::OnIncomingCapturedFrame() requires
258     // it to do so.  Plumbing things through is intrusive, for now
259     // just deliver an adjusted buffer.
260     // TODO(nick): This workaround could probably be eliminated by using
261     // VideoCaptureController::OnIncomingCapturedVideoFrame, which supports
262     // pitches.
263     UInt8* addressToPass = static_cast<UInt8*>(baseAddress);
264     // UYVY is 2 bytes per pixel.
265     size_t expectedBytesPerRow = frameWidth * 2;
266     if (bytesPerRow > expectedBytesPerRow) {
267       // TODO(shess): frameHeight and frameHeight_ are not the same,
268       // try to do what the surrounding code seems to assume.
269       // Ironically, captureCapability and frameSize are ignored
270       // anyhow.
271       adjustedFrame_.resize(expectedBytesPerRow * frameHeight);
272       // std::vector is contiguous according to standard.
273       UInt8* adjustedAddress = &adjustedFrame_[0];
274
275       for (size_t y = 0; y < frameHeight; ++y) {
276         memcpy(adjustedAddress + y * expectedBytesPerRow,
277                addressToPass + y * bytesPerRow,
278                expectedBytesPerRow);
279       }
280
281       addressToPass = adjustedAddress;
282       frameSize = frameHeight * expectedBytesPerRow;
283     }
284     media::VideoCaptureCapability captureCapability;
285     captureCapability.width = frameWidth;
286     captureCapability.height = frameHeight;
287     captureCapability.frame_rate = frameRate_;
288     captureCapability.color = media::PIXEL_FORMAT_UYVY;
289
290     // The aspect ratio dictionary is often missing, in which case we report
291     // a pixel aspect ratio of 0:0.
292     int aspectNumerator = 0, aspectDenominator = 0;
293     CFDictionaryRef aspectRatioDict = (CFDictionaryRef)CVBufferGetAttachment(
294         videoFrame, kCVImageBufferPixelAspectRatioKey, NULL);
295     if (aspectRatioDict) {
296       CFNumberRef aspectNumeratorRef = (CFNumberRef)CFDictionaryGetValue(
297           aspectRatioDict, kCVImageBufferPixelAspectRatioHorizontalSpacingKey);
298       CFNumberRef aspectDenominatorRef = (CFNumberRef)CFDictionaryGetValue(
299           aspectRatioDict, kCVImageBufferPixelAspectRatioVerticalSpacingKey);
300       DCHECK(aspectNumeratorRef && aspectDenominatorRef) <<
301           "Aspect Ratio dictionary missing its entries.";
302       CFNumberGetValue(aspectNumeratorRef, kCFNumberIntType, &aspectNumerator);
303       CFNumberGetValue(
304           aspectDenominatorRef, kCFNumberIntType, &aspectDenominator);
305     }
306
307     // Deliver the captured video frame.
308     frameReceiver_->ReceiveFrame(addressToPass, frameSize, captureCapability,
309         aspectNumerator, aspectDenominator);
310
311     CVPixelBufferUnlockBaseAddress(videoFrame, kLockFlags);
312   }
313   [lock_ unlock];
314 }
315
316 - (void)handleNotification:(NSNotification *)errorNotification {
317   NSError * error = (NSError *)[[errorNotification userInfo]
318       objectForKey:QTCaptureSessionErrorKey];
319   frameReceiver_->ReceiveError([[error localizedDescription] UTF8String]);
320 }
321
322 @end