1 // Copyright 2013 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_avfoundation_mac.h"
7 #import <CoreVideo/CoreVideo.h>
9 #include "base/logging.h"
10 #include "base/mac/foundation_util.h"
11 #include "media/video/capture/mac/video_capture_device_mac.h"
12 #include "ui/gfx/size.h"
14 @implementation VideoCaptureDeviceAVFoundation
16 #pragma mark Class methods
18 + (void)getDeviceNames:(NSMutableDictionary*)deviceNames {
19 // At this stage we already know that AVFoundation is supported and the whole
20 // library is loaded and initialised, by the device monitoring.
21 NSArray* devices = [AVCaptureDeviceGlue devices];
22 for (CrAVCaptureDevice* device in devices) {
23 if ([device hasMediaType:AVFoundationGlue::AVMediaTypeVideo()] ||
24 [device hasMediaType:AVFoundationGlue::AVMediaTypeMuxed()]) {
25 [deviceNames setObject:[device localizedName]
26 forKey:[device uniqueID]];
31 + (NSDictionary*)deviceNames {
32 NSMutableDictionary* deviceNames =
33 [[[NSMutableDictionary alloc] init] autorelease];
34 // The device name retrieval is not going to happen in the main thread, and
35 // this might cause instabilities (it did in QTKit), so keep an eye here.
36 [self getDeviceNames:deviceNames];
40 #pragma mark Public methods
42 - (id)initWithFrameReceiver:(media::VideoCaptureDeviceMac*)frameReceiver {
43 if ((self = [super init])) {
44 DCHECK(thread_checker_.CalledOnValidThread());
45 DCHECK(frameReceiver);
46 [self setFrameReceiver:frameReceiver];
47 captureSession_.reset(
48 [[AVFoundationGlue::AVCaptureSessionClass() alloc] init]);
58 - (void)setFrameReceiver:(media::VideoCaptureDeviceMac*)frameReceiver {
59 base::AutoLock lock(lock_);
60 frameReceiver_ = frameReceiver;
63 - (BOOL)setCaptureDevice:(NSString*)deviceId {
64 DCHECK(captureSession_);
65 DCHECK(thread_checker_.CalledOnValidThread());
68 // First stop the capture session, if it's running.
70 // Now remove the input and output from the capture session.
71 [captureSession_ removeOutput:captureVideoDataOutput_];
72 if (captureDeviceInput_) {
73 [captureSession_ removeInput:captureDeviceInput_];
74 // No need to release |captureDeviceInput_|, is owned by the session.
75 captureDeviceInput_ = nil;
80 // Look for input device with requested name.
81 captureDevice_ = [AVCaptureDeviceGlue deviceWithUniqueID:deviceId];
82 if (!captureDevice_) {
83 DLOG(ERROR) << "Could not open video capture device.";
87 // Create the capture input associated with the device. Easy peasy.
89 captureDeviceInput_ = [AVCaptureDeviceInputGlue
90 deviceInputWithDevice:captureDevice_
92 if (!captureDeviceInput_) {
94 DLOG(ERROR) << "Could not create video capture input: "
95 << [[error localizedDescription] UTF8String];
98 [captureSession_ addInput:captureDeviceInput_];
100 // Create a new data output for video. The data output is configured to
101 // discard late frames by default.
102 captureVideoDataOutput_.reset(
103 [[AVFoundationGlue::AVCaptureVideoDataOutputClass() alloc] init]);
104 if (!captureVideoDataOutput_) {
105 [captureSession_ removeInput:captureDeviceInput_];
106 DLOG(ERROR) << "Could not create video data output.";
109 [captureVideoDataOutput_
110 setSampleBufferDelegate:self
111 queue:dispatch_get_global_queue(
112 DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
113 [captureSession_ addOutput:captureVideoDataOutput_];
117 - (BOOL)setCaptureHeight:(int)height width:(int)width frameRate:(int)frameRate {
118 DCHECK(thread_checker_.CalledOnValidThread());
120 frameHeight_ = height;
121 frameRate_ = frameRate;
123 // Identify the sessionPreset that corresponds to the desired resolution.
124 NSString* sessionPreset;
125 if (width == 1280 && height == 720 && [captureSession_ canSetSessionPreset:
126 AVFoundationGlue::AVCaptureSessionPreset1280x720()]) {
127 sessionPreset = AVFoundationGlue::AVCaptureSessionPreset1280x720();
128 } else if (width == 640 && height == 480 && [captureSession_
130 AVFoundationGlue::AVCaptureSessionPreset640x480()]) {
131 sessionPreset = AVFoundationGlue::AVCaptureSessionPreset640x480();
132 } else if (width == 320 && height == 240 && [captureSession_
134 AVFoundationGlue::AVCaptureSessionPreset320x240()]) {
135 sessionPreset = AVFoundationGlue::AVCaptureSessionPreset320x240();
137 DLOG(ERROR) << "Unsupported resolution (" << width << "x" << height << ")";
140 [captureSession_ setSessionPreset:sessionPreset];
142 // Check that our capture Device can be used with the current preset.
143 if (![captureDevice_ supportsAVCaptureSessionPreset:
144 [captureSession_ sessionPreset]]){
145 DLOG(ERROR) << "Video capture device does not support current preset";
149 // Despite all Mac documentation detailing that setting the sessionPreset is
150 // enough, that is not the case for, at least, the MacBook Air built-in
151 // FaceTime HD Camera, and the capture output has to be configured as well.
152 // The reason for this mismatch is probably because most of the AVFoundation
153 // docs are written for iOS and not for MacOsX.
154 // AVVideoScalingModeKey() refers to letterboxing yes/no and preserve aspect
155 // ratio yes/no when scaling. Currently we set letterbox and preservation.
156 NSDictionary* videoSettingsDictionary = @{
157 (id)kCVPixelBufferWidthKey : @(width),
158 (id)kCVPixelBufferHeightKey : @(height),
159 (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_422YpCbCr8),
160 AVFoundationGlue::AVVideoScalingModeKey() :
161 AVFoundationGlue::AVVideoScalingModeResizeAspect()
163 [captureVideoDataOutput_ setVideoSettings:videoSettingsDictionary];
165 CrAVCaptureConnection* captureConnection = [captureVideoDataOutput_
166 connectionWithMediaType:AVFoundationGlue::AVMediaTypeVideo()];
167 // TODO(mcasas): Check selector existence, related to bugs
168 // http://crbug.com/327532 and http://crbug.com/328096.
169 if ([captureConnection
170 respondsToSelector:@selector(isVideoMinFrameDurationSupported)] &&
171 [captureConnection isVideoMinFrameDurationSupported]) {
172 [captureConnection setVideoMinFrameDuration:
173 CoreMediaGlue::CMTimeMake(1, frameRate)];
175 if ([captureConnection
176 respondsToSelector:@selector(isVideoMaxFrameDurationSupported)] &&
177 [captureConnection isVideoMaxFrameDurationSupported]) {
178 [captureConnection setVideoMaxFrameDuration:
179 CoreMediaGlue::CMTimeMake(1, frameRate)];
184 - (BOOL)startCapture {
185 DCHECK(thread_checker_.CalledOnValidThread());
186 if (!captureSession_) {
187 DLOG(ERROR) << "Video capture session not initialized.";
190 // Connect the notifications.
191 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
193 selector:@selector(onVideoError:)
194 name:AVFoundationGlue::AVCaptureSessionRuntimeErrorNotification()
195 object:captureSession_];
196 [captureSession_ startRunning];
200 - (void)stopCapture {
201 DCHECK(thread_checker_.CalledOnValidThread());
202 if ([captureSession_ isRunning])
203 [captureSession_ stopRunning]; // Synchronous.
204 [[NSNotificationCenter defaultCenter] removeObserver:self];
207 #pragma mark Private methods
209 // |captureOutput| is called by the capture device to deliver a new frame.
210 - (void)captureOutput:(CrAVCaptureOutput*)captureOutput
211 didOutputSampleBuffer:(CoreMediaGlue::CMSampleBufferRef)sampleBuffer
212 fromConnection:(CrAVCaptureConnection*)connection {
213 CVImageBufferRef videoFrame =
214 CoreMediaGlue::CMSampleBufferGetImageBuffer(sampleBuffer);
215 // Lock the frame and calculate frame size.
216 const int kLockFlags = 0;
217 if (CVPixelBufferLockBaseAddress(videoFrame, kLockFlags) ==
219 void* baseAddress = CVPixelBufferGetBaseAddress(videoFrame);
220 size_t bytesPerRow = CVPixelBufferGetBytesPerRow(videoFrame);
221 size_t frameWidth = CVPixelBufferGetWidth(videoFrame);
222 size_t frameHeight = CVPixelBufferGetHeight(videoFrame);
223 size_t frameSize = bytesPerRow * frameHeight;
224 UInt8* addressToPass = reinterpret_cast<UInt8*>(baseAddress);
226 media::VideoCaptureFormat captureFormat(
227 gfx::Size(frameWidth, frameHeight),
229 media::PIXEL_FORMAT_UYVY);
230 base::AutoLock lock(lock_);
233 frameReceiver_->ReceiveFrame(addressToPass, frameSize, captureFormat, 0, 0);
234 CVPixelBufferUnlockBaseAddress(videoFrame, kLockFlags);
238 - (void)onVideoError:(NSNotification*)errorNotification {
239 NSError* error = base::mac::ObjCCast<NSError>([[errorNotification userInfo]
240 objectForKey:AVFoundationGlue::AVCaptureSessionErrorKey()]);
241 base::AutoLock lock(lock_);
243 frameReceiver_->ReceiveError([[error localizedDescription] UTF8String]);