diff options
Diffstat (limited to 'chromium/media/capture/video/mac/video_capture_device_qtkit_mac.mm')
-rw-r--r-- | chromium/media/capture/video/mac/video_capture_device_qtkit_mac.mm | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/chromium/media/capture/video/mac/video_capture_device_qtkit_mac.mm b/chromium/media/capture/video/mac/video_capture_device_qtkit_mac.mm new file mode 100644 index 00000000000..1ea4c9e4498 --- /dev/null +++ b/chromium/media/capture/video/mac/video_capture_device_qtkit_mac.mm @@ -0,0 +1,360 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "media/capture/video/mac/video_capture_device_qtkit_mac.h" + +#import <QTKit/QTKit.h> + +#include "base/debug/crash_logging.h" +#include "base/logging.h" +#include "media/base/video_capture_types.h" +#include "media/capture/video/mac/video_capture_device_mac.h" +#include "media/capture/video/video_capture_device.h" +#include "ui/gfx/geometry/size.h" + +@implementation VideoCaptureDeviceQTKit + +#pragma mark Class methods + ++ (void)getDeviceNames:(NSMutableDictionary*)deviceNames { + // Third-party drivers often throw exceptions. The following catches any + // exceptions and continues in an orderly fashion with no devices detected. + NSArray* captureDevices = nil; + @try { + captureDevices = + [QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeVideo]; + } @catch (id exception) { + } + + for (QTCaptureDevice* device in captureDevices) { + if ([[device attributeForKey:QTCaptureDeviceSuspendedAttribute] boolValue]) + continue; + DeviceNameAndTransportType* nameAndTransportType = [[ + [DeviceNameAndTransportType alloc] + initWithName:[device localizedDisplayName] + transportType:media::kIOAudioDeviceTransportTypeUnknown] autorelease]; + [deviceNames setObject:nameAndTransportType forKey:[device uniqueID]]; + } +} + ++ (NSDictionary*)deviceNames { + NSMutableDictionary* deviceNames = + [[[NSMutableDictionary alloc] init] autorelease]; + + // TODO(shess): Post to the main thread to see if that helps + // http://crbug.com/139164 + [self performSelectorOnMainThread:@selector(getDeviceNames:) + withObject:deviceNames + waitUntilDone:YES]; + return deviceNames; +} + +#pragma mark Public methods + +- (id)initWithFrameReceiver:(media::VideoCaptureDeviceMac*)frameReceiver { + self = [super init]; + if (self) { + frameReceiver_ = frameReceiver; + lock_ = [[NSLock alloc] init]; + } + return self; +} + +- (void)dealloc { + [captureSession_ release]; + [captureDeviceInput_ release]; + [super dealloc]; +} + +- (void)setFrameReceiver:(media::VideoCaptureDeviceMac*)frameReceiver { + [lock_ lock]; + frameReceiver_ = frameReceiver; + [lock_ unlock]; +} + +- (BOOL)setCaptureDevice:(NSString*)deviceId { + if (deviceId) { + // Set the capture device. + if (captureDeviceInput_) { + DLOG(ERROR) << "Video capture device already set."; + return NO; + } + + // TODO(mcasas): Consider using [QTCaptureDevice deviceWithUniqueID] instead + // of explicitly forcing reenumeration of devices. + NSArray* captureDevices = + [QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeVideo]; + NSArray* captureDevicesNames = [captureDevices valueForKey:@"uniqueID"]; + NSUInteger index = [captureDevicesNames indexOfObject:deviceId]; + if (index == NSNotFound) { + [self sendErrorString:[NSString stringWithUTF8String: + "Video capture device not found."]]; + return NO; + } + QTCaptureDevice* device = [captureDevices objectAtIndex:index]; + if ([[device + attributeForKey:QTCaptureDeviceSuspendedAttribute] boolValue]) { + [self sendErrorString: + [NSString stringWithUTF8String: + "Cannot open suspended video capture device."]]; + return NO; + } + NSError* error; + if (![device open:&error]) { + [self sendErrorString: + [NSString stringWithFormat: + @"Could not open video capture device (%@): %@", + [error localizedDescription], + [error localizedFailureReason]]]; + return NO; + } + captureDeviceInput_ = [[QTCaptureDeviceInput alloc] initWithDevice:device]; + captureSession_ = [[QTCaptureSession alloc] init]; + + QTCaptureDecompressedVideoOutput* captureDecompressedOutput = + [[[QTCaptureDecompressedVideoOutput alloc] init] autorelease]; + [captureDecompressedOutput setDelegate:self]; + [captureDecompressedOutput setAutomaticallyDropsLateVideoFrames:YES]; + if (![captureSession_ addOutput:captureDecompressedOutput error:&error]) { + [self + sendErrorString: + [NSString stringWithFormat: + @"Could not connect video capture output (%@): %@", + [error localizedDescription], + [error localizedFailureReason]]]; + return NO; + } + + // This key can be used to check if video capture code was related to a + // particular crash. + base::debug::SetCrashKeyValue("VideoCaptureDeviceQTKit", "OpenedDevice"); + + // Set the video pixel format to 2VUY (a.k.a UYVY, packed 4:2:2). + NSDictionary* captureDictionary = [NSDictionary + dictionaryWithObject: + [NSNumber numberWithUnsignedInt:kCVPixelFormatType_422YpCbCr8] + forKey:(id)kCVPixelBufferPixelFormatTypeKey]; + [captureDecompressedOutput setPixelBufferAttributes:captureDictionary]; + + return YES; + } else { + // Remove the previously set capture device. + if (!captureDeviceInput_) { + // Being here means stopping a device that never started OK in the first + // place, log it. + [self sendLogString:[NSString + stringWithUTF8String: + "No video capture device set, on removal."]]; + return YES; + } + // Tear down input and output, stop the capture and deregister observers. + [self stopCapture]; + [captureSession_ release]; + captureSession_ = nil; + [captureDeviceInput_ release]; + captureDeviceInput_ = nil; + return YES; + } +} + +- (BOOL)setCaptureHeight:(int)height + width:(int)width + frameRate:(float)frameRate { + if (!captureDeviceInput_) { + [self sendErrorString: + [NSString stringWithUTF8String:"No video capture device set."]]; + return NO; + } + if ([[captureSession_ outputs] count] != 1) { + [self sendErrorString:[NSString + stringWithUTF8String: + "Video capture capabilities already set."]]; + return NO; + } + if (frameRate <= 0.0f) { + [self sendErrorString:[NSString stringWithUTF8String:"Wrong frame rate."]]; + return NO; + } + + frameRate_ = frameRate; + + QTCaptureDecompressedVideoOutput* output = + [[captureSession_ outputs] objectAtIndex:0]; + + // Set up desired output properties. The old capture dictionary is used to + // retrieve the initial pixel format, which must be maintained. + NSDictionary* videoSettingsDictionary = @{ + (id)kCVPixelBufferWidthKey : @(width), (id) + kCVPixelBufferHeightKey : @(height), (id) + kCVPixelBufferPixelFormatTypeKey : [[output pixelBufferAttributes] + valueForKey:(id)kCVPixelBufferPixelFormatTypeKey] + }; + [output setPixelBufferAttributes:videoSettingsDictionary]; + + [output setMinimumVideoFrameInterval:(NSTimeInterval)1 / frameRate]; + return YES; +} + +- (BOOL)startCapture { + if ([[captureSession_ outputs] count] == 0) { + // Capture properties not set. + [self + sendErrorString:[NSString stringWithUTF8String: + "Video capture device not initialized."]]; + return NO; + } + if ([[captureSession_ inputs] count] == 0) { + NSError* error; + if (![captureSession_ addInput:captureDeviceInput_ error:&error]) { + [self + sendErrorString: + [NSString stringWithFormat: + @"Could not connect video capture device (%@): %@", + [error localizedDescription], + [error localizedFailureReason]]]; + + return NO; + } + NSNotificationCenter* notificationCenter = + [NSNotificationCenter defaultCenter]; + [notificationCenter addObserver:self + selector:@selector(handleNotification:) + name:QTCaptureSessionRuntimeErrorNotification + object:captureSession_]; + [captureSession_ startRunning]; + } + return YES; +} + +- (void)stopCapture { + // QTKit achieves thread safety and asynchronous execution by posting messages + // to the main thread, e.g. -addOutput:. Both -removeOutput: and -removeInput: + // post a message to the main thread while holding a lock that the + // notification handler might need. To avoid a deadlock, we perform those + // tasks in the main thread. See bugs http://crbug.com/152757 and + // http://crbug.com/399792. + [self performSelectorOnMainThread:@selector(stopCaptureOnUIThread:) + withObject:nil + waitUntilDone:YES]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)stopCaptureOnUIThread:(id)dummy { + if ([[captureSession_ inputs] count] > 0) { + DCHECK_EQ([[captureSession_ inputs] count], 1u); + [captureSession_ removeInput:captureDeviceInput_]; + [captureSession_ stopRunning]; + } + if ([[captureSession_ outputs] count] > 0) { + DCHECK_EQ([[captureSession_ outputs] count], 1u); + id output = [[captureSession_ outputs] objectAtIndex:0]; + [output setDelegate:nil]; + [captureSession_ removeOutput:output]; + } +} + +// |captureOutput| is called by the capture device to deliver a new frame. +- (void)captureOutput:(QTCaptureOutput*)captureOutput + didOutputVideoFrame:(CVImageBufferRef)videoFrame + withSampleBuffer:(QTSampleBuffer*)sampleBuffer + fromConnection:(QTCaptureConnection*)connection { + [lock_ lock]; + if (!frameReceiver_) { + [lock_ unlock]; + return; + } + + // Lock the frame and calculate frame size. + const int kLockFlags = 0; + if (CVPixelBufferLockBaseAddress(videoFrame, kLockFlags) == + kCVReturnSuccess) { + void* baseAddress = CVPixelBufferGetBaseAddress(videoFrame); + size_t bytesPerRow = CVPixelBufferGetBytesPerRow(videoFrame); + size_t frameWidth = CVPixelBufferGetWidth(videoFrame); + size_t frameHeight = CVPixelBufferGetHeight(videoFrame); + size_t frameSize = bytesPerRow * frameHeight; + + // TODO(shess): bytesPerRow may not correspond to frameWidth_*2, + // but VideoCaptureController::OnIncomingCapturedData() requires + // it to do so. Plumbing things through is intrusive, for now + // just deliver an adjusted buffer. + // TODO(nick): This workaround could probably be eliminated by using + // VideoCaptureController::OnIncomingCapturedVideoFrame, which supports + // pitches. + UInt8* addressToPass = static_cast<UInt8*>(baseAddress); + // UYVY is 2 bytes per pixel. + size_t expectedBytesPerRow = frameWidth * 2; + if (bytesPerRow > expectedBytesPerRow) { + // TODO(shess): frameHeight and frameHeight_ are not the same, + // try to do what the surrounding code seems to assume. + // Ironically, captureCapability and frameSize are ignored + // anyhow. + adjustedFrame_.resize(expectedBytesPerRow * frameHeight); + // std::vector is contiguous according to standard. + UInt8* adjustedAddress = &adjustedFrame_[0]; + + for (size_t y = 0; y < frameHeight; ++y) { + memcpy(adjustedAddress + y * expectedBytesPerRow, + addressToPass + y * bytesPerRow, expectedBytesPerRow); + } + + addressToPass = adjustedAddress; + frameSize = frameHeight * expectedBytesPerRow; + } + + media::VideoCaptureFormat captureFormat( + gfx::Size(frameWidth, frameHeight), frameRate_, + media::PIXEL_FORMAT_UYVY); + + // The aspect ratio dictionary is often missing, in which case we report + // a pixel aspect ratio of 0:0. + int aspectNumerator = 0, aspectDenominator = 0; + CFDictionaryRef aspectRatioDict = (CFDictionaryRef)CVBufferGetAttachment( + videoFrame, kCVImageBufferPixelAspectRatioKey, NULL); + if (aspectRatioDict) { + CFNumberRef aspectNumeratorRef = (CFNumberRef)CFDictionaryGetValue( + aspectRatioDict, kCVImageBufferPixelAspectRatioHorizontalSpacingKey); + CFNumberRef aspectDenominatorRef = (CFNumberRef)CFDictionaryGetValue( + aspectRatioDict, kCVImageBufferPixelAspectRatioVerticalSpacingKey); + DCHECK(aspectNumeratorRef && aspectDenominatorRef) + << "Aspect Ratio dictionary missing its entries."; + CFNumberGetValue(aspectNumeratorRef, kCFNumberIntType, &aspectNumerator); + CFNumberGetValue(aspectDenominatorRef, kCFNumberIntType, + &aspectDenominator); + } + + // Deliver the captured video frame. + frameReceiver_->ReceiveFrame(addressToPass, frameSize, captureFormat, + aspectNumerator, aspectDenominator); + + CVPixelBufferUnlockBaseAddress(videoFrame, kLockFlags); + } + [lock_ unlock]; +} + +- (void)handleNotification:(NSNotification*)errorNotification { + NSError* error = (NSError*) + [[errorNotification userInfo] objectForKey:QTCaptureSessionErrorKey]; + [self sendErrorString: + [NSString stringWithFormat:@"%@: %@", [error localizedDescription], + [error localizedFailureReason]]]; +} + +- (void)sendErrorString:(NSString*)error { + DLOG(ERROR) << [error UTF8String]; + [lock_ lock]; + if (frameReceiver_) + frameReceiver_->ReceiveError([error UTF8String]); + [lock_ unlock]; +} + +- (void)sendLogString:(NSString*)message { + DVLOG(1) << [message UTF8String]; + [lock_ lock]; + if (frameReceiver_) + frameReceiver_->LogMessage([message UTF8String]); + [lock_ unlock]; +} + +@end |