blob: 48f734cc3973ede5f9e55d50ebfdaa3bfd4a3ada [file] [log] [blame]
andrew@webrtc.orga7b57da2012-10-22 18:19:23 +00001/*
2 * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11#define DEFAULT_CAPTURE_DEVICE_INDEX 1
12#define DEFAULT_FRAME_RATE 30
13#define DEFAULT_FRAME_WIDTH 352
14#define DEFAULT_FRAME_HEIGHT 288
15#define ROTATE_CAPTURED_FRAME 1
16#define LOW_QUALITY 1
17
18#import "video_capture_qtkit_objc.h"
19#include "video_capture_qtkit_utility.h"
20#include "trace.h"
21
22using namespace webrtc;
23using namespace videocapturemodule;
24
25@implementation VideoCaptureMacQTKitObjC
26
27#pragma mark **** over-written OS methods
28
29/// ***** Objective-C. Similar to C++ constructor, although must be invoked
30/// manually.
31/// ***** Potentially returns an instance of self
32-(id)init{
33 self = [super init];
34 if(nil != self)
35 {
36 [self checkOSSupported];
37 [self initializeVariables];
38 }
39 else
40 {
41 return nil;
42 }
43 return self;
44}
45
46/// ***** Objective-C. Similar to C++ destructor
47/// ***** Returns nothing
48- (void)dealloc {
49 if(_captureSession)
50 {
51 [_captureSession stopRunning];
52 [_captureSession release];
53 }
54 [super dealloc];
55}
56
57#pragma mark **** public methods
58
59
60
61/// ***** Registers the class's owner, which is where the delivered frames are
62/// sent
63/// ***** Returns 0 on success, -1 otherwise.
64- (NSNumber*)registerOwner:(VideoCaptureMacQTKit*)owner{
65 if(!owner){
66 return [NSNumber numberWithInt:-1];
67 }
68 _owner = owner;
69 return [NSNumber numberWithInt:0];
70}
71
72/// ***** Sets the QTCaptureSession's input device from a char*
73/// ***** Sets several member variables. Can signal the error system if one has
74/// occurred
75/// ***** Returns 0 on success, -1 otherwise.
76- (NSNumber*)setCaptureDeviceById:(char*)uniqueId{
77 if(NO == _OSSupported)
78 {
79 WEBRTC_TRACE(kTraceInfo, kTraceVideoCapture, 0,
80 "%s:%d OS version does not support necessary APIs",
81 __FUNCTION__, __LINE__);
82 return [NSNumber numberWithInt:0];
83 }
84
85 if(!uniqueId || (0 == strcmp("", uniqueId)))
86 {
87 WEBRTC_TRACE(kTraceInfo, kTraceVideoCapture, 0,
88 "%s:%d \"\" was passed in for capture device name",
89 __FUNCTION__, __LINE__);
90 memset(_captureDeviceNameUTF8, 0, 1024);
91 return [NSNumber numberWithInt:0];
92 }
93
94 if(0 == strcmp(uniqueId, _captureDeviceNameUniqueID))
95 {
96 // camera already set
97 WEBRTC_TRACE(kTraceInfo, kTraceVideoCapture, 0,
98 "%s:%d Capture device is already set to %s", __FUNCTION__,
99 __LINE__, _captureDeviceNameUTF8);
100 return [NSNumber numberWithInt:0];
101 }
102
103 bool success = NO;
104 QTCaptureDevice* tempCaptureDevice;
105 for(int index = 0; index < _captureDeviceCount; index++)
106 {
107 tempCaptureDevice = (QTCaptureDevice*)[_captureDevices
108 objectAtIndex:index];
109 char tempCaptureDeviceId[1024] = "";
110 [[tempCaptureDevice uniqueID]
111 getCString:tempCaptureDeviceId maxLength:1024
112 encoding:NSUTF8StringEncoding];
113 if(0 == strcmp(uniqueId, tempCaptureDeviceId))
114 {
115 WEBRTC_TRACE(kTraceInfo, kTraceVideoCapture, 0,
116 "%s:%d Found capture device id %s as index %d",
117 __FUNCTION__, __LINE__, tempCaptureDeviceId, index);
118 success = YES;
119 [[tempCaptureDevice localizedDisplayName]
120 getCString:_captureDeviceNameUTF8
121 maxLength:1024
122 encoding:NSUTF8StringEncoding];
123 [[tempCaptureDevice uniqueID]
124 getCString:_captureDeviceNameUniqueID
125 maxLength:1024
126 encoding:NSUTF8StringEncoding];
127 break;
128 }
129
130 }
131
132 if(NO == success)
133 {
134 // camera not found
135 // nothing has been changed yet, so capture device will stay in it's
136 // state
137 WEBRTC_TRACE(kTraceInfo, kTraceVideoCapture, 0,
138 "%s:%d Capture device id %s was not found in list of "
139 "available devices.", __FUNCTION__, __LINE__, uniqueId);
140 return [NSNumber numberWithInt:0];
141 }
142
143 NSError* error;
144 success = [tempCaptureDevice open:&error];
145 if(!success)
146 {
147 WEBRTC_TRACE(kTraceError, kTraceVideoCapture, 0,
148 "%s:%d Failed to open capture device: %s",
149 __FUNCTION__, __LINE__, _captureDeviceNameUTF8);
150 return [NSNumber numberWithInt:-1];
151 }
152
153 if(_captureVideoDeviceInput)
154 {
155 [_captureVideoDeviceInput release];
156 }
157 _captureVideoDeviceInput = [[QTCaptureDeviceInput alloc]
158 initWithDevice:tempCaptureDevice];
159
160 success = [_captureSession addInput:_captureVideoDeviceInput error:&error];
161 if(!success)
162 {
163 WEBRTC_TRACE(kTraceError, kTraceVideoCapture, 0,
164 "%s:%d Failed to add input from %s to the capture session",
165 __FUNCTION__, __LINE__, _captureDeviceNameUTF8);
166 return [NSNumber numberWithInt:-1];
167 }
168
169 WEBRTC_TRACE(kTraceInfo, kTraceVideoCapture, 0,
170 "%s:%d successfully added capture device: %s", __FUNCTION__,
171 __LINE__, _captureDeviceNameUTF8);
172 return [NSNumber numberWithInt:0];
173}
174
175
176/// ***** Updates the capture devices size and frequency
177/// ***** Sets member variables _frame* and _captureDecompressedVideoOutput
178/// ***** Returns 0 on success, -1 otherwise.
179- (NSNumber*)setCaptureHeight:(int)height AndWidth:(int)width
180 AndFrameRate:(int)frameRate{
181 if(NO == _OSSupported)
182 {
183 return [NSNumber numberWithInt:0];
184 }
185
186 _frameWidth = width;
187 _frameHeight = height;
188 _frameRate = frameRate;
189
190 // TODO(mflodman) Check fps settings.
191 // [_captureDecompressedVideoOutput
192 // setMinimumVideoFrameInterval:(NSTimeInterval)1/(float)_frameRate];
193 NSDictionary* captureDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
194 [NSNumber numberWithDouble:_frameWidth], (id)kCVPixelBufferWidthKey,
195 [NSNumber numberWithDouble:_frameHeight], (id)kCVPixelBufferHeightKey,
196 [NSNumber numberWithUnsignedInt:kCVPixelFormatType_32ARGB],
197 (id)kCVPixelBufferPixelFormatTypeKey, nil];
198 [_captureDecompressedVideoOutput performSelectorOnMainThread:@selector(setPixelBufferAttributes:) withObject:captureDictionary waitUntilDone:NO];
199// [_captureDecompressedVideoOutput setPixelBufferAttributes:captureDictionary];
200
201
202 // these methods return type void so there isn't much we can do about
203 // checking success
204 return [NSNumber numberWithInt:0];
205}
206
207/// ***** Starts the QTCaptureSession, assuming correct state. Also ensures that
208/// an NSRunLoop is running
209/// ***** Without and NSRunLoop to process events, the OS doesn't check for a
210/// new frame.
211/// ***** Sets member variables _capturing
212/// ***** Returns 0 on success, -1 otherwise.
213- (NSNumber*)startCapture{
214 if(NO == _OSSupported)
215 {
216 return [NSNumber numberWithInt:0];
217 }
218
219 if(YES == _capturing)
220 {
221 return [NSNumber numberWithInt:0];
222 }
223
224// NSLog(@"--------------- before ---------------");
225 [[NSRunLoop mainRunLoop] runUntilDate:[NSDate distantFuture]];
226// NSLog(@"--------------- after ---------------");
227
228 if(NO == _captureInitialized)
229 {
230 // this should never be called..... it is initialized on class init
231 [self initializeVideoCapture];
232 }
233 [_captureSession startRunning];
234
235
236 _capturing = YES;
237
238 return [NSNumber numberWithInt:0];
239}
240
241/// ***** Stops the QTCaptureSession, assuming correct state
242/// ***** Sets member variables _capturing
243/// ***** Returns 0 on success, -1 otherwise.
244- (NSNumber*)stopCapture{
245
246 if(NO == _OSSupported)
247 {
248 return [NSNumber numberWithInt:0];
249 }
250
251 if(nil == _captureSession)
252 {
253 return [NSNumber numberWithInt:0];
254 }
255
256 if(NO == _capturing)
257 {
258 return [NSNumber numberWithInt:0];
259 }
260
261 if(YES == _capturing)
262 {
263 [_captureSession stopRunning];
264 }
265
266 _capturing = NO;
267 return [NSNumber numberWithInt:0];
268}
269
270// ********** "private" functions below here **********
271#pragma mark **** "private" methods
272
273/// ***** Class member variables are initialized here
274/// ***** Returns 0 on success, -1 otherwise.
275- (NSNumber*)initializeVariables{
276
277 if(NO == _OSSupported)
278 {
279 return [NSNumber numberWithInt:0];
280 }
281
282 _pool = [[NSAutoreleasePool alloc]init];
283
284 memset(_captureDeviceNameUTF8, 0, 1024);
285 _counter = 0;
286 _framesDelivered = 0;
287 _framesRendered = 0;
288 _captureDeviceCount = 0;
289 _capturing = NO;
290 _captureInitialized = NO;
291 _frameRate = DEFAULT_FRAME_RATE;
292 _frameWidth = DEFAULT_FRAME_WIDTH;
293 _frameHeight = DEFAULT_FRAME_HEIGHT;
294 _captureDeviceName = [[NSString alloc] initWithFormat:@""];
295 _rLock = [[VideoCaptureRecursiveLock alloc] init];
296 _captureSession = [[QTCaptureSession alloc] init];
297 _captureDecompressedVideoOutput = [[QTCaptureDecompressedVideoOutput alloc]
298 init];
299 [_captureDecompressedVideoOutput setDelegate:self];
300
301 [self getCaptureDevices];
302 [self initializeVideoCapture];
303
304 return [NSNumber numberWithInt:0];
305
306}
307
308// Checks to see if the QTCaptureSession framework is available in the OS
309// If it is not, isOSSupprted = NO.
310// Throughout the rest of the class isOSSupprted is checked and functions
311// are/aren't called depending
312// The user can use weak linking to the QTKit framework and run on older
313// versions of the OS. I.E. Backwards compaitibility
314// Returns nothing. Sets member variable
315- (void)checkOSSupported{
316
317 Class osSupportedTest = NSClassFromString(@"QTCaptureSession");
318 _OSSupported = NO;
319 if(nil == osSupportedTest)
320 {
321 }
322 _OSSupported = YES;
323}
324
325/// ***** Retrieves the number of capture devices currently available
326/// ***** Stores them in an NSArray instance
327/// ***** Returns 0 on success, -1 otherwise.
328- (NSNumber*)getCaptureDevices{
329
330 if(NO == _OSSupported)
331 {
332 return [NSNumber numberWithInt:0];
333 }
334
335 if(_captureDevices)
336 {
337 [_captureDevices release];
338 }
339 _captureDevices = [[NSArray alloc] initWithArray:
340 [QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeVideo]];
341
342 _captureDeviceCount = _captureDevices.count;
343 if(_captureDeviceCount < 1)
344 {
345 return [NSNumber numberWithInt:0];
346 }
347 return [NSNumber numberWithInt:0];
348}
349
350// Initializes a QTCaptureSession (member variable) to deliver frames via
351// callback
352// QTCapture* member variables affected
353// The image format and frequency are setup here
354// Returns 0 on success, -1 otherwise.
355- (NSNumber*)initializeVideoCapture{
356
357 if(YES == _captureInitialized)
358 {
359 return [NSNumber numberWithInt:-1];
360 }
361
362 QTCaptureDevice* videoDevice =
363 (QTCaptureDevice*)[_captureDevices objectAtIndex:0];
364
365 bool success = NO;
366 NSError* error;
367
368 success = [videoDevice open:&error];
369 if(!success)
370 {
371 return [NSNumber numberWithInt:-1];
372 }
373
374 [_captureDecompressedVideoOutput setPixelBufferAttributes:
375 [NSDictionary dictionaryWithObjectsAndKeys:
376 [NSNumber numberWithDouble:_frameWidth], (id)kCVPixelBufferWidthKey,
377 [NSNumber numberWithDouble:_frameHeight], (id)kCVPixelBufferHeightKey,
378 [NSNumber numberWithUnsignedInt:kCVPixelFormatType_32ARGB],
379 (id)kCVPixelBufferPixelFormatTypeKey, nil]];
380
381 // TODO(mflodman) Check fps settings.
382 //[_captureDecompressedVideoOutput setMinimumVideoFrameInterval:
383 // (NSTimeInterval)1/(float)_frameRate];
384 //[_captureDecompressedVideoOutput setAutomaticallyDropsLateVideoFrames:YES];
385
386 success = [_captureSession addOutput:_captureDecompressedVideoOutput
387 error:&error];
388
389 if(!success)
390 {
391 return [NSNumber numberWithInt:-1];
392 }
393
394 _captureInitialized = YES;
395
396 return [NSNumber numberWithInt:0];
397}
398
399// This is the callback that is called when the OS has a frame to deliver to us.
400// Starts being called when [_captureSession startRunning] is called. Stopped
401// similarly.
402// Parameter videoFrame contains the image. The format, size, and frequency
403// were setup earlier.
404// Returns 0 on success, -1 otherwise.
405- (void)captureOutput:(QTCaptureOutput *)captureOutput
406 didOutputVideoFrame:(CVImageBufferRef)videoFrame
407 withSampleBuffer:(QTSampleBuffer *)sampleBuffer
408 fromConnection:(QTCaptureConnection *)connection{
409
410 if(YES == [_rLock tryLock])
411 {
412 [_rLock lock];
413 }
414 else
415 {
416 return;
417 }
418
419 if(NO == _OSSupported)
420 {
421 return;
422 }
423
424 const int LOCK_FLAGS = 0; // documentation says to pass 0
425
426 // get size of the frame
427 CVPixelBufferLockBaseAddress(videoFrame, LOCK_FLAGS);
428 void* baseAddress = CVPixelBufferGetBaseAddress(videoFrame);
429 size_t bytesPerRow = CVPixelBufferGetBytesPerRow(videoFrame);
430 int frameHeight = CVPixelBufferGetHeight(videoFrame);
431 CVPixelBufferUnlockBaseAddress(videoFrame, LOCK_FLAGS);
432
433 if(_owner)
434 {
435
436 int frameSize = bytesPerRow * frameHeight; // 32 bit ARGB format
437 CVBufferRetain(videoFrame);
438 VideoCaptureCapability tempCaptureCapability;
439 tempCaptureCapability.width = _frameWidth;
440 tempCaptureCapability.height = _frameHeight;
441 tempCaptureCapability.maxFPS = _frameRate;
442 // TODO(wu) : Update actual type and not hard-coded value.
443 tempCaptureCapability.rawType = kVideoBGRA;
444
445 _owner->IncomingFrame((unsigned char*)baseAddress,
446 frameSize,
447 tempCaptureCapability,
448 0);
449
450 CVBufferRelease(videoFrame);
451 }
452
453 _framesDelivered++;
454 _framesRendered++;
455
456 if(YES == [_rLock locked])
457 {
458 [_rLock unlock];
459 }
460}
461
462@end