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