445 lines
14 KiB
Text
445 lines
14 KiB
Text
//
|
|
// CvAbstractCamera2.mm
|
|
//
|
|
// Created by Giles Payne on 2020/04/01.
|
|
//
|
|
|
|
#import "CvCamera2.h"
|
|
|
|
#pragma mark - Private Interface
|
|
|
|
@interface CvAbstractCamera2 ()
|
|
|
|
@property (nonatomic, strong) AVCaptureVideoPreviewLayer* captureVideoPreviewLayer;
|
|
|
|
- (void)deviceOrientationDidChange:(NSNotification*)notification;
|
|
- (void)startCaptureSession;
|
|
|
|
- (void)setDesiredCameraPosition:(AVCaptureDevicePosition)desiredPosition;
|
|
|
|
- (void)updateSize;
|
|
|
|
@end
|
|
|
|
|
|
#pragma mark - Implementation
|
|
|
|
|
|
@implementation CvAbstractCamera2
|
|
|
|
#pragma mark - Constructors
|
|
|
|
- (id)init;
|
|
{
|
|
self = [super init];
|
|
if (self) {
|
|
// react to device orientation notifications
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(deviceOrientationDidChange:)
|
|
name:UIDeviceOrientationDidChangeNotification
|
|
object:nil];
|
|
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
|
|
self.currentDeviceOrientation = [[UIDevice currentDevice] orientation];
|
|
|
|
|
|
// check if camera available
|
|
self.cameraAvailable = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera];
|
|
NSLog(@"camera available: %@", (self.cameraAvailable ? @"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;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
|
|
|
|
- (id)initWithParentView:(UIView*)parent;
|
|
{
|
|
self = [super init];
|
|
if (self) {
|
|
// react to device orientation notifications
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(deviceOrientationDidChange:)
|
|
name:UIDeviceOrientationDidChangeNotification
|
|
object:nil];
|
|
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
|
|
self.currentDeviceOrientation = [[UIDevice currentDevice] orientation];
|
|
|
|
|
|
// check if camera available
|
|
self.cameraAvailable = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera];
|
|
NSLog(@"camera available: %@", (self.cameraAvailable ? @"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;
|
|
}
|
|
|
|
- (void)dealloc;
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
|
|
}
|
|
|
|
#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;
|
|
}
|
|
|
|
if (self.running == YES) {
|
|
return;
|
|
}
|
|
_running = YES;
|
|
|
|
// TODO: update image size data before actually starting (needed for recording)
|
|
[self updateSize];
|
|
|
|
if (self.cameraAvailable) {
|
|
[self startCaptureSession];
|
|
}
|
|
}
|
|
|
|
|
|
- (void)pause;
|
|
{
|
|
_running = NO;
|
|
[self.captureSession stopRunning];
|
|
}
|
|
|
|
|
|
|
|
- (void)stop;
|
|
{
|
|
_running = NO;
|
|
|
|
// Release any retained subviews of the main view.
|
|
// e.g. self.myOutlet = nil;
|
|
if (self.captureSession) {
|
|
for (AVCaptureInput *input in self.captureSession.inputs) {
|
|
[self.captureSession removeInput:input];
|
|
}
|
|
|
|
for (AVCaptureOutput *output in self.captureSession.outputs) {
|
|
[self.captureSession removeOutput:output];
|
|
}
|
|
|
|
[self.captureSession stopRunning];
|
|
}
|
|
|
|
_captureSessionLoaded = NO;
|
|
}
|
|
|
|
|
|
|
|
// use front/back camera
|
|
- (void)switchCameras;
|
|
{
|
|
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];
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - Device Orientation Changes
|
|
|
|
|
|
- (void)deviceOrientationDidChange:(NSNotification*)notification
|
|
{
|
|
(void)notification;
|
|
UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
|
|
|
|
switch (orientation)
|
|
{
|
|
case UIDeviceOrientationPortrait:
|
|
case UIDeviceOrientationPortraitUpsideDown:
|
|
case UIDeviceOrientationLandscapeLeft:
|
|
case UIDeviceOrientationLandscapeRight:
|
|
self.currentDeviceOrientation = orientation;
|
|
break;
|
|
|
|
case UIDeviceOrientationFaceUp:
|
|
case UIDeviceOrientationFaceDown:
|
|
default:
|
|
break;
|
|
}
|
|
NSLog(@"deviceOrientationDidChange: %d", (int)orientation);
|
|
|
|
[self updateOrientation];
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - Private Interface
|
|
|
|
- (void)createCaptureSession;
|
|
{
|
|
// 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");
|
|
}
|
|
}
|
|
|
|
- (void)createCaptureDevice;
|
|
{
|
|
// 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");
|
|
}
|
|
|
|
|
|
- (void)createVideoPreviewLayer;
|
|
{
|
|
self.captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.captureSession];
|
|
|
|
if ([self.captureVideoPreviewLayer.connection isVideoOrientationSupported])
|
|
{
|
|
[self.captureVideoPreviewLayer.connection setVideoOrientation:self.defaultAVCaptureVideoOrientation];
|
|
}
|
|
|
|
if (self.parentView != nil) {
|
|
self.captureVideoPreviewLayer.frame = self.parentView.bounds;
|
|
self.captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
|
|
[self.parentView.layer addSublayer:self.captureVideoPreviewLayer];
|
|
}
|
|
NSLog(@"[Camera] created AVCaptureVideoPreviewLayer");
|
|
}
|
|
|
|
- (void)setDesiredCameraPosition:(AVCaptureDevicePosition)desiredPosition;
|
|
{
|
|
for (AVCaptureDevice *device in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) {
|
|
if ([device position] == desiredPosition) {
|
|
[self.captureSession beginConfiguration];
|
|
|
|
NSError* error = nil;
|
|
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
|
|
if (!input) {
|
|
NSLog(@"error creating input %@", [error description]);
|
|
}
|
|
|
|
// support for autofocus
|
|
if ([device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {
|
|
error = nil;
|
|
if ([device lockForConfiguration:&error]) {
|
|
device.focusMode = AVCaptureFocusModeContinuousAutoFocus;
|
|
[device unlockForConfiguration];
|
|
} else {
|
|
NSLog(@"unable to lock device for autofocus configuration %@", [error description]);
|
|
}
|
|
}
|
|
[self.captureSession addInput:input];
|
|
|
|
for (AVCaptureInput *oldInput in self.captureSession.inputs) {
|
|
[self.captureSession removeInput:oldInput];
|
|
}
|
|
[self.captureSession addInput:input];
|
|
[self.captureSession commitConfiguration];
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- (void)startCaptureSession
|
|
{
|
|
if (!self.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];
|
|
}
|
|
|
|
|
|
- (void)createCaptureOutput;
|
|
{
|
|
[NSException raise:NSInternalInconsistencyException
|
|
format:@"You must override %s in a subclass", __FUNCTION__];
|
|
}
|
|
|
|
- (void)createCustomVideoPreview;
|
|
{
|
|
[NSException raise:NSInternalInconsistencyException
|
|
format:@"You must override %s in a subclass", __FUNCTION__];
|
|
}
|
|
|
|
- (void)updateOrientation;
|
|
{
|
|
// nothing to do here
|
|
}
|
|
|
|
|
|
- (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;
|
|
self.imageHeight = 480;
|
|
} 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;
|
|
}
|
|
}
|
|
|
|
- (void)lockFocus;
|
|
{
|
|
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
|
|
if ([device isFocusModeSupported:AVCaptureFocusModeLocked]) {
|
|
NSError *error = nil;
|
|
if ([device lockForConfiguration:&error]) {
|
|
device.focusMode = AVCaptureFocusModeLocked;
|
|
[device unlockForConfiguration];
|
|
} else {
|
|
NSLog(@"unable to lock device for locked focus configuration %@", [error description]);
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void) unlockFocus;
|
|
{
|
|
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
|
|
if ([device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {
|
|
NSError *error = nil;
|
|
if ([device lockForConfiguration:&error]) {
|
|
device.focusMode = AVCaptureFocusModeContinuousAutoFocus;
|
|
[device unlockForConfiguration];
|
|
} else {
|
|
NSLog(@"unable to lock device for autofocus configuration %@", [error description]);
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)lockExposure;
|
|
{
|
|
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
|
|
if ([device isExposureModeSupported:AVCaptureExposureModeLocked]) {
|
|
NSError *error = nil;
|
|
if ([device lockForConfiguration:&error]) {
|
|
device.exposureMode = AVCaptureExposureModeLocked;
|
|
[device unlockForConfiguration];
|
|
} else {
|
|
NSLog(@"unable to lock device for locked exposure configuration %@", [error description]);
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void) unlockExposure;
|
|
{
|
|
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
|
|
if ([device isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) {
|
|
NSError *error = nil;
|
|
if ([device lockForConfiguration:&error]) {
|
|
device.exposureMode = AVCaptureExposureModeContinuousAutoExposure;
|
|
[device unlockForConfiguration];
|
|
} else {
|
|
NSLog(@"unable to lock device for autoexposure configuration %@", [error description]);
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)lockBalance;
|
|
{
|
|
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
|
|
if ([device isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeLocked]) {
|
|
NSError *error = nil;
|
|
if ([device lockForConfiguration:&error]) {
|
|
device.whiteBalanceMode = AVCaptureWhiteBalanceModeLocked;
|
|
[device unlockForConfiguration];
|
|
} else {
|
|
NSLog(@"unable to lock device for locked white balance configuration %@", [error description]);
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void) unlockBalance;
|
|
{
|
|
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
|
|
if ([device isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance]) {
|
|
NSError *error = nil;
|
|
if ([device lockForConfiguration:&error]) {
|
|
device.whiteBalanceMode = AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance;
|
|
[device unlockForConfiguration];
|
|
} else {
|
|
NSLog(@"unable to lock device for auto white balance configuration %@", [error description]);
|
|
}
|
|
}
|
|
}
|
|
|
|
@end
|