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 // 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."]];
93 QTCaptureDevice *device = [captureDevices objectAtIndex:index];
94 if ([[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
96 [self sendErrorString:[NSString
97 stringWithUTF8String:"Cannot open suspended video capture device."]];
101 if (![device open:&error]) {
102 [self sendErrorString:[NSString
103 stringWithFormat:@"Could not open video capture device (%@): %@",
104 [error localizedDescription],
105 [error localizedFailureReason]]];
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]]];
122 // This key can be used to check if video capture code was related to a
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];
135 // Remove the previously set capture device.
136 if (!captureDeviceInput_) {
137 [self sendErrorString:[NSString
138 stringWithUTF8String:"No video capture device set, on removal."]];
141 if ([[captureSession_ inputs] count] > 0) {
142 // The device is still running.
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:)
163 [captureSession_ release];
164 captureSession_ = nil;
165 [captureDeviceInput_ release];
166 captureDeviceInput_ = nil;
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."]];
177 if ([[captureSession_ outputs] count] != 1) {
178 [self sendErrorString:[NSString
179 stringWithUTF8String:"Video capture capabilities already set."]];
182 if (frameRate <= 0) {
183 [self sendErrorString:[NSString stringWithUTF8String: "Wrong frame rate."]];
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]
200 [output setPixelBufferAttributes:videoSettingsDictionary];
202 [output setMinimumVideoFrameInterval:(NSTimeInterval)1/(float)frameRate];
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."]];
213 if ([[captureSession_ inputs] count] == 0) {
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]]];
223 NSNotificationCenter * notificationCenter =
224 [NSNotificationCenter defaultCenter];
225 [notificationCenter addObserver:self
226 selector:@selector(handleNotification:)
227 name:QTCaptureSessionRuntimeErrorNotification
228 object:captureSession_];
229 [captureSession_ startRunning];
234 - (void)stopCapture {
235 if ([[captureSession_ inputs] count] == 1) {
236 [captureSession_ removeInput:captureDeviceInput_];
237 [captureSession_ stopRunning];
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 {
249 if(!frameReceiver_) {
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
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
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);
289 addressToPass = adjustedAddress;
290 frameSize = frameHeight * expectedBytesPerRow;
293 media::VideoCaptureFormat captureFormat(gfx::Size(frameWidth, frameHeight),
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);
311 aspectDenominatorRef, kCFNumberIntType, &aspectDenominator);
314 // Deliver the captured video frame.
315 frameReceiver_->ReceiveFrame(addressToPass, frameSize, captureFormat,
316 aspectNumerator, aspectDenominator);
318 CVPixelBufferUnlockBaseAddress(videoFrame, kLockFlags);
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];
336 frameReceiver_->ReceiveError([error UTF8String]);