Upstream version 5.34.92.0
[platform/framework/web/crosswalk.git] / src / media / video / capture / mac / video_capture_device_avfoundation_mac.mm
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.
4
5 #import "media/video/capture/mac/video_capture_device_avfoundation_mac.h"
6
7 #import <CoreVideo/CoreVideo.h>
8
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"
13
14 @implementation VideoCaptureDeviceAVFoundation
15
16 #pragma mark Class methods
17
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]];
27     }
28   }
29 }
30
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];
37   return deviceNames;
38 }
39
40 #pragma mark Public methods
41
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]);
49   }
50   return self;
51 }
52
53 - (void)dealloc {
54   [self stopCapture];
55   [super dealloc];
56 }
57
58 - (void)setFrameReceiver:(media::VideoCaptureDeviceMac*)frameReceiver {
59   base::AutoLock lock(lock_);
60   frameReceiver_ = frameReceiver;
61 }
62
63 - (BOOL)setCaptureDevice:(NSString*)deviceId {
64   DCHECK(captureSession_);
65   DCHECK(thread_checker_.CalledOnValidThread());
66
67   if (!deviceId) {
68     // First stop the capture session, if it's running.
69     [self stopCapture];
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;
76     }
77     return YES;
78   }
79
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.";
84     return NO;
85   }
86
87   // Create the capture input associated with the device. Easy peasy.
88   NSError* error = nil;
89   captureDeviceInput_ = [AVCaptureDeviceInputGlue
90       deviceInputWithDevice:captureDevice_
91                       error:&error];
92   if (!captureDeviceInput_) {
93     captureDevice_ = nil;
94     DLOG(ERROR) << "Could not create video capture input: "
95                 << [[error localizedDescription] UTF8String];
96     return NO;
97   }
98   [captureSession_ addInput:captureDeviceInput_];
99
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.";
107     return NO;
108   }
109   [captureVideoDataOutput_
110       setSampleBufferDelegate:self
111                         queue:dispatch_get_global_queue(
112                             DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
113   [captureSession_ addOutput:captureVideoDataOutput_];
114   return YES;
115 }
116
117 - (BOOL)setCaptureHeight:(int)height width:(int)width frameRate:(int)frameRate {
118   DCHECK(thread_checker_.CalledOnValidThread());
119   frameWidth_ = width;
120   frameHeight_ = height;
121   frameRate_ = frameRate;
122
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_
129           canSetSessionPreset:
130               AVFoundationGlue::AVCaptureSessionPreset640x480()]) {
131     sessionPreset = AVFoundationGlue::AVCaptureSessionPreset640x480();
132   } else if (width == 320 && height == 240 && [captureSession_
133           canSetSessionPreset:
134               AVFoundationGlue::AVCaptureSessionPreset320x240()]) {
135     sessionPreset = AVFoundationGlue::AVCaptureSessionPreset320x240();
136   } else {
137     DLOG(ERROR) << "Unsupported resolution (" << width << "x" << height << ")";
138     return NO;
139   }
140   [captureSession_ setSessionPreset:sessionPreset];
141
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";
146     return NO;
147   }
148
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()
162   };
163   [captureVideoDataOutput_ setVideoSettings:videoSettingsDictionary];
164
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)];
174   }
175   if ([captureConnection
176            respondsToSelector:@selector(isVideoMaxFrameDurationSupported)] &&
177       [captureConnection isVideoMaxFrameDurationSupported]) {
178     [captureConnection setVideoMaxFrameDuration:
179         CoreMediaGlue::CMTimeMake(1, frameRate)];
180   }
181   return YES;
182 }
183
184 - (BOOL)startCapture {
185   DCHECK(thread_checker_.CalledOnValidThread());
186   if (!captureSession_) {
187     DLOG(ERROR) << "Video capture session not initialized.";
188     return NO;
189   }
190   // Connect the notifications.
191   NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
192   [nc addObserver:self
193          selector:@selector(onVideoError:)
194              name:AVFoundationGlue::AVCaptureSessionRuntimeErrorNotification()
195            object:captureSession_];
196   [captureSession_ startRunning];
197   return YES;
198 }
199
200 - (void)stopCapture {
201   DCHECK(thread_checker_.CalledOnValidThread());
202   if ([captureSession_ isRunning])
203     [captureSession_ stopRunning];  // Synchronous.
204   [[NSNotificationCenter defaultCenter] removeObserver:self];
205 }
206
207 #pragma mark Private methods
208
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) ==
218           kCVReturnSuccess) {
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);
225
226     media::VideoCaptureFormat captureFormat(
227         gfx::Size(frameWidth, frameHeight),
228         frameRate_,
229         media::PIXEL_FORMAT_UYVY);
230     base::AutoLock lock(lock_);
231     if (!frameReceiver_)
232       return;
233     frameReceiver_->ReceiveFrame(addressToPass, frameSize, captureFormat, 0, 0);
234     CVPixelBufferUnlockBaseAddress(videoFrame, kLockFlags);
235   }
236 }
237
238 - (void)onVideoError:(NSNotification*)errorNotification {
239   NSError* error = base::mac::ObjCCast<NSError>([[errorNotification userInfo]
240       objectForKey:AVFoundationGlue::AVCaptureSessionErrorKey()]);
241   base::AutoLock lock(lock_);
242   if (frameReceiver_)
243     frameReceiver_->ReceiveError([[error localizedDescription] UTF8String]);
244 }
245
246 @end