diff --git a/README.md b/README.md index e43a9b3..0c9a2b7 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,22 @@ Region monitoring demo using iBeacon. ![](http://f.cl.ly/items/220D2F210D1x1D0L1Q20/beacon__.gif) +Related Repository: [PulsingHalo](https://github.com/shu223/PulsingHalo) + + +###120fps Video Recording + +SLO-MO video recorder using AVFoundation. It works with 120fps on iPhone5s. + +![](http://f.cl.ly/items/360a271y1G3Q2C2a3p2d/IMG_8907_r1_c1.jpg) + +Example: + +![](http://f.cl.ly/items/1b3Q0h0k3k2m261s3R3n/samplemovie__.gif) + +

See the 120fps Slo-Mo video in Vimeo 120fps.

+ +Related Repository: [SlowMotionVideoRecorder](https://github.com/shu223/SlowMotionVideoRecorder) ###Smile Detection diff --git a/iOS7Sampler.xcodeproj/project.pbxproj b/iOS7Sampler.xcodeproj/project.pbxproj index 803aa99..f7a0b8b 100644 --- a/iOS7Sampler.xcodeproj/project.pbxproj +++ b/iOS7Sampler.xcodeproj/project.pbxproj @@ -119,6 +119,9 @@ 8AC907551850A66000A74F2E /* PulsingHaloLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8AC907541850A66000A74F2E /* PulsingHaloLayer.m */; }; 8AC907571850AB8000A74F2E /* CoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8AC907561850AB8000A74F2E /* CoreBluetooth.framework */; }; 8AC907591850B2F600A74F2E /* IPhone_5s@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8AC907581850B2F600A74F2E /* IPhone_5s@2x.png */; }; + 8ACDEF3E1878F61D00BC94F6 /* AVCaptureManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 8ACDEF3D1878F61D00BC94F6 /* AVCaptureManager.m */; }; + 8ACDEF421878F68D00BC94F6 /* SloMoVideoRecordViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8ACDEF401878F68D00BC94F6 /* SloMoVideoRecordViewController.m */; }; + 8ACDEF431878F68D00BC94F6 /* SloMoVideoRecordViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8ACDEF411878F68D00BC94F6 /* SloMoVideoRecordViewController.xib */; }; 8AEF0F9417FBCCF600D2B9BE /* ReadingListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8AEF0F9217FBCCF600D2B9BE /* ReadingListViewController.m */; }; 8AEF0F9517FBCCF600D2B9BE /* ReadingListViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8AEF0F9317FBCCF600D2B9BE /* ReadingListViewController.xib */; }; 8AEF0F9717FBCDDB00D2B9BE /* SafariServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8AEF0F9617FBCDDB00D2B9BE /* SafariServices.framework */; }; @@ -282,6 +285,11 @@ 8AC907541850A66000A74F2E /* PulsingHaloLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PulsingHaloLayer.m; sourceTree = ""; }; 8AC907561850AB8000A74F2E /* CoreBluetooth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreBluetooth.framework; path = System/Library/Frameworks/CoreBluetooth.framework; sourceTree = SDKROOT; }; 8AC907581850B2F600A74F2E /* IPhone_5s@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "IPhone_5s@2x.png"; sourceTree = ""; }; + 8ACDEF3C1878F61D00BC94F6 /* AVCaptureManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AVCaptureManager.h; sourceTree = ""; }; + 8ACDEF3D1878F61D00BC94F6 /* AVCaptureManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AVCaptureManager.m; sourceTree = ""; }; + 8ACDEF3F1878F68D00BC94F6 /* SloMoVideoRecordViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SloMoVideoRecordViewController.h; sourceTree = ""; }; + 8ACDEF401878F68D00BC94F6 /* SloMoVideoRecordViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SloMoVideoRecordViewController.m; sourceTree = ""; }; + 8ACDEF411878F68D00BC94F6 /* SloMoVideoRecordViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SloMoVideoRecordViewController.xib; sourceTree = ""; }; 8AEF0F9117FBCCF600D2B9BE /* ReadingListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReadingListViewController.h; sourceTree = ""; }; 8AEF0F9217FBCCF600D2B9BE /* ReadingListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReadingListViewController.m; sourceTree = ""; }; 8AEF0F9317FBCCF600D2B9BE /* ReadingListViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ReadingListViewController.xib; sourceTree = ""; }; @@ -490,6 +498,9 @@ 8AC9074D1850931C00A74F2E /* BeaconViewController.h */, 8AC9074E1850931C00A74F2E /* BeaconViewController.m */, 8AC9074F1850931C00A74F2E /* BeaconViewController.xib */, + 8ACDEF3F1878F68D00BC94F6 /* SloMoVideoRecordViewController.h */, + 8ACDEF401878F68D00BC94F6 /* SloMoVideoRecordViewController.m */, + 8ACDEF411878F68D00BC94F6 /* SloMoVideoRecordViewController.xib */, ); path = SampleViewControllers; sourceTree = ""; @@ -566,6 +577,7 @@ 8A7BF3B117ED8B43004EF216 /* vendor */ = { isa = PBXGroup; children = ( + 8ACDEF3B1878F61D00BC94F6 /* AVCaptureManager */, 8AC907521850A66000A74F2E /* PulsingHalo */, 8A43D5B917F12307005E8BE8 /* ZBCustomTransitions */, 8A43D5B317F11E72005E8BE8 /* SVProgressHUD */, @@ -598,6 +610,15 @@ path = PulsingHalo; sourceTree = ""; }; + 8ACDEF3B1878F61D00BC94F6 /* AVCaptureManager */ = { + isa = PBXGroup; + children = ( + 8ACDEF3C1878F61D00BC94F6 /* AVCaptureManager.h */, + 8ACDEF3D1878F61D00BC94F6 /* AVCaptureManager.m */, + ); + path = AVCaptureManager; + sourceTree = ""; + }; 8AEFF739181007D5004182BD /* plists */ = { isa = PBXGroup; children = ( @@ -697,6 +718,7 @@ 8A43D5B717F11E72005E8BE8 /* SVProgressHUD.bundle in Resources */, 8A7BF35E17ED5BB1004EF216 /* m1@2x.png in Resources */, 8A7BF35F17ED5BB1004EF216 /* m2@2x.png in Resources */, + 8ACDEF431878F68D00BC94F6 /* SloMoVideoRecordViewController.xib in Resources */, 8A7BF37517ED5BB1004EF216 /* m25@2x.png in Resources */, 8A7BF3A517ED7F5D004EF216 /* stage_4@2x.png in Resources */, 8A7BF39517ED7EF7004EF216 /* MotionEffectViewController.xib in Resources */, @@ -786,6 +808,8 @@ 8A7BF3BB17ED8B43004EF216 /* HUTransitionAnimator.m in Sources */, 8A6DBC7117F19718004311FE /* MapDirectionsViewController.m in Sources */, 8A6DBC8417F2CDC0004311FE /* ImageFiltersViewController.m in Sources */, + 8ACDEF3E1878F61D00BC94F6 /* AVCaptureManager.m in Sources */, + 8ACDEF421878F68D00BC94F6 /* SloMoVideoRecordViewController.m in Sources */, 8A7BF38817ED6882004EF216 /* ActivityTypesViewController.m in Sources */, 8A7BF3AF17ED8A9E004EF216 /* CustomTransitionViewController.m in Sources */, 8A7BF1D717ED384C004EF216 /* MasterViewController.m in Sources */, diff --git a/iOS7Sampler/Images.xcassets/ShutterButtonStart.imageset/Contents.json b/iOS7Sampler/Images.xcassets/ShutterButtonStart.imageset/Contents.json new file mode 100644 index 0000000..75676f7 --- /dev/null +++ b/iOS7Sampler/Images.xcassets/ShutterButtonStart.imageset/Contents.json @@ -0,0 +1,17 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "ShutterButton1@2x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/iOS7Sampler/Images.xcassets/ShutterButtonStart.imageset/ShutterButton1@2x.png b/iOS7Sampler/Images.xcassets/ShutterButtonStart.imageset/ShutterButton1@2x.png new file mode 100644 index 0000000..bb223b0 Binary files /dev/null and b/iOS7Sampler/Images.xcassets/ShutterButtonStart.imageset/ShutterButton1@2x.png differ diff --git a/iOS7Sampler/Images.xcassets/ShutterButtonStop.imageset/Contents.json b/iOS7Sampler/Images.xcassets/ShutterButtonStop.imageset/Contents.json new file mode 100644 index 0000000..b66fb24 --- /dev/null +++ b/iOS7Sampler/Images.xcassets/ShutterButtonStop.imageset/Contents.json @@ -0,0 +1,17 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "ShutterButtonStop@2x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/iOS7Sampler/Images.xcassets/ShutterButtonStop.imageset/ShutterButtonStop@2x.png b/iOS7Sampler/Images.xcassets/ShutterButtonStop.imageset/ShutterButtonStop@2x.png new file mode 100644 index 0000000..e9a190c Binary files /dev/null and b/iOS7Sampler/Images.xcassets/ShutterButtonStop.imageset/ShutterButtonStop@2x.png differ diff --git a/iOS7Sampler/Images.xcassets/outer1.imageset/Contents.json b/iOS7Sampler/Images.xcassets/outer1.imageset/Contents.json new file mode 100644 index 0000000..8ad6212 --- /dev/null +++ b/iOS7Sampler/Images.xcassets/outer1.imageset/Contents.json @@ -0,0 +1,17 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "outer1@2x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/iOS7Sampler/Images.xcassets/outer1.imageset/outer1@2x.png b/iOS7Sampler/Images.xcassets/outer1.imageset/outer1@2x.png new file mode 100644 index 0000000..aa62bcf Binary files /dev/null and b/iOS7Sampler/Images.xcassets/outer1.imageset/outer1@2x.png differ diff --git a/iOS7Sampler/Images.xcassets/outer2.imageset/Contents.json b/iOS7Sampler/Images.xcassets/outer2.imageset/Contents.json new file mode 100644 index 0000000..94582e5 --- /dev/null +++ b/iOS7Sampler/Images.xcassets/outer2.imageset/Contents.json @@ -0,0 +1,17 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "outer2@2x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/iOS7Sampler/Images.xcassets/outer2.imageset/outer2@2x.png b/iOS7Sampler/Images.xcassets/outer2.imageset/outer2@2x.png new file mode 100644 index 0000000..d8d7954 Binary files /dev/null and b/iOS7Sampler/Images.xcassets/outer2.imageset/outer2@2x.png differ diff --git a/iOS7Sampler/MasterViewController.m b/iOS7Sampler/MasterViewController.m index b24e8b0..7d00e0e 100644 --- a/iOS7Sampler/MasterViewController.m +++ b/iOS7Sampler/MasterViewController.m @@ -61,6 +61,12 @@ - (void)viewDidLoad kItemKeyClassPrefix: @"Beacon", }, + // 120fps Video Recording + @{kItemKeyTitle: @"120 fps SLO-MO video recording", + kItemKeyDescription: @"SLO-MO video recorder using AVFoundation. It works with 120fps on iPhone5s.", + kItemKeyClassPrefix: @"SloMoVideoRecord", + }, + // Smile Detection @{kItemKeyTitle: @"Smile Detection", kItemKeyDescription: @"Smile Detection using CIDetectorSmile and new properties of CIFeature such as \"bounds\".", @@ -114,7 +120,7 @@ - (void)viewDidLoad kItemKeyDescription: @"Counting steps and monitoring the activity using CMStepCounter and CMMotionActivityManager. It works only on iPhone5s (M7 chip).", kItemKeyClassPrefix: @"ActivityTracking", }, - + // Static Map Snapshots @{kItemKeyTitle: @"Static Map Snapshots", kItemKeyDescription: @"Creating a snapshot with MKMapSnapshotOptions, MKMapSnapshotter.", diff --git a/iOS7Sampler/SampleViewControllers/SloMoVideoRecordViewController.h b/iOS7Sampler/SampleViewControllers/SloMoVideoRecordViewController.h new file mode 100644 index 0000000..f273917 --- /dev/null +++ b/iOS7Sampler/SampleViewControllers/SloMoVideoRecordViewController.h @@ -0,0 +1,13 @@ +// +// SloMoVideoRecordViewController.h +// https://github.com/shu223/SlowMotionVideoRecorder +// +// Created by shuichi on 1/5/14. +// Copyright (c) 2014 Shuichi Tsutsumi. All rights reserved. +// + +#import + +@interface SloMoVideoRecordViewController : UIViewController + +@end diff --git a/iOS7Sampler/SampleViewControllers/SloMoVideoRecordViewController.m b/iOS7Sampler/SampleViewControllers/SloMoVideoRecordViewController.m new file mode 100644 index 0000000..483d730 --- /dev/null +++ b/iOS7Sampler/SampleViewControllers/SloMoVideoRecordViewController.m @@ -0,0 +1,248 @@ +// +// SloMoVideoRecordViewController.m +// https://github.com/shu223/SlowMotionVideoRecorder +// +// Created by shuichi on 1/5/14. +// Copyright (c) 2014 Shuichi Tsutsumi. All rights reserved. +// + +#import "SloMoVideoRecordViewController.h" +#import "SVProgressHUD.h" +#import "AVCaptureManager.h" +#import + + +@interface SloMoVideoRecordViewController () + +{ + NSTimeInterval startTime; + BOOL isNeededToSave; +} +@property (nonatomic, strong) AVCaptureManager *captureManager; +@property (nonatomic, assign) NSTimer *timer; +@property (nonatomic, strong) UIImage *recStartImage; +@property (nonatomic, strong) UIImage *recStopImage; +@property (nonatomic, strong) UIImage *outerImage1; +@property (nonatomic, strong) UIImage *outerImage2; + +@property (nonatomic, weak) IBOutlet UILabel *statusLabel; +@property (nonatomic, weak) IBOutlet UISegmentedControl *fpsControl; +@property (nonatomic, weak) IBOutlet UIButton *recBtn; +@property (nonatomic, weak) IBOutlet UIImageView *outerImageView; +@end + + +@implementation SloMoVideoRecordViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.captureManager = [[AVCaptureManager alloc] initWithPreviewView:self.view]; + self.captureManager.delegate = self; + + UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self + action:@selector(handleDoubleTap:)]; + tapGesture.numberOfTapsRequired = 2; + [self.view addGestureRecognizer:tapGesture]; + + + // Setup images for the Shutter Button + UIImage *image; + image = [UIImage imageNamed:@"ShutterButtonStart"]; + self.recStartImage = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + [self.recBtn setImage:self.recStartImage + forState:UIControlStateNormal]; + + image = [UIImage imageNamed:@"ShutterButtonStop"]; + self.recStopImage = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + + [self.recBtn setTintColor:[UIColor colorWithRed:245./255. + green:51./255. + blue:51./255. + alpha:1.0]]; + self.outerImage1 = [UIImage imageNamed:@"outer1"]; + self.outerImage2 = [UIImage imageNamed:@"outer2"]; + self.outerImageView.image = self.outerImage1; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + + +// ============================================================================= +#pragma mark - Gesture Handler + +- (void)handleDoubleTap:(UITapGestureRecognizer *)sender { + + [self.captureManager toggleContentsGravity]; +} + + +// ============================================================================= +#pragma mark - Private + + +- (void)saveRecordedFile:(NSURL *)recordedFile { + + [SVProgressHUD showWithStatus:@"Saving..." + maskType:SVProgressHUDMaskTypeGradient]; + + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_async(queue, ^{ + + ALAssetsLibrary *assetLibrary = [[ALAssetsLibrary alloc] init]; + [assetLibrary writeVideoAtPathToSavedPhotosAlbum:recordedFile + completionBlock: + ^(NSURL *assetURL, NSError *error) { + + dispatch_async(dispatch_get_main_queue(), ^{ + + [SVProgressHUD dismiss]; + + NSString *title; + NSString *message; + + if (error != nil) { + + title = @"Failed to save video"; + message = [error localizedDescription]; + } + else { + title = @"Saved!"; + message = nil; + } + + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title + message:message + delegate:nil + cancelButtonTitle:@"OK" + otherButtonTitles:nil]; + [alert show]; + }); + }]; + }); +} + + + +// ============================================================================= +#pragma mark - Timer Handler + +- (void)timerHandler:(NSTimer *)timer { + + NSTimeInterval current = [[NSDate date] timeIntervalSince1970]; + NSTimeInterval recorded = current - startTime; + + self.statusLabel.text = [NSString stringWithFormat:@"%.2f", recorded]; +} + + + +// ============================================================================= +#pragma mark - AVCaptureManagerDeleagte + +- (void)didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL error:(NSError *)error { + + if (error) { + NSLog(@"error:%@", error); + return; + } + + if (!isNeededToSave) { + return; + } + + [self saveRecordedFile:outputFileURL]; +} + + +// ============================================================================= +#pragma mark - IBAction + +- (IBAction)recButtonTapped:(id)sender { + + // REC START + if (!self.captureManager.isRecording) { + + // change UI + [self.recBtn setImage:self.recStopImage + forState:UIControlStateNormal]; + self.fpsControl.enabled = NO; + + // timer start + startTime = [[NSDate date] timeIntervalSince1970]; + self.timer = [NSTimer scheduledTimerWithTimeInterval:0.01 + target:self + selector:@selector(timerHandler:) + userInfo:nil + repeats:YES]; + + [self.captureManager startRecording]; + } + // REC STOP + else { + + isNeededToSave = YES; + [self.captureManager stopRecording]; + + [self.timer invalidate]; + self.timer = nil; + + // change UI + [self.recBtn setImage:self.recStartImage + forState:UIControlStateNormal]; + self.fpsControl.enabled = YES; + } +} + +- (IBAction)fpsChanged:(UISegmentedControl *)sender { + + // Switch FPS + + CGFloat desiredFps = 0.0;; + switch (self.fpsControl.selectedSegmentIndex) { + case 0: + default: + { + break; + } + case 1: + desiredFps = 60.0; + break; + case 2: + desiredFps = 120.0; + break; + } + + + [SVProgressHUD showWithStatus:@"Switching..." + maskType:SVProgressHUDMaskTypeGradient]; + + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_async(queue, ^{ + + if (desiredFps > 0.0) { + [self.captureManager switchFormatWithDesiredFPS:desiredFps]; + } + else { + [self.captureManager resetFormat]; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + + if (desiredFps > 30.0) { + self.outerImageView.image = self.outerImage2; + } + else { + self.outerImageView.image = self.outerImage1; + } + [SVProgressHUD dismiss]; + }); + }); +} + +@end diff --git a/iOS7Sampler/SampleViewControllers/SloMoVideoRecordViewController.xib b/iOS7Sampler/SampleViewControllers/SloMoVideoRecordViewController.xib new file mode 100644 index 0000000..2abca39 --- /dev/null +++ b/iOS7Sampler/SampleViewControllers/SloMoVideoRecordViewController.xib @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/AVCaptureManager/AVCaptureManager.h b/vendor/AVCaptureManager/AVCaptureManager.h new file mode 100644 index 0000000..4cc59ea --- /dev/null +++ b/vendor/AVCaptureManager/AVCaptureManager.h @@ -0,0 +1,31 @@ +// +// AVCaptureManager.h +// SlowMotionVideoRecorder +// https://github.com/shu223/SlowMotionVideoRecorder +// +// Created by shuichi on 12/17/13. +// Copyright (c) 2013 Shuichi Tsutsumi. All rights reserved. +// + +#import + + +@protocol AVCaptureManagerDelegate +- (void)didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL + error:(NSError *)error; +@end + + +@interface AVCaptureManager : NSObject + +@property (nonatomic, assign) id delegate; +@property (nonatomic, readonly) BOOL isRecording; + +- (id)initWithPreviewView:(UIView *)previewView; +- (void)toggleContentsGravity; +- (void)resetFormat; +- (void)switchFormatWithDesiredFPS:(CGFloat)desiredFPS; +- (void)startRecording; +- (void)stopRecording; + +@end diff --git a/vendor/AVCaptureManager/AVCaptureManager.m b/vendor/AVCaptureManager/AVCaptureManager.m new file mode 100644 index 0000000..48f484e --- /dev/null +++ b/vendor/AVCaptureManager/AVCaptureManager.m @@ -0,0 +1,202 @@ +// +// AVCaptureManager.m +// SlowMotionVideoRecorder +// https://github.com/shu223/SlowMotionVideoRecorder +// +// Created by shuichi on 12/17/13. +// Copyright (c) 2013 Shuichi Tsutsumi. All rights reserved. +// + +#import "AVCaptureManager.h" +#import + + +@interface AVCaptureManager () + +{ + CMTime defaultVideoMaxFrameDuration; +} +@property (nonatomic, strong) AVCaptureSession *captureSession; +@property (nonatomic, strong) AVCaptureMovieFileOutput *fileOutput; +@property (nonatomic, strong) AVCaptureDeviceFormat *defaultFormat; +@property (nonatomic, strong) AVCaptureVideoPreviewLayer *previewLayer; +@end + + +@implementation AVCaptureManager + +- (id)initWithPreviewView:(UIView *)previewView { + + self = [super init]; + + if (self) { + + NSError *error; + + self.captureSession = [[AVCaptureSession alloc] init]; + self.captureSession.sessionPreset = AVCaptureSessionPresetInputPriority; + + AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + AVCaptureDeviceInput *videoIn = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error]; + + if (error) { + NSLog(@"Video input creation failed"); + return nil; + } + + if (![self.captureSession canAddInput:videoIn]) { + NSLog(@"Video input add-to-session failed"); + return nil; + } + [self.captureSession addInput:videoIn]; + + + // save the default format + self.defaultFormat = videoDevice.activeFormat; + defaultVideoMaxFrameDuration = videoDevice.activeVideoMaxFrameDuration; + + + AVCaptureDevice *audioDevice= [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; + AVCaptureDeviceInput *audioIn = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error]; + [self.captureSession addInput:audioIn]; + + self.fileOutput = [[AVCaptureMovieFileOutput alloc] init]; + [self.captureSession addOutput:self.fileOutput]; + + + self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.captureSession]; + self.previewLayer.frame = previewView.bounds; + self.previewLayer.contentsGravity = kCAGravityResizeAspectFill; + self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; + [previewView.layer insertSublayer:self.previewLayer atIndex:0]; + + [self.captureSession startRunning]; + } + return self; +} + + + +// ============================================================================= +#pragma mark - Public + +- (void)toggleContentsGravity { + + if ([self.previewLayer.videoGravity isEqualToString:AVLayerVideoGravityResizeAspectFill]) { + + self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspect; + } + else { + self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; + } +} + +- (void)resetFormat { + + BOOL isRunning = self.captureSession.isRunning; + + if (isRunning) { + [self.captureSession stopRunning]; + } + + AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + [videoDevice lockForConfiguration:nil]; + videoDevice.activeFormat = self.defaultFormat; + videoDevice.activeVideoMaxFrameDuration = defaultVideoMaxFrameDuration; + [videoDevice unlockForConfiguration]; + + if (isRunning) { + [self.captureSession startRunning]; + } +} + +- (void)switchFormatWithDesiredFPS:(CGFloat)desiredFPS +{ + BOOL isRunning = self.captureSession.isRunning; + + if (isRunning) [self.captureSession stopRunning]; + + AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + AVCaptureDeviceFormat *selectedFormat = nil; + int32_t maxWidth = 0; + AVFrameRateRange *frameRateRange = nil; + + for (AVCaptureDeviceFormat *format in [videoDevice formats]) { + + for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges) { + + CMFormatDescriptionRef desc = format.formatDescription; + CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions(desc); + int32_t width = dimensions.width; + + if (range.minFrameRate <= desiredFPS && desiredFPS <= range.maxFrameRate && width >= maxWidth) { + + selectedFormat = format; + frameRateRange = range; + maxWidth = width; + } + } + } + + if (selectedFormat) { + + if ([videoDevice lockForConfiguration:nil]) { + + NSLog(@"selected format:%@", selectedFormat); + videoDevice.activeFormat = selectedFormat; + videoDevice.activeVideoMinFrameDuration = CMTimeMake(1, (int32_t)desiredFPS); + videoDevice.activeVideoMaxFrameDuration = CMTimeMake(1, (int32_t)desiredFPS); + [videoDevice unlockForConfiguration]; + } + } + + if (isRunning) [self.captureSession startRunning]; +} + +- (void)startRecording { + + NSDateFormatter* formatter = [[NSDateFormatter alloc] init]; + [formatter setDateFormat:@"yyyy-MM-dd-HH-mm-ss"]; + NSString* dateTimePrefix = [formatter stringFromDate:[NSDate date]]; + + int fileNamePostfix = 0; + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = [paths objectAtIndex:0]; + NSString *filePath = nil; + do + filePath =[NSString stringWithFormat:@"/%@/%@-%i.mp4", documentsDirectory, dateTimePrefix, fileNamePostfix++]; + while ([[NSFileManager defaultManager] fileExistsAtPath:filePath]); + + NSURL *fileURL = [NSURL URLWithString:[@"file://" stringByAppendingString:filePath]]; + [self.fileOutput startRecordingToOutputFileURL:fileURL recordingDelegate:self]; +} + +- (void)stopRecording { + + [self.fileOutput stopRecording]; +} + + +// ============================================================================= +#pragma mark - AVCaptureFileOutputRecordingDelegate + +- (void) captureOutput:(AVCaptureFileOutput *)captureOutput + didStartRecordingToOutputFileAtURL:(NSURL *)fileURL + fromConnections:(NSArray *)connections +{ + _isRecording = YES; +} + +- (void) captureOutput:(AVCaptureFileOutput *)captureOutput + didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL + fromConnections:(NSArray *)connections error:(NSError *)error +{ +// [self saveRecordedFile:outputFileURL]; + _isRecording = NO; + + if ([self.delegate respondsToSelector:@selector(didFinishRecordingToOutputFileAtURL:error:)]) { + [self.delegate didFinishRecordingToOutputFileAtURL:outputFileURL error:error]; + } +} + +@end