From 7a1b70f893effaacbcb3d1fbd461cc17d4c98156 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Mon, 7 Nov 2016 21:17:41 -0500 Subject: [PATCH 01/27] =?UTF-8?q?If=20UIDeviceOrientation=20is=20FaceUp=20?= =?UTF-8?q?or=20FaceDown,=20then=20don=E2=80=99t=20assume=20portrait=20?= =?UTF-8?q?=E2=80=94=20instead,=20use=20statusBarOrientation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FastttCamera/FastttCamera.m | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/FastttCamera/FastttCamera.m b/FastttCamera/FastttCamera.m index 75a2213..b578cbe 100644 --- a/FastttCamera/FastttCamera.m +++ b/FastttCamera/FastttCamera.m @@ -687,6 +687,10 @@ + (AVCaptureVideoOrientation)_videoOrientationForDeviceOrientation:(UIDeviceOrie case UIDeviceOrientationLandscapeRight: return AVCaptureVideoOrientationLandscapeLeft; + case UIDeviceOrientationFaceUp: + case UIDeviceOrientationFaceDown: + return [self.class _videoOrientationFromStatusBarOrientation]; + default: break; } @@ -694,6 +698,25 @@ + (AVCaptureVideoOrientation)_videoOrientationForDeviceOrientation:(UIDeviceOrie return AVCaptureVideoOrientationPortrait; } ++ (AVCaptureVideoOrientation)_videoOrientationFromStatusBarOrientation { + switch ([UIApplication sharedApplication].statusBarOrientation) { + case UIInterfaceOrientationLandscapeLeft: + return AVCaptureVideoOrientationLandscapeLeft; + + case UIInterfaceOrientationLandscapeRight: + return AVCaptureVideoOrientationLandscapeRight; + + case UIInterfaceOrientationPortrait: + return AVCaptureVideoOrientationPortrait; + + case UIInterfaceOrientationPortraitUpsideDown: + return AVCaptureVideoOrientationPortraitUpsideDown; + + default: + return AVCaptureVideoOrientationPortrait; + } +} + #pragma mark - Camera Permissions - (void)_checkDeviceAuthorizationWithCompletion:(void (^)(BOOL isAuthorized))completion From 4eaa9ce5624f18a8962100d16fca45bff3ab0842 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Mon, 7 Nov 2016 21:19:49 -0500 Subject: [PATCH 02/27] Podspec bumped to 0.3.5 --- FastttCamera.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FastttCamera.podspec b/FastttCamera.podspec index 1b4a8f2..3ad54a7 100644 --- a/FastttCamera.podspec +++ b/FastttCamera.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "FastttCamera" - s.version = "0.3.4" + s.version = "0.3.5" s.summary = "A fast, straightforward implementation of AVFoundation camera with customizable real-time photo filters." s.homepage = "https://github.com/IFTTT/FastttCamera" s.license = 'MIT' From a0b40f06671d451e64ee239fda92ac04027ab111 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Mon, 7 Nov 2016 22:48:15 -0500 Subject: [PATCH 03/27] Support high resolution still captures --- FastttCamera/FastttCamera.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/FastttCamera/FastttCamera.m b/FastttCamera/FastttCamera.m index b578cbe..0804e72 100644 --- a/FastttCamera/FastttCamera.m +++ b/FastttCamera/FastttCamera.m @@ -453,6 +453,7 @@ - (void)_setupCaptureSession _stillImageOutput = [AVCaptureStillImageOutput new]; _stillImageOutput.outputSettings = outputSettings; + _stillImageOutput.highResolutionStillImageOutputEnabled = YES; [_session addOutput:_stillImageOutput]; @@ -507,6 +508,8 @@ - (void)_takePhoto BOOL needsPreviewRotation = ![self.deviceOrientation deviceOrientationMatchesInterfaceOrientation]; AVCaptureConnection *videoConnection = [self _currentCaptureConnection]; + if (!videoConnection.isActive) + return; if ([videoConnection isVideoOrientationSupported]) { [videoConnection setVideoOrientation:[self _currentCaptureVideoOrientationForDevice]]; From f40aa915da691cc21ffa9bd6fd57cc192c16aff3 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 12 Jan 2017 01:21:56 -0500 Subject: [PATCH 04/27] Support manually setting the mirroring, rather than auto setting it on front/back camera selection --- FastttCamera.podspec | 2 +- FastttCamera/FastttCamera.m | 14 ++++++++++++-- FastttCamera/FastttCameraInterface.h | 6 ++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/FastttCamera.podspec b/FastttCamera.podspec index 3ad54a7..8efc25d 100644 --- a/FastttCamera.podspec +++ b/FastttCamera.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "FastttCamera" - s.version = "0.3.5" + s.version = "0.3.6" s.summary = "A fast, straightforward implementation of AVFoundation camera with customizable real-time photo filters." s.homepage = "https://github.com/IFTTT/FastttCamera" s.license = 'MIT' diff --git a/FastttCamera/FastttCamera.m b/FastttCamera/FastttCamera.m index 0804e72..858098c 100644 --- a/FastttCamera/FastttCamera.m +++ b/FastttCamera/FastttCamera.m @@ -48,7 +48,8 @@ @implementation FastttCamera scalesImage = _scalesImage, cameraDevice = _cameraDevice, cameraFlashMode = _cameraFlashMode, - cameraTorchMode = _cameraTorchMode; + cameraTorchMode = _cameraTorchMode, + mirrorsOutput = _mirrorsOutput; - (instancetype)init { @@ -144,6 +145,7 @@ - (void)viewWillAppear:(BOOL)animated [self _insertPreviewLayer]; [self _setPreviewVideoOrientation]; + [self _setPreviewVideoMirroring]; } - (void)viewDidDisappear:(BOOL)animated @@ -516,7 +518,7 @@ - (void)_takePhoto } if ([videoConnection isVideoMirroringSupported]) { - [videoConnection setVideoMirrored:(_cameraDevice == FastttCameraDeviceFront)]; + [videoConnection setVideoMirrored:self.mirrorsOutput]; } #if TARGET_IPHONE_SIMULATOR @@ -646,6 +648,14 @@ - (void)_setPreviewVideoOrientation } } +- (void)_setPreviewVideoMirroring { + AVCaptureConnection *videoConnection = [_previewLayer connection]; + videoConnection.automaticallyAdjustsVideoMirroring = NO; + if ([videoConnection isVideoMirroringSupported]) { + [videoConnection setVideoMirrored:self.mirrorsOutput]; + } +} + - (AVCaptureVideoOrientation)_currentCaptureVideoOrientationForDevice { UIDeviceOrientation actualOrientation = self.deviceOrientation.orientation; diff --git a/FastttCamera/FastttCameraInterface.h b/FastttCamera/FastttCameraInterface.h index 13cd05c..a8f9dde 100644 --- a/FastttCamera/FastttCameraInterface.h +++ b/FastttCamera/FastttCameraInterface.h @@ -120,6 +120,12 @@ */ @property (nonatomic, assign) UIDeviceOrientation fixedInterfaceOrientation; +/** + * Whether the output of the camera is mirrored or not. Earlier versions of FastttCamera automatically mirrored if the front camera + * was used, here you need to set this property instead. + */ +@property (nonatomic, assign) BOOL mirrorsOutput; + #pragma mark - Camera State /** From d1f5e4d22a66073c25aaeb77185862d604808e35 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 26 Apr 2017 20:25:13 -0400 Subject: [PATCH 05/27] Support adding in views other than self.view --- FastttCamera/UIViewController+FastttCamera.h | 6 ++++++ FastttCamera/UIViewController+FastttCamera.m | 10 +++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/FastttCamera/UIViewController+FastttCamera.h b/FastttCamera/UIViewController+FastttCamera.h index 6d09a05..61fbdb5 100644 --- a/FastttCamera/UIViewController+FastttCamera.h +++ b/FastttCamera/UIViewController+FastttCamera.h @@ -31,6 +31,12 @@ */ - (void)fastttAddChildViewController:(UIViewController *)childViewController belowSubview:(UIView *)siblingSubview; +/** + * Allows specifying which view the subview belongs to, not just self.view + * + */ +- (void)fastttAddChildViewController:(UIViewController *)childViewController inView:(UIView*)view belowSubview:(UIView *)subview; + /** * Removes the given child view controller from this view controller and handles * view appearance transition event calls. diff --git a/FastttCamera/UIViewController+FastttCamera.m b/FastttCamera/UIViewController+FastttCamera.m index a450682..6fceafc 100644 --- a/FastttCamera/UIViewController+FastttCamera.m +++ b/FastttCamera/UIViewController+FastttCamera.m @@ -27,7 +27,15 @@ - (void)fastttAddChildViewController:(UIViewController *)childViewController bel [childViewController didMoveToParentViewController:self]; [childViewController endAppearanceTransition]; } - + +- (void)fastttAddChildViewController:(UIViewController *)childViewController inView:(UIView*)view belowSubview:(UIView *)subview { + [childViewController beginAppearanceTransition:YES animated:NO]; + [self addChildViewController:childViewController]; + [view insertSubview:childViewController.view belowSubview:subview]; + [childViewController didMoveToParentViewController:self]; + [childViewController endAppearanceTransition]; +} + - (void)fastttRemoveChildViewController:(UIViewController *)childViewController { [childViewController willMoveToParentViewController:nil]; From 38ff1a9e9bdcb98fcbbd65e845aebd55a4f28c07 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 11 May 2017 17:46:25 -0400 Subject: [PATCH 06/27] More options where to add the view --- FastttCamera/UIViewController+FastttCamera.h | 2 ++ FastttCamera/UIViewController+FastttCamera.m | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/FastttCamera/UIViewController+FastttCamera.h b/FastttCamera/UIViewController+FastttCamera.h index 61fbdb5..ec42140 100644 --- a/FastttCamera/UIViewController+FastttCamera.h +++ b/FastttCamera/UIViewController+FastttCamera.h @@ -35,7 +35,9 @@ * Allows specifying which view the subview belongs to, not just self.view * */ +- (void)fastttAddChildViewController:(UIViewController *)childViewController inView:(UIView*)view; - (void)fastttAddChildViewController:(UIViewController *)childViewController inView:(UIView*)view belowSubview:(UIView *)subview; +- (void)fastttAddChildViewController:(UIViewController *)childViewController inView:(UIView*)view aboveSubview:(UIView *)subview; /** * Removes the given child view controller from this view controller and handles diff --git a/FastttCamera/UIViewController+FastttCamera.m b/FastttCamera/UIViewController+FastttCamera.m index 6fceafc..5534179 100644 --- a/FastttCamera/UIViewController+FastttCamera.m +++ b/FastttCamera/UIViewController+FastttCamera.m @@ -28,6 +28,14 @@ - (void)fastttAddChildViewController:(UIViewController *)childViewController bel [childViewController endAppearanceTransition]; } +- (void)fastttAddChildViewController:(UIViewController *)childViewController inView:(UIView*)view { + [childViewController beginAppearanceTransition:YES animated:NO]; + [self addChildViewController:childViewController]; + [view addSubview:childViewController.view]; + [childViewController didMoveToParentViewController:self]; + [childViewController endAppearanceTransition]; +} + - (void)fastttAddChildViewController:(UIViewController *)childViewController inView:(UIView*)view belowSubview:(UIView *)subview { [childViewController beginAppearanceTransition:YES animated:NO]; [self addChildViewController:childViewController]; @@ -36,6 +44,14 @@ - (void)fastttAddChildViewController:(UIViewController *)childViewController inV [childViewController endAppearanceTransition]; } +- (void)fastttAddChildViewController:(UIViewController *)childViewController inView:(UIView*)view aboveSubview:(UIView *)subview { + [childViewController beginAppearanceTransition:YES animated:NO]; + [self addChildViewController:childViewController]; + [view insertSubview:childViewController.view aboveSubview:subview]; + [childViewController didMoveToParentViewController:self]; + [childViewController endAppearanceTransition]; +} + - (void)fastttRemoveChildViewController:(UIViewController *)childViewController { [childViewController willMoveToParentViewController:nil]; From fd4c0514bddf39d8590685829bd0729948808f0a Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 11 May 2017 17:47:31 -0400 Subject: [PATCH 07/27] Bump podspec to 0.3.7 --- FastttCamera.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FastttCamera.podspec b/FastttCamera.podspec index 8efc25d..3c4e4fe 100644 --- a/FastttCamera.podspec +++ b/FastttCamera.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "FastttCamera" - s.version = "0.3.6" + s.version = "0.3.7" s.summary = "A fast, straightforward implementation of AVFoundation camera with customizable real-time photo filters." s.homepage = "https://github.com/IFTTT/FastttCamera" s.license = 'MIT' From 8bf19ec5ee25213a8f2818d37c6b91d4d7dfacbd Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 30 Jun 2017 09:54:45 -0400 Subject: [PATCH 08/27] 0.3.8: if app not in foreground, don't try to take photo as it'll crash (Apple Watch case) --- FastttCamera.podspec | 2 +- FastttCamera/FastttCamera.m | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/FastttCamera.podspec b/FastttCamera.podspec index 3c4e4fe..f32e594 100644 --- a/FastttCamera.podspec +++ b/FastttCamera.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "FastttCamera" - s.version = "0.3.7" + s.version = "0.3.8" s.summary = "A fast, straightforward implementation of AVFoundation camera with customizable real-time photo filters." s.homepage = "https://github.com/IFTTT/FastttCamera" s.license = 'MIT' diff --git a/FastttCamera/FastttCamera.m b/FastttCamera/FastttCamera.m index 858098c..b3fd149 100644 --- a/FastttCamera/FastttCamera.m +++ b/FastttCamera/FastttCamera.m @@ -530,6 +530,10 @@ - (void)_takePhoto #else UIDeviceOrientation previewOrientation = [self _currentPreviewDeviceOrientation]; + if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive) { + NSLog(@"FastttCamera: must be showing video to take photo"); + return; + } [_stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) { From b6dd134abb62a50fe23340a9dd800d0015753d82 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Sun, 13 Aug 2017 16:24:08 -0400 Subject: [PATCH 09/27] Extra check on videoConnection added for _takePhoto --- FastttCamera.podspec | 2 +- FastttCamera/FastttCamera.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/FastttCamera.podspec b/FastttCamera.podspec index f32e594..1ed94e5 100644 --- a/FastttCamera.podspec +++ b/FastttCamera.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "FastttCamera" - s.version = "0.3.8" + s.version = "0.3.9" s.summary = "A fast, straightforward implementation of AVFoundation camera with customizable real-time photo filters." s.homepage = "https://github.com/IFTTT/FastttCamera" s.license = 'MIT' diff --git a/FastttCamera/FastttCamera.m b/FastttCamera/FastttCamera.m index b3fd149..508c1e2 100644 --- a/FastttCamera/FastttCamera.m +++ b/FastttCamera/FastttCamera.m @@ -510,7 +510,7 @@ - (void)_takePhoto BOOL needsPreviewRotation = ![self.deviceOrientation deviceOrientationMatchesInterfaceOrientation]; AVCaptureConnection *videoConnection = [self _currentCaptureConnection]; - if (!videoConnection.isActive) + if (!videoConnection.isActive || !videoConnection.isEnabled) return; if ([videoConnection isVideoOrientationSupported]) { From fd9cdcefd7b2711ef73c31d7721d2c2fd0930d9d Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Sun, 13 Aug 2017 16:33:22 -0400 Subject: [PATCH 10/27] Extra fail check on session init --- FastttCamera/FastttCamera.m | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/FastttCamera/FastttCamera.m b/FastttCamera/FastttCamera.m index 508c1e2..156b831 100644 --- a/FastttCamera/FastttCamera.m +++ b/FastttCamera/FastttCamera.m @@ -347,6 +347,8 @@ - (void)setCameraTorchMode:(FastttCameraTorchMode)cameraTorchMode - (void)startRunning { + if (!_session) + return; if (![_session isRunning]) { [_session startRunning]; } @@ -354,6 +356,8 @@ - (void)startRunning - (void)stopRunning { + if (!_session) + return; if ([_session isRunning]) { [_session stopRunning]; } @@ -361,7 +365,7 @@ - (void)stopRunning - (void)_insertPreviewLayer { - if (!_deviceAuthorized) { + if (!_deviceAuthorized || !_session) { return; } @@ -433,6 +437,10 @@ - (void)_setupCaptureSession #if !TARGET_IPHONE_SIMULATOR AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil]; + if (!deviceInput) { + _session = nil; + return; + } [_session addInput:deviceInput]; switch (device.position) { From ce067ac0e1b72e408d9a2d07248ad6b2d165ac42 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 29 Aug 2017 17:43:05 -0400 Subject: [PATCH 11/27] FIx bugs where after a failure, no more photos will be taken Update interface to return error if no photo will be taken --- FastttCamera.podspec | 2 +- FastttCamera/FastttCamera.m | 27 ++++++++++++++++----------- FastttCamera/FastttCameraInterface.h | 4 ++-- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/FastttCamera.podspec b/FastttCamera.podspec index 1ed94e5..d9b694a 100644 --- a/FastttCamera.podspec +++ b/FastttCamera.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "FastttCamera" - s.version = "0.3.9" + s.version = "0.3.10" s.summary = "A fast, straightforward implementation of AVFoundation camera with customizable real-time photo filters." s.homepage = "https://github.com/IFTTT/FastttCamera" s.license = 'MIT' diff --git a/FastttCamera/FastttCamera.m b/FastttCamera/FastttCamera.m index 156b831..526535c 100644 --- a/FastttCamera/FastttCamera.m +++ b/FastttCamera/FastttCamera.m @@ -204,13 +204,13 @@ - (BOOL)isReadyToCapturePhoto return !self.isCapturingImage; } -- (void)takePicture +- (BOOL)takePicture { if (!_deviceAuthorized) { - return; + return NO; } - [self _takePhoto]; + return [self _takePhoto]; } - (void)cancelImageProcessing @@ -508,18 +508,18 @@ - (void)_teardownCaptureSession #pragma mark - Capturing a Photo -- (void)_takePhoto +- (BOOL)_takePhoto { if (self.isCapturingImage) { - return; + return NO; } self.isCapturingImage = YES; - BOOL needsPreviewRotation = ![self.deviceOrientation deviceOrientationMatchesInterfaceOrientation]; - AVCaptureConnection *videoConnection = [self _currentCaptureConnection]; - if (!videoConnection.isActive || !videoConnection.isEnabled) - return; + if (!videoConnection.isActive || !videoConnection.isEnabled) { + self.isCapturingImage = NO; + return NO; + } if ([videoConnection isVideoOrientationSupported]) { [videoConnection setVideoOrientation:[self _currentCaptureVideoOrientationForDevice]]; @@ -528,6 +528,8 @@ - (void)_takePhoto if ([videoConnection isVideoMirroringSupported]) { [videoConnection setVideoMirrored:self.mirrorsOutput]; } + + BOOL needsPreviewRotation = ![self.deviceOrientation deviceOrientationMatchesInterfaceOrientation]; #if TARGET_IPHONE_SIMULATOR [self _insertPreviewLayer]; @@ -539,13 +541,15 @@ - (void)_takePhoto UIDeviceOrientation previewOrientation = [self _currentPreviewDeviceOrientation]; if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive) { - NSLog(@"FastttCamera: must be showing video to take photo"); - return; + self.isCapturingImage = NO; + NSLog(@"FastttCamera: must be UIApplicationStateActive to take photo"); + return NO; } [_stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) { if (!imageDataSampleBuffer) { + self.isCapturingImage = NO; return; } @@ -567,6 +571,7 @@ - (void)_takePhoto }); }]; #endif + return YES; } #pragma mark - Processing a Photo diff --git a/FastttCamera/FastttCameraInterface.h b/FastttCamera/FastttCameraInterface.h index a8f9dde..ee84da3 100644 --- a/FastttCamera/FastttCameraInterface.h +++ b/FastttCamera/FastttCameraInterface.h @@ -224,9 +224,9 @@ - (BOOL)isReadyToCapturePhoto; /** - * Triggers the camera to take a photo. + * Triggers the camera to take a photo. Returns false if picture was not taken. */ -- (void)takePicture; +- (BOOL)takePicture; #pragma mark - Process a photo From 17e4e2cff505b74453532028f3887cbf2cc4c8d7 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 27 Sep 2017 11:07:01 -0400 Subject: [PATCH 12/27] Support for sending individual frames of preview video to the delegate --- FastttCamera/FastttCamera.h | 5 +- FastttCamera/FastttCamera.m | 97 +++++++++++++++++++++++++++- FastttCamera/FastttCameraInterface.h | 4 ++ 3 files changed, 102 insertions(+), 4 deletions(-) diff --git a/FastttCamera/FastttCamera.h b/FastttCamera/FastttCamera.h index 5fb47fc..77a4c42 100644 --- a/FastttCamera/FastttCamera.h +++ b/FastttCamera/FastttCamera.h @@ -18,6 +18,9 @@ * @note If you want to use filters with your live camera preview, * use an instance of FastttFilterCamera instead. */ -@interface FastttCamera : UIViewController +@interface FastttCamera : UIViewController + +// use this if you want the delegate to receive video frames +- (instancetype)initWithSendIndividualVideoFrames:(BOOL)sendIndividualVideoFrames; @end diff --git a/FastttCamera/FastttCamera.m b/FastttCamera/FastttCamera.m index 526535c..23adb38 100644 --- a/FastttCamera/FastttCamera.m +++ b/FastttCamera/FastttCamera.m @@ -26,6 +26,9 @@ @interface FastttCamera () @property (nonatomic, strong) AVCaptureStillImageOutput *stillImageOutput; @property (nonatomic, assign) BOOL deviceAuthorized; @property (nonatomic, assign) BOOL isCapturingImage; +@property (nonatomic, strong) dispatch_queue_t sampleBufferQueue; +@property (nonatomic, strong) AVCaptureVideoDataOutput *videoOutput; +@property (nonatomic, assign) BOOL sendIndividualVideoFrames; @end @@ -51,11 +54,17 @@ @implementation FastttCamera cameraTorchMode = _cameraTorchMode, mirrorsOutput = _mirrorsOutput; +- (instancetype)initWithSendIndividualVideoFrames:(BOOL)sendIndividualVideoFrames { + _sendIndividualVideoFrames = sendIndividualVideoFrames; + return [self init]; +} + - (instancetype)init { if ((self = [super init])) { - [self _setupCaptureSession]; + _sampleBufferQueue = dispatch_queue_create("com.xaphod.fastttcamera.samplebuffer", NULL); + [self _setupCaptureSession]; // warning, concurrent/multi-threaded _handlesTapFocus = YES; _showsFocusView = YES; @@ -464,11 +473,17 @@ - (void)_setupCaptureSession _stillImageOutput = [AVCaptureStillImageOutput new]; _stillImageOutput.outputSettings = outputSettings; _stillImageOutput.highResolutionStillImageOutputEnabled = YES; - [_session addOutput:_stillImageOutput]; _deviceOrientation = [IFTTTDeviceOrientation new]; + if (self.sendIndividualVideoFrames) { + self.videoOutput = [[AVCaptureVideoDataOutput alloc] init]; + self.videoOutput.videoSettings = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) }; + [self.videoOutput setSampleBufferDelegate:self queue:self.sampleBufferQueue]; + [_session addOutput:self.videoOutput]; + } + if (self.isViewLoaded && self.view.window) { [self startRunning]; [self _insertPreviewLayer]; @@ -501,6 +516,11 @@ - (void)_teardownCaptureSession [_session removeOutput:_stillImageOutput]; _stillImageOutput = nil; + if (self.videoOutput) { + [_session removeOutput:self.videoOutput]; + self.videoOutput = nil; + } + [self _removePreviewLayer]; _session = nil; @@ -659,9 +679,17 @@ - (void)_processImage:(UIImage *)image withCropRect:(CGRect)cropRect maxDimensio - (void)_setPreviewVideoOrientation { AVCaptureConnection *videoConnection = [_previewLayer connection]; + AVCaptureVideoOrientation orientation = [self _currentPreviewVideoOrientationForDevice]; if ([videoConnection isVideoOrientationSupported]) { - [videoConnection setVideoOrientation:[self _currentPreviewVideoOrientationForDevice]]; + [videoConnection setVideoOrientation:orientation]; + } + + if (self.sendIndividualVideoFrames) { + AVCaptureConnection* connection = self.videoOutput.connections.firstObject; + if ([connection isVideoOrientationSupported]) { + [connection setVideoOrientation:orientation]; + } } } @@ -671,6 +699,14 @@ - (void)_setPreviewVideoMirroring { if ([videoConnection isVideoMirroringSupported]) { [videoConnection setVideoMirrored:self.mirrorsOutput]; } + + if (self.sendIndividualVideoFrames) { + AVCaptureConnection* connection = self.videoOutput.connections.firstObject; + connection.automaticallyAdjustsVideoMirroring = NO; + if ([connection isVideoMirroringSupported]) { + [connection setVideoMirrored:self.mirrorsOutput]; + } + } } - (AVCaptureVideoOrientation)_currentCaptureVideoOrientationForDevice @@ -825,4 +861,59 @@ - (BOOL)handlePinchZoomWithScale:(CGFloat)zoomScale return ([self zoomToScale:zoomScale] && self.showsZoomView); } +#pragma mark - AVCaptureVideoDataOutputSampleBufferDelegate + +- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { + if (self.sendIndividualVideoFrames && self.delegate) { + // Get a CMSampleBuffer's Core Video image buffer for the media data + CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); + // Lock the base address of the pixel buffer + CVPixelBufferLockBaseAddress(imageBuffer, 0); + + // Get the number of bytes per row for the pixel buffer + void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer); + + // Get the number of bytes per row for the pixel buffer + size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); + // Get the pixel buffer width and height + size_t width = CVPixelBufferGetWidth(imageBuffer); + size_t height = CVPixelBufferGetHeight(imageBuffer); + + // Create a device-dependent RGB color space + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + + // Create a bitmap graphics context with the sample buffer data + CGContextRef context = CGBitmapContextCreate(baseAddress, width, height, 8, + bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst); + // Create a Quartz image from the pixel data in the bitmap graphics context + CGImageRef quartzImage = CGBitmapContextCreateImage(context); + // Unlock the pixel buffer + CVPixelBufferUnlockBaseAddress(imageBuffer,0); + + // Free up the context and color space + CGContextRelease(context); + CGColorSpaceRelease(colorSpace); + + // Create an image object from the Quartz image + UIImage *image = [UIImage imageWithCGImage:quartzImage]; + FastttCapturedImage *capturedImage = [FastttCapturedImage fastttCapturedFullImage: image]; + + // Release the Quartz image + CGImageRelease(quartzImage); + + BOOL needsPreviewRotation = ![self.deviceOrientation deviceOrientationMatchesInterfaceOrientation] || !self.interfaceRotatesWithOrientation; + UIDeviceOrientation previewOrientation = [self _currentPreviewDeviceOrientation]; + + if (needsPreviewRotation) { + capturedImage.fullImage = [capturedImage.fullImage fastttRotatedImageMatchingCameraViewWithOrientation:previewOrientation]; + } + + [capturedImage normalizeWithCallback:^(FastttCapturedImage *capturedImage){ + if (capturedImage) { + [self.delegate cameraController:self didCaptureVideoFrame:capturedImage.fullImage]; + } + }]; + } +} + @end diff --git a/FastttCamera/FastttCameraInterface.h b/FastttCamera/FastttCameraInterface.h index ee84da3..df3b820 100644 --- a/FastttCamera/FastttCameraInterface.h +++ b/FastttCamera/FastttCameraInterface.h @@ -297,6 +297,10 @@ @optional +// Added by Tim - called when a frame of the video (via sample buffer) is ready +// requires sendIndividualVideoFrames = true +- (void)cameraController:(id)cameraController didCaptureVideoFrame:(UIImage*)videoFrame; + /** * Called when the camera controller has obtained the raw data containing the image and metadata. * From 26d989f929b902be5ffe6a55dcb92841b7ed7557 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 27 Sep 2017 13:40:35 -0400 Subject: [PATCH 13/27] Podspec to 0.3.11 --- FastttCamera.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FastttCamera.podspec b/FastttCamera.podspec index d9b694a..c033ec3 100644 --- a/FastttCamera.podspec +++ b/FastttCamera.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "FastttCamera" - s.version = "0.3.10" + s.version = "0.3.11" s.summary = "A fast, straightforward implementation of AVFoundation camera with customizable real-time photo filters." s.homepage = "https://github.com/IFTTT/FastttCamera" s.license = 'MIT' From 7b7a916f9136ac4d636504eea773667a3f2893c0 Mon Sep 17 00:00:00 2001 From: David Pejinovic Date: Thu, 21 Sep 2017 15:10:07 +0200 Subject: [PATCH 14/27] - faster cropping --- FastttCamera/UIImage+FastttCamera.m | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/FastttCamera/UIImage+FastttCamera.m b/FastttCamera/UIImage+FastttCamera.m index 914582e..ccb1dfe 100644 --- a/FastttCamera/UIImage+FastttCamera.m +++ b/FastttCamera/UIImage+FastttCamera.m @@ -190,11 +190,12 @@ - (UIImage *)fastttImageWithNormalizedOrientation FastttRound(self.size.width), FastttRound(self.size.height)); - UIGraphicsBeginImageContextWithOptions(newRect.size, YES, self.scale); - [self drawInRect:newRect]; - - UIImage *normalized = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + CGImageRef imageRef = CGImageRetain(CGImageCreateWithImageInRect([self CGImage], newRect)); + UIImage *normalized = + [UIImage imageWithCGImage:imageRef + scale:[self scale] + orientation: self.imageOrientation]; + CGImageRelease(imageRef); return normalized; } From f221e1102858a3b1e891fa846d7987e7ffac8c93 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 27 Sep 2017 15:21:15 -0400 Subject: [PATCH 15/27] Don't init an extra queue when it won't be used --- FastttCamera.podspec | 2 +- FastttCamera/FastttCamera.m | 2 +- FastttCamera/FastttCameraInterface.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/FastttCamera.podspec b/FastttCamera.podspec index c033ec3..23386b2 100644 --- a/FastttCamera.podspec +++ b/FastttCamera.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "FastttCamera" - s.version = "0.3.11" + s.version = "0.3.12" s.summary = "A fast, straightforward implementation of AVFoundation camera with customizable real-time photo filters." s.homepage = "https://github.com/IFTTT/FastttCamera" s.license = 'MIT' diff --git a/FastttCamera/FastttCamera.m b/FastttCamera/FastttCamera.m index 23adb38..99b32e7 100644 --- a/FastttCamera/FastttCamera.m +++ b/FastttCamera/FastttCamera.m @@ -55,6 +55,7 @@ @implementation FastttCamera mirrorsOutput = _mirrorsOutput; - (instancetype)initWithSendIndividualVideoFrames:(BOOL)sendIndividualVideoFrames { + _sampleBufferQueue = dispatch_queue_create("com.xaphod.fastttcamera.samplebuffer", NULL); _sendIndividualVideoFrames = sendIndividualVideoFrames; return [self init]; } @@ -63,7 +64,6 @@ - (instancetype)init { if ((self = [super init])) { - _sampleBufferQueue = dispatch_queue_create("com.xaphod.fastttcamera.samplebuffer", NULL); [self _setupCaptureSession]; // warning, concurrent/multi-threaded _handlesTapFocus = YES; diff --git a/FastttCamera/FastttCameraInterface.h b/FastttCamera/FastttCameraInterface.h index df3b820..4d7b287 100644 --- a/FastttCamera/FastttCameraInterface.h +++ b/FastttCamera/FastttCameraInterface.h @@ -298,7 +298,7 @@ @optional // Added by Tim - called when a frame of the video (via sample buffer) is ready -// requires sendIndividualVideoFrames = true +// requires using the init for FasttCamera that specifies sendIndividualVideoFrames as true - (void)cameraController:(id)cameraController didCaptureVideoFrame:(UIImage*)videoFrame; /** From 020b852163e21881d8843f47a0687431a5566fcf Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 28 Sep 2017 10:04:11 -0400 Subject: [PATCH 16/27] Revert "- faster cropping" This reverts commit 7b7a916f9136ac4d636504eea773667a3f2893c0. --- FastttCamera/UIImage+FastttCamera.m | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/FastttCamera/UIImage+FastttCamera.m b/FastttCamera/UIImage+FastttCamera.m index ccb1dfe..914582e 100644 --- a/FastttCamera/UIImage+FastttCamera.m +++ b/FastttCamera/UIImage+FastttCamera.m @@ -190,12 +190,11 @@ - (UIImage *)fastttImageWithNormalizedOrientation FastttRound(self.size.width), FastttRound(self.size.height)); - CGImageRef imageRef = CGImageRetain(CGImageCreateWithImageInRect([self CGImage], newRect)); - UIImage *normalized = - [UIImage imageWithCGImage:imageRef - scale:[self scale] - orientation: self.imageOrientation]; - CGImageRelease(imageRef); + UIGraphicsBeginImageContextWithOptions(newRect.size, YES, self.scale); + [self drawInRect:newRect]; + + UIImage *normalized = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); return normalized; } From 89ebd88ee645e05e55a1fb6e30891a0970e266bc Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 28 Sep 2017 10:10:36 -0400 Subject: [PATCH 17/27] podspec to 0.3.13 --- FastttCamera.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FastttCamera.podspec b/FastttCamera.podspec index 23386b2..451b81b 100644 --- a/FastttCamera.podspec +++ b/FastttCamera.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "FastttCamera" - s.version = "0.3.12" + s.version = "0.3.13" s.summary = "A fast, straightforward implementation of AVFoundation camera with customizable real-time photo filters." s.homepage = "https://github.com/IFTTT/FastttCamera" s.license = 'MIT' From 87cf995d525dc9b51303cae4897142fe124407aa Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 3 Nov 2017 12:46:58 -0400 Subject: [PATCH 18/27] Detect session interruptions, and session not running on takePhoto Add a notification that UI can catch when interruptions occur --- FastttCamera/FastttCamera.h | 4 ++ FastttCamera/FastttCamera.m | 62 ++++++++++++++++++++++++++--- FastttCamera/UIImage+FastttCamera.h | 2 +- 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/FastttCamera/FastttCamera.h b/FastttCamera/FastttCamera.h index 77a4c42..7a04632 100644 --- a/FastttCamera/FastttCamera.h +++ b/FastttCamera/FastttCamera.h @@ -9,6 +9,10 @@ #import #import "FastttCameraInterface.h" +FOUNDATION_EXPORT NSString* const FastttCameraStateNotificationName; +FOUNDATION_EXPORT NSString* const FastttCameraStateNotificationErrorKey; +FOUNDATION_EXPORT NSString* const FastttCameraStateNotificationStateKey; + /** * Public class for you to use to create a standard FastttCamera! * diff --git a/FastttCamera/FastttCamera.m b/FastttCamera/FastttCamera.m index 99b32e7..561f808 100644 --- a/FastttCamera/FastttCamera.m +++ b/FastttCamera/FastttCamera.m @@ -16,6 +16,10 @@ #import "FastttZoom.h" #import "FastttCapturedImage+Process.h" +NSString* const FastttCameraStateNotificationName = @"stateNotification"; +NSString* const FastttCameraStateNotificationErrorKey = @"errorKey"; +NSString* const FastttCameraStateNotificationStateKey = @"stateKey"; + @interface FastttCamera () @property (nonatomic, strong) IFTTTDeviceOrientation *deviceOrientation; @@ -60,8 +64,7 @@ - (instancetype)initWithSendIndividualVideoFrames:(BOOL)sendIndividualVideoFrame return [self init]; } -- (instancetype)init -{ +- (instancetype)init { if ((self = [super init])) { [self _setupCaptureSession]; // warning, concurrent/multi-threaded @@ -116,6 +119,37 @@ - (void)dealloc [[NSNotificationCenter defaultCenter] removeObserver:self]; } +- (void)sessionRuntimeError:(NSNotification *)notification { + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + dict[FastttCameraStateNotificationStateKey] = @"sessionRuntimeError"; + NSError *error = notification.userInfo[AVCaptureSessionErrorKey]; + if (error) + dict[FastttCameraStateNotificationErrorKey] = error; + [[NSNotificationCenter defaultCenter] postNotificationName:FastttCameraStateNotificationName object:nil userInfo:dict]; + + [self _teardownCaptureSession]; +} + +- (void)sessionWasInterrupted:(NSNotification *)notification { + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + dict[FastttCameraStateNotificationStateKey] = @"sessionWasInterrupted"; + id reason = notification.userInfo[AVCaptureSessionInterruptionReasonKey]; + if (reason) + dict[FastttCameraStateNotificationErrorKey] = reason; + [[NSNotificationCenter defaultCenter] postNotificationName:FastttCameraStateNotificationName object:nil userInfo:dict]; + + [self _teardownCaptureSession]; +} + +- (void)sessionInterruptionEnded:(NSNotification *)notification { + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + dict[FastttCameraStateNotificationStateKey] = @"sessionInterruptionEnded"; + [[NSNotificationCenter defaultCenter] postNotificationName:FastttCameraStateNotificationName object:nil userInfo:dict]; + + [self _teardownCaptureSession]; + [self _setupCaptureSession]; +} + #pragma mark - View Events - (void)viewDidLoad @@ -427,7 +461,11 @@ - (void)_setupCaptureSession _session = [AVCaptureSession new]; _session.sessionPreset = AVCaptureSessionPresetPhoto; - + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sessionRuntimeError:) name:AVCaptureSessionRuntimeErrorNotification object:self.session]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sessionWasInterrupted:) name:AVCaptureSessionWasInterruptedNotification object:self.session]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sessionInterruptionEnded:) name:AVCaptureSessionInterruptionEndedNotification object:self.session]; + AVCaptureDevice *device = [AVCaptureDevice cameraDevice:self.cameraDevice]; if (!device) { @@ -533,14 +571,28 @@ - (BOOL)_takePhoto if (self.isCapturingImage) { return NO; } - self.isCapturingImage = YES; + + if (!_session.isRunning) { + return NO; + } AVCaptureConnection *videoConnection = [self _currentCaptureConnection]; if (!videoConnection.isActive || !videoConnection.isEnabled) { - self.isCapturingImage = NO; return NO; } + BOOL stillImageOutputConnected = NO; + for (AVCaptureConnection *conn in _stillImageOutput.connections) { + if (conn == videoConnection) { + stillImageOutputConnected = YES; + } + } + if (!stillImageOutputConnected) { + return NO; + } + + self.isCapturingImage = YES; + if ([videoConnection isVideoOrientationSupported]) { [videoConnection setVideoOrientation:[self _currentCaptureVideoOrientationForDevice]]; } diff --git a/FastttCamera/UIImage+FastttCamera.h b/FastttCamera/UIImage+FastttCamera.h index 0a63c9a..446c658 100644 --- a/FastttCamera/UIImage+FastttCamera.h +++ b/FastttCamera/UIImage+FastttCamera.h @@ -83,7 +83,7 @@ /** * Scales the image to the given maximum dimension. * - * @param size The destination maximum dimension of the image. + * @param maxDimension The destination maximum dimension of the image. * * @return The scaled image. */ From 7ffa8ee94205d07a242eba66db27ed0e2990c8ff Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 3 Nov 2017 12:47:31 -0400 Subject: [PATCH 19/27] podspec to 0.3.14 --- FastttCamera.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FastttCamera.podspec b/FastttCamera.podspec index 451b81b..f364d79 100644 --- a/FastttCamera.podspec +++ b/FastttCamera.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "FastttCamera" - s.version = "0.3.13" + s.version = "0.3.14" s.summary = "A fast, straightforward implementation of AVFoundation camera with customizable real-time photo filters." s.homepage = "https://github.com/IFTTT/FastttCamera" s.license = 'MIT' From cab11e0c0f192dfd979a62cbe3cf2c731f6edcdb Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 8 Nov 2017 19:49:07 -0500 Subject: [PATCH 20/27] Remove sesson state notifications, they caused loops / deadlocks --- FastttCamera.podspec | 2 +- FastttCamera/FastttCamera.h | 4 ---- FastttCamera/FastttCamera.m | 39 ------------------------------------- 3 files changed, 1 insertion(+), 44 deletions(-) diff --git a/FastttCamera.podspec b/FastttCamera.podspec index f364d79..4be20e2 100644 --- a/FastttCamera.podspec +++ b/FastttCamera.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "FastttCamera" - s.version = "0.3.14" + s.version = "0.3.15" s.summary = "A fast, straightforward implementation of AVFoundation camera with customizable real-time photo filters." s.homepage = "https://github.com/IFTTT/FastttCamera" s.license = 'MIT' diff --git a/FastttCamera/FastttCamera.h b/FastttCamera/FastttCamera.h index 7a04632..77a4c42 100644 --- a/FastttCamera/FastttCamera.h +++ b/FastttCamera/FastttCamera.h @@ -9,10 +9,6 @@ #import #import "FastttCameraInterface.h" -FOUNDATION_EXPORT NSString* const FastttCameraStateNotificationName; -FOUNDATION_EXPORT NSString* const FastttCameraStateNotificationErrorKey; -FOUNDATION_EXPORT NSString* const FastttCameraStateNotificationStateKey; - /** * Public class for you to use to create a standard FastttCamera! * diff --git a/FastttCamera/FastttCamera.m b/FastttCamera/FastttCamera.m index 561f808..041cad5 100644 --- a/FastttCamera/FastttCamera.m +++ b/FastttCamera/FastttCamera.m @@ -16,10 +16,6 @@ #import "FastttZoom.h" #import "FastttCapturedImage+Process.h" -NSString* const FastttCameraStateNotificationName = @"stateNotification"; -NSString* const FastttCameraStateNotificationErrorKey = @"errorKey"; -NSString* const FastttCameraStateNotificationStateKey = @"stateKey"; - @interface FastttCamera () @property (nonatomic, strong) IFTTTDeviceOrientation *deviceOrientation; @@ -119,37 +115,6 @@ - (void)dealloc [[NSNotificationCenter defaultCenter] removeObserver:self]; } -- (void)sessionRuntimeError:(NSNotification *)notification { - NSMutableDictionary *dict = [NSMutableDictionary dictionary]; - dict[FastttCameraStateNotificationStateKey] = @"sessionRuntimeError"; - NSError *error = notification.userInfo[AVCaptureSessionErrorKey]; - if (error) - dict[FastttCameraStateNotificationErrorKey] = error; - [[NSNotificationCenter defaultCenter] postNotificationName:FastttCameraStateNotificationName object:nil userInfo:dict]; - - [self _teardownCaptureSession]; -} - -- (void)sessionWasInterrupted:(NSNotification *)notification { - NSMutableDictionary *dict = [NSMutableDictionary dictionary]; - dict[FastttCameraStateNotificationStateKey] = @"sessionWasInterrupted"; - id reason = notification.userInfo[AVCaptureSessionInterruptionReasonKey]; - if (reason) - dict[FastttCameraStateNotificationErrorKey] = reason; - [[NSNotificationCenter defaultCenter] postNotificationName:FastttCameraStateNotificationName object:nil userInfo:dict]; - - [self _teardownCaptureSession]; -} - -- (void)sessionInterruptionEnded:(NSNotification *)notification { - NSMutableDictionary *dict = [NSMutableDictionary dictionary]; - dict[FastttCameraStateNotificationStateKey] = @"sessionInterruptionEnded"; - [[NSNotificationCenter defaultCenter] postNotificationName:FastttCameraStateNotificationName object:nil userInfo:dict]; - - [self _teardownCaptureSession]; - [self _setupCaptureSession]; -} - #pragma mark - View Events - (void)viewDidLoad @@ -462,10 +427,6 @@ - (void)_setupCaptureSession _session = [AVCaptureSession new]; _session.sessionPreset = AVCaptureSessionPresetPhoto; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sessionRuntimeError:) name:AVCaptureSessionRuntimeErrorNotification object:self.session]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sessionWasInterrupted:) name:AVCaptureSessionWasInterruptedNotification object:self.session]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sessionInterruptionEnded:) name:AVCaptureSessionInterruptionEndedNotification object:self.session]; - AVCaptureDevice *device = [AVCaptureDevice cameraDevice:self.cameraDevice]; if (!device) { From 2a7c17c59b154b375a970588bf1cab51bdcde30c Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Mon, 20 Nov 2017 22:34:33 -0500 Subject: [PATCH 21/27] Refactor: allow changing sending vid frames as property. - Requesting Cam auth changed: now checked on init, but only notified when you attempt to take an action like takePhoto - _setupSession is synchronous again - Image processing moved to high priority queue --- FastttCamera/FastttCamera.h | 4 +- FastttCamera/FastttCamera.m | 229 +++++++++++++++++++----------------- 2 files changed, 121 insertions(+), 112 deletions(-) diff --git a/FastttCamera/FastttCamera.h b/FastttCamera/FastttCamera.h index 77a4c42..e74625e 100644 --- a/FastttCamera/FastttCamera.h +++ b/FastttCamera/FastttCamera.h @@ -20,7 +20,7 @@ */ @interface FastttCamera : UIViewController -// use this if you want the delegate to receive video frames -- (instancetype)initWithSendIndividualVideoFrames:(BOOL)sendIndividualVideoFrames; +// if you want the delegate to receive video frames +@property (nonatomic, assign) BOOL sendIndividualVideoFrames; @end diff --git a/FastttCamera/FastttCamera.m b/FastttCamera/FastttCamera.m index 041cad5..d74127f 100644 --- a/FastttCamera/FastttCamera.m +++ b/FastttCamera/FastttCamera.m @@ -28,7 +28,6 @@ @interface FastttCamera () @property (nonatomic, assign) BOOL isCapturingImage; @property (nonatomic, strong) dispatch_queue_t sampleBufferQueue; @property (nonatomic, strong) AVCaptureVideoDataOutput *videoOutput; -@property (nonatomic, assign) BOOL sendIndividualVideoFrames; @end @@ -54,17 +53,10 @@ @implementation FastttCamera cameraTorchMode = _cameraTorchMode, mirrorsOutput = _mirrorsOutput; -- (instancetype)initWithSendIndividualVideoFrames:(BOOL)sendIndividualVideoFrames { - _sampleBufferQueue = dispatch_queue_create("com.xaphod.fastttcamera.samplebuffer", NULL); - _sendIndividualVideoFrames = sendIndividualVideoFrames; - return [self init]; -} - - (instancetype)init { if ((self = [super init])) { - [self _setupCaptureSession]; // warning, concurrent/multi-threaded - + _sampleBufferQueue = dispatch_queue_create("com.xaphod.fastttcamera.samplebuffer", NULL); _handlesTapFocus = YES; _showsFocusView = YES; _handlesZoom = YES; @@ -102,6 +94,17 @@ - (instancetype)init { object:nil]; } +#if !TARGET_IPHONE_SIMULATOR + [self _checkDeviceAuthorizationWithCompletion:^(BOOL isAuthorized) { + + _deviceAuthorized = isAuthorized; +#else + _deviceAuthorized = YES; +#endif + +#if !TARGET_IPHONE_SIMULATOR + }]; +#endif return self; } @@ -120,7 +123,7 @@ - (void)dealloc - (void)viewDidLoad { [super viewDidLoad]; - + [self _setupCaptureSession]; [self _insertPreviewLayer]; UIView *viewForGestures = self.view; @@ -209,15 +212,29 @@ - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrie - (BOOL)isReadyToCapturePhoto { + if (!_deviceAuthorized) { + if ([self.delegate respondsToSelector:@selector(userDeniedCameraPermissionsForCameraController:)]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.delegate userDeniedCameraPermissionsForCameraController:self]; + }); + } + return NO; + } + return !self.isCapturingImage; } - (BOOL)takePicture { if (!_deviceAuthorized) { + if ([self.delegate respondsToSelector:@selector(userDeniedCameraPermissionsForCameraController:)]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.delegate userDeniedCameraPermissionsForCameraController:self]; + }); + } return NO; } - + return [self _takePhoto]; } @@ -355,8 +372,15 @@ - (void)setCameraTorchMode:(FastttCameraTorchMode)cameraTorchMode - (void)startRunning { - if (!_session) + if (!_deviceAuthorized) { + if ([self.delegate respondsToSelector:@selector(userDeniedCameraPermissionsForCameraController:)]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.delegate userDeniedCameraPermissionsForCameraController:self]; + }); + } return; + } + if (![_session isRunning]) { [_session startRunning]; } @@ -364,8 +388,6 @@ - (void)startRunning - (void)stopRunning { - if (!_session) - return; if ([_session isRunning]) { [_session stopRunning]; } @@ -407,93 +429,73 @@ - (void)_setupCaptureSession return; } -#if !TARGET_IPHONE_SIMULATOR - [self _checkDeviceAuthorizationWithCompletion:^(BOOL isAuthorized) { - - _deviceAuthorized = isAuthorized; -#else - _deviceAuthorized = YES; -#endif - if (!_deviceAuthorized && [self.delegate respondsToSelector:@selector(userDeniedCameraPermissionsForCameraController:)]) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self.delegate userDeniedCameraPermissionsForCameraController:self]; - }); + NSAssert([NSThread currentThread].isMainThread, @"ERROR must be main thread here"); + + _session = [AVCaptureSession new]; + _session.sessionPreset = AVCaptureSessionPresetPhoto; + + AVCaptureDevice *device = [AVCaptureDevice cameraDevice:self.cameraDevice]; + + if (!device) { + device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + } + + if ([device lockForConfiguration:nil]) { + if([device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]){ + device.focusMode = AVCaptureFocusModeContinuousAutoFocus; } - if (_deviceAuthorized) { - - dispatch_async(dispatch_get_main_queue(), ^{ - - _session = [AVCaptureSession new]; - _session.sessionPreset = AVCaptureSessionPresetPhoto; - - AVCaptureDevice *device = [AVCaptureDevice cameraDevice:self.cameraDevice]; - - if (!device) { - device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; - } - - if ([device lockForConfiguration:nil]) { - if([device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]){ - device.focusMode = AVCaptureFocusModeContinuousAutoFocus; - } - - device.exposureMode = AVCaptureExposureModeContinuousAutoExposure; - - [device unlockForConfiguration]; - } - -#if !TARGET_IPHONE_SIMULATOR - AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil]; - if (!deviceInput) { - _session = nil; - return; - } - [_session addInput:deviceInput]; - - switch (device.position) { - case AVCaptureDevicePositionBack: - _cameraDevice = FastttCameraDeviceRear; - break; - - case AVCaptureDevicePositionFront: - _cameraDevice = FastttCameraDeviceFront; - break; - - default: - break; - } - - [self setCameraFlashMode:_cameraFlashMode]; -#endif - - NSDictionary *outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG}; - - _stillImageOutput = [AVCaptureStillImageOutput new]; - _stillImageOutput.outputSettings = outputSettings; - _stillImageOutput.highResolutionStillImageOutputEnabled = YES; - [_session addOutput:_stillImageOutput]; - - _deviceOrientation = [IFTTTDeviceOrientation new]; - - if (self.sendIndividualVideoFrames) { - self.videoOutput = [[AVCaptureVideoDataOutput alloc] init]; - self.videoOutput.videoSettings = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) }; - [self.videoOutput setSampleBufferDelegate:self queue:self.sampleBufferQueue]; - [_session addOutput:self.videoOutput]; - } - - if (self.isViewLoaded && self.view.window) { - [self startRunning]; - [self _insertPreviewLayer]; - [self _setPreviewVideoOrientation]; - [self _resetZoom]; - } - }); - } + device.exposureMode = AVCaptureExposureModeContinuousAutoExposure; + + [device unlockForConfiguration]; + } + #if !TARGET_IPHONE_SIMULATOR - }]; + AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil]; + if (!deviceInput) { + _session = nil; + return; + } + [_session addInput:deviceInput]; + + switch (device.position) { + case AVCaptureDevicePositionBack: + _cameraDevice = FastttCameraDeviceRear; + break; + + case AVCaptureDevicePositionFront: + _cameraDevice = FastttCameraDeviceFront; + break; + + default: + break; + } + + [self setCameraFlashMode:_cameraFlashMode]; #endif + + NSDictionary *outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG}; + + _stillImageOutput = [AVCaptureStillImageOutput new]; + _stillImageOutput.outputSettings = outputSettings; + _stillImageOutput.highResolutionStillImageOutputEnabled = YES; + [_session addOutput:_stillImageOutput]; + + _deviceOrientation = [IFTTTDeviceOrientation new]; + + if (self.sendIndividualVideoFrames) { + self.videoOutput = [[AVCaptureVideoDataOutput alloc] init]; + self.videoOutput.videoSettings = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) }; + [self.videoOutput setSampleBufferDelegate:self queue:self.sampleBufferQueue]; + [_session addOutput:self.videoOutput]; + } + + if (self.isViewLoaded && self.view.window) { + [self startRunning]; + [self _insertPreviewLayer]; + [self _setPreviewVideoOrientation]; + [self _resetZoom]; + } } - (void)_teardownCaptureSession @@ -536,7 +538,13 @@ - (BOOL)_takePhoto if (!_session.isRunning) { return NO; } - + + if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive) { + self.isCapturingImage = NO; + NSLog(@"FastttCamera: must be UIApplicationStateActive to take photo"); + return NO; + } + AVCaptureConnection *videoConnection = [self _currentCaptureConnection]; if (!videoConnection.isActive || !videoConnection.isEnabled) { return NO; @@ -573,11 +581,6 @@ - (BOOL)_takePhoto #else UIDeviceOrientation previewOrientation = [self _currentPreviewDeviceOrientation]; - if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive) { - self.isCapturingImage = NO; - NSLog(@"FastttCamera: must be UIApplicationStateActive to take photo"); - return NO; - } [_stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) { @@ -596,12 +599,8 @@ - (BOOL)_takePhoto [self.delegate cameraController:self didFinishCapturingImageData:imageData]; } - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - - UIImage *image = [UIImage imageWithData:imageData]; - - [self _processCameraPhoto:image needsPreviewRotation:needsPreviewRotation previewOrientation:previewOrientation]; - }); + UIImage *image = [UIImage imageWithData:imageData]; + [self _processCameraPhoto:image needsPreviewRotation:needsPreviewRotation previewOrientation:previewOrientation]; }]; #endif return YES; @@ -621,7 +620,7 @@ - (void)_processCameraPhoto:(UIImage *)image needsPreviewRotation:(BOOL)needsPre - (void)_processImage:(UIImage *)image withCropRect:(CGRect)cropRect maxDimension:(CGFloat)maxDimension fromCamera:(BOOL)fromCamera needsPreviewRotation:(BOOL)needsPreviewRotation previewOrientation:(UIDeviceOrientation)previewOrientation { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ if (fromCamera && !self.isCapturingImage) { return; } @@ -929,4 +928,14 @@ - (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleB } } +// if the property for sending invidiual video frames changes, then the session needs to be rebuilt +- (void)setSendIndividualVideoFrames:(BOOL)sendIndividualVideoFrames { + BOOL restartSession = !_sendIndividualVideoFrames && sendIndividualVideoFrames; + _sendIndividualVideoFrames = sendIndividualVideoFrames; + if (restartSession && _session) { + [self _teardownCaptureSession]; + [self _setupCaptureSession]; + } +} + @end From 2a7195f6395136f5af71d4bd0d707c4230bc1611 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 22 Nov 2017 10:54:42 -0500 Subject: [PATCH 22/27] takePicture offers a completionBlock, fix rotation issue in video frames --- FastttCamera/FastttCamera.m | 102 ++++++++++++++------------- FastttCamera/FastttCameraInterface.h | 7 +- 2 files changed, 58 insertions(+), 51 deletions(-) diff --git a/FastttCamera/FastttCamera.m b/FastttCamera/FastttCamera.m index d74127f..7cd12b3 100644 --- a/FastttCamera/FastttCamera.m +++ b/FastttCamera/FastttCamera.m @@ -224,18 +224,19 @@ - (BOOL)isReadyToCapturePhoto return !self.isCapturingImage; } -- (BOOL)takePicture -{ +- (void)takePicture:(void(^)(UIImage*))completionBlock { if (!_deviceAuthorized) { if ([self.delegate respondsToSelector:@selector(userDeniedCameraPermissionsForCameraController:)]) { dispatch_async(dispatch_get_main_queue(), ^{ [self.delegate userDeniedCameraPermissionsForCameraController:self]; }); } - return NO; + if (completionBlock) + completionBlock(nil); + return; } - return [self _takePhoto]; + [self _takePhoto:completionBlock]; } - (void)cancelImageProcessing @@ -247,19 +248,16 @@ - (void)cancelImageProcessing #pragma mark - Processing a Photo -- (void)processImage:(UIImage *)image withMaxDimension:(CGFloat)maxDimension -{ - [self _processImage:image withCropRect:CGRectNull maxDimension:maxDimension fromCamera:NO needsPreviewRotation:NO previewOrientation:UIDeviceOrientationUnknown]; +- (void)processImage:(UIImage *)image withMaxDimension:(CGFloat)maxDimension { + [self _processImage:image withCropRect:CGRectNull maxDimension:maxDimension fromCamera:NO needsPreviewRotation:NO previewOrientation:UIDeviceOrientationUnknown completionBlock:nil]; } -- (void)processImage:(UIImage *)image withCropRect:(CGRect)cropRect -{ - [self _processImage:image withCropRect:cropRect maxDimension:0.f fromCamera:NO needsPreviewRotation:NO previewOrientation:UIDeviceOrientationUnknown]; +- (void)processImage:(UIImage *)image withCropRect:(CGRect)cropRect { + [self _processImage:image withCropRect:cropRect maxDimension:0.f fromCamera:NO needsPreviewRotation:NO previewOrientation:UIDeviceOrientationUnknown completionBlock:nil]; } -- (void)processImage:(UIImage *)image withCropRect:(CGRect)cropRect maxDimension:(CGFloat)maxDimension -{ - [self _processImage:image withCropRect:cropRect maxDimension:maxDimension fromCamera:NO needsPreviewRotation:NO previewOrientation:UIDeviceOrientationUnknown]; +- (void)processImage:(UIImage *)image withCropRect:(CGRect)cropRect maxDimension:(CGFloat)maxDimension { + [self _processImage:image withCropRect:cropRect maxDimension:maxDimension fromCamera:NO needsPreviewRotation:NO previewOrientation:UIDeviceOrientationUnknown completionBlock:nil]; } #pragma mark - Camera State @@ -529,25 +527,25 @@ - (void)_teardownCaptureSession #pragma mark - Capturing a Photo -- (BOOL)_takePhoto -{ - if (self.isCapturingImage) { - return NO; - } - - if (!_session.isRunning) { - return NO; +- (void)_takePhoto:(void(^)(UIImage*))completionBlock { + if (self.isCapturingImage || !_session.isRunning) { + if (completionBlock) + completionBlock(nil); + return; } - if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive) { self.isCapturingImage = NO; NSLog(@"FastttCamera: must be UIApplicationStateActive to take photo"); - return NO; + if (completionBlock) + completionBlock(nil); + return; } AVCaptureConnection *videoConnection = [self _currentCaptureConnection]; if (!videoConnection.isActive || !videoConnection.isEnabled) { - return NO; + if (completionBlock) + completionBlock(nil); + return; } BOOL stillImageOutputConnected = NO; @@ -557,7 +555,9 @@ - (BOOL)_takePhoto } } if (!stillImageOutputConnected) { - return NO; + if (completionBlock) + completionBlock(nil); + return; } self.isCapturingImage = YES; @@ -576,7 +576,7 @@ - (BOOL)_takePhoto [self _insertPreviewLayer]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ UIImage *fakeImage = [UIImage fastttFakeTestImage]; - [self _processCameraPhoto:fakeImage needsPreviewRotation:needsPreviewRotation previewOrientation:UIDeviceOrientationPortrait]; + [self _processCameraPhoto:fakeImage needsPreviewRotation:needsPreviewRotation previewOrientation:UIDeviceOrientationPortrait completionBlock:completionBlock]; }); #else UIDeviceOrientation previewOrientation = [self _currentPreviewDeviceOrientation]; @@ -584,44 +584,42 @@ - (BOOL)_takePhoto [_stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) { - if (!imageDataSampleBuffer) { + if (!imageDataSampleBuffer || !self.isCapturingImage) { self.isCapturingImage = NO; - return; - } - - if (!self.isCapturingImage) { + if (completionBlock) + completionBlock(nil); return; } NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer]; - if ([self.delegate respondsToSelector:@selector(cameraController:didFinishCapturingImageData:)]) { [self.delegate cameraController:self didFinishCapturingImageData:imageData]; } UIImage *image = [UIImage imageWithData:imageData]; - [self _processCameraPhoto:image needsPreviewRotation:needsPreviewRotation previewOrientation:previewOrientation]; + [self _processCameraPhoto:image needsPreviewRotation:needsPreviewRotation previewOrientation:previewOrientation completionBlock:completionBlock]; }]; #endif - return YES; } #pragma mark - Processing a Photo -- (void)_processCameraPhoto:(UIImage *)image needsPreviewRotation:(BOOL)needsPreviewRotation previewOrientation:(UIDeviceOrientation)previewOrientation -{ +- (void)_processCameraPhoto:(UIImage *)image needsPreviewRotation:(BOOL)needsPreviewRotation previewOrientation:(UIDeviceOrientation)previewOrientation completionBlock:(void(^)(UIImage*))completionBlock { + CGRect cropRect = CGRectNull; if (self.cropsImageToVisibleAspectRatio) { cropRect = [image fastttCropRectFromPreviewLayer:_previewLayer]; } - [self _processImage:image withCropRect:cropRect maxDimension:self.maxScaledDimension fromCamera:YES needsPreviewRotation:(needsPreviewRotation || !self.interfaceRotatesWithOrientation) previewOrientation:previewOrientation]; + [self _processImage:image withCropRect:cropRect maxDimension:self.maxScaledDimension fromCamera:YES needsPreviewRotation:(needsPreviewRotation || !self.interfaceRotatesWithOrientation) previewOrientation:previewOrientation completionBlock:completionBlock]; } -- (void)_processImage:(UIImage *)image withCropRect:(CGRect)cropRect maxDimension:(CGFloat)maxDimension fromCamera:(BOOL)fromCamera needsPreviewRotation:(BOOL)needsPreviewRotation previewOrientation:(UIDeviceOrientation)previewOrientation +- (void)_processImage:(UIImage *)image withCropRect:(CGRect)cropRect maxDimension:(CGFloat)maxDimension fromCamera:(BOOL)fromCamera needsPreviewRotation:(BOOL)needsPreviewRotation previewOrientation:(UIDeviceOrientation)previewOrientation completionBlock:(void(^)(UIImage*))completionBlock { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ if (fromCamera && !self.isCapturingImage) { + if (completionBlock) + completionBlock(nil); return; } @@ -633,6 +631,8 @@ - (void)_processImage:(UIImage *)image withCropRect:(CGRect)cropRect maxDimensio withPreviewOrientation:previewOrientation withCallback:^(FastttCapturedImage *capturedImage){ if (fromCamera && !self.isCapturingImage) { + if (completionBlock) + completionBlock(nil); return; } if ([self.delegate respondsToSelector:@selector(cameraController:didFinishCapturingImage:)]) { @@ -644,6 +644,8 @@ - (void)_processImage:(UIImage *)image withCropRect:(CGRect)cropRect maxDimensio void (^scaleCallback)(FastttCapturedImage *capturedImage) = ^(FastttCapturedImage *capturedImage) { if (fromCamera && !self.isCapturingImage) { + if (completionBlock) + completionBlock(nil); return; } if ([self.delegate respondsToSelector:@selector(cameraController:didFinishScalingCapturedImage:)]) { @@ -654,6 +656,8 @@ - (void)_processImage:(UIImage *)image withCropRect:(CGRect)cropRect maxDimensio }; if (fromCamera && !self.isCapturingImage) { + if (completionBlock) + completionBlock(nil); return; } @@ -666,19 +670,25 @@ - (void)_processImage:(UIImage *)image withCropRect:(CGRect)cropRect maxDimensio } if (fromCamera && !self.isCapturingImage) { + if (completionBlock) + completionBlock(nil); return; } if (self.normalizesImageOrientations) { [capturedImage normalizeWithCallback:^(FastttCapturedImage *capturedImage){ if (fromCamera && !self.isCapturingImage) { + if (completionBlock) + completionBlock(nil); return; } - if ([self.delegate respondsToSelector:@selector(cameraController:didFinishNormalizingCapturedImage:)]) { - dispatch_async(dispatch_get_main_queue(), ^{ + dispatch_async(dispatch_get_main_queue(), ^{ + if ([self.delegate respondsToSelector:@selector(cameraController:didFinishNormalizingCapturedImage:)]) { [self.delegate cameraController:self didFinishNormalizingCapturedImage:capturedImage]; - }); - } + } + if (completionBlock) + completionBlock(capturedImage.fullImage); + }); }]; } @@ -913,13 +923,6 @@ - (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleB // Release the Quartz image CGImageRelease(quartzImage); - BOOL needsPreviewRotation = ![self.deviceOrientation deviceOrientationMatchesInterfaceOrientation] || !self.interfaceRotatesWithOrientation; - UIDeviceOrientation previewOrientation = [self _currentPreviewDeviceOrientation]; - - if (needsPreviewRotation) { - capturedImage.fullImage = [capturedImage.fullImage fastttRotatedImageMatchingCameraViewWithOrientation:previewOrientation]; - } - [capturedImage normalizeWithCallback:^(FastttCapturedImage *capturedImage){ if (capturedImage) { [self.delegate cameraController:self didCaptureVideoFrame:capturedImage.fullImage]; @@ -933,6 +936,7 @@ - (void)setSendIndividualVideoFrames:(BOOL)sendIndividualVideoFrames { BOOL restartSession = !_sendIndividualVideoFrames && sendIndividualVideoFrames; _sendIndividualVideoFrames = sendIndividualVideoFrames; if (restartSession && _session) { + NSLog(@"FastttCamera: restarting session due to change in sendIndividualVideoFrames"); [self _teardownCaptureSession]; [self _setupCaptureSession]; } diff --git a/FastttCamera/FastttCameraInterface.h b/FastttCamera/FastttCameraInterface.h index 4d7b287..5feed60 100644 --- a/FastttCamera/FastttCameraInterface.h +++ b/FastttCamera/FastttCameraInterface.h @@ -224,9 +224,12 @@ - (BOOL)isReadyToCapturePhoto; /** - * Triggers the camera to take a photo. Returns false if picture was not taken. + * Triggers the camera to take a photo. + * To use completionBlock, you must set normalizesImageOrientations to YES, otherwise use delegate callbacks + * The completionBlock is optional - you can use it, or the delegate callbacks, or both + * CompletionBlock will be called (potentially immediately) with nil if there's an error */ -- (BOOL)takePicture; +- (void)takePicture:(void(^)(UIImage*))completionBlock; #pragma mark - Process a photo From cf37813a3bd879bb16cc5014e28f091ff3fc64b0 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 22 Nov 2017 14:55:57 -0500 Subject: [PATCH 23/27] Bump podspec to 0.3.16 --- FastttCamera.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FastttCamera.podspec b/FastttCamera.podspec index 4be20e2..05b3676 100644 --- a/FastttCamera.podspec +++ b/FastttCamera.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "FastttCamera" - s.version = "0.3.15" + s.version = "0.3.16" s.summary = "A fast, straightforward implementation of AVFoundation camera with customizable real-time photo filters." s.homepage = "https://github.com/IFTTT/FastttCamera" s.license = 'MIT' From f59fb2b499b57231ffd81234a84f7c4706665c87 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 23 Nov 2017 23:07:20 -0500 Subject: [PATCH 24/27] Don't restart capture session for change of sendIndividualVideoFrames --- FastttCamera.podspec | 2 +- FastttCamera/FastttCamera.m | 38 +++++++++++++++++++++++++++++++------ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/FastttCamera.podspec b/FastttCamera.podspec index 05b3676..65a48cd 100644 --- a/FastttCamera.podspec +++ b/FastttCamera.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "FastttCamera" - s.version = "0.3.16" + s.version = "0.3.17" s.summary = "A fast, straightforward implementation of AVFoundation camera with customizable real-time photo filters." s.homepage = "https://github.com/IFTTT/FastttCamera" s.license = 'MIT' diff --git a/FastttCamera/FastttCamera.m b/FastttCamera/FastttCamera.m index 7cd12b3..ff9e832 100644 --- a/FastttCamera/FastttCamera.m +++ b/FastttCamera/FastttCamera.m @@ -933,13 +933,39 @@ - (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleB // if the property for sending invidiual video frames changes, then the session needs to be rebuilt - (void)setSendIndividualVideoFrames:(BOOL)sendIndividualVideoFrames { - BOOL restartSession = !_sendIndividualVideoFrames && sendIndividualVideoFrames; - _sendIndividualVideoFrames = sendIndividualVideoFrames; - if (restartSession && _session) { - NSLog(@"FastttCamera: restarting session due to change in sendIndividualVideoFrames"); - [self _teardownCaptureSession]; - [self _setupCaptureSession]; + if (_session) { + NSLog(@"FastttCamera: updating existing session for change to sendIndividualVideoFrames"); + if (sendIndividualVideoFrames) { + // turn it on + for (AVCaptureOutput *output in _session.outputs) { + if ([output isKindOfClass:AVCaptureVideoDataOutput.class]) { + if (output == self.videoOutput) { + NSLog(@"FastttCamera: ... no-op"); + return; + } else { + NSAssert(false, @"ERROR"); + [_session removeOutput:output]; + } + } + } + self.videoOutput = [[AVCaptureVideoDataOutput alloc] init]; + self.videoOutput.videoSettings = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) }; + [self.videoOutput setSampleBufferDelegate:self queue:self.sampleBufferQueue]; + [_session addOutput:self.videoOutput]; + } else { + // turn it off + self.videoOutput = nil; + for (AVCaptureOutput *output in _session.outputs) { + if ([output isKindOfClass:AVCaptureVideoDataOutput.class]) { + [(AVCaptureVideoDataOutput*)output setSampleBufferDelegate:nil queue:nil]; + [_session removeOutput:output]; + break; + } + } + } } + + _sendIndividualVideoFrames = sendIndividualVideoFrames; } @end From 362adebc4024511c1be99246e948820637abe0fb Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 22 Dec 2017 13:01:00 -0500 Subject: [PATCH 25/27] Fixes for iOS 10 simulator crashing --- FastttCamera.podspec | 2 +- FastttCamera/FastttCamera.m | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/FastttCamera.podspec b/FastttCamera.podspec index 65a48cd..f84000b 100644 --- a/FastttCamera.podspec +++ b/FastttCamera.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "FastttCamera" - s.version = "0.3.17" + s.version = "0.3.18" s.summary = "A fast, straightforward implementation of AVFoundation camera with customizable real-time photo filters." s.homepage = "https://github.com/IFTTT/FastttCamera" s.license = 'MIT' diff --git a/FastttCamera/FastttCamera.m b/FastttCamera/FastttCamera.m index ff9e832..dba801f 100644 --- a/FastttCamera/FastttCamera.m +++ b/FastttCamera/FastttCamera.m @@ -393,6 +393,10 @@ - (void)stopRunning - (void)_insertPreviewLayer { +#if TARGET_IPHONE_SIMULATOR + return; +#endif + if (!_deviceAuthorized || !_session) { return; } @@ -430,6 +434,8 @@ - (void)_setupCaptureSession NSAssert([NSThread currentThread].isMainThread, @"ERROR must be main thread here"); _session = [AVCaptureSession new]; + +#if !TARGET_IPHONE_SIMULATOR _session.sessionPreset = AVCaptureSessionPresetPhoto; AVCaptureDevice *device = [AVCaptureDevice cameraDevice:self.cameraDevice]; @@ -448,7 +454,6 @@ - (void)_setupCaptureSession [device unlockForConfiguration]; } -#if !TARGET_IPHONE_SIMULATOR AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil]; if (!deviceInput) { _session = nil; @@ -470,7 +475,6 @@ - (void)_setupCaptureSession } [self setCameraFlashMode:_cameraFlashMode]; -#endif NSDictionary *outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG}; @@ -487,7 +491,8 @@ - (void)_setupCaptureSession [self.videoOutput setSampleBufferDelegate:self queue:self.sampleBufferQueue]; [_session addOutput:self.videoOutput]; } - +#endif + if (self.isViewLoaded && self.view.window) { [self startRunning]; [self _insertPreviewLayer]; From 47f0c769f181cd95e614621651549266b7b8589f Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 5 Jan 2018 20:10:57 -0500 Subject: [PATCH 26/27] Split mirroring into two settings, one for liveview and one for photos --- FastttCamera.podspec | 2 +- FastttCamera/FastttCamera.m | 9 +++++---- FastttCamera/FastttCameraInterface.h | 3 ++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/FastttCamera.podspec b/FastttCamera.podspec index f84000b..2dc0b51 100644 --- a/FastttCamera.podspec +++ b/FastttCamera.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "FastttCamera" - s.version = "0.3.18" + s.version = "0.3.19" s.summary = "A fast, straightforward implementation of AVFoundation camera with customizable real-time photo filters." s.homepage = "https://github.com/IFTTT/FastttCamera" s.license = 'MIT' diff --git a/FastttCamera/FastttCamera.m b/FastttCamera/FastttCamera.m index dba801f..3e8b560 100644 --- a/FastttCamera/FastttCamera.m +++ b/FastttCamera/FastttCamera.m @@ -51,7 +51,8 @@ @implementation FastttCamera cameraDevice = _cameraDevice, cameraFlashMode = _cameraFlashMode, cameraTorchMode = _cameraTorchMode, - mirrorsOutput = _mirrorsOutput; + mirrorsVideo = _mirrorsVideo, + mirrorsTakePhoto = _mirrorsTakePhoto; - (instancetype)init { if ((self = [super init])) { @@ -572,7 +573,7 @@ - (void)_takePhoto:(void(^)(UIImage*))completionBlock { } if ([videoConnection isVideoMirroringSupported]) { - [videoConnection setVideoMirrored:self.mirrorsOutput]; + [videoConnection setVideoMirrored:self.mirrorsTakePhoto]; } BOOL needsPreviewRotation = ![self.deviceOrientation deviceOrientationMatchesInterfaceOrientation]; @@ -724,14 +725,14 @@ - (void)_setPreviewVideoMirroring { AVCaptureConnection *videoConnection = [_previewLayer connection]; videoConnection.automaticallyAdjustsVideoMirroring = NO; if ([videoConnection isVideoMirroringSupported]) { - [videoConnection setVideoMirrored:self.mirrorsOutput]; + [videoConnection setVideoMirrored:self.mirrorsVideo]; } if (self.sendIndividualVideoFrames) { AVCaptureConnection* connection = self.videoOutput.connections.firstObject; connection.automaticallyAdjustsVideoMirroring = NO; if ([connection isVideoMirroringSupported]) { - [connection setVideoMirrored:self.mirrorsOutput]; + [connection setVideoMirrored:self.mirrorsVideo]; } } } diff --git a/FastttCamera/FastttCameraInterface.h b/FastttCamera/FastttCameraInterface.h index 5feed60..a35d51c 100644 --- a/FastttCamera/FastttCameraInterface.h +++ b/FastttCamera/FastttCameraInterface.h @@ -124,7 +124,8 @@ * Whether the output of the camera is mirrored or not. Earlier versions of FastttCamera automatically mirrored if the front camera * was used, here you need to set this property instead. */ -@property (nonatomic, assign) BOOL mirrorsOutput; +@property (nonatomic, assign) BOOL mirrorsVideo; +@property (nonatomic, assign) BOOL mirrorsTakePhoto; #pragma mark - Camera State From e8cc43bea92922b8a19798e0b974166471fbeca9 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Mon, 17 Sep 2018 10:32:45 -0400 Subject: [PATCH 27/27] If video is mirrored, correctly set orientation of video images, and stop normalizing them --- FastttCamera.podspec | 2 +- FastttCamera/FastttCamera.m | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/FastttCamera.podspec b/FastttCamera.podspec index 2dc0b51..b70e723 100644 --- a/FastttCamera.podspec +++ b/FastttCamera.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "FastttCamera" - s.version = "0.3.19" + s.version = "0.3.20" s.summary = "A fast, straightforward implementation of AVFoundation camera with customizable real-time photo filters." s.homepage = "https://github.com/IFTTT/FastttCamera" s.license = 'MIT' diff --git a/FastttCamera/FastttCamera.m b/FastttCamera/FastttCamera.m index 3e8b560..e6c85b0 100644 --- a/FastttCamera/FastttCamera.m +++ b/FastttCamera/FastttCamera.m @@ -923,17 +923,13 @@ - (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleB CGColorSpaceRelease(colorSpace); // Create an image object from the Quartz image - UIImage *image = [UIImage imageWithCGImage:quartzImage]; - FastttCapturedImage *capturedImage = [FastttCapturedImage fastttCapturedFullImage: image]; + NSAssert([connection isVideoOrientationSupported], @"This code assumes up-oriented images"); + UIImage *image = [UIImage imageWithCGImage:quartzImage scale:1 orientation:(self.mirrorsVideo ? UIImageOrientationUpMirrored : UIImageOrientationUp)]; // Release the Quartz image CGImageRelease(quartzImage); - [capturedImage normalizeWithCallback:^(FastttCapturedImage *capturedImage){ - if (capturedImage) { - [self.delegate cameraController:self didCaptureVideoFrame:capturedImage.fullImage]; - } - }]; + [self.delegate cameraController:self didCaptureVideoFrame:image]; } }