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