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