1/* 2 * cap_ios_abstract_camera.mm 3 * For iOS video I/O 4 * by Eduard Feicho on 29/07/12 5 * by Alexander Shishkov on 17/07/13 6 * Copyright 2012. All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions are met: 10 * 11 * 1. Redistributions of source code must retain the above copyright notice, 12 * this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright notice, 14 * this list of conditions and the following disclaimer in the documentation 15 * and/or other materials provided with the distribution. 16 * 3. The name of the author may not be used to endorse or promote products 17 * derived from this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED 20 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 21 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 22 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 24 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 25 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 26 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 27 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 28 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 * 30 */ 31 32 33#import "opencv2/videoio/cap_ios.h" 34#include "precomp.hpp" 35 36#pragma mark - Private Interface 37 38@interface CvAbstractCamera () 39 40@property (nonatomic, retain) AVCaptureVideoPreviewLayer* captureVideoPreviewLayer; 41 42- (void)deviceOrientationDidChange:(NSNotification*)notification; 43- (void)startCaptureSession; 44 45- (void)setDesiredCameraPosition:(AVCaptureDevicePosition)desiredPosition; 46 47- (void)updateSize; 48 49@end 50 51 52#pragma mark - Implementation 53 54 55@implementation CvAbstractCamera 56 57 58 59#pragma mark Public 60 61@synthesize imageWidth; 62@synthesize imageHeight; 63 64 65@synthesize defaultFPS; 66@synthesize defaultAVCaptureDevicePosition; 67@synthesize defaultAVCaptureVideoOrientation; 68@synthesize defaultAVCaptureSessionPreset; 69 70 71 72@synthesize captureSession; 73@synthesize captureVideoPreviewLayer; 74@synthesize videoCaptureConnection; 75@synthesize running; 76@synthesize captureSessionLoaded; 77@synthesize useAVCaptureVideoPreviewLayer; 78 79@synthesize parentView; 80 81#pragma mark - Constructors 82 83- (id)init; 84{ 85 self = [super init]; 86 if (self) { 87 // react to device orientation notifications 88 [[NSNotificationCenter defaultCenter] addObserver:self 89 selector:@selector(deviceOrientationDidChange:) 90 name:UIDeviceOrientationDidChangeNotification 91 object:nil]; 92 [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; 93 currentDeviceOrientation = [[UIDevice currentDevice] orientation]; 94 95 96 // check if camera available 97 cameraAvailable = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]; 98 NSLog(@"camera available: %@", (cameraAvailable == YES ? @"YES" : @"NO") ); 99 100 running = NO; 101 102 // set camera default configuration 103 self.defaultAVCaptureDevicePosition = AVCaptureDevicePositionFront; 104 self.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationLandscapeLeft; 105 self.defaultFPS = 15; 106 self.defaultAVCaptureSessionPreset = AVCaptureSessionPreset352x288; 107 108 self.parentView = nil; 109 self.useAVCaptureVideoPreviewLayer = NO; 110 } 111 return self; 112} 113 114 115 116- (id)initWithParentView:(UIView*)parent; 117{ 118 self = [super init]; 119 if (self) { 120 // react to device orientation notifications 121 [[NSNotificationCenter defaultCenter] addObserver:self 122 selector:@selector(deviceOrientationDidChange:) 123 name:UIDeviceOrientationDidChangeNotification 124 object:nil]; 125 [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; 126 currentDeviceOrientation = [[UIDevice currentDevice] orientation]; 127 128 129 // check if camera available 130 cameraAvailable = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]; 131 NSLog(@"camera available: %@", (cameraAvailable == YES ? @"YES" : @"NO") ); 132 133 running = NO; 134 135 // set camera default configuration 136 self.defaultAVCaptureDevicePosition = AVCaptureDevicePositionFront; 137 self.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationLandscapeLeft; 138 self.defaultFPS = 15; 139 self.defaultAVCaptureSessionPreset = AVCaptureSessionPreset640x480; 140 141 self.parentView = parent; 142 self.useAVCaptureVideoPreviewLayer = YES; 143 } 144 return self; 145} 146 147 148 149- (void)dealloc; 150{ 151 [[NSNotificationCenter defaultCenter] removeObserver:self]; 152 [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications]; 153 [super dealloc]; 154} 155 156 157#pragma mark - Public interface 158 159 160- (void)start; 161{ 162 if (![NSThread isMainThread]) { 163 NSLog(@"[Camera] Warning: Call start only from main thread"); 164 [self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO]; 165 return; 166 } 167 168 if (running == YES) { 169 return; 170 } 171 running = YES; 172 173 // TOOD update image size data before actually starting (needed for recording) 174 [self updateSize]; 175 176 if (cameraAvailable) { 177 [self startCaptureSession]; 178 } 179} 180 181 182- (void)pause; 183{ 184 running = NO; 185 [self.captureSession stopRunning]; 186} 187 188 189 190- (void)stop; 191{ 192 running = NO; 193 194 // Release any retained subviews of the main view. 195 // e.g. self.myOutlet = nil; 196 for (AVCaptureInput *input in self.captureSession.inputs) { 197 [self.captureSession removeInput:input]; 198 } 199 200 for (AVCaptureOutput *output in self.captureSession.outputs) { 201 [self.captureSession removeOutput:output]; 202 } 203 204 [self.captureSession stopRunning]; 205 self.captureSession = nil; 206 self.captureVideoPreviewLayer = nil; 207 self.videoCaptureConnection = nil; 208 captureSessionLoaded = NO; 209} 210 211 212 213// use front/back camera 214- (void)switchCameras; 215{ 216 BOOL was_running = self.running; 217 if (was_running) { 218 [self stop]; 219 } 220 if (self.defaultAVCaptureDevicePosition == AVCaptureDevicePositionFront) { 221 self.defaultAVCaptureDevicePosition = AVCaptureDevicePositionBack; 222 } else { 223 self.defaultAVCaptureDevicePosition = AVCaptureDevicePositionFront; 224 } 225 if (was_running) { 226 [self start]; 227 } 228} 229 230 231 232#pragma mark - Device Orientation Changes 233 234 235- (void)deviceOrientationDidChange:(NSNotification*)notification 236{ 237 (void)notification; 238 UIDeviceOrientation orientation = [UIDevice currentDevice].orientation; 239 240 switch (orientation) 241 { 242 case UIDeviceOrientationPortrait: 243 case UIDeviceOrientationPortraitUpsideDown: 244 case UIDeviceOrientationLandscapeLeft: 245 case UIDeviceOrientationLandscapeRight: 246 currentDeviceOrientation = orientation; 247 break; 248 249 case UIDeviceOrientationFaceUp: 250 case UIDeviceOrientationFaceDown: 251 default: 252 break; 253 } 254 NSLog(@"deviceOrientationDidChange: %d", (int)orientation); 255 256 [self updateOrientation]; 257} 258 259 260 261#pragma mark - Private Interface 262 263- (void)createCaptureSession; 264{ 265 // set a av capture session preset 266 self.captureSession = [[AVCaptureSession alloc] init]; 267 if ([self.captureSession canSetSessionPreset:self.defaultAVCaptureSessionPreset]) { 268 [self.captureSession setSessionPreset:self.defaultAVCaptureSessionPreset]; 269 } else if ([self.captureSession canSetSessionPreset:AVCaptureSessionPresetLow]) { 270 [self.captureSession setSessionPreset:AVCaptureSessionPresetLow]; 271 } else { 272 NSLog(@"[Camera] Error: could not set session preset"); 273 } 274} 275 276- (void)createCaptureDevice; 277{ 278 // setup the device 279 AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; 280 [self setDesiredCameraPosition:self.defaultAVCaptureDevicePosition]; 281 NSLog(@"[Camera] device connected? %@", device.connected ? @"YES" : @"NO"); 282 NSLog(@"[Camera] device position %@", (device.position == AVCaptureDevicePositionBack) ? @"back" : @"front"); 283} 284 285 286- (void)createVideoPreviewLayer; 287{ 288 self.captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.captureSession]; 289 290 if ([self.captureVideoPreviewLayer respondsToSelector:@selector(connection)]) 291 { 292 if ([self.captureVideoPreviewLayer.connection isVideoOrientationSupported]) 293 { 294 [self.captureVideoPreviewLayer.connection setVideoOrientation:self.defaultAVCaptureVideoOrientation]; 295 } 296 } 297 else 298 { 299 // Deprecated in 6.0; here for backward compatibility 300 if ([self.captureVideoPreviewLayer isOrientationSupported]) 301 { 302 [self.captureVideoPreviewLayer setOrientation:self.defaultAVCaptureVideoOrientation]; 303 } 304 } 305 306 if (parentView != nil) { 307 self.captureVideoPreviewLayer.frame = self.parentView.bounds; 308 self.captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; 309 [self.parentView.layer addSublayer:self.captureVideoPreviewLayer]; 310 } 311 NSLog(@"[Camera] created AVCaptureVideoPreviewLayer"); 312} 313 314- (void)setDesiredCameraPosition:(AVCaptureDevicePosition)desiredPosition; 315{ 316 for (AVCaptureDevice *device in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) { 317 if ([device position] == desiredPosition) { 318 [self.captureSession beginConfiguration]; 319 320 NSError* error = nil; 321 AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error]; 322 if (!input) { 323 NSLog(@"error creating input %@", [error localizedDescription]); 324 } 325 326 // support for autofocus 327 if ([device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) { 328 error = nil; 329 if ([device lockForConfiguration:&error]) { 330 device.focusMode = AVCaptureFocusModeContinuousAutoFocus; 331 [device unlockForConfiguration]; 332 } else { 333 NSLog(@"unable to lock device for autofocos configuration %@", [error localizedDescription]); 334 } 335 } 336 [self.captureSession addInput:input]; 337 338 for (AVCaptureInput *oldInput in self.captureSession.inputs) { 339 [self.captureSession removeInput:oldInput]; 340 } 341 [self.captureSession addInput:input]; 342 [self.captureSession commitConfiguration]; 343 344 break; 345 } 346 } 347} 348 349 350 351- (void)startCaptureSession 352{ 353 if (!cameraAvailable) { 354 return; 355 } 356 357 if (self.captureSessionLoaded == NO) { 358 [self createCaptureSession]; 359 [self createCaptureDevice]; 360 [self createCaptureOutput]; 361 362 // setup preview layer 363 if (self.useAVCaptureVideoPreviewLayer) { 364 [self createVideoPreviewLayer]; 365 } else { 366 [self createCustomVideoPreview]; 367 } 368 369 captureSessionLoaded = YES; 370 } 371 372 [self.captureSession startRunning]; 373} 374 375 376- (void)createCaptureOutput; 377{ 378 [NSException raise:NSInternalInconsistencyException 379 format:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]; 380} 381 382- (void)createCustomVideoPreview; 383{ 384 [NSException raise:NSInternalInconsistencyException 385 format:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]; 386} 387 388- (void)updateOrientation; 389{ 390 // nothing to do here 391} 392 393 394- (void)updateSize; 395{ 396 if ([self.defaultAVCaptureSessionPreset isEqualToString:AVCaptureSessionPresetPhoto]) { 397 //TODO: find the correct resolution 398 self.imageWidth = 640; 399 self.imageHeight = 480; 400 } else if ([self.defaultAVCaptureSessionPreset isEqualToString:AVCaptureSessionPresetHigh]) { 401 //TODO: find the correct resolution 402 self.imageWidth = 640; 403 self.imageHeight = 480; 404 } else if ([self.defaultAVCaptureSessionPreset isEqualToString:AVCaptureSessionPresetMedium]) { 405 //TODO: find the correct resolution 406 self.imageWidth = 640; 407 self.imageHeight = 480; 408 } else if ([self.defaultAVCaptureSessionPreset isEqualToString:AVCaptureSessionPresetLow]) { 409 //TODO: find the correct resolution 410 self.imageWidth = 640; 411 self.imageHeight = 480; 412 } else if ([self.defaultAVCaptureSessionPreset isEqualToString:AVCaptureSessionPreset352x288]) { 413 self.imageWidth = 352; 414 self.imageHeight = 288; 415 } else if ([self.defaultAVCaptureSessionPreset isEqualToString:AVCaptureSessionPreset640x480]) { 416 self.imageWidth = 640; 417 self.imageHeight = 480; 418 } else if ([self.defaultAVCaptureSessionPreset isEqualToString:AVCaptureSessionPreset1280x720]) { 419 self.imageWidth = 1280; 420 self.imageHeight = 720; 421 } else { 422 self.imageWidth = 640; 423 self.imageHeight = 480; 424 } 425} 426 427- (void)lockFocus; 428{ 429 AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; 430 if ([device isFocusModeSupported:AVCaptureFocusModeLocked]) { 431 NSError *error = nil; 432 if ([device lockForConfiguration:&error]) { 433 device.focusMode = AVCaptureFocusModeLocked; 434 [device unlockForConfiguration]; 435 } else { 436 NSLog(@"unable to lock device for locked focus configuration %@", [error localizedDescription]); 437 } 438 } 439} 440 441- (void) unlockFocus; 442{ 443 AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; 444 if ([device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) { 445 NSError *error = nil; 446 if ([device lockForConfiguration:&error]) { 447 device.focusMode = AVCaptureFocusModeContinuousAutoFocus; 448 [device unlockForConfiguration]; 449 } else { 450 NSLog(@"unable to lock device for autofocus configuration %@", [error localizedDescription]); 451 } 452 } 453} 454 455- (void)lockExposure; 456{ 457 AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; 458 if ([device isExposureModeSupported:AVCaptureExposureModeLocked]) { 459 NSError *error = nil; 460 if ([device lockForConfiguration:&error]) { 461 device.exposureMode = AVCaptureExposureModeLocked; 462 [device unlockForConfiguration]; 463 } else { 464 NSLog(@"unable to lock device for locked exposure configuration %@", [error localizedDescription]); 465 } 466 } 467} 468 469- (void) unlockExposure; 470{ 471 AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; 472 if ([device isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) { 473 NSError *error = nil; 474 if ([device lockForConfiguration:&error]) { 475 device.exposureMode = AVCaptureExposureModeContinuousAutoExposure; 476 [device unlockForConfiguration]; 477 } else { 478 NSLog(@"unable to lock device for autoexposure configuration %@", [error localizedDescription]); 479 } 480 } 481} 482 483- (void)lockBalance; 484{ 485 AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; 486 if ([device isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeLocked]) { 487 NSError *error = nil; 488 if ([device lockForConfiguration:&error]) { 489 device.whiteBalanceMode = AVCaptureWhiteBalanceModeLocked; 490 [device unlockForConfiguration]; 491 } else { 492 NSLog(@"unable to lock device for locked white balance configuration %@", [error localizedDescription]); 493 } 494 } 495} 496 497- (void) unlockBalance; 498{ 499 AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; 500 if ([device isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance]) { 501 NSError *error = nil; 502 if ([device lockForConfiguration:&error]) { 503 device.whiteBalanceMode = AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance; 504 [device unlockForConfiguration]; 505 } else { 506 NSLog(@"unable to lock device for auto white balance configuration %@", [error localizedDescription]); 507 } 508 } 509} 510 511@end 512