Upstream version 9.38.198.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         ![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]];
32     }
33   }
34 }
35
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];
42   return deviceNames;
43 }
44
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())
51       break;
52   }
53   if (device == nil)
54     return;
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;
63         break;
64       case CoreMediaGlue::kCMPixelFormat_422YpCbCr8_yuvs:
65         pixelFormat = media::PIXEL_FORMAT_YUY2;
66         break;
67       case CoreMediaGlue::kCMVideoCodecType_JPEG_OpenDML:
68         pixelFormat = media::PIXEL_FORMAT_MJPEG;
69       default:
70         break;
71     }
72
73   CoreMediaGlue::CMVideoDimensions dimensions =
74         CoreMediaGlue::CMVideoFormatDescriptionGetDimensions(
75             [format formatDescription]);
76
77   for (CrAVFrameRateRange* frameRate in
78            [format videoSupportedFrameRateRanges]) {
79       media::VideoCaptureFormat format(
80           gfx::Size(dimensions.width, dimensions.height),
81           frameRate.maxFrameRate,
82           pixelFormat);
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;
88     }
89   }
90
91 }
92
93 #pragma mark Public methods
94
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]);
102   }
103   return self;
104 }
105
106 - (void)dealloc {
107   [self stopCapture];
108   [super dealloc];
109 }
110
111 - (void)setFrameReceiver:(media::VideoCaptureDeviceMac*)frameReceiver {
112   base::AutoLock lock(lock_);
113   frameReceiver_ = frameReceiver;
114 }
115
116 - (BOOL)setCaptureDevice:(NSString*)deviceId {
117   DCHECK(captureSession_);
118   DCHECK(main_thread_checker_.CalledOnValidThread());
119
120   if (!deviceId) {
121     // First stop the capture session, if it's running.
122     [self stopCapture];
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;
129     }
130     return YES;
131   }
132
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."]];
138     return NO;
139   }
140
141   // Create the capture input associated with the device. Easy peasy.
142   NSError* error = nil;
143   captureDeviceInput_ = [AVCaptureDeviceInputGlue
144       deviceInputWithDevice:captureDevice_
145                       error:&error];
146   if (!captureDeviceInput_) {
147     captureDevice_ = nil;
148     [self sendErrorString:[NSString
149         stringWithFormat:@"Could not create video capture input (%@): %@",
150                          [error localizedDescription],
151                          [error localizedFailureReason]]];
152     return NO;
153   }
154   [captureSession_ addInput:captureDeviceInput_];
155
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."]];
164     return NO;
165   }
166   [captureVideoDataOutput_
167       setSampleBufferDelegate:self
168                         queue:dispatch_get_global_queue(
169                             DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
170   [captureSession_ addOutput:captureVideoDataOutput_];
171   return YES;
172 }
173
174 - (BOOL)setCaptureHeight:(int)height
175                    width:(int)width
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());
183
184   frameWidth_ = width;
185   frameHeight_ = height;
186   frameRate_ = frameRate;
187
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()
200   };
201   [captureVideoDataOutput_ setVideoSettings:videoSettingsDictionary];
202
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))];
214   }
215   if ([captureConnection
216            respondsToSelector:@selector(isVideoMaxFrameDurationSupported)] &&
217       [captureConnection isVideoMaxFrameDurationSupported]) {
218     [captureConnection setVideoMaxFrameDuration:
219         CoreMediaGlue::CMTimeMake(media::kFrameRatePrecision,
220             (int)(frameRate * media::kFrameRatePrecision))];
221   }
222   return YES;
223 }
224
225 - (BOOL)startCapture {
226   DCHECK(main_thread_checker_.CalledOnValidThread());
227   if (!captureSession_) {
228     DLOG(ERROR) << "Video capture session not initialized.";
229     return NO;
230   }
231   // Connect the notifications.
232   NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
233   [nc addObserver:self
234          selector:@selector(onVideoError:)
235              name:AVFoundationGlue::AVCaptureSessionRuntimeErrorNotification()
236            object:captureSession_];
237   [captureSession_ startRunning];
238   return YES;
239 }
240
241 - (void)stopCapture {
242   DCHECK(main_thread_checker_.CalledOnValidThread());
243   if ([captureSession_ isRunning])
244     [captureSession_ stopRunning];  // Synchronous.
245   [[NSNotificationCenter defaultCenter] removeObserver:self];
246 }
247
248 #pragma mark Private methods
249
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) ==
263           kCVReturnSuccess) {
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);
270
271     media::VideoCaptureFormat captureFormat(
272         gfx::Size(frameWidth, frameHeight),
273         frameRate_,
274         media::PIXEL_FORMAT_UYVY);
275     base::AutoLock lock(lock_);
276     if (!frameReceiver_)
277       return;
278     frameReceiver_->ReceiveFrame(addressToPass, frameSize, captureFormat, 0, 0);
279     CVPixelBufferUnlockBaseAddress(videoFrame, kLockFlags);
280   }
281 }
282
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]]];
290 }
291
292 - (void)sendErrorString:(NSString*)error {
293   DLOG(ERROR) << [error UTF8String];
294   base::AutoLock lock(lock_);
295   if (frameReceiver_)
296     frameReceiver_->ReceiveError([error UTF8String]);
297 }
298
299 @end