2012-08-20 17:36:36 +02:00
|
|
|
/*
|
|
|
|
* cap_ios_abstract_camera.mm
|
|
|
|
* For iOS video I/O
|
|
|
|
* by Eduard Feicho on 29/07/12
|
|
|
|
* Copyright 2012. All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions are met:
|
|
|
|
*
|
|
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
|
|
* this list of conditions and the following disclaimer.
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
|
|
* and/or other materials provided with the distribution.
|
|
|
|
* 3. The name of the author may not be used to endorse or promote products
|
|
|
|
* derived from this software without specific prior written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
|
|
|
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
|
|
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
|
|
|
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
|
|
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
|
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#import "opencv2/highgui/cap_ios.h"
|
|
|
|
#include "precomp.hpp"
|
|
|
|
|
|
|
|
#pragma mark - Private Interface
|
|
|
|
|
|
|
|
@interface CvAbstractCamera ()
|
|
|
|
|
|
|
|
@property (nonatomic, retain) AVCaptureVideoPreviewLayer* captureVideoPreviewLayer;
|
|
|
|
|
|
|
|
- (void)deviceOrientationDidChange:(NSNotification*)notification;
|
|
|
|
- (void)startCaptureSession;
|
|
|
|
|
|
|
|
- (void)setDesiredCameraPosition:(AVCaptureDevicePosition)desiredPosition;
|
|
|
|
|
|
|
|
- (void)updateSize;
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - Implementation
|
|
|
|
|
|
|
|
|
|
|
|
@implementation CvAbstractCamera
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark Public
|
|
|
|
|
|
|
|
@synthesize imageWidth;
|
|
|
|
@synthesize imageHeight;
|
|
|
|
|
|
|
|
|
|
|
|
@synthesize defaultFPS;
|
|
|
|
@synthesize defaultAVCaptureDevicePosition;
|
|
|
|
@synthesize defaultAVCaptureVideoOrientation;
|
|
|
|
@synthesize defaultAVCaptureSessionPreset;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@synthesize captureSession;
|
|
|
|
@synthesize captureVideoPreviewLayer;
|
|
|
|
@synthesize videoCaptureConnection;
|
|
|
|
@synthesize running;
|
|
|
|
@synthesize captureSessionLoaded;
|
|
|
|
@synthesize useAVCaptureVideoPreviewLayer;
|
|
|
|
|
|
|
|
@synthesize parentView;
|
|
|
|
|
|
|
|
#pragma mark - Constructors
|
|
|
|
|
|
|
|
- (id)init;
|
|
|
|
{
|
2012-10-17 09:12:04 +02:00
|
|
|
self = [super init];
|
|
|
|
if (self) {
|
|
|
|
// react to device orientation notifications
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
selector:@selector(deviceOrientationDidChange:)
|
|
|
|
name:UIDeviceOrientationDidChangeNotification
|
|
|
|
object:nil];
|
|
|
|
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
|
|
|
|
currentDeviceOrientation = [[UIDevice currentDevice] orientation];
|
|
|
|
|
|
|
|
|
|
|
|
// check if camera available
|
|
|
|
cameraAvailable = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera];
|
|
|
|
NSLog(@"camera available: %@", (cameraAvailable == YES ? @"YES" : @"NO") );
|
|
|
|
|
|
|
|
running = NO;
|
|
|
|
|
|
|
|
// set camera default configuration
|
|
|
|
self.defaultAVCaptureDevicePosition = AVCaptureDevicePositionFront;
|
|
|
|
self.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationLandscapeLeft;
|
|
|
|
self.defaultFPS = 15;
|
|
|
|
self.defaultAVCaptureSessionPreset = AVCaptureSessionPreset352x288;
|
|
|
|
|
|
|
|
self.parentView = nil;
|
|
|
|
self.useAVCaptureVideoPreviewLayer = NO;
|
2012-08-20 17:36:36 +02:00
|
|
|
}
|
2012-10-17 09:12:04 +02:00
|
|
|
return self;
|
2012-08-20 17:36:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (id)initWithParentView:(UIView*)parent;
|
|
|
|
{
|
2012-10-17 09:12:04 +02:00
|
|
|
self = [super init];
|
|
|
|
if (self) {
|
|
|
|
// react to device orientation notifications
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
selector:@selector(deviceOrientationDidChange:)
|
|
|
|
name:UIDeviceOrientationDidChangeNotification
|
|
|
|
object:nil];
|
|
|
|
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
|
|
|
|
currentDeviceOrientation = [[UIDevice currentDevice] orientation];
|
|
|
|
|
|
|
|
|
|
|
|
// check if camera available
|
|
|
|
cameraAvailable = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera];
|
|
|
|
NSLog(@"camera available: %@", (cameraAvailable == YES ? @"YES" : @"NO") );
|
|
|
|
|
|
|
|
running = NO;
|
|
|
|
|
|
|
|
// set camera default configuration
|
|
|
|
self.defaultAVCaptureDevicePosition = AVCaptureDevicePositionFront;
|
|
|
|
self.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationLandscapeLeft;
|
|
|
|
self.defaultFPS = 15;
|
|
|
|
self.defaultAVCaptureSessionPreset = AVCaptureSessionPreset640x480;
|
|
|
|
|
|
|
|
self.parentView = parent;
|
|
|
|
self.useAVCaptureVideoPreviewLayer = YES;
|
|
|
|
}
|
|
|
|
return self;
|
2012-08-20 17:36:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (void)dealloc;
|
|
|
|
{
|
2012-10-17 09:12:04 +02:00
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
|
|
[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
|
2012-08-20 17:36:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - Public interface
|
|
|
|
|
|
|
|
|
|
|
|
- (void)start;
|
|
|
|
{
|
|
|
|
if (![NSThread isMainThread]) {
|
|
|
|
NSLog(@"[Camera] Warning: Call start only from main thread");
|
|
|
|
[self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
|
|
|
|
return;
|
|
|
|
}
|
2012-10-17 09:12:04 +02:00
|
|
|
|
|
|
|
if (running == YES) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
running = YES;
|
|
|
|
|
2012-08-20 17:36:36 +02:00
|
|
|
// TOOD update image size data before actually starting (needed for recording)
|
|
|
|
[self updateSize];
|
2012-10-17 09:12:04 +02:00
|
|
|
|
|
|
|
if (cameraAvailable) {
|
|
|
|
[self startCaptureSession];
|
|
|
|
}
|
2012-08-20 17:36:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)pause;
|
|
|
|
{
|
2012-10-17 09:12:04 +02:00
|
|
|
running = NO;
|
|
|
|
[self.captureSession stopRunning];
|
2012-08-20 17:36:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (void)stop;
|
|
|
|
{
|
2012-10-17 09:12:04 +02:00
|
|
|
running = NO;
|
|
|
|
|
|
|
|
// Release any retained subviews of the main view.
|
|
|
|
// e.g. self.myOutlet = nil;
|
|
|
|
|
|
|
|
[self.captureSession stopRunning];
|
|
|
|
self.captureSession = nil;
|
|
|
|
self.captureVideoPreviewLayer = nil;
|
|
|
|
self.videoCaptureConnection = nil;
|
|
|
|
captureSessionLoaded = NO;
|
2012-08-20 17:36:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// use front/back camera
|
|
|
|
- (void)switchCameras;
|
|
|
|
{
|
2012-10-17 09:12:04 +02:00
|
|
|
BOOL was_running = self.running;
|
|
|
|
if (was_running) {
|
|
|
|
[self stop];
|
|
|
|
}
|
|
|
|
if (self.defaultAVCaptureDevicePosition == AVCaptureDevicePositionFront) {
|
|
|
|
self.defaultAVCaptureDevicePosition = AVCaptureDevicePositionBack;
|
|
|
|
} else {
|
|
|
|
self.defaultAVCaptureDevicePosition = AVCaptureDevicePositionFront;
|
|
|
|
}
|
|
|
|
if (was_running) {
|
|
|
|
[self start];
|
|
|
|
}
|
2012-08-20 17:36:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - Device Orientation Changes
|
|
|
|
|
|
|
|
|
|
|
|
- (void)deviceOrientationDidChange:(NSNotification*)notification
|
|
|
|
{
|
2012-10-17 09:12:04 +02:00
|
|
|
UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
|
|
|
|
|
|
|
|
switch (orientation)
|
|
|
|
{
|
|
|
|
case UIDeviceOrientationPortrait:
|
|
|
|
case UIDeviceOrientationPortraitUpsideDown:
|
|
|
|
case UIDeviceOrientationLandscapeLeft:
|
|
|
|
case UIDeviceOrientationLandscapeRight:
|
|
|
|
currentDeviceOrientation = orientation;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case UIDeviceOrientationFaceUp:
|
|
|
|
case UIDeviceOrientationFaceDown:
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
NSLog(@"deviceOrientationDidChange: %d", orientation);
|
|
|
|
|
|
|
|
[self updateOrientation];
|
2012-08-20 17:36:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - Private Interface
|
|
|
|
|
|
|
|
- (void)createCaptureSession;
|
|
|
|
{
|
2012-10-17 09:12:04 +02:00
|
|
|
// set a av capture session preset
|
|
|
|
self.captureSession = [[AVCaptureSession alloc] init];
|
|
|
|
if ([self.captureSession canSetSessionPreset:self.defaultAVCaptureSessionPreset]) {
|
|
|
|
[self.captureSession setSessionPreset:self.defaultAVCaptureSessionPreset];
|
|
|
|
} else if ([self.captureSession canSetSessionPreset:AVCaptureSessionPresetLow]) {
|
|
|
|
[self.captureSession setSessionPreset:AVCaptureSessionPresetLow];
|
|
|
|
} else {
|
|
|
|
NSLog(@"[Camera] Error: could not set session preset");
|
|
|
|
}
|
2012-08-20 17:36:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)createCaptureDevice;
|
|
|
|
{
|
2012-10-17 09:12:04 +02:00
|
|
|
// setup the device
|
|
|
|
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
|
|
|
|
[self setDesiredCameraPosition:self.defaultAVCaptureDevicePosition];
|
|
|
|
NSLog(@"[Camera] device connected? %@", device.connected ? @"YES" : @"NO");
|
|
|
|
NSLog(@"[Camera] device position %@", (device.position == AVCaptureDevicePositionBack) ? @"back" : @"front");
|
2012-08-20 17:36:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)createVideoPreviewLayer;
|
|
|
|
{
|
2012-10-17 09:12:04 +02:00
|
|
|
self.captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.captureSession];
|
|
|
|
|
|
|
|
if ([self.captureVideoPreviewLayer isOrientationSupported]) {
|
|
|
|
[self.captureVideoPreviewLayer setOrientation:self.defaultAVCaptureVideoOrientation];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parentView != nil) {
|
|
|
|
self.captureVideoPreviewLayer.frame = self.parentView.bounds;
|
|
|
|
self.captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
|
|
|
|
[self.parentView.layer addSublayer:self.captureVideoPreviewLayer];
|
|
|
|
}
|
|
|
|
NSLog(@"[Camera] created AVCaptureVideoPreviewLayer");
|
2012-08-20 17:36:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (void)setDesiredCameraPosition:(AVCaptureDevicePosition)desiredPosition;
|
|
|
|
{
|
2012-10-17 09:12:04 +02:00
|
|
|
for (AVCaptureDevice *device in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) {
|
|
|
|
if ([device position] == desiredPosition) {
|
|
|
|
[self.captureSession beginConfiguration];
|
|
|
|
|
|
|
|
NSError* error;
|
|
|
|
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
|
|
|
|
if (!input) {
|
|
|
|
NSLog(@"error creating input %@", [error localizedDescription]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// support for autofocus
|
|
|
|
if ([device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {
|
|
|
|
NSError *error = nil;
|
|
|
|
if ([device lockForConfiguration:&error]) {
|
|
|
|
device.focusMode = AVCaptureFocusModeContinuousAutoFocus;
|
|
|
|
[device unlockForConfiguration];
|
|
|
|
} else {
|
|
|
|
NSLog(@"unable to lock device for autofocos configuration %@", [error localizedDescription]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
[self.captureSession addInput:input];
|
|
|
|
|
|
|
|
for (AVCaptureInput *oldInput in self.captureSession.inputs) {
|
|
|
|
[self.captureSession removeInput:oldInput];
|
|
|
|
}
|
|
|
|
[self.captureSession addInput:input];
|
|
|
|
[self.captureSession commitConfiguration];
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2012-08-20 17:36:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (void)startCaptureSession
|
|
|
|
{
|
2012-10-17 09:12:04 +02:00
|
|
|
if (!cameraAvailable) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (self.captureSessionLoaded == NO) {
|
|
|
|
[self createCaptureSession];
|
|
|
|
[self createCaptureDevice];
|
|
|
|
[self createCaptureOutput];
|
|
|
|
|
|
|
|
// setup preview layer
|
|
|
|
if (self.useAVCaptureVideoPreviewLayer) {
|
|
|
|
[self createVideoPreviewLayer];
|
|
|
|
} else {
|
|
|
|
[self createCustomVideoPreview];
|
|
|
|
}
|
|
|
|
|
|
|
|
captureSessionLoaded = YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
[self.captureSession startRunning];
|
2012-08-20 17:36:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)createCaptureOutput;
|
|
|
|
{
|
2012-10-17 09:12:04 +02:00
|
|
|
[NSException raise:NSInternalInconsistencyException
|
|
|
|
format:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)];
|
2012-08-20 17:36:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)createCustomVideoPreview;
|
|
|
|
{
|
2012-10-17 09:12:04 +02:00
|
|
|
[NSException raise:NSInternalInconsistencyException
|
|
|
|
format:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)];
|
2012-08-20 17:36:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)updateOrientation;
|
|
|
|
{
|
2012-10-17 09:12:04 +02:00
|
|
|
// nothing to do here
|
2012-08-20 17:36:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)updateSize;
|
|
|
|
{
|
|
|
|
if ([self.defaultAVCaptureSessionPreset isEqualToString:AVCaptureSessionPresetPhoto]) {
|
|
|
|
//TODO: find the correct resolution
|
|
|
|
self.imageWidth = 640;
|
|
|
|
self.imageHeight = 480;
|
|
|
|
} else if ([self.defaultAVCaptureSessionPreset isEqualToString:AVCaptureSessionPresetHigh]) {
|
|
|
|
//TODO: find the correct resolution
|
|
|
|
self.imageWidth = 640;
|
|
|
|
self.imageHeight = 480;
|
|
|
|
} else if ([self.defaultAVCaptureSessionPreset isEqualToString:AVCaptureSessionPresetMedium]) {
|
|
|
|
//TODO: find the correct resolution
|
|
|
|
self.imageWidth = 640;
|
2012-10-17 09:12:04 +02:00
|
|
|
self.imageHeight = 480;
|
2012-08-20 17:36:36 +02:00
|
|
|
} else if ([self.defaultAVCaptureSessionPreset isEqualToString:AVCaptureSessionPresetLow]) {
|
|
|
|
//TODO: find the correct resolution
|
|
|
|
self.imageWidth = 640;
|
|
|
|
self.imageHeight = 480;
|
|
|
|
} else if ([self.defaultAVCaptureSessionPreset isEqualToString:AVCaptureSessionPreset352x288]) {
|
|
|
|
self.imageWidth = 352;
|
|
|
|
self.imageHeight = 288;
|
|
|
|
} else if ([self.defaultAVCaptureSessionPreset isEqualToString:AVCaptureSessionPreset640x480]) {
|
|
|
|
self.imageWidth = 640;
|
|
|
|
self.imageHeight = 480;
|
|
|
|
} else if ([self.defaultAVCaptureSessionPreset isEqualToString:AVCaptureSessionPreset1280x720]) {
|
|
|
|
self.imageWidth = 1280;
|
|
|
|
self.imageHeight = 720;
|
|
|
|
} else {
|
|
|
|
self.imageWidth = 640;
|
|
|
|
self.imageHeight = 480;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|