diff --git a/FastttCamera.podspec b/FastttCamera.podspec index 1b4a8f2..b70e723 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.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.h b/FastttCamera/FastttCamera.h index 5fb47fc..e74625e 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 + +// 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 75a2213..e6c85b0 100644 --- a/FastttCamera/FastttCamera.m +++ b/FastttCamera/FastttCamera.m @@ -26,6 +26,8 @@ @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; @end @@ -48,14 +50,14 @@ @implementation FastttCamera scalesImage = _scalesImage, cameraDevice = _cameraDevice, cameraFlashMode = _cameraFlashMode, - cameraTorchMode = _cameraTorchMode; + cameraTorchMode = _cameraTorchMode, + mirrorsVideo = _mirrorsVideo, + mirrorsTakePhoto = _mirrorsTakePhoto; -- (instancetype)init -{ +- (instancetype)init { if ((self = [super init])) { - [self _setupCaptureSession]; - + _sampleBufferQueue = dispatch_queue_create("com.xaphod.fastttcamera.samplebuffer", NULL); _handlesTapFocus = YES; _showsFocusView = YES; _handlesZoom = YES; @@ -93,6 +95,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; } @@ -111,7 +124,7 @@ - (void)dealloc - (void)viewDidLoad { [super viewDidLoad]; - + [self _setupCaptureSession]; [self _insertPreviewLayer]; UIView *viewForGestures = self.view; @@ -144,6 +157,7 @@ - (void)viewWillAppear:(BOOL)animated [self _insertPreviewLayer]; [self _setPreviewVideoOrientation]; + [self _setPreviewVideoMirroring]; } - (void)viewDidDisappear:(BOOL)animated @@ -199,16 +213,31 @@ - (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; } -- (void)takePicture -{ +- (void)takePicture:(void(^)(UIImage*))completionBlock { if (!_deviceAuthorized) { + if ([self.delegate respondsToSelector:@selector(userDeniedCameraPermissionsForCameraController:)]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.delegate userDeniedCameraPermissionsForCameraController:self]; + }); + } + if (completionBlock) + completionBlock(nil); return; } - - [self _takePhoto]; + + [self _takePhoto:completionBlock]; } - (void)cancelImageProcessing @@ -220,19 +249,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 @@ -345,6 +371,15 @@ - (void)setCameraTorchMode:(FastttCameraTorchMode)cameraTorchMode - (void)startRunning { + if (!_deviceAuthorized) { + if ([self.delegate respondsToSelector:@selector(userDeniedCameraPermissionsForCameraController:)]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.delegate userDeniedCameraPermissionsForCameraController:self]; + }); + } + return; + } + if (![_session isRunning]) { [_session startRunning]; } @@ -359,7 +394,11 @@ - (void)stopRunning - (void)_insertPreviewLayer { - if (!_deviceAuthorized) { +#if TARGET_IPHONE_SIMULATOR + return; +#endif + + if (!_deviceAuthorized || !_session) { return; } @@ -393,82 +432,74 @@ - (void)_setupCaptureSession return; } + NSAssert([NSThread currentThread].isMainThread, @"ERROR must be main thread here"); + + _session = [AVCaptureSession new]; + #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]; - }); + _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) { + device.exposureMode = AVCaptureExposureModeContinuousAutoExposure; + + [device unlockForConfiguration]; + } + + AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil]; + if (!deviceInput) { + _session = nil; + return; + } + [_session addInput:deviceInput]; + + switch (device.position) { + case AVCaptureDevicePositionBack: + _cameraDevice = FastttCameraDeviceRear; + break; - 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]; - [_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; - - [_session addOutput:_stillImageOutput]; - - _deviceOrientation = [IFTTTDeviceOrientation new]; - - if (self.isViewLoaded && self.view.window) { - [self startRunning]; - [self _insertPreviewLayer]; - [self _setPreviewVideoOrientation]; - [self _resetZoom]; - } - }); - } -#if !TARGET_IPHONE_SIMULATOR - }]; + case AVCaptureDevicePositionFront: + _cameraDevice = FastttCameraDeviceFront; + break; + + default: + break; + } + + [self setCameraFlashMode:_cameraFlashMode]; + + 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]; + } #endif + + if (self.isViewLoaded && self.view.window) { + [self startRunning]; + [self _insertPreviewLayer]; + [self _setPreviewVideoOrientation]; + [self _resetZoom]; + } } - (void)_teardownCaptureSession @@ -490,6 +521,11 @@ - (void)_teardownCaptureSession [_session removeOutput:_stillImageOutput]; _stillImageOutput = nil; + if (self.videoOutput) { + [_session removeOutput:self.videoOutput]; + self.videoOutput = nil; + } + [self _removePreviewLayer]; _session = nil; @@ -497,30 +533,56 @@ - (void)_teardownCaptureSession #pragma mark - Capturing a Photo -- (void)_takePhoto -{ - if (self.isCapturingImage) { +- (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"); + if (completionBlock) + completionBlock(nil); + return; + } + + AVCaptureConnection *videoConnection = [self _currentCaptureConnection]; + if (!videoConnection.isActive || !videoConnection.isEnabled) { + if (completionBlock) + completionBlock(nil); return; } - self.isCapturingImage = YES; - BOOL needsPreviewRotation = ![self.deviceOrientation deviceOrientationMatchesInterfaceOrientation]; + BOOL stillImageOutputConnected = NO; + for (AVCaptureConnection *conn in _stillImageOutput.connections) { + if (conn == videoConnection) { + stillImageOutputConnected = YES; + } + } + if (!stillImageOutputConnected) { + if (completionBlock) + completionBlock(nil); + return; + } - AVCaptureConnection *videoConnection = [self _currentCaptureConnection]; + self.isCapturingImage = YES; if ([videoConnection isVideoOrientationSupported]) { [videoConnection setVideoOrientation:[self _currentCaptureVideoOrientationForDevice]]; } if ([videoConnection isVideoMirroringSupported]) { - [videoConnection setVideoMirrored:(_cameraDevice == FastttCameraDeviceFront)]; + [videoConnection setVideoMirrored:self.mirrorsTakePhoto]; } + + BOOL needsPreviewRotation = ![self.deviceOrientation deviceOrientationMatchesInterfaceOrientation]; #if TARGET_IPHONE_SIMULATOR [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]; @@ -528,46 +590,42 @@ - (void)_takePhoto [_stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) { - if (!imageDataSampleBuffer) { - return; - } - - if (!self.isCapturingImage) { + if (!imageDataSampleBuffer || !self.isCapturingImage) { + self.isCapturingImage = NO; + if (completionBlock) + completionBlock(nil); return; } NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer]; - if ([self.delegate respondsToSelector:@selector(cameraController:didFinishCapturingImageData:)]) { [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 completionBlock:completionBlock]; }]; #endif } #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_DEFAULT, 0), ^{ + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ if (fromCamera && !self.isCapturingImage) { + if (completionBlock) + completionBlock(nil); return; } @@ -579,6 +637,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:)]) { @@ -590,6 +650,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:)]) { @@ -600,6 +662,8 @@ - (void)_processImage:(UIImage *)image withCropRect:(CGRect)cropRect maxDimensio }; if (fromCamera && !self.isCapturingImage) { + if (completionBlock) + completionBlock(nil); return; } @@ -612,19 +676,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); + }); }]; } @@ -637,9 +707,33 @@ - (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]; + } + } +} + +- (void)_setPreviewVideoMirroring { + AVCaptureConnection *videoConnection = [_previewLayer connection]; + videoConnection.automaticallyAdjustsVideoMirroring = NO; + if ([videoConnection isVideoMirroringSupported]) { + [videoConnection setVideoMirrored:self.mirrorsVideo]; + } + + if (self.sendIndividualVideoFrames) { + AVCaptureConnection* connection = self.videoOutput.connections.firstObject; + connection.automaticallyAdjustsVideoMirroring = NO; + if ([connection isVideoMirroringSupported]) { + [connection setVideoMirrored:self.mirrorsVideo]; + } } } @@ -687,6 +781,10 @@ + (AVCaptureVideoOrientation)_videoOrientationForDeviceOrientation:(UIDeviceOrie case UIDeviceOrientationLandscapeRight: return AVCaptureVideoOrientationLandscapeLeft; + case UIDeviceOrientationFaceUp: + case UIDeviceOrientationFaceDown: + return [self.class _videoOrientationFromStatusBarOrientation]; + default: break; } @@ -694,6 +792,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 @@ -772,4 +889,85 @@ - (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 + 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); + + [self.delegate cameraController:self didCaptureVideoFrame:image]; + } +} + +// if the property for sending invidiual video frames changes, then the session needs to be rebuilt +- (void)setSendIndividualVideoFrames:(BOOL)sendIndividualVideoFrames { + 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 diff --git a/FastttCamera/FastttCameraInterface.h b/FastttCamera/FastttCameraInterface.h index 13cd05c..a35d51c 100644 --- a/FastttCamera/FastttCameraInterface.h +++ b/FastttCamera/FastttCameraInterface.h @@ -120,6 +120,13 @@ */ @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 mirrorsVideo; +@property (nonatomic, assign) BOOL mirrorsTakePhoto; + #pragma mark - Camera State /** @@ -219,8 +226,11 @@ /** * 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 */ -- (void)takePicture; +- (void)takePicture:(void(^)(UIImage*))completionBlock; #pragma mark - Process a photo @@ -291,6 +301,10 @@ @optional +// Added by Tim - called when a frame of the video (via sample buffer) is ready +// requires using the init for FasttCamera that specifies sendIndividualVideoFrames as true +- (void)cameraController:(id)cameraController didCaptureVideoFrame:(UIImage*)videoFrame; + /** * Called when the camera controller has obtained the raw data containing the image and metadata. * 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. */ diff --git a/FastttCamera/UIViewController+FastttCamera.h b/FastttCamera/UIViewController+FastttCamera.h index 6d09a05..ec42140 100644 --- a/FastttCamera/UIViewController+FastttCamera.h +++ b/FastttCamera/UIViewController+FastttCamera.h @@ -31,6 +31,14 @@ */ - (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; +- (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 * view appearance transition event calls. diff --git a/FastttCamera/UIViewController+FastttCamera.m b/FastttCamera/UIViewController+FastttCamera.m index a450682..5534179 100644 --- a/FastttCamera/UIViewController+FastttCamera.m +++ b/FastttCamera/UIViewController+FastttCamera.m @@ -27,7 +27,31 @@ - (void)fastttAddChildViewController:(UIViewController *)childViewController bel [childViewController didMoveToParentViewController:self]; [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]; + [view insertSubview:childViewController.view belowSubview:subview]; + [childViewController didMoveToParentViewController:self]; + [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];