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"
16 @implementation VideoCaptureDeviceQTKit
18 #pragma mark Class methods
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];
30 for (QTCaptureDevice* device in captureDevices) {
31 if (![[device attributeForKey:QTCaptureDeviceSuspendedAttribute] boolValue])
32 [deviceNames setObject:[device localizedDisplayName]
33 forKey:[device uniqueID]];
37 + (NSDictionary*)deviceNames {
38 NSMutableDictionary* deviceNames =
39 [[[NSMutableDictionary alloc] init] autorelease];
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
49 #pragma mark Public methods
51 - (id)initWithFrameReceiver:(media::VideoCaptureDeviceMac *)frameReceiver {
54 frameReceiver_ = frameReceiver;
55 lock_ = [[NSLock alloc] init];
61 [captureSession_ release];
62 [captureDeviceInput_ release];
66 - (void)setFrameReceiver:(media::VideoCaptureDeviceMac *)frameReceiver {
68 frameReceiver_ = frameReceiver;
72 - (BOOL)setCaptureDevice:(NSString *)deviceId {
74 // Set the capture device.
75 if (captureDeviceInput_) {
76 DLOG(ERROR) << "Video capture device already set.";
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.";
89 QTCaptureDevice *device = [captureDevices objectAtIndex:index];
90 if ([[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
92 DLOG(ERROR) << "Cannot open suspended video capture device.";
96 if (![device open:&error]) {
97 DLOG(ERROR) << "Could not open video capture device."
98 << [[error localizedDescription] UTF8String];
101 captureDeviceInput_ = [[QTCaptureDeviceInput alloc] initWithDevice:device];
102 captureSession_ = [[QTCaptureSession alloc] init];
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];
113 // This key can be used to check if video capture code was related to a
115 base::debug::SetCrashKeyValue("VideoCaptureDeviceQTKit", "OpenedDevice");
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];
126 // Remove the previously set capture device.
127 if (!captureDeviceInput_) {
128 DLOG(ERROR) << "No video capture device set.";
131 if ([[captureSession_ inputs] count] > 0) {
132 // The device is still running.
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];
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:)
154 [captureSession_ release];
155 captureSession_ = nil;
156 [captureDeviceInput_ release];
157 captureDeviceInput_ = nil;
162 - (BOOL)setCaptureHeight:(int)height width:(int)width frameRate:(int)frameRate {
163 if (!captureDeviceInput_) {
164 DLOG(ERROR) << "No video capture device set.";
167 if ([[captureSession_ outputs] count] != 1) {
168 DLOG(ERROR) << "Video capture capabilities already set.";
171 if (frameRate <= 0) {
172 DLOG(ERROR) << "Wrong frame rate.";
176 frameRate_ = frameRate;
178 QTCaptureDecompressedVideoOutput *output =
179 [[captureSession_ outputs] objectAtIndex:0];
181 // The old capture dictionary is used to retrieve the initial pixel
182 // format, which must be maintained.
183 NSDictionary *oldCaptureDictionary = [output pixelBufferAttributes];
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,
196 [output setPixelBufferAttributes:captureDictionary];
198 [output setMinimumVideoFrameInterval:(NSTimeInterval)1/(float)frameRate];
202 - (BOOL)startCapture {
203 if ([[captureSession_ outputs] count] == 0) {
204 // Capture properties not set.
205 DLOG(ERROR) << "Video capture device not initialized.";
208 if ([[captureSession_ inputs] count] == 0) {
210 if (![captureSession_ addInput:captureDeviceInput_ error:&error]) {
211 DLOG(ERROR) << "Could not connect video capture device."
212 << [[error localizedDescription] UTF8String];
215 NSNotificationCenter * notificationCenter =
216 [NSNotificationCenter defaultCenter];
217 [notificationCenter addObserver:self
218 selector:@selector(handleNotification:)
219 name:QTCaptureSessionRuntimeErrorNotification
220 object:captureSession_];
221 [captureSession_ startRunning];
226 - (void)stopCapture {
227 if ([[captureSession_ inputs] count] == 1) {
228 [captureSession_ removeInput:captureDeviceInput_];
229 [captureSession_ stopRunning];
232 [[NSNotificationCenter defaultCenter] removeObserver:self];
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 {
241 if(!frameReceiver_) {
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;
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
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
271 adjustedFrame_.resize(expectedBytesPerRow * frameHeight);
272 // std::vector is contiguous according to standard.
273 UInt8* adjustedAddress = &adjustedFrame_[0];
275 for (size_t y = 0; y < frameHeight; ++y) {
276 memcpy(adjustedAddress + y * expectedBytesPerRow,
277 addressToPass + y * bytesPerRow,
278 expectedBytesPerRow);
281 addressToPass = adjustedAddress;
282 frameSize = frameHeight * expectedBytesPerRow;
284 media::VideoCaptureCapability captureCapability;
285 captureCapability.width = frameWidth;
286 captureCapability.height = frameHeight;
287 captureCapability.frame_rate = frameRate_;
288 captureCapability.color = media::PIXEL_FORMAT_UYVY;
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);
304 aspectDenominatorRef, kCFNumberIntType, &aspectDenominator);
307 // Deliver the captured video frame.
308 frameReceiver_->ReceiveFrame(addressToPass, frameSize, captureCapability,
309 aspectNumerator, aspectDenominator);
311 CVPixelBufferUnlockBaseAddress(videoFrame, kLockFlags);
316 - (void)handleNotification:(NSNotification *)errorNotification {
317 NSError * error = (NSError *)[[errorNotification userInfo]
318 objectForKey:QTCaptureSessionErrorKey];
319 frameReceiver_->ReceiveError([[error localizedDescription] UTF8String]);