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 ![device isSuspended]) {
26 DeviceNameAndTransportType* nameAndTransportType =
27 [[[DeviceNameAndTransportType alloc]
28 initWithName:[device localizedName]
29 transportType:[device transportType]] autorelease];
30 [deviceNames setObject:nameAndTransportType
31 forKey:[device uniqueID]];
36 + (NSDictionary*)deviceNames {
37 NSMutableDictionary* deviceNames =
38 [[[NSMutableDictionary alloc] init] autorelease];
39 // The device name retrieval is not going to happen in the main thread, and
40 // this might cause instabilities (it did in QTKit), so keep an eye here.
41 [self getDeviceNames:deviceNames];
45 + (void)getDevice:(const media::VideoCaptureDevice::Name&)name
46 supportedFormats:(media::VideoCaptureFormats*)formats{
47 NSArray* devices = [AVCaptureDeviceGlue devices];
48 CrAVCaptureDevice* device = nil;
49 for (device in devices) {
50 if ([[device uniqueID] UTF8String] == name.id())
55 for (CrAVCaptureDeviceFormat* format in device.formats) {
56 // MediaSubType is a CMPixelFormatType but can be used as CVPixelFormatType
57 // as well according to CMFormatDescription.h
58 media::VideoPixelFormat pixelFormat = media::PIXEL_FORMAT_UNKNOWN;
59 switch (CoreMediaGlue::CMFormatDescriptionGetMediaSubType(
60 [format formatDescription])) {
61 case kCVPixelFormatType_422YpCbCr8: // Typical.
62 pixelFormat = media::PIXEL_FORMAT_UYVY;
64 case CoreMediaGlue::kCMPixelFormat_422YpCbCr8_yuvs:
65 pixelFormat = media::PIXEL_FORMAT_YUY2;
67 case CoreMediaGlue::kCMVideoCodecType_JPEG_OpenDML:
68 pixelFormat = media::PIXEL_FORMAT_MJPEG;
73 CoreMediaGlue::CMVideoDimensions dimensions =
74 CoreMediaGlue::CMVideoFormatDescriptionGetDimensions(
75 [format formatDescription]);
77 for (CrAVFrameRateRange* frameRate in
78 [format videoSupportedFrameRateRanges]) {
79 media::VideoCaptureFormat format(
80 gfx::Size(dimensions.width, dimensions.height),
81 frameRate.maxFrameRate,
83 formats->push_back(format);
84 DVLOG(2) << name.name() << " resolution: "
85 << format.frame_size.ToString() << ", fps: "
86 << format.frame_rate << ", pixel format: "
87 << format.pixel_format;
93 #pragma mark Public methods
95 - (id)initWithFrameReceiver:(media::VideoCaptureDeviceMac*)frameReceiver {
96 if ((self = [super init])) {
97 DCHECK(main_thread_checker_.CalledOnValidThread());
98 DCHECK(frameReceiver);
99 [self setFrameReceiver:frameReceiver];
100 captureSession_.reset(
101 [[AVFoundationGlue::AVCaptureSessionClass() alloc] init]);
111 - (void)setFrameReceiver:(media::VideoCaptureDeviceMac*)frameReceiver {
112 base::AutoLock lock(lock_);
113 frameReceiver_ = frameReceiver;
116 - (BOOL)setCaptureDevice:(NSString*)deviceId {
117 DCHECK(captureSession_);
118 DCHECK(main_thread_checker_.CalledOnValidThread());
121 // First stop the capture session, if it's running.
123 // Now remove the input and output from the capture session.
124 [captureSession_ removeOutput:captureVideoDataOutput_];
125 if (captureDeviceInput_) {
126 [captureSession_ removeInput:captureDeviceInput_];
127 // No need to release |captureDeviceInput_|, is owned by the session.
128 captureDeviceInput_ = nil;
133 // Look for input device with requested name.
134 captureDevice_ = [AVCaptureDeviceGlue deviceWithUniqueID:deviceId];
135 if (!captureDevice_) {
136 [self sendErrorString:[NSString
137 stringWithUTF8String:"Could not open video capture device."]];
141 // Create the capture input associated with the device. Easy peasy.
142 NSError* error = nil;
143 captureDeviceInput_ = [AVCaptureDeviceInputGlue
144 deviceInputWithDevice:captureDevice_
146 if (!captureDeviceInput_) {
147 captureDevice_ = nil;
148 [self sendErrorString:[NSString
149 stringWithFormat:@"Could not create video capture input (%@): %@",
150 [error localizedDescription],
151 [error localizedFailureReason]]];
154 [captureSession_ addInput:captureDeviceInput_];
156 // Create a new data output for video. The data output is configured to
157 // discard late frames by default.
158 captureVideoDataOutput_.reset(
159 [[AVFoundationGlue::AVCaptureVideoDataOutputClass() alloc] init]);
160 if (!captureVideoDataOutput_) {
161 [captureSession_ removeInput:captureDeviceInput_];
162 [self sendErrorString:[NSString
163 stringWithUTF8String:"Could not create video data output."]];
166 [captureVideoDataOutput_
167 setSampleBufferDelegate:self
168 queue:dispatch_get_global_queue(
169 DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
170 [captureSession_ addOutput:captureVideoDataOutput_];
174 - (BOOL)setCaptureHeight:(int)height
176 frameRate:(float)frameRate {
177 // Check if either of VideoCaptureDeviceMac::AllocateAndStart() or
178 // VideoCaptureDeviceMac::ReceiveFrame() is calling here, depending on the
179 // running state. VCDM::ReceiveFrame() calls here to change aspect ratio.
180 DCHECK((![captureSession_ isRunning] &&
181 main_thread_checker_.CalledOnValidThread()) ||
182 callback_thread_checker_.CalledOnValidThread());
185 frameHeight_ = height;
186 frameRate_ = frameRate;
188 // The capture output has to be configured, despite Mac documentation
189 // detailing that setting the sessionPreset would be enough. The reason for
190 // this mismatch is probably because most of the AVFoundation docs are written
191 // for iOS and not for MacOsX. AVVideoScalingModeKey() refers to letterboxing
192 // yes/no and preserve aspect ratio yes/no when scaling. Currently we set
193 // cropping and preservation.
194 NSDictionary* videoSettingsDictionary = @{
195 (id)kCVPixelBufferWidthKey : @(width),
196 (id)kCVPixelBufferHeightKey : @(height),
197 (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_422YpCbCr8),
198 AVFoundationGlue::AVVideoScalingModeKey() :
199 AVFoundationGlue::AVVideoScalingModeResizeAspectFill()
201 [captureVideoDataOutput_ setVideoSettings:videoSettingsDictionary];
203 CrAVCaptureConnection* captureConnection = [captureVideoDataOutput_
204 connectionWithMediaType:AVFoundationGlue::AVMediaTypeVideo()];
205 // Check selector existence, related to bugs http://crbug.com/327532 and
206 // http://crbug.com/328096.
207 // CMTimeMake accepts integer argumenst but |frameRate| is float, round it.
208 if ([captureConnection
209 respondsToSelector:@selector(isVideoMinFrameDurationSupported)] &&
210 [captureConnection isVideoMinFrameDurationSupported]) {
211 [captureConnection setVideoMinFrameDuration:
212 CoreMediaGlue::CMTimeMake(media::kFrameRatePrecision,
213 (int)(frameRate * media::kFrameRatePrecision))];
215 if ([captureConnection
216 respondsToSelector:@selector(isVideoMaxFrameDurationSupported)] &&
217 [captureConnection isVideoMaxFrameDurationSupported]) {
218 [captureConnection setVideoMaxFrameDuration:
219 CoreMediaGlue::CMTimeMake(media::kFrameRatePrecision,
220 (int)(frameRate * media::kFrameRatePrecision))];
225 - (BOOL)startCapture {
226 DCHECK(main_thread_checker_.CalledOnValidThread());
227 if (!captureSession_) {
228 DLOG(ERROR) << "Video capture session not initialized.";
231 // Connect the notifications.
232 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
234 selector:@selector(onVideoError:)
235 name:AVFoundationGlue::AVCaptureSessionRuntimeErrorNotification()
236 object:captureSession_];
237 [captureSession_ startRunning];
241 - (void)stopCapture {
242 DCHECK(main_thread_checker_.CalledOnValidThread());
243 if ([captureSession_ isRunning])
244 [captureSession_ stopRunning]; // Synchronous.
245 [[NSNotificationCenter defaultCenter] removeObserver:self];
248 #pragma mark Private methods
250 // |captureOutput| is called by the capture device to deliver a new frame.
251 - (void)captureOutput:(CrAVCaptureOutput*)captureOutput
252 didOutputSampleBuffer:(CoreMediaGlue::CMSampleBufferRef)sampleBuffer
253 fromConnection:(CrAVCaptureConnection*)connection {
254 // AVFoundation calls from a number of threads, depending on, at least, if
255 // Chrome is on foreground or background. Sample the actual thread here.
256 callback_thread_checker_.DetachFromThread();
257 callback_thread_checker_.CalledOnValidThread();
258 CVImageBufferRef videoFrame =
259 CoreMediaGlue::CMSampleBufferGetImageBuffer(sampleBuffer);
260 // Lock the frame and calculate frame size.
261 const int kLockFlags = 0;
262 if (CVPixelBufferLockBaseAddress(videoFrame, kLockFlags) ==
264 void* baseAddress = CVPixelBufferGetBaseAddress(videoFrame);
265 size_t bytesPerRow = CVPixelBufferGetBytesPerRow(videoFrame);
266 size_t frameWidth = CVPixelBufferGetWidth(videoFrame);
267 size_t frameHeight = CVPixelBufferGetHeight(videoFrame);
268 size_t frameSize = bytesPerRow * frameHeight;
269 UInt8* addressToPass = reinterpret_cast<UInt8*>(baseAddress);
271 media::VideoCaptureFormat captureFormat(
272 gfx::Size(frameWidth, frameHeight),
274 media::PIXEL_FORMAT_UYVY);
275 base::AutoLock lock(lock_);
278 frameReceiver_->ReceiveFrame(addressToPass, frameSize, captureFormat, 0, 0);
279 CVPixelBufferUnlockBaseAddress(videoFrame, kLockFlags);
283 - (void)onVideoError:(NSNotification*)errorNotification {
284 NSError* error = base::mac::ObjCCast<NSError>([[errorNotification userInfo]
285 objectForKey:AVFoundationGlue::AVCaptureSessionErrorKey()]);
286 [self sendErrorString:[NSString
287 stringWithFormat:@"%@: %@",
288 [error localizedDescription],
289 [error localizedFailureReason]]];
292 - (void)sendErrorString:(NSString*)error {
293 DLOG(ERROR) << [error UTF8String];
294 base::AutoLock lock(lock_);
296 frameReceiver_->ReceiveError([error UTF8String]);