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];
31 for (QTCaptureDevice* device in captureDevices) {
32 if (![[device attributeForKey:QTCaptureDeviceSuspendedAttribute] boolValue])
33 [deviceNames setObject:[device localizedDisplayName]
34 forKey:[device uniqueID]];
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
50 #pragma mark Public methods
52 - (id)initWithFrameReceiver:(media::VideoCaptureDeviceMac*)frameReceiver {
55 frameReceiver_ = frameReceiver;
56 lock_ = [[NSLock alloc] init];
62 [captureSession_ release];
63 [captureDeviceInput_ release];
67 - (void)setFrameReceiver:(media::VideoCaptureDeviceMac*)frameReceiver {
69 frameReceiver_ = frameReceiver;
73 - (BOOL)setCaptureDevice:(NSString*)deviceId {
75 // Set the capture device.
76 if (captureDeviceInput_) {
77 DLOG(ERROR) << "Video capture device already set.";
81 NSArray *captureDevices =
82 [QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeVideo];
83 NSArray *captureDevicesNames =
84 [captureDevices valueForKey:@"uniqueID"];
85 NSUInteger index = [captureDevicesNames indexOfObject:deviceId];
86 if (index == NSNotFound) {
87 DLOG(ERROR) << "Video capture device not found.";
90 QTCaptureDevice *device = [captureDevices objectAtIndex:index];
91 if ([[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
93 DLOG(ERROR) << "Cannot open suspended video capture device.";
97 if (![device open:&error]) {
98 DLOG(ERROR) << "Could not open video capture device."
99 << [[error localizedDescription] UTF8String];
102 captureDeviceInput_ = [[QTCaptureDeviceInput alloc] initWithDevice:device];
103 captureSession_ = [[QTCaptureSession alloc] init];
105 QTCaptureDecompressedVideoOutput *captureDecompressedOutput =
106 [[[QTCaptureDecompressedVideoOutput alloc] init] autorelease];
107 [captureDecompressedOutput setDelegate:self];
108 if (![captureSession_ addOutput:captureDecompressedOutput error:&error]) {
109 DLOG(ERROR) << "Could not connect video capture output."
110 << [[error localizedDescription] UTF8String];
114 // This key can be used to check if video capture code was related to a
116 base::debug::SetCrashKeyValue("VideoCaptureDeviceQTKit", "OpenedDevice");
118 // Set the video pixel format to 2VUY (a.k.a UYVY, packed 4:2:2).
119 NSDictionary *captureDictionary = [NSDictionary
120 dictionaryWithObject:
121 [NSNumber numberWithUnsignedInt:kCVPixelFormatType_422YpCbCr8]
122 forKey:(id)kCVPixelBufferPixelFormatTypeKey];
123 [captureDecompressedOutput setPixelBufferAttributes:captureDictionary];
127 // Remove the previously set capture device.
128 if (!captureDeviceInput_) {
129 DLOG(ERROR) << "No video capture device set.";
132 if ([[captureSession_ inputs] count] > 0) {
133 // The device is still running.
136 if ([[captureSession_ outputs] count] > 0) {
137 // Only one output is set for |captureSession_|.
138 DCHECK_EQ([[captureSession_ outputs] count], 1u);
139 id output = [[captureSession_ outputs] objectAtIndex:0];
140 [output setDelegate:nil];
142 // TODO(shess): QTKit achieves thread safety by posting messages
143 // to the main thread. As part of -addOutput:, it posts a
144 // message to the main thread which in turn posts a notification
145 // which will run in a future spin after the original method
146 // returns. -removeOutput: can post a main-thread message in
147 // between while holding a lock which the notification handler
148 // will need. Posting either -addOutput: or -removeOutput: to
149 // the main thread should fix it, remove is likely safer.
150 // http://crbug.com/152757
151 [captureSession_ performSelectorOnMainThread:@selector(removeOutput:)
155 [captureSession_ release];
156 captureSession_ = nil;
157 [captureDeviceInput_ release];
158 captureDeviceInput_ = nil;
163 - (BOOL)setCaptureHeight:(int)height width:(int)width frameRate:(int)frameRate {
164 if (!captureDeviceInput_) {
165 DLOG(ERROR) << "No video capture device set.";
168 if ([[captureSession_ outputs] count] != 1) {
169 DLOG(ERROR) << "Video capture capabilities already set.";
172 if (frameRate <= 0) {
173 DLOG(ERROR) << "Wrong frame rate.";
177 frameRate_ = frameRate;
179 QTCaptureDecompressedVideoOutput *output =
180 [[captureSession_ outputs] objectAtIndex:0];
182 // Set up desired output properties. The old capture dictionary is used to
183 // retrieve the initial pixel format, which must be maintained.
184 NSDictionary* videoSettingsDictionary = @{
185 (id)kCVPixelBufferWidthKey : @(width),
186 (id)kCVPixelBufferHeightKey : @(height),
187 (id)kCVPixelBufferPixelFormatTypeKey : [[output pixelBufferAttributes]
188 valueForKey:(id)kCVPixelBufferPixelFormatTypeKey]
190 [output setPixelBufferAttributes:videoSettingsDictionary];
192 [output setMinimumVideoFrameInterval:(NSTimeInterval)1/(float)frameRate];
196 - (BOOL)startCapture {
197 if ([[captureSession_ outputs] count] == 0) {
198 // Capture properties not set.
199 DLOG(ERROR) << "Video capture device not initialized.";
202 if ([[captureSession_ inputs] count] == 0) {
204 if (![captureSession_ addInput:captureDeviceInput_ error:&error]) {
205 DLOG(ERROR) << "Could not connect video capture device."
206 << [[error localizedDescription] UTF8String];
209 NSNotificationCenter * notificationCenter =
210 [NSNotificationCenter defaultCenter];
211 [notificationCenter addObserver:self
212 selector:@selector(handleNotification:)
213 name:QTCaptureSessionRuntimeErrorNotification
214 object:captureSession_];
215 [captureSession_ startRunning];
220 - (void)stopCapture {
221 if ([[captureSession_ inputs] count] == 1) {
222 [captureSession_ removeInput:captureDeviceInput_];
223 [captureSession_ stopRunning];
226 [[NSNotificationCenter defaultCenter] removeObserver:self];
229 // |captureOutput| is called by the capture device to deliver a new frame.
230 - (void)captureOutput:(QTCaptureOutput*)captureOutput
231 didOutputVideoFrame:(CVImageBufferRef)videoFrame
232 withSampleBuffer:(QTSampleBuffer*)sampleBuffer
233 fromConnection:(QTCaptureConnection*)connection {
235 if(!frameReceiver_) {
240 // Lock the frame and calculate frame size.
241 const int kLockFlags = 0;
242 if (CVPixelBufferLockBaseAddress(videoFrame, kLockFlags)
243 == kCVReturnSuccess) {
244 void *baseAddress = CVPixelBufferGetBaseAddress(videoFrame);
245 size_t bytesPerRow = CVPixelBufferGetBytesPerRow(videoFrame);
246 size_t frameWidth = CVPixelBufferGetWidth(videoFrame);
247 size_t frameHeight = CVPixelBufferGetHeight(videoFrame);
248 size_t frameSize = bytesPerRow * frameHeight;
250 // TODO(shess): bytesPerRow may not correspond to frameWidth_*2,
251 // but VideoCaptureController::OnIncomingCapturedData() requires
252 // it to do so. Plumbing things through is intrusive, for now
253 // just deliver an adjusted buffer.
254 // TODO(nick): This workaround could probably be eliminated by using
255 // VideoCaptureController::OnIncomingCapturedVideoFrame, which supports
257 UInt8* addressToPass = static_cast<UInt8*>(baseAddress);
258 // UYVY is 2 bytes per pixel.
259 size_t expectedBytesPerRow = frameWidth * 2;
260 if (bytesPerRow > expectedBytesPerRow) {
261 // TODO(shess): frameHeight and frameHeight_ are not the same,
262 // try to do what the surrounding code seems to assume.
263 // Ironically, captureCapability and frameSize are ignored
265 adjustedFrame_.resize(expectedBytesPerRow * frameHeight);
266 // std::vector is contiguous according to standard.
267 UInt8* adjustedAddress = &adjustedFrame_[0];
269 for (size_t y = 0; y < frameHeight; ++y) {
270 memcpy(adjustedAddress + y * expectedBytesPerRow,
271 addressToPass + y * bytesPerRow,
272 expectedBytesPerRow);
275 addressToPass = adjustedAddress;
276 frameSize = frameHeight * expectedBytesPerRow;
279 media::VideoCaptureFormat captureFormat(gfx::Size(frameWidth, frameHeight),
281 media::PIXEL_FORMAT_UYVY);
283 // The aspect ratio dictionary is often missing, in which case we report
284 // a pixel aspect ratio of 0:0.
285 int aspectNumerator = 0, aspectDenominator = 0;
286 CFDictionaryRef aspectRatioDict = (CFDictionaryRef)CVBufferGetAttachment(
287 videoFrame, kCVImageBufferPixelAspectRatioKey, NULL);
288 if (aspectRatioDict) {
289 CFNumberRef aspectNumeratorRef = (CFNumberRef)CFDictionaryGetValue(
290 aspectRatioDict, kCVImageBufferPixelAspectRatioHorizontalSpacingKey);
291 CFNumberRef aspectDenominatorRef = (CFNumberRef)CFDictionaryGetValue(
292 aspectRatioDict, kCVImageBufferPixelAspectRatioVerticalSpacingKey);
293 DCHECK(aspectNumeratorRef && aspectDenominatorRef) <<
294 "Aspect Ratio dictionary missing its entries.";
295 CFNumberGetValue(aspectNumeratorRef, kCFNumberIntType, &aspectNumerator);
297 aspectDenominatorRef, kCFNumberIntType, &aspectDenominator);
300 // Deliver the captured video frame.
301 frameReceiver_->ReceiveFrame(addressToPass, frameSize, captureFormat,
302 aspectNumerator, aspectDenominator);
304 CVPixelBufferUnlockBaseAddress(videoFrame, kLockFlags);
309 - (void)handleNotification:(NSNotification*)errorNotification {
310 NSError * error = (NSError*)[[errorNotification userInfo]
311 objectForKey:QTCaptureSessionErrorKey];
312 NSString* str_error =
313 [NSString stringWithFormat:@"%@: %@",
314 [error localizedDescription],
315 [error localizedFailureReason]];
317 frameReceiver_->ReceiveError([str_error UTF8String]);