├── VideoCaptureDemo.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── project.pbxproj ├── README.md ├── VideoCaptureDemo ├── ViewController.h ├── AppDelegate.h ├── IDCaptureSessionMovieFileOutputCoordinator.h ├── IDImagePickerCoordinator.h ├── main.m ├── IDCaptureSessionAssetWriterCoordinator.h ├── IDFileManager.h ├── IDPermissionsManager.h ├── IDCaptureSessionPipelineViewController.h ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── IDAssetWriterCoordinator.h ├── IDCaptureSessionCoordinator.h ├── IDCaptureSessionMovieFileOutputCoordinator.m ├── AppDelegate.m ├── ViewController.m ├── IDPermissionsManager.m ├── IDFileManager.m ├── IDImagePickerCoordinator.m ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── IDCaptureSessionPipelineViewController.m ├── IDCaptureSessionCoordinator.m ├── IDCaptureSessionAssetWriterCoordinator.m └── IDAssetWriterCoordinator.m ├── VideoCaptureDemoTests ├── Info.plist └── VideoCaptureDemoTests.m └── .gitignore /VideoCaptureDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VideoCaptureDemo 2 | 3 | A sample project demonstrating different video capture pipelines on iOS 4 | - UIImagePickerController 5 | - AVCaptureSession + AVMovieFileOutput 6 | - AVCaptureSession + AVAssetWriter 7 | 8 | Please read objc.io issue 23 for the accompanying article 9 | -------------------------------------------------------------------------------- /VideoCaptureDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // VideoCaptureDemo 4 | // 5 | // Created by Adriaan Stellingwerff on 31/03/2015. 6 | // Copyright (c) 2015 Infoding. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UITableViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /VideoCaptureDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // VideoCaptureDemo 4 | // 5 | // Created by Adriaan Stellingwerff on 31/03/2015. 6 | // Copyright (c) 2015 Infoding. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface AppDelegate : UIResponder 13 | 14 | @property (strong, nonatomic) UIWindow *window; 15 | 16 | 17 | @end 18 | 19 | -------------------------------------------------------------------------------- /VideoCaptureDemo/IDCaptureSessionMovieFileOutputCoordinator.h: -------------------------------------------------------------------------------- 1 | // 2 | // IDCaptureSessionMovieFileOutputCoordinator.h 3 | // VideoCaptureDemo 4 | // 5 | // Created by Adriaan Stellingwerff on 9/04/2015. 6 | // Copyright (c) 2015 Infoding. All rights reserved. 7 | // 8 | 9 | #import "IDCaptureSessionCoordinator.h" 10 | 11 | @interface IDCaptureSessionMovieFileOutputCoordinator : IDCaptureSessionCoordinator 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /VideoCaptureDemo/IDImagePickerCoordinator.h: -------------------------------------------------------------------------------- 1 | // 2 | // IDImagePickerCoordinator.h 3 | // VideoCaptureDemo 4 | // 5 | // Created by Adriaan Stellingwerff on 1/04/2015. 6 | // Copyright (c) 2015 Infoding. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface IDImagePickerCoordinator : NSObject 13 | 14 | - (UIImagePickerController *)cameraVC; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /VideoCaptureDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // VideoCaptureDemo 4 | // 5 | // Created by Adriaan Stellingwerff on 31/03/2015. 6 | // Copyright (c) 2015 Infoding. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /VideoCaptureDemo/IDCaptureSessionAssetWriterCoordinator.h: -------------------------------------------------------------------------------- 1 | // 2 | // IDCaptureSessionAssetWriterCoordinator.h 3 | // VideoCaptureDemo 4 | // 5 | // Created by Adriaan Stellingwerff on 9/04/2015. 6 | // Copyright (c) 2015 Infoding. All rights reserved. 7 | // 8 | 9 | #import "IDCaptureSessionCoordinator.h" 10 | 11 | @protocol IDCaptureSessionAssetWriterCoordinatorDelegate; 12 | 13 | @interface IDCaptureSessionAssetWriterCoordinator : IDCaptureSessionCoordinator 14 | 15 | @end -------------------------------------------------------------------------------- /VideoCaptureDemo/IDFileManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // IDFileManager.h 3 | // VideoCaptureDemo 4 | // 5 | // Created by Adriaan Stellingwerff on 9/04/2015. 6 | // Copyright (c) 2015 Infoding. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface IDFileManager : NSObject 12 | 13 | - (NSURL *) tempFileURL; 14 | - (void) removeFile:(NSURL *)outputFileURL; 15 | - (void) copyFileToDocuments:(NSURL *)fileURL; 16 | - (void) copyFileToCameraRoll:(NSURL *)fileURL; 17 | @end 18 | -------------------------------------------------------------------------------- /VideoCaptureDemo/IDPermissionsManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // IDCameraPermissionsManager.h 3 | // VideoCameraDemo 4 | // 5 | // Created by Adriaan Stellingwerff on 10/03/2014. 6 | // Copyright (c) 2014 Infoding. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface IDPermissionsManager : NSObject 12 | 13 | - (void)checkMicrophonePermissionsWithBlock:(void(^)(BOOL granted))block; 14 | - (void)checkCameraAuthorizationStatusWithBlock:(void(^)(BOOL granted))block; 15 | 16 | @end -------------------------------------------------------------------------------- /VideoCaptureDemo/IDCaptureSessionPipelineViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // IDCaptureSessionPipelineViewController.h 3 | // VideoCaptureDemo 4 | // 5 | // Created by Adriaan Stellingwerff on 9/04/2015. 6 | // Copyright (c) 2015 Infoding. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(NSInteger, PipelineMode) 12 | { 13 | PipelineModeMovieFileOutput = 0, 14 | PipelineModeAssetWriter, 15 | }; // internal state machine 16 | 17 | @interface IDCaptureSessionPipelineViewController : UIViewController 18 | 19 | - (void)setupWithPipelineMode:(PipelineMode)mode; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /VideoCaptureDemo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /VideoCaptureDemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.infoding.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /VideoCaptureDemoTests/VideoCaptureDemoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // VideoCaptureDemoTests.m 3 | // VideoCaptureDemoTests 4 | // 5 | // Created by Adriaan Stellingwerff on 31/03/2015. 6 | // Copyright (c) 2015 Infoding. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface VideoCaptureDemoTests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation VideoCaptureDemoTests 17 | 18 | - (void)setUp { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testExample { 29 | // This is an example of a functional test case. 30 | XCTAssert(YES, @"Pass"); 31 | } 32 | 33 | - (void)testPerformanceExample { 34 | // This is an example of a performance test case. 35 | [self measureBlock:^{ 36 | // Put the code you want to measure the time of here. 37 | }]; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /VideoCaptureDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.infoding.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarHidden 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /VideoCaptureDemo/IDAssetWriterCoordinator.h: -------------------------------------------------------------------------------- 1 | // 2 | // IDAssetWriterCoordinator.h 3 | // VideoCaptureDemo 4 | // 5 | // Created by Adriaan Stellingwerff on 9/04/2015. 6 | // Copyright (c) 2015 Infoding. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | @protocol IDAssetWriterCoordinatorDelegate; 14 | 15 | @interface IDAssetWriterCoordinator : NSObject 16 | 17 | - (instancetype)initWithURL:(NSURL *)URL; 18 | - (void)addVideoTrackWithSourceFormatDescription:(CMFormatDescriptionRef)formatDescription settings:(NSDictionary *)videoSettings; 19 | - (void)addAudioTrackWithSourceFormatDescription:(CMFormatDescriptionRef)formatDescription settings:(NSDictionary *)audioSettings; 20 | - (void)setDelegate:(id)delegate callbackQueue:(dispatch_queue_t)delegateCallbackQueue; 21 | 22 | - (void)prepareToRecord; 23 | - (void)appendVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer; 24 | - (void)appendAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer; 25 | - (void)finishRecording; 26 | 27 | @end 28 | 29 | 30 | @protocol IDAssetWriterCoordinatorDelegate 31 | 32 | - (void)writerCoordinatorDidFinishPreparing:(IDAssetWriterCoordinator *)coordinator; 33 | - (void)writerCoordinator:(IDAssetWriterCoordinator *)coordinator didFailWithError:(NSError *)error; 34 | - (void)writerCoordinatorDidFinishRecording:(IDAssetWriterCoordinator *)coordinator; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /VideoCaptureDemo/IDCaptureSessionCoordinator.h: -------------------------------------------------------------------------------- 1 | // 2 | // IDCaptureSessionCoordinator.h 3 | // VideoCaptureDemo 4 | // 5 | // Created by Adriaan Stellingwerff on 1/04/2015. 6 | // Copyright (c) 2015 Infoding. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @protocol IDCaptureSessionCoordinatorDelegate; 13 | 14 | @interface IDCaptureSessionCoordinator : NSObject 15 | 16 | @property (nonatomic, strong) AVCaptureSession *captureSession; 17 | @property (nonatomic, strong) AVCaptureDevice *cameraDevice; 18 | @property (nonatomic, strong) dispatch_queue_t delegateCallbackQueue; 19 | @property (nonatomic, weak) id delegate; 20 | 21 | - (void)setDelegate:(id)delegate callbackQueue:(dispatch_queue_t)delegateCallbackQueue; 22 | 23 | - (BOOL)addInput:(AVCaptureDeviceInput *)input toCaptureSession:(AVCaptureSession *)captureSession; 24 | - (BOOL)addOutput:(AVCaptureOutput *)output toCaptureSession:(AVCaptureSession *)captureSession; 25 | 26 | - (void)startRunning; 27 | - (void)stopRunning; 28 | 29 | - (void)startRecording; 30 | - (void)stopRecording; 31 | 32 | - (AVCaptureVideoPreviewLayer *)previewLayer; 33 | 34 | @end 35 | 36 | @protocol IDCaptureSessionCoordinatorDelegate 37 | 38 | @required 39 | 40 | - (void)coordinatorDidBeginRecording:(IDCaptureSessionCoordinator *)coordinator; 41 | - (void)coordinator:(IDCaptureSessionCoordinator *)coordinator didFinishRecordingToOutputFileURL:(NSURL *)outputFileURL error:(NSError *)error; 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | 3 | ### Xcode ### 4 | build/ 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | xcuserdata 14 | *.xccheckout 15 | *.moved-aside 16 | DerivedData 17 | *.xcuserstate 18 | 19 | 20 | ### AppCode ### 21 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 22 | 23 | *.iml 24 | 25 | ## Directory-based project format: 26 | .idea/ 27 | # if you remove the above rule, at least ignore the following: 28 | 29 | # User-specific stuff: 30 | # .idea/workspace.xml 31 | # .idea/tasks.xml 32 | # .idea/dictionaries 33 | 34 | # Sensitive or high-churn files: 35 | # .idea/dataSources.ids 36 | # .idea/dataSources.xml 37 | # .idea/sqlDataSources.xml 38 | # .idea/dynamic.xml 39 | # .idea/uiDesigner.xml 40 | 41 | # Gradle: 42 | # .idea/gradle.xml 43 | # .idea/libraries 44 | 45 | # Mongo Explorer plugin: 46 | # .idea/mongoSettings.xml 47 | 48 | ## File-based project format: 49 | *.ipr 50 | *.iws 51 | 52 | ## Plugin-specific files: 53 | 54 | # IntelliJ 55 | out/ 56 | 57 | # mpeltonen/sbt-idea plugin 58 | .idea_modules/ 59 | 60 | # JIRA plugin 61 | atlassian-ide-plugin.xml 62 | 63 | # Crashlytics plugin (for Android Studio and IntelliJ) 64 | com_crashlytics_export_strings.xml 65 | crashlytics.properties 66 | crashlytics-build.properties 67 | 68 | 69 | ### OSX ### 70 | .DS_Store 71 | .AppleDouble 72 | .LSOverride 73 | 74 | # Icon must end with two \r 75 | Icon 76 | 77 | 78 | # Thumbnails 79 | ._* 80 | 81 | # Files that might appear on external disk 82 | .Spotlight-V100 83 | .Trashes 84 | 85 | # Directories potentially created on remote AFP share 86 | .AppleDB 87 | .AppleDesktop 88 | Network Trash Folder 89 | Temporary Items 90 | .apdisk 91 | 92 | Pods -------------------------------------------------------------------------------- /VideoCaptureDemo/IDCaptureSessionMovieFileOutputCoordinator.m: -------------------------------------------------------------------------------- 1 | // 2 | // IDCaptureSessionMovieFileOutputCoordinator.m 3 | // VideoCaptureDemo 4 | // 5 | // Created by Adriaan Stellingwerff on 9/04/2015. 6 | // Copyright (c) 2015 Infoding. All rights reserved. 7 | // 8 | 9 | #import "IDCaptureSessionMovieFileOutputCoordinator.h" 10 | #import "IDFileManager.h" 11 | 12 | @interface IDCaptureSessionMovieFileOutputCoordinator () 13 | 14 | @property (nonatomic, strong) AVCaptureMovieFileOutput *movieFileOutput; 15 | 16 | @end 17 | 18 | @implementation IDCaptureSessionMovieFileOutputCoordinator 19 | 20 | - (instancetype)init 21 | { 22 | self = [super init]; 23 | if(self){ 24 | [self addMovieFileOutputToCaptureSession:self.captureSession]; 25 | } 26 | return self; 27 | } 28 | 29 | #pragma mark - Private methods 30 | 31 | - (BOOL)addMovieFileOutputToCaptureSession:(AVCaptureSession *)captureSession 32 | { 33 | self.movieFileOutput = [AVCaptureMovieFileOutput new]; 34 | return [self addOutput:_movieFileOutput toCaptureSession:captureSession]; 35 | } 36 | 37 | #pragma mark - Recording 38 | 39 | - (void)startRecording 40 | { 41 | IDFileManager *fm = [IDFileManager new]; 42 | NSURL *tempURL = [fm tempFileURL]; 43 | [_movieFileOutput startRecordingToOutputFileURL:tempURL recordingDelegate:self]; 44 | } 45 | 46 | - (void)stopRecording 47 | { 48 | [_movieFileOutput stopRecording]; 49 | } 50 | 51 | #pragma mark - AVCaptureFileOutputRecordingDelegate methods 52 | 53 | - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL 54 | fromConnections:(NSArray *)connections 55 | { 56 | //Recording started 57 | [self.delegate coordinatorDidBeginRecording:self]; 58 | } 59 | 60 | - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error 61 | { 62 | //Recording finished - do something with the file at outputFileURL 63 | [self.delegate coordinator:self didFinishRecordingToOutputFileURL:outputFileURL error:error]; 64 | 65 | } 66 | 67 | 68 | @end 69 | 70 | -------------------------------------------------------------------------------- /VideoCaptureDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // VideoCaptureDemo 4 | // 5 | // Created by Adriaan Stellingwerff on 31/03/2015. 6 | // Copyright (c) 2015 Infoding. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | 44 | } 45 | 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /VideoCaptureDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // VideoCaptureDemo 4 | // 5 | // Created by Adriaan Stellingwerff on 31/03/2015. 6 | // Copyright (c) 2015 Infoding. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "IDImagePickerCoordinator.h" 11 | #import "IDCaptureSessionPipelineViewController.h" 12 | 13 | @interface ViewController () 14 | 15 | @property (nonatomic, strong) IDImagePickerCoordinator *imagePickerCoordinator; 16 | 17 | @end 18 | 19 | @implementation ViewController 20 | 21 | - (void)viewDidLoad { 22 | [super viewDidLoad]; 23 | } 24 | 25 | - (void)viewDidAppear:(BOOL)animated 26 | { 27 | [super viewDidAppear:animated]; 28 | } 29 | 30 | - (void)didReceiveMemoryWarning { 31 | [super didReceiveMemoryWarning]; 32 | // Dispose of any resources that can be recreated. 33 | } 34 | 35 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 36 | { 37 | switch (indexPath.row) { 38 | case 0: 39 | self.imagePickerCoordinator = [IDImagePickerCoordinator new]; 40 | [self presentViewController:[_imagePickerCoordinator cameraVC] animated:YES completion:nil]; 41 | break; 42 | case 1: 43 | { 44 | UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; 45 | IDCaptureSessionPipelineViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:@"captureSessionVC"]; 46 | [viewController setupWithPipelineMode:PipelineModeMovieFileOutput]; 47 | [self presentViewController:viewController animated:YES completion:nil]; 48 | break; 49 | } 50 | case 2: 51 | { 52 | UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; 53 | IDCaptureSessionPipelineViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:@"captureSessionVC"]; 54 | [viewController setupWithPipelineMode:PipelineModeAssetWriter]; 55 | [self presentViewController:viewController animated:YES completion:nil]; 56 | break; 57 | } 58 | default: 59 | break; 60 | } 61 | } 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /VideoCaptureDemo/IDPermissionsManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // IDCameraPermissionsManager.m 3 | // VideoCameraDemo 4 | // 5 | // Created by Adriaan Stellingwerff on 10/03/2014. 6 | // Copyright (c) 2014 Infoding. All rights reserved. 7 | // 8 | 9 | #import "IDPermissionsManager.h" 10 | #import 11 | #import 12 | 13 | 14 | @interface IDPermissionsManager () 15 | 16 | 17 | @end 18 | 19 | @implementation IDPermissionsManager 20 | 21 | - (void)checkMicrophonePermissionsWithBlock:(void(^)(BOOL granted))block 22 | { 23 | NSString *mediaType = AVMediaTypeAudio; 24 | [AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) { 25 | if(!granted){ 26 | dispatch_async(dispatch_get_main_queue(), ^{ 27 | 28 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Microphone Disabled" 29 | message:@"To enable sound recording with your video please go to the Settings app > Privacy > Microphone and enable access." 30 | delegate:self 31 | cancelButtonTitle:@"OK" 32 | otherButtonTitles:@"Settings", nil]; 33 | alert.delegate = self; 34 | [alert show]; 35 | }); 36 | } 37 | if(block != nil) 38 | block(granted); 39 | }]; 40 | } 41 | 42 | 43 | - (void)checkCameraAuthorizationStatusWithBlock:(void(^)(BOOL granted))block 44 | { 45 | NSString *mediaType = AVMediaTypeVideo; 46 | [AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) { 47 | if (!granted){ 48 | //Not granted access to mediaType 49 | dispatch_async(dispatch_get_main_queue(), ^{ 50 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Camera disabled" 51 | message:@"This app doesn't have permission to use the camera, please go to the Settings app > Privacy > Camera and enable access." 52 | delegate:self 53 | cancelButtonTitle:@"OK" 54 | otherButtonTitles:@"Settings", nil]; 55 | alert.delegate = self; 56 | [alert show]; 57 | }); 58 | } 59 | if(block) 60 | block(granted); 61 | }]; 62 | } 63 | 64 | #pragma mark - UIAlertViewDelegate methods 65 | 66 | - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex 67 | { 68 | if(buttonIndex == 1){ 69 | [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; 70 | } 71 | } 72 | 73 | 74 | @end 75 | -------------------------------------------------------------------------------- /VideoCaptureDemo/IDFileManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // IDFileManager.m 3 | // VideoCaptureDemo 4 | // 5 | // Created by Adriaan Stellingwerff on 9/04/2015. 6 | // Copyright (c) 2015 Infoding. All rights reserved. 7 | // 8 | 9 | #import "IDFileManager.h" 10 | #import 11 | 12 | @implementation IDFileManager 13 | 14 | 15 | - (NSURL *)tempFileURL 16 | { 17 | NSString *path = nil; 18 | NSFileManager *fm = [NSFileManager defaultManager]; 19 | NSInteger i = 0; 20 | while(path == nil || [fm fileExistsAtPath:path]){ 21 | path = [NSString stringWithFormat:@"%@output%ld.mov", NSTemporaryDirectory(), (long)i]; 22 | i++; 23 | } 24 | return [NSURL fileURLWithPath:path]; 25 | } 26 | 27 | - (void) removeFile:(NSURL *)fileURL 28 | { 29 | NSString *filePath = [fileURL path]; 30 | NSFileManager *fileManager = [NSFileManager defaultManager]; 31 | if ([fileManager fileExistsAtPath:filePath]) { 32 | NSError *error; 33 | [fileManager removeItemAtPath:filePath error:&error]; 34 | if(error){ 35 | NSLog(@"error removing file: %@", [error localizedDescription]); 36 | } 37 | } 38 | } 39 | 40 | - (void) copyFileToDocuments:(NSURL *)fileURL 41 | { 42 | NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; 43 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 44 | [dateFormatter setDateFormat:@"yyyy-MM-dd_HH-mm-ss"]; 45 | NSString *destinationPath = [documentsDirectory stringByAppendingFormat:@"/output_%@.mov", [dateFormatter stringFromDate:[NSDate date]]]; 46 | NSError *error; 47 | [[NSFileManager defaultManager] copyItemAtURL:fileURL toURL:[NSURL fileURLWithPath:destinationPath] error:&error]; 48 | if(error){ 49 | NSLog(@"error copying file: %@", [error localizedDescription]); 50 | } 51 | } 52 | 53 | - (void)copyFileToCameraRoll:(NSURL *)fileURL 54 | { 55 | ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; 56 | if(![library videoAtPathIsCompatibleWithSavedPhotosAlbum:fileURL]){ 57 | NSLog(@"video incompatible with camera roll"); 58 | } 59 | [library writeVideoAtPathToSavedPhotosAlbum:fileURL completionBlock:^(NSURL *assetURL, NSError *error) { 60 | 61 | if(error){ 62 | NSLog(@"Error: Domain = %@, Code = %@", [error domain], [error localizedDescription]); 63 | } else if(assetURL == nil){ 64 | 65 | //It's possible for writing to camera roll to fail, without receiving an error message, but assetURL will be nil 66 | //Happens when disk is (almost) full 67 | NSLog(@"Error saving to camera roll: no error message, but no url returned"); 68 | 69 | } else { 70 | //remove temp file 71 | NSError *error; 72 | [[NSFileManager defaultManager] removeItemAtURL:fileURL error:&error]; 73 | if(error){ 74 | NSLog(@"error: %@", [error localizedDescription]); 75 | } 76 | 77 | } 78 | }]; 79 | 80 | } 81 | 82 | 83 | @end 84 | -------------------------------------------------------------------------------- /VideoCaptureDemo/IDImagePickerCoordinator.m: -------------------------------------------------------------------------------- 1 | // 2 | // IDImagePickerCoordinator.m 3 | // VideoCaptureDemo 4 | // 5 | // Created by Adriaan Stellingwerff on 1/04/2015. 6 | // Copyright (c) 2015 Infoding. All rights reserved. 7 | // 8 | 9 | #import "IDImagePickerCoordinator.h" 10 | #import 11 | #import 12 | 13 | @interface IDImagePickerCoordinator () 14 | 15 | @property (nonatomic, strong) UIImagePickerController *camera; 16 | 17 | @end 18 | 19 | @implementation IDImagePickerCoordinator 20 | 21 | 22 | - (instancetype)init 23 | { 24 | self = [super init]; 25 | if(self){ 26 | _camera = [self setupImagePicker]; 27 | } 28 | return self; 29 | } 30 | 31 | - (UIImagePickerController *)cameraVC 32 | { 33 | return _camera; 34 | } 35 | 36 | #pragma mark - Private methods 37 | 38 | - (UIImagePickerController *)setupImagePicker 39 | { 40 | UIImagePickerController *camera; 41 | if([self isVideoRecordingAvailable]){ 42 | camera = [UIImagePickerController new]; 43 | camera.sourceType = UIImagePickerControllerSourceTypeCamera; 44 | camera.mediaTypes = @[(NSString *)kUTTypeMovie]; 45 | camera.delegate = self; 46 | } 47 | return camera; 48 | } 49 | 50 | 51 | - (BOOL)isVideoRecordingAvailable 52 | { 53 | if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]){ 54 | NSArray *availableMediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera]; 55 | if([availableMediaTypes containsObject:(NSString *)kUTTypeMovie]){ 56 | return YES; 57 | } 58 | } 59 | return NO; 60 | } 61 | 62 | - (BOOL)setFrontFacingCameraOnImagePicker:(UIImagePickerController *)picker 63 | { 64 | if([UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront]){ 65 | [picker setCameraDevice:UIImagePickerControllerCameraDeviceFront]; 66 | return YES; 67 | } 68 | return NO; 69 | } 70 | 71 | - (void)configureCustomUIOnImagePicker:(UIImagePickerController *)picker 72 | { 73 | UIView *cameraOverlay = [[UIView alloc] init]; 74 | picker.showsCameraControls = NO; 75 | picker.cameraOverlayView = cameraOverlay; 76 | } 77 | 78 | #pragma mark - UIImagePickerControllerDelegate methods 79 | 80 | - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info 81 | { 82 | ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; 83 | NSURL *recordedVideoURL= [info objectForKey:UIImagePickerControllerMediaURL]; 84 | if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:recordedVideoURL]) { 85 | [library writeVideoAtPathToSavedPhotosAlbum:recordedVideoURL 86 | completionBlock:^(NSURL *assetURL, NSError *error){} 87 | ]; 88 | } 89 | 90 | [picker dismissViewControllerAnimated:YES completion:nil]; 91 | } 92 | 93 | - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker 94 | { 95 | [picker dismissViewControllerAnimated:YES completion:nil]; 96 | } 97 | 98 | 99 | @end 100 | -------------------------------------------------------------------------------- /VideoCaptureDemo/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /VideoCaptureDemo/IDCaptureSessionPipelineViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // IDCaptureSessionPipelineViewController.m 3 | // VideoCaptureDemo 4 | // 5 | // Created by Adriaan Stellingwerff on 9/04/2015. 6 | // Copyright (c) 2015 Infoding. All rights reserved. 7 | // 8 | 9 | #import "IDCaptureSessionPipelineViewController.h" 10 | #import "IDCaptureSessionAssetWriterCoordinator.h" 11 | #import "IDCaptureSessionMovieFileOutputCoordinator.h" 12 | #import "IDFileManager.h" 13 | #import "IDPermissionsManager.h" 14 | 15 | //TODO: add backgrounding stuff 16 | 17 | @interface IDCaptureSessionPipelineViewController () 18 | 19 | @property (nonatomic, strong) IDCaptureSessionCoordinator *captureSessionCoordinator; 20 | @property (nonatomic, retain) IBOutlet UIBarButtonItem *recordButton; 21 | 22 | @property (nonatomic, assign) BOOL recording; 23 | @property (nonatomic, assign) BOOL dismissing; 24 | 25 | 26 | @end 27 | 28 | @implementation IDCaptureSessionPipelineViewController 29 | 30 | 31 | - (void)setupWithPipelineMode:(PipelineMode)mode 32 | { 33 | //Let's check permissions for microphone and camera access before we get started 34 | [self checkPermissions]; 35 | 36 | switch (mode) { 37 | case PipelineModeMovieFileOutput: 38 | _captureSessionCoordinator = [IDCaptureSessionMovieFileOutputCoordinator new]; 39 | break; 40 | case PipelineModeAssetWriter: 41 | _captureSessionCoordinator = [IDCaptureSessionAssetWriterCoordinator new]; 42 | break; 43 | default: 44 | break; 45 | } 46 | [_captureSessionCoordinator setDelegate:self callbackQueue:dispatch_get_main_queue()]; 47 | [self configureInterface]; 48 | } 49 | 50 | - (IBAction)toggleRecording:(id)sender 51 | { 52 | if(_recording){ 53 | [_captureSessionCoordinator stopRecording]; 54 | } else { 55 | // Disable the idle timer while recording 56 | [UIApplication sharedApplication].idleTimerDisabled = YES; 57 | 58 | self.recordButton.enabled = NO; // re-enabled once recording has finished starting 59 | self.recordButton.title = @"Stop"; 60 | 61 | [self.captureSessionCoordinator startRecording]; 62 | 63 | _recording = YES; 64 | } 65 | } 66 | 67 | - (IBAction)closeCamera:(id)sender 68 | { 69 | //TODO: tear down pipeline 70 | if(_recording){ 71 | _dismissing = YES; 72 | [_captureSessionCoordinator stopRecording]; 73 | } else { 74 | [self stopPipelineAndDismiss]; 75 | } 76 | } 77 | 78 | #pragma mark - Private methods 79 | 80 | - (void)configureInterface 81 | { 82 | AVCaptureVideoPreviewLayer *previewLayer = [_captureSessionCoordinator previewLayer]; 83 | previewLayer.frame = self.view.bounds; 84 | [self.view.layer insertSublayer:previewLayer atIndex:0]; 85 | 86 | [_captureSessionCoordinator startRunning]; 87 | } 88 | 89 | - (void)stopPipelineAndDismiss 90 | { 91 | [_captureSessionCoordinator stopRunning]; 92 | [self dismissViewControllerAnimated:YES completion:nil]; 93 | _dismissing = NO; 94 | } 95 | 96 | - (void)checkPermissions 97 | { 98 | IDPermissionsManager *pm = [IDPermissionsManager new]; 99 | [pm checkCameraAuthorizationStatusWithBlock:^(BOOL granted) { 100 | if(!granted){ 101 | NSLog(@"we don't have permission to use the camera"); 102 | } 103 | }]; 104 | [pm checkMicrophonePermissionsWithBlock:^(BOOL granted) { 105 | if(!granted){ 106 | NSLog(@"we don't have permission to use the microphone"); 107 | } 108 | }]; 109 | } 110 | 111 | #pragma mark = IDCaptureSessionCoordinatorDelegate methods 112 | 113 | - (void)coordinatorDidBeginRecording:(IDCaptureSessionCoordinator *)coordinator 114 | { 115 | _recordButton.enabled = YES; 116 | } 117 | 118 | - (void)coordinator:(IDCaptureSessionCoordinator *)coordinator didFinishRecordingToOutputFileURL:(NSURL *)outputFileURL error:(NSError *)error 119 | { 120 | [UIApplication sharedApplication].idleTimerDisabled = NO; 121 | 122 | _recordButton.title = @"Record"; 123 | _recording = NO; 124 | 125 | //Do something useful with the video file available at the outputFileURL 126 | IDFileManager *fm = [IDFileManager new]; 127 | [fm copyFileToCameraRoll:outputFileURL]; 128 | 129 | 130 | //Dismiss camera (when user taps cancel while camera is recording) 131 | if(_dismissing){ 132 | [self stopPipelineAndDismiss]; 133 | } 134 | } 135 | 136 | @end -------------------------------------------------------------------------------- /VideoCaptureDemo/IDCaptureSessionCoordinator.m: -------------------------------------------------------------------------------- 1 | // 2 | // IDCaptureSessionCoordinator.m 3 | // VideoCaptureDemo 4 | // 5 | // Created by Adriaan Stellingwerff on 1/04/2015. 6 | // Copyright (c) 2015 Infoding. All rights reserved. 7 | // 8 | 9 | #import "IDCaptureSessionCoordinator.h" 10 | 11 | @interface IDCaptureSessionCoordinator () 12 | 13 | @property (nonatomic, strong) dispatch_queue_t sessionQueue; 14 | @property (nonatomic, strong) AVCaptureVideoPreviewLayer *previewLayer; 15 | 16 | @end 17 | 18 | @implementation IDCaptureSessionCoordinator 19 | 20 | 21 | - (instancetype)init 22 | { 23 | self = [super init]; 24 | if(self){ 25 | _sessionQueue = dispatch_queue_create( "com.example.capturepipeline.session", DISPATCH_QUEUE_SERIAL ); 26 | _captureSession = [self setupCaptureSession]; 27 | } 28 | return self; 29 | } 30 | 31 | - (void)setDelegate:(id)delegate callbackQueue:(dispatch_queue_t)delegateCallbackQueue 32 | { 33 | if(delegate && ( delegateCallbackQueue == NULL)){ 34 | @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Caller must provide a delegateCallbackQueue" userInfo:nil]; 35 | } 36 | @synchronized(self) 37 | { 38 | _delegate = delegate; 39 | if (delegateCallbackQueue != _delegateCallbackQueue){ 40 | _delegateCallbackQueue = delegateCallbackQueue; 41 | } 42 | } 43 | } 44 | 45 | - (AVCaptureVideoPreviewLayer *)previewLayer 46 | { 47 | if(!_previewLayer && _captureSession){ 48 | _previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:_captureSession]; 49 | } 50 | return _previewLayer; 51 | } 52 | 53 | - (void)startRunning 54 | { 55 | dispatch_sync( _sessionQueue, ^{ 56 | [_captureSession startRunning]; 57 | } ); 58 | } 59 | 60 | - (void)stopRunning 61 | { 62 | dispatch_sync( _sessionQueue, ^{ 63 | // the captureSessionDidStopRunning method will stop recording if necessary as well, but we do it here so that the last video and audio samples are better aligned 64 | [self stopRecording]; // does nothing if we aren't currently recording 65 | [_captureSession stopRunning]; 66 | } ); 67 | } 68 | 69 | 70 | - (void)startRecording 71 | { 72 | //overwritten by subclass 73 | } 74 | 75 | - (void)stopRecording 76 | { 77 | //overwritten by subclass 78 | } 79 | 80 | 81 | 82 | #pragma mark - Capture Session Setup 83 | 84 | 85 | - (AVCaptureSession *)setupCaptureSession 86 | { 87 | AVCaptureSession *captureSession = [AVCaptureSession new]; 88 | 89 | if(![self addDefaultCameraInputToCaptureSession:captureSession]){ 90 | NSLog(@"failed to add camera input to capture session"); 91 | } 92 | if(![self addDefaultMicInputToCaptureSession:captureSession]){ 93 | NSLog(@"failed to add mic input to capture session"); 94 | } 95 | 96 | return captureSession; 97 | } 98 | 99 | - (BOOL)addDefaultCameraInputToCaptureSession:(AVCaptureSession *)captureSession 100 | { 101 | NSError *error; 102 | AVCaptureDeviceInput *cameraDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo] error:&error]; 103 | 104 | if(error){ 105 | NSLog(@"error configuring camera input: %@", [error localizedDescription]); 106 | return NO; 107 | } else { 108 | BOOL success = [self addInput:cameraDeviceInput toCaptureSession:captureSession]; 109 | _cameraDevice = cameraDeviceInput.device; 110 | return success; 111 | } 112 | } 113 | 114 | //Not used in this project, but illustration of how to select a specific camera 115 | - (BOOL)addCameraAtPosition:(AVCaptureDevicePosition)position toCaptureSession:(AVCaptureSession *)captureSession 116 | { 117 | NSError *error; 118 | NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; 119 | AVCaptureDeviceInput *cameraDeviceInput; 120 | for(AVCaptureDevice *device in devices){ 121 | if(device.position == position){ 122 | cameraDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:device error:&error]; 123 | } 124 | } 125 | if(!cameraDeviceInput){ 126 | NSLog(@"No capture device found for requested position"); 127 | return NO; 128 | } 129 | 130 | if(error){ 131 | NSLog(@"error configuring camera input: %@", [error localizedDescription]); 132 | return NO; 133 | } else { 134 | BOOL success = [self addInput:cameraDeviceInput toCaptureSession:captureSession]; 135 | _cameraDevice = cameraDeviceInput.device; 136 | return success; 137 | } 138 | } 139 | 140 | - (BOOL)addDefaultMicInputToCaptureSession:(AVCaptureSession *)captureSession 141 | { 142 | NSError *error; 143 | AVCaptureDeviceInput *micDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio] error:&error]; 144 | if(error){ 145 | NSLog(@"error configuring mic input: %@", [error localizedDescription]); 146 | return NO; 147 | } else { 148 | BOOL success = [self addInput:micDeviceInput toCaptureSession:captureSession]; 149 | return success; 150 | } 151 | } 152 | 153 | - (BOOL)addInput:(AVCaptureDeviceInput *)input toCaptureSession:(AVCaptureSession *)captureSession 154 | { 155 | if([captureSession canAddInput:input]){ 156 | [captureSession addInput:input]; 157 | return YES; 158 | } else { 159 | NSLog(@"can't add input: %@", [input description]); 160 | } 161 | return NO; 162 | } 163 | 164 | 165 | - (BOOL)addOutput:(AVCaptureOutput *)output toCaptureSession:(AVCaptureSession *)captureSession 166 | { 167 | if([captureSession canAddOutput:output]){ 168 | [captureSession addOutput:output]; 169 | return YES; 170 | } else { 171 | NSLog(@"can't add output: %@", [output description]); 172 | } 173 | return NO; 174 | } 175 | 176 | 177 | #pragma mark - Methods discussed in the article but not used in this demo app 178 | 179 | - (void)setFrameRateWithDuration:(CMTime)frameDuration OnCaptureDevice:(AVCaptureDevice *)device 180 | { 181 | NSError *error; 182 | NSArray *supportedFrameRateRanges = [device.activeFormat videoSupportedFrameRateRanges]; 183 | BOOL frameRateSupported = NO; 184 | for(AVFrameRateRange *range in supportedFrameRateRanges){ 185 | if(CMTIME_COMPARE_INLINE(frameDuration, >=, range.minFrameDuration) && CMTIME_COMPARE_INLINE(frameDuration, <=, range.maxFrameDuration)){ 186 | frameRateSupported = YES; 187 | } 188 | } 189 | 190 | if(frameRateSupported && [device lockForConfiguration:&error]){ 191 | [device setActiveVideoMaxFrameDuration:frameDuration]; 192 | [device setActiveVideoMinFrameDuration:frameDuration]; 193 | [device unlockForConfiguration]; 194 | } 195 | } 196 | 197 | 198 | - (void)listCamerasAndMics 199 | { 200 | NSLog(@"%@", [[AVCaptureDevice devices] description]); 201 | NSError *error; 202 | AVAudioSession *audioSession = [AVAudioSession sharedInstance]; 203 | [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&error]; 204 | if(error){ 205 | NSLog(@"%@", [error localizedDescription]); 206 | } 207 | [audioSession setActive:YES error:&error]; 208 | 209 | NSArray *availableAudioInputs = [audioSession availableInputs]; 210 | NSLog(@"audio inputs: %@", [availableAudioInputs description]); 211 | for(AVAudioSessionPortDescription *portDescription in availableAudioInputs){ 212 | NSLog(@"data sources: %@", [[portDescription dataSources] description]); 213 | } 214 | if([availableAudioInputs count] > 0){ 215 | AVAudioSessionPortDescription *portDescription = [availableAudioInputs firstObject]; 216 | if([[portDescription dataSources] count] > 0){ 217 | NSError *error; 218 | AVAudioSessionDataSourceDescription *dataSource = [[portDescription dataSources] lastObject]; 219 | 220 | [portDescription setPreferredDataSource:dataSource error:&error]; 221 | [self logError:error]; 222 | 223 | [audioSession setPreferredInput:portDescription error:&error]; 224 | [self logError:error]; 225 | 226 | NSArray *availableAudioInputs = [audioSession availableInputs]; 227 | NSLog(@"audio inputs: %@", [availableAudioInputs description]); 228 | 229 | } 230 | } 231 | } 232 | 233 | - (void)logError:(NSError *)error 234 | { 235 | if(error){ 236 | NSLog(@"%@", [error localizedDescription]); 237 | } 238 | } 239 | 240 | - (void)configureFrontMic 241 | { 242 | NSError *error; 243 | AVAudioSession *audioSession = [AVAudioSession sharedInstance]; 244 | [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&error]; 245 | if(error){ 246 | NSLog(@"%@", [error localizedDescription]); 247 | } 248 | [audioSession setActive:YES error:&error]; 249 | 250 | NSArray* inputs = [audioSession availableInputs]; 251 | AVAudioSessionPortDescription *builtInMic = nil; 252 | for (AVAudioSessionPortDescription* port in inputs){ 253 | if ([port.portType isEqualToString:AVAudioSessionPortBuiltInMic]){ 254 | builtInMic = port; 255 | break; 256 | } 257 | } 258 | 259 | for (AVAudioSessionDataSourceDescription* source in builtInMic.dataSources){ 260 | if ([source.orientation isEqual:AVAudioSessionOrientationFront]){ 261 | [builtInMic setPreferredDataSource:source error:nil]; 262 | [audioSession setPreferredInput:builtInMic error:&error]; 263 | break; 264 | } 265 | } 266 | } 267 | 268 | 269 | @end -------------------------------------------------------------------------------- /VideoCaptureDemo/IDCaptureSessionAssetWriterCoordinator.m: -------------------------------------------------------------------------------- 1 | // 2 | // IDCaptureSessionAssetWriterCoordinator.m 3 | // VideoCaptureDemo 4 | // 5 | // Created by Adriaan Stellingwerff on 9/04/2015. 6 | // Copyright (c) 2015 Infoding. All rights reserved. 7 | // 8 | 9 | #import "IDCaptureSessionAssetWriterCoordinator.h" 10 | 11 | #import 12 | #import "IDAssetWriterCoordinator.h" 13 | #import "IDFileManager.h" 14 | 15 | 16 | typedef NS_ENUM( NSInteger, RecordingStatus ) 17 | { 18 | RecordingStatusIdle = 0, 19 | RecordingStatusStartingRecording, 20 | RecordingStatusRecording, 21 | RecordingStatusStoppingRecording, 22 | }; // internal state machine 23 | 24 | 25 | @interface IDCaptureSessionAssetWriterCoordinator () 26 | 27 | @property (nonatomic, strong) dispatch_queue_t videoDataOutputQueue; 28 | @property (nonatomic, strong) dispatch_queue_t audioDataOutputQueue; 29 | 30 | @property (nonatomic, strong) AVCaptureVideoDataOutput *videoDataOutput; 31 | @property (nonatomic, strong) AVCaptureAudioDataOutput *audioDataOutput; 32 | 33 | @property (nonatomic, strong) AVCaptureConnection *audioConnection; 34 | @property (nonatomic, strong) AVCaptureConnection *videoConnection; 35 | 36 | @property (nonatomic, strong) NSDictionary *videoCompressionSettings; 37 | @property (nonatomic, strong) NSDictionary *audioCompressionSettings; 38 | 39 | @property (nonatomic, strong) AVAssetWriter *assetWriter; 40 | 41 | @property (nonatomic, assign) RecordingStatus recordingStatus; 42 | @property (nonatomic, strong) NSURL *recordingURL; 43 | 44 | @property(nonatomic, retain) __attribute__((NSObject)) CMFormatDescriptionRef outputVideoFormatDescription; 45 | @property(nonatomic, retain) __attribute__((NSObject)) CMFormatDescriptionRef outputAudioFormatDescription; 46 | @property(nonatomic, retain) IDAssetWriterCoordinator *assetWriterCoordinator; 47 | 48 | @end 49 | 50 | @implementation IDCaptureSessionAssetWriterCoordinator 51 | 52 | 53 | - (instancetype)init 54 | { 55 | self = [super init]; 56 | if(self){ 57 | self.videoDataOutputQueue = dispatch_queue_create( "com.example.capturesession.videodata", DISPATCH_QUEUE_SERIAL ); 58 | dispatch_set_target_queue( _videoDataOutputQueue, dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0 ) ); 59 | self.audioDataOutputQueue = dispatch_queue_create( "com.example.capturesession.audiodata", DISPATCH_QUEUE_SERIAL ); 60 | [self addDataOutputsToCaptureSession:self.captureSession]; 61 | 62 | } 63 | return self; 64 | } 65 | 66 | #pragma mark - Recording 67 | 68 | - (void)startRecording 69 | { 70 | @synchronized(self) 71 | { 72 | if(_recordingStatus != RecordingStatusIdle) { 73 | @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Already recording" userInfo:nil]; 74 | return; 75 | } 76 | [self transitionToRecordingStatus:RecordingStatusStartingRecording error:nil]; 77 | } 78 | 79 | IDFileManager *fm = [IDFileManager new]; 80 | _recordingURL = [fm tempFileURL]; 81 | 82 | 83 | self.assetWriterCoordinator = [[IDAssetWriterCoordinator alloc] initWithURL:_recordingURL]; 84 | if(_outputAudioFormatDescription != nil){ 85 | [_assetWriterCoordinator addAudioTrackWithSourceFormatDescription:self.outputAudioFormatDescription settings:_audioCompressionSettings]; 86 | } 87 | [_assetWriterCoordinator addVideoTrackWithSourceFormatDescription:self.outputVideoFormatDescription settings:_videoCompressionSettings]; 88 | 89 | dispatch_queue_t callbackQueue = dispatch_queue_create( "com.example.capturesession.writercallback", DISPATCH_QUEUE_SERIAL ); // guarantee ordering of callbacks with a serial queue 90 | [_assetWriterCoordinator setDelegate:self callbackQueue:callbackQueue]; 91 | [_assetWriterCoordinator prepareToRecord]; // asynchronous, will call us back with recorderDidFinishPreparing: or recorder:didFailWithError: when done 92 | } 93 | 94 | - (void)stopRecording 95 | { 96 | @synchronized(self) 97 | { 98 | if (_recordingStatus != RecordingStatusRecording){ 99 | return; 100 | } 101 | [self transitionToRecordingStatus:RecordingStatusStoppingRecording error:nil]; 102 | } 103 | [self.assetWriterCoordinator finishRecording]; // asynchronous, will call us back with 104 | } 105 | 106 | #pragma mark - Private methods 107 | 108 | - (void)addDataOutputsToCaptureSession:(AVCaptureSession *)captureSession 109 | { 110 | self.videoDataOutput = [AVCaptureVideoDataOutput new]; 111 | _videoDataOutput.videoSettings = nil; 112 | _videoDataOutput.alwaysDiscardsLateVideoFrames = NO; 113 | [_videoDataOutput setSampleBufferDelegate:self queue:_videoDataOutputQueue]; 114 | 115 | self.audioDataOutput = [AVCaptureAudioDataOutput new]; 116 | [_audioDataOutput setSampleBufferDelegate:self queue:_audioDataOutputQueue]; 117 | 118 | [self addOutput:_videoDataOutput toCaptureSession:self.captureSession]; 119 | _videoConnection = [_videoDataOutput connectionWithMediaType:AVMediaTypeVideo]; 120 | 121 | [self addOutput:_audioDataOutput toCaptureSession:self.captureSession]; 122 | _audioConnection = [_audioDataOutput connectionWithMediaType:AVMediaTypeAudio]; 123 | 124 | [self setCompressionSettings]; 125 | } 126 | 127 | - (void)setupVideoPipelineWithInputFormatDescription:(CMFormatDescriptionRef)inputFormatDescription 128 | { 129 | self.outputVideoFormatDescription = inputFormatDescription; 130 | } 131 | 132 | - (void)teardownVideoPipeline 133 | { 134 | self.outputVideoFormatDescription = nil; 135 | } 136 | 137 | - (void)setCompressionSettings 138 | { 139 | _videoCompressionSettings = [_videoDataOutput recommendedVideoSettingsForAssetWriterWithOutputFileType:AVFileTypeQuickTimeMovie]; 140 | _audioCompressionSettings = [_audioDataOutput recommendedAudioSettingsForAssetWriterWithOutputFileType:AVFileTypeQuickTimeMovie]; 141 | } 142 | 143 | #pragma mark - SampleBufferDelegate methods 144 | 145 | - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection 146 | { 147 | CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer); 148 | 149 | if (connection == _videoConnection){ 150 | if (self.outputVideoFormatDescription == nil) { 151 | // Don't render the first sample buffer. 152 | // This gives us one frame interval (33ms at 30fps) for setupVideoPipelineWithInputFormatDescription: to complete. 153 | // Ideally this would be done asynchronously to ensure frames don't back up on slower devices. 154 | 155 | //TODO: outputVideoFormatDescription should be updated whenever video configuration is changed (frame rate, etc.) 156 | //Currently we don't use the outputVideoFormatDescription in IDAssetWriterRecoredSession 157 | [self setupVideoPipelineWithInputFormatDescription:formatDescription]; 158 | } else { 159 | self.outputVideoFormatDescription = formatDescription; 160 | @synchronized(self) { 161 | if(_recordingStatus == RecordingStatusRecording){ 162 | [_assetWriterCoordinator appendVideoSampleBuffer:sampleBuffer]; 163 | } 164 | } 165 | } 166 | } else if ( connection == _audioConnection ){ 167 | self.outputAudioFormatDescription = formatDescription; 168 | @synchronized( self ) { 169 | if(_recordingStatus == RecordingStatusRecording){ 170 | [_assetWriterCoordinator appendAudioSampleBuffer:sampleBuffer]; 171 | } 172 | } 173 | } 174 | } 175 | 176 | #pragma mark - IDAssetWriterCoordinatorDelegate methods 177 | 178 | - (void)writerCoordinatorDidFinishPreparing:(IDAssetWriterCoordinator *)coordinator 179 | { 180 | @synchronized(self) 181 | { 182 | if(_recordingStatus != RecordingStatusStartingRecording){ 183 | @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Expected to be in StartingRecording state" userInfo:nil]; 184 | return; 185 | } 186 | [self transitionToRecordingStatus:RecordingStatusRecording error:nil]; 187 | } 188 | } 189 | 190 | - (void)writerCoordinator:(IDAssetWriterCoordinator *)recorder didFailWithError:(NSError *)error 191 | { 192 | @synchronized( self ) { 193 | self.assetWriterCoordinator = nil; 194 | [self transitionToRecordingStatus:RecordingStatusIdle error:error]; 195 | } 196 | } 197 | 198 | - (void)writerCoordinatorDidFinishRecording:(IDAssetWriterCoordinator *)coordinator 199 | { 200 | @synchronized( self ) 201 | { 202 | if ( _recordingStatus != RecordingStatusStoppingRecording ) { 203 | @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Expected to be in StoppingRecording state" userInfo:nil]; 204 | return; 205 | } 206 | // No state transition, we are still in the process of stopping. 207 | // We will be stopped once we save to the assets library. 208 | } 209 | 210 | self.assetWriterCoordinator = nil; 211 | 212 | @synchronized( self ) { 213 | [self transitionToRecordingStatus:RecordingStatusIdle error:nil]; 214 | } 215 | } 216 | 217 | 218 | #pragma mark - Recording State Machine 219 | 220 | // call under @synchonized( self ) 221 | - (void)transitionToRecordingStatus:(RecordingStatus)newStatus error:(NSError *)error 222 | { 223 | RecordingStatus oldStatus = _recordingStatus; 224 | _recordingStatus = newStatus; 225 | 226 | if (newStatus != oldStatus){ 227 | if (error && (newStatus == RecordingStatusIdle)){ 228 | dispatch_async( self.delegateCallbackQueue, ^{ 229 | @autoreleasepool 230 | { 231 | [self.delegate coordinator:self didFinishRecordingToOutputFileURL:_recordingURL error:nil]; 232 | } 233 | }); 234 | } else { 235 | error = nil; // only the above delegate method takes an error 236 | if (oldStatus == RecordingStatusStartingRecording && newStatus == RecordingStatusRecording){ 237 | dispatch_async( self.delegateCallbackQueue, ^{ 238 | @autoreleasepool 239 | { 240 | [self.delegate coordinatorDidBeginRecording:self]; 241 | } 242 | }); 243 | } else if (oldStatus == RecordingStatusStoppingRecording && newStatus == RecordingStatusIdle) { 244 | dispatch_async( self.delegateCallbackQueue, ^{ 245 | @autoreleasepool 246 | { 247 | [self.delegate coordinator:self didFinishRecordingToOutputFileURL:_recordingURL error:nil]; 248 | } 249 | }); 250 | } 251 | } 252 | } 253 | } 254 | 255 | @end -------------------------------------------------------------------------------- /VideoCaptureDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 64 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /VideoCaptureDemo/IDAssetWriterCoordinator.m: -------------------------------------------------------------------------------- 1 | // 2 | // IDAssetWriterCoordinator.m 3 | // VideoCaptureDemo 4 | // 5 | // Created by Adriaan Stellingwerff on 9/04/2015. 6 | // Copyright (c) 2015 Infoding. All rights reserved. 7 | // 8 | 9 | #import "IDAssetWriterCoordinator.h" 10 | 11 | typedef NS_ENUM(NSInteger, WriterStatus){ 12 | WriterStatusIdle = 0, 13 | WriterStatusPreparingToRecord, 14 | WriterStatusRecording, 15 | WriterStatusFinishingRecordingPart1, // waiting for inflight buffers to be appended 16 | WriterStatusFinishingRecordingPart2, // calling finish writing on the asset writer 17 | WriterStatusFinished, // terminal state 18 | WriterStatusFailed // terminal state 19 | }; // internal state machine 20 | 21 | @interface IDAssetWriterCoordinator () 22 | 23 | @property (nonatomic, weak) id delegate; 24 | 25 | @property (nonatomic, assign) WriterStatus status; 26 | 27 | @property (nonatomic) dispatch_queue_t writingQueue; 28 | @property (nonatomic) dispatch_queue_t delegateCallbackQueue; 29 | 30 | @property (nonatomic) NSURL *URL; 31 | 32 | @property (nonatomic) AVAssetWriter *assetWriter; 33 | @property (nonatomic) BOOL haveStartedSession; 34 | 35 | @property (nonatomic) CMFormatDescriptionRef audioTrackSourceFormatDescription; 36 | @property (nonatomic) NSDictionary *audioTrackSettings; 37 | @property (nonatomic) AVAssetWriterInput *audioInput; 38 | 39 | @property (nonatomic) CMFormatDescriptionRef videoTrackSourceFormatDescription; 40 | @property (nonatomic) CGAffineTransform videoTrackTransform; 41 | @property (nonatomic) NSDictionary *videoTrackSettings; 42 | @property (nonatomic) AVAssetWriterInput *videoInput; 43 | 44 | @end 45 | 46 | @implementation IDAssetWriterCoordinator 47 | 48 | 49 | - (instancetype)initWithURL:(NSURL *)URL 50 | { 51 | if (!URL) { 52 | return nil; 53 | } 54 | 55 | self = [super init]; 56 | if (self) { 57 | _writingQueue = dispatch_queue_create( "com.example.assetwriter.writing", DISPATCH_QUEUE_SERIAL ); 58 | _videoTrackTransform = CGAffineTransformMakeRotation(M_PI_2); //portrait orientation 59 | _URL = URL; 60 | } 61 | return self; 62 | } 63 | 64 | - (void)addVideoTrackWithSourceFormatDescription:(CMFormatDescriptionRef)formatDescription settings:(NSDictionary *)videoSettings 65 | { 66 | if ( formatDescription == NULL ){ 67 | @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"NULL format description" userInfo:nil]; 68 | return; 69 | } 70 | @synchronized( self ) 71 | { 72 | if (_status != WriterStatusIdle){ 73 | @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Cannot add tracks while not idle" userInfo:nil]; 74 | return; 75 | } 76 | 77 | if(_videoTrackSourceFormatDescription ){ 78 | @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Cannot add more than one video track" userInfo:nil]; 79 | return; 80 | } 81 | 82 | _videoTrackSourceFormatDescription = (CMFormatDescriptionRef)CFRetain( formatDescription ); 83 | _videoTrackSettings = [videoSettings copy]; 84 | } 85 | } 86 | 87 | - (void)addAudioTrackWithSourceFormatDescription:(CMFormatDescriptionRef)formatDescription settings:(NSDictionary *)audioSettings 88 | { 89 | if ( formatDescription == NULL ) { 90 | @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"NULL format description" userInfo:nil]; 91 | return; 92 | } 93 | 94 | @synchronized( self ) 95 | { 96 | if ( _status != WriterStatusIdle ) { 97 | @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Cannot add tracks while not idle" userInfo:nil]; 98 | return; 99 | } 100 | 101 | if ( _audioTrackSourceFormatDescription ) { 102 | @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Cannot add more than one audio track" userInfo:nil]; 103 | return; 104 | } 105 | 106 | _audioTrackSourceFormatDescription = (CMFormatDescriptionRef)CFRetain( formatDescription ); 107 | _audioTrackSettings = [audioSettings copy]; 108 | } 109 | } 110 | 111 | 112 | - (void)setDelegate:(id)delegate callbackQueue:(dispatch_queue_t)delegateCallbackQueue; // delegate is weak referenced 113 | { 114 | if ( delegate && ( delegateCallbackQueue == NULL ) ) { 115 | @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Caller must provide a delegateCallbackQueue" userInfo:nil]; 116 | } 117 | 118 | @synchronized( self ) 119 | { 120 | _delegate = delegate; 121 | if ( delegateCallbackQueue != _delegateCallbackQueue ) { 122 | _delegateCallbackQueue = delegateCallbackQueue; 123 | } 124 | } 125 | } 126 | 127 | - (void)prepareToRecord 128 | { 129 | @synchronized( self ) 130 | { 131 | if (_status != WriterStatusIdle){ 132 | @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Already prepared, cannot prepare again" userInfo:nil]; 133 | return; 134 | } 135 | [self transitionToStatus:WriterStatusPreparingToRecord error:nil]; 136 | } 137 | 138 | dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0 ), ^{ 139 | @autoreleasepool 140 | { 141 | NSError *error = nil; 142 | // AVAssetWriter will not write over an existing file. 143 | [[NSFileManager defaultManager] removeItemAtURL:_URL error:NULL]; 144 | _assetWriter = [[AVAssetWriter alloc] initWithURL:_URL fileType:AVFileTypeQuickTimeMovie error:&error]; 145 | 146 | // Create and add inputs 147 | if (!error && _videoTrackSourceFormatDescription) { 148 | [self setupAssetWriterVideoInputWithSourceFormatDescription:_videoTrackSourceFormatDescription transform:_videoTrackTransform settings:_videoTrackSettings error:&error]; 149 | } 150 | if(!error && _audioTrackSourceFormatDescription) { 151 | [self setupAssetWriterAudioInputWithSourceFormatDescription:_audioTrackSourceFormatDescription settings:_audioTrackSettings error:&error]; 152 | } 153 | if(!error) { 154 | BOOL success = [_assetWriter startWriting]; 155 | if (!success) { 156 | error = _assetWriter.error; 157 | } 158 | } 159 | 160 | @synchronized(self) 161 | { 162 | if (error) { 163 | [self transitionToStatus:WriterStatusFailed error:error]; 164 | } else { 165 | [self transitionToStatus:WriterStatusRecording error:nil]; 166 | } 167 | } 168 | } 169 | } ); 170 | } 171 | 172 | - (void)appendVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer 173 | { 174 | [self appendSampleBuffer:sampleBuffer ofMediaType:AVMediaTypeVideo]; 175 | } 176 | 177 | - (void)appendAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer 178 | { 179 | [self appendSampleBuffer:sampleBuffer ofMediaType:AVMediaTypeAudio]; 180 | } 181 | 182 | - (void)finishRecording 183 | { 184 | @synchronized(self) 185 | { 186 | BOOL shouldFinishRecording = NO; 187 | switch (_status) 188 | { 189 | case WriterStatusIdle: 190 | case WriterStatusPreparingToRecord: 191 | case WriterStatusFinishingRecordingPart1: 192 | case WriterStatusFinishingRecordingPart2: 193 | case WriterStatusFinished: 194 | @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Not recording" userInfo:nil]; 195 | break; 196 | case WriterStatusFailed: 197 | // From the client's perspective the movie recorder can asynchronously transition to an error state as the result of an append. 198 | // Because of this we are lenient when finishRecording is called and we are in an error state. 199 | NSLog( @"Recording has failed, nothing to do" ); 200 | break; 201 | case WriterStatusRecording: 202 | shouldFinishRecording = YES; 203 | break; 204 | } 205 | 206 | if (shouldFinishRecording){ 207 | [self transitionToStatus:WriterStatusFinishingRecordingPart1 error:nil]; 208 | } 209 | else { 210 | return; 211 | } 212 | } 213 | 214 | dispatch_async( _writingQueue, ^{ 215 | @autoreleasepool 216 | { 217 | @synchronized(self) 218 | { 219 | // We may have transitioned to an error state as we appended inflight buffers. In that case there is nothing to do now. 220 | if ( _status != WriterStatusFinishingRecordingPart1 ) { 221 | return; 222 | } 223 | 224 | // It is not safe to call -[AVAssetWriter finishWriting*] concurrently with -[AVAssetWriterInput appendSampleBuffer:] 225 | // We transition to MovieRecorderStatusFinishingRecordingPart2 while on _writingQueue, which guarantees that no more buffers will be appended. 226 | [self transitionToStatus:WriterStatusFinishingRecordingPart2 error:nil]; 227 | } 228 | [_assetWriter finishWritingWithCompletionHandler:^{ 229 | @synchronized( self ) 230 | { 231 | NSError *error = _assetWriter.error; 232 | if(error){ 233 | [self transitionToStatus:WriterStatusFailed error:error]; 234 | } 235 | else { 236 | [self transitionToStatus:WriterStatusFinished error:nil]; 237 | } 238 | } 239 | }]; 240 | } 241 | } ); 242 | } 243 | 244 | 245 | #pragma mark - Private methods 246 | 247 | - (BOOL)setupAssetWriterAudioInputWithSourceFormatDescription:(CMFormatDescriptionRef)audioFormatDescription settings:(NSDictionary *)audioSettings error:(NSError **)errorOut 248 | { 249 | if (!audioSettings) { 250 | audioSettings = @{ AVFormatIDKey : @(kAudioFormatMPEG4AAC) }; 251 | } 252 | 253 | if ( [_assetWriter canApplyOutputSettings:audioSettings forMediaType:AVMediaTypeAudio] ){ 254 | _audioInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:audioSettings sourceFormatHint:audioFormatDescription]; 255 | _audioInput.expectsMediaDataInRealTime = YES; 256 | 257 | if ([_assetWriter canAddInput:_audioInput]){ 258 | [_assetWriter addInput:_audioInput]; 259 | } else { 260 | if (errorOut ) { 261 | *errorOut = [self cannotSetupInputError]; 262 | } 263 | return NO; 264 | } 265 | } 266 | else 267 | { 268 | if (errorOut) { 269 | *errorOut = [self cannotSetupInputError]; 270 | } 271 | return NO; 272 | } 273 | 274 | return YES; 275 | } 276 | 277 | - (BOOL)setupAssetWriterVideoInputWithSourceFormatDescription:(CMFormatDescriptionRef)videoFormatDescription transform:(CGAffineTransform)transform settings:(NSDictionary *)videoSettings error:(NSError **)errorOut 278 | { 279 | if (!videoSettings){ 280 | videoSettings = [self fallbackVideoSettingsForSourceFormatDescription:videoFormatDescription]; 281 | } 282 | 283 | if ([_assetWriter canApplyOutputSettings:videoSettings forMediaType:AVMediaTypeVideo]){ 284 | _videoInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo outputSettings:videoSettings sourceFormatHint:videoFormatDescription]; 285 | _videoInput.expectsMediaDataInRealTime = YES; 286 | _videoInput.transform = transform; 287 | 288 | if ([_assetWriter canAddInput:_videoInput]){ 289 | [_assetWriter addInput:_videoInput]; 290 | } else { 291 | if ( errorOut ) { 292 | *errorOut = [self cannotSetupInputError]; 293 | } 294 | return NO; 295 | } 296 | } else { 297 | if ( errorOut ) { 298 | *errorOut = [self cannotSetupInputError]; 299 | } 300 | return NO; 301 | } 302 | return YES; 303 | } 304 | 305 | - (NSDictionary *)fallbackVideoSettingsForSourceFormatDescription:(CMFormatDescriptionRef)videoFormatDescription 306 | { 307 | float bitsPerPixel; 308 | CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions(videoFormatDescription); 309 | int numPixels = dimensions.width * dimensions.height; 310 | int bitsPerSecond; 311 | 312 | NSLog( @"No video settings provided, using default settings" ); 313 | 314 | // Assume that lower-than-SD resolutions are intended for streaming, and use a lower bitrate 315 | if ( numPixels < ( 640 * 480 ) ) { 316 | bitsPerPixel = 4.05; // This bitrate approximately matches the quality produced by AVCaptureSessionPresetMedium or Low. 317 | } 318 | else { 319 | bitsPerPixel = 10.1; // This bitrate approximately matches the quality produced by AVCaptureSessionPresetHigh. 320 | } 321 | 322 | bitsPerSecond = numPixels * bitsPerPixel; 323 | 324 | NSDictionary *compressionProperties = @{ AVVideoAverageBitRateKey : @(bitsPerSecond), 325 | AVVideoExpectedSourceFrameRateKey : @(30), 326 | AVVideoMaxKeyFrameIntervalKey : @(30) }; 327 | 328 | return @{ AVVideoCodecKey : AVVideoCodecH264, 329 | AVVideoWidthKey : @(dimensions.width), 330 | AVVideoHeightKey : @(dimensions.height), 331 | AVVideoCompressionPropertiesKey : compressionProperties }; 332 | 333 | } 334 | 335 | - (void)appendSampleBuffer:(CMSampleBufferRef)sampleBuffer ofMediaType:(NSString *)mediaType 336 | { 337 | if(sampleBuffer == NULL){ 338 | @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"NULL sample buffer" userInfo:nil]; 339 | return; 340 | } 341 | 342 | @synchronized(self){ 343 | if (_status < WriterStatusRecording){ 344 | @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Not ready to record yet" userInfo:nil]; 345 | return; 346 | } 347 | } 348 | 349 | CFRetain(sampleBuffer); 350 | dispatch_async( _writingQueue, ^{ 351 | @autoreleasepool 352 | { 353 | @synchronized(self) 354 | { 355 | // From the client's perspective the movie recorder can asynchronously transition to an error state as the result of an append. 356 | // Because of this we are lenient when samples are appended and we are no longer recording. 357 | // Instead of throwing an exception we just release the sample buffers and return. 358 | if (_status > WriterStatusFinishingRecordingPart1){ 359 | CFRelease(sampleBuffer); 360 | return; 361 | } 362 | } 363 | 364 | if(!_haveStartedSession && mediaType == AVMediaTypeVideo) { 365 | [_assetWriter startSessionAtSourceTime:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)]; 366 | _haveStartedSession = YES; 367 | } 368 | 369 | AVAssetWriterInput *input = ( mediaType == AVMediaTypeVideo ) ? _videoInput : _audioInput; 370 | 371 | if(input.readyForMoreMediaData){ 372 | BOOL success = [input appendSampleBuffer:sampleBuffer]; 373 | if (!success){ 374 | NSError *error = _assetWriter.error; 375 | @synchronized(self){ 376 | [self transitionToStatus:WriterStatusFailed error:error]; 377 | } 378 | } 379 | } else { 380 | NSLog( @"%@ input not ready for more media data, dropping buffer", mediaType ); 381 | } 382 | CFRelease(sampleBuffer); 383 | } 384 | } ); 385 | } 386 | 387 | // call under @synchonized( self ) 388 | - (void)transitionToStatus:(WriterStatus)newStatus error:(NSError *)error 389 | { 390 | BOOL shouldNotifyDelegate = NO; 391 | 392 | if (newStatus != _status){ 393 | // terminal states 394 | if ((newStatus == WriterStatusFinished) || (newStatus == WriterStatusFailed)){ 395 | shouldNotifyDelegate = YES; 396 | // make sure there are no more sample buffers in flight before we tear down the asset writer and inputs 397 | 398 | dispatch_async(_writingQueue, ^{ 399 | _assetWriter = nil; 400 | _videoInput = nil; 401 | _audioInput = nil; 402 | if (newStatus == WriterStatusFailed) { 403 | [[NSFileManager defaultManager] removeItemAtURL:_URL error:NULL]; 404 | } 405 | } ); 406 | } else if (newStatus == WriterStatusRecording){ 407 | shouldNotifyDelegate = YES; 408 | } 409 | _status = newStatus; 410 | } 411 | 412 | if (shouldNotifyDelegate && self.delegate){ 413 | dispatch_async( _delegateCallbackQueue, ^{ 414 | 415 | @autoreleasepool 416 | { 417 | switch(newStatus){ 418 | case WriterStatusRecording: 419 | [self.delegate writerCoordinatorDidFinishPreparing:self]; 420 | break; 421 | case WriterStatusFinished: 422 | [self.delegate writerCoordinatorDidFinishRecording:self]; 423 | break; 424 | case WriterStatusFailed: 425 | [self.delegate writerCoordinator:self didFailWithError:error]; 426 | break; 427 | default: 428 | break; 429 | } 430 | } 431 | }); 432 | } 433 | } 434 | 435 | - (NSError *)cannotSetupInputError 436 | { 437 | NSString *localizedDescription = NSLocalizedString( @"Recording cannot be started", nil ); 438 | NSString *localizedFailureReason = NSLocalizedString( @"Cannot setup asset writer input.", nil ); 439 | NSDictionary *errorDict = @{ NSLocalizedDescriptionKey : localizedDescription, 440 | NSLocalizedFailureReasonErrorKey : localizedFailureReason }; 441 | return [NSError errorWithDomain:@"com.example" code:0 userInfo:errorDict]; 442 | } 443 | 444 | @end 445 | -------------------------------------------------------------------------------- /VideoCaptureDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 638854751AD6D9F7007277EA /* IDCaptureSessionPipelineViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 638854741AD6D9F7007277EA /* IDCaptureSessionPipelineViewController.m */; }; 11 | 638854771AD70862007277EA /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 638854761AD70862007277EA /* AssetsLibrary.framework */; }; 12 | 63AC51F11AD66EE700E0B680 /* IDCaptureSessionMovieFileOutputCoordinator.m in Sources */ = {isa = PBXBuildFile; fileRef = 63AC51F01AD66EE700E0B680 /* IDCaptureSessionMovieFileOutputCoordinator.m */; }; 13 | 63AC51F51AD6736300E0B680 /* IDFileManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 63AC51F41AD6736300E0B680 /* IDFileManager.m */; }; 14 | 63AC51F81AD6799300E0B680 /* IDCaptureSessionAssetWriterCoordinator.m in Sources */ = {isa = PBXBuildFile; fileRef = 63AC51F71AD6799300E0B680 /* IDCaptureSessionAssetWriterCoordinator.m */; }; 15 | 63AC51FD1AD6934D00E0B680 /* IDAssetWriterCoordinator.m in Sources */ = {isa = PBXBuildFile; fileRef = 63AC51FC1AD6934D00E0B680 /* IDAssetWriterCoordinator.m */; }; 16 | 63AC52011AD6C48600E0B680 /* IDPermissionsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 63AC52001AD6C48600E0B680 /* IDPermissionsManager.m */; }; 17 | 63C7CD9F1ACC068F00CD4024 /* IDImagePickerCoordinator.m in Sources */ = {isa = PBXBuildFile; fileRef = 63C7CD9E1ACC068F00CD4024 /* IDImagePickerCoordinator.m */; }; 18 | 63C7CDA31ACC0A4700CD4024 /* IDCaptureSessionCoordinator.m in Sources */ = {isa = PBXBuildFile; fileRef = 63C7CDA21ACC0A4700CD4024 /* IDCaptureSessionCoordinator.m */; }; 19 | 63C7CDA51ACC0AAC00CD4024 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 63C7CDA41ACC0AAC00CD4024 /* AVFoundation.framework */; }; 20 | 63F381CA1ACAC6A8001ECC7E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 63F381C91ACAC6A8001ECC7E /* main.m */; }; 21 | 63F381CD1ACAC6A8001ECC7E /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 63F381CC1ACAC6A8001ECC7E /* AppDelegate.m */; }; 22 | 63F381D31ACAC6A8001ECC7E /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 63F381D21ACAC6A8001ECC7E /* ViewController.m */; }; 23 | 63F381D61ACAC6A8001ECC7E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 63F381D41ACAC6A8001ECC7E /* Main.storyboard */; }; 24 | 63F381D81ACAC6A8001ECC7E /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 63F381D71ACAC6A8001ECC7E /* Images.xcassets */; }; 25 | 63F381DB1ACAC6A8001ECC7E /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 63F381D91ACAC6A8001ECC7E /* LaunchScreen.xib */; }; 26 | 63F381E71ACAC6A8001ECC7E /* VideoCaptureDemoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 63F381E61ACAC6A8001ECC7E /* VideoCaptureDemoTests.m */; }; 27 | 63F381F11ACAC931001ECC7E /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 63F381F01ACAC931001ECC7E /* MobileCoreServices.framework */; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXContainerItemProxy section */ 31 | 63F381E11ACAC6A8001ECC7E /* PBXContainerItemProxy */ = { 32 | isa = PBXContainerItemProxy; 33 | containerPortal = 63F381BC1ACAC6A8001ECC7E /* Project object */; 34 | proxyType = 1; 35 | remoteGlobalIDString = 63F381C31ACAC6A8001ECC7E; 36 | remoteInfo = VideoCaptureDemo; 37 | }; 38 | /* End PBXContainerItemProxy section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | 638854731AD6D9F7007277EA /* IDCaptureSessionPipelineViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IDCaptureSessionPipelineViewController.h; sourceTree = ""; }; 42 | 638854741AD6D9F7007277EA /* IDCaptureSessionPipelineViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IDCaptureSessionPipelineViewController.m; sourceTree = ""; }; 43 | 638854761AD70862007277EA /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; }; 44 | 63AC51EF1AD66EE700E0B680 /* IDCaptureSessionMovieFileOutputCoordinator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IDCaptureSessionMovieFileOutputCoordinator.h; sourceTree = ""; }; 45 | 63AC51F01AD66EE700E0B680 /* IDCaptureSessionMovieFileOutputCoordinator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IDCaptureSessionMovieFileOutputCoordinator.m; sourceTree = ""; }; 46 | 63AC51F31AD6736300E0B680 /* IDFileManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IDFileManager.h; sourceTree = ""; }; 47 | 63AC51F41AD6736300E0B680 /* IDFileManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IDFileManager.m; sourceTree = ""; }; 48 | 63AC51F61AD6799300E0B680 /* IDCaptureSessionAssetWriterCoordinator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IDCaptureSessionAssetWriterCoordinator.h; sourceTree = ""; }; 49 | 63AC51F71AD6799300E0B680 /* IDCaptureSessionAssetWriterCoordinator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IDCaptureSessionAssetWriterCoordinator.m; sourceTree = ""; }; 50 | 63AC51FB1AD6934D00E0B680 /* IDAssetWriterCoordinator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IDAssetWriterCoordinator.h; sourceTree = ""; }; 51 | 63AC51FC1AD6934D00E0B680 /* IDAssetWriterCoordinator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IDAssetWriterCoordinator.m; sourceTree = ""; }; 52 | 63AC51FF1AD6C48600E0B680 /* IDPermissionsManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IDPermissionsManager.h; sourceTree = ""; }; 53 | 63AC52001AD6C48600E0B680 /* IDPermissionsManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IDPermissionsManager.m; sourceTree = ""; }; 54 | 63C7CD9D1ACC068F00CD4024 /* IDImagePickerCoordinator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IDImagePickerCoordinator.h; sourceTree = ""; }; 55 | 63C7CD9E1ACC068F00CD4024 /* IDImagePickerCoordinator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IDImagePickerCoordinator.m; sourceTree = ""; }; 56 | 63C7CDA11ACC0A4700CD4024 /* IDCaptureSessionCoordinator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IDCaptureSessionCoordinator.h; sourceTree = ""; }; 57 | 63C7CDA21ACC0A4700CD4024 /* IDCaptureSessionCoordinator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IDCaptureSessionCoordinator.m; sourceTree = ""; }; 58 | 63C7CDA41ACC0AAC00CD4024 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; 59 | 63F381C41ACAC6A8001ECC7E /* VideoCaptureDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VideoCaptureDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 60 | 63F381C81ACAC6A8001ECC7E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 61 | 63F381C91ACAC6A8001ECC7E /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 62 | 63F381CB1ACAC6A8001ECC7E /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 63 | 63F381CC1ACAC6A8001ECC7E /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 64 | 63F381D11ACAC6A8001ECC7E /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 65 | 63F381D21ACAC6A8001ECC7E /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 66 | 63F381D51ACAC6A8001ECC7E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 67 | 63F381D71ACAC6A8001ECC7E /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 68 | 63F381DA1ACAC6A8001ECC7E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 69 | 63F381E01ACAC6A8001ECC7E /* VideoCaptureDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = VideoCaptureDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 70 | 63F381E51ACAC6A8001ECC7E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 71 | 63F381E61ACAC6A8001ECC7E /* VideoCaptureDemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VideoCaptureDemoTests.m; sourceTree = ""; }; 72 | 63F381F01ACAC931001ECC7E /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; 73 | /* End PBXFileReference section */ 74 | 75 | /* Begin PBXFrameworksBuildPhase section */ 76 | 63F381C11ACAC6A8001ECC7E /* Frameworks */ = { 77 | isa = PBXFrameworksBuildPhase; 78 | buildActionMask = 2147483647; 79 | files = ( 80 | 638854771AD70862007277EA /* AssetsLibrary.framework in Frameworks */, 81 | 63C7CDA51ACC0AAC00CD4024 /* AVFoundation.framework in Frameworks */, 82 | 63F381F11ACAC931001ECC7E /* MobileCoreServices.framework in Frameworks */, 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | 63F381DD1ACAC6A8001ECC7E /* Frameworks */ = { 87 | isa = PBXFrameworksBuildPhase; 88 | buildActionMask = 2147483647; 89 | files = ( 90 | ); 91 | runOnlyForDeploymentPostprocessing = 0; 92 | }; 93 | /* End PBXFrameworksBuildPhase section */ 94 | 95 | /* Begin PBXGroup section */ 96 | 63AC51F21AD6734500E0B680 /* Utilities */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 63AC51FF1AD6C48600E0B680 /* IDPermissionsManager.h */, 100 | 63AC52001AD6C48600E0B680 /* IDPermissionsManager.m */, 101 | 63AC51F31AD6736300E0B680 /* IDFileManager.h */, 102 | 63AC51F41AD6736300E0B680 /* IDFileManager.m */, 103 | ); 104 | name = Utilities; 105 | sourceTree = ""; 106 | }; 107 | 63AC51F91AD6924E00E0B680 /* AVMovieFileOutput */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 63AC51EF1AD66EE700E0B680 /* IDCaptureSessionMovieFileOutputCoordinator.h */, 111 | 63AC51F01AD66EE700E0B680 /* IDCaptureSessionMovieFileOutputCoordinator.m */, 112 | ); 113 | name = AVMovieFileOutput; 114 | sourceTree = ""; 115 | }; 116 | 63AC51FA1AD6927700E0B680 /* AVAssetWriter */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | 63AC51F61AD6799300E0B680 /* IDCaptureSessionAssetWriterCoordinator.h */, 120 | 63AC51F71AD6799300E0B680 /* IDCaptureSessionAssetWriterCoordinator.m */, 121 | 63AC51FB1AD6934D00E0B680 /* IDAssetWriterCoordinator.h */, 122 | 63AC51FC1AD6934D00E0B680 /* IDAssetWriterCoordinator.m */, 123 | ); 124 | name = AVAssetWriter; 125 | sourceTree = ""; 126 | }; 127 | 63C7CD9C1ACC067500CD4024 /* UIImagePickerController */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 63C7CD9D1ACC068F00CD4024 /* IDImagePickerCoordinator.h */, 131 | 63C7CD9E1ACC068F00CD4024 /* IDImagePickerCoordinator.m */, 132 | ); 133 | name = UIImagePickerController; 134 | sourceTree = ""; 135 | }; 136 | 63C7CDA01ACC090700CD4024 /* AVFoundation */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | 63AC51F91AD6924E00E0B680 /* AVMovieFileOutput */, 140 | 63AC51FA1AD6927700E0B680 /* AVAssetWriter */, 141 | 63C7CDA11ACC0A4700CD4024 /* IDCaptureSessionCoordinator.h */, 142 | 63C7CDA21ACC0A4700CD4024 /* IDCaptureSessionCoordinator.m */, 143 | ); 144 | name = AVFoundation; 145 | sourceTree = ""; 146 | }; 147 | 63F381BB1ACAC6A8001ECC7E = { 148 | isa = PBXGroup; 149 | children = ( 150 | 63F381C61ACAC6A8001ECC7E /* VideoCaptureDemo */, 151 | 63F381E31ACAC6A8001ECC7E /* VideoCaptureDemoTests */, 152 | 63F381C51ACAC6A8001ECC7E /* Products */, 153 | ); 154 | sourceTree = ""; 155 | }; 156 | 63F381C51ACAC6A8001ECC7E /* Products */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | 63F381C41ACAC6A8001ECC7E /* VideoCaptureDemo.app */, 160 | 63F381E01ACAC6A8001ECC7E /* VideoCaptureDemoTests.xctest */, 161 | ); 162 | name = Products; 163 | sourceTree = ""; 164 | }; 165 | 63F381C61ACAC6A8001ECC7E /* VideoCaptureDemo */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | 63AC51F21AD6734500E0B680 /* Utilities */, 169 | 63C7CDA01ACC090700CD4024 /* AVFoundation */, 170 | 63C7CD9C1ACC067500CD4024 /* UIImagePickerController */, 171 | 63F381CB1ACAC6A8001ECC7E /* AppDelegate.h */, 172 | 63F381CC1ACAC6A8001ECC7E /* AppDelegate.m */, 173 | 63F381D11ACAC6A8001ECC7E /* ViewController.h */, 174 | 63F381D21ACAC6A8001ECC7E /* ViewController.m */, 175 | 638854731AD6D9F7007277EA /* IDCaptureSessionPipelineViewController.h */, 176 | 638854741AD6D9F7007277EA /* IDCaptureSessionPipelineViewController.m */, 177 | 63F381D41ACAC6A8001ECC7E /* Main.storyboard */, 178 | 63F381D71ACAC6A8001ECC7E /* Images.xcassets */, 179 | 63F381D91ACAC6A8001ECC7E /* LaunchScreen.xib */, 180 | 63F381C71ACAC6A8001ECC7E /* Supporting Files */, 181 | ); 182 | path = VideoCaptureDemo; 183 | sourceTree = ""; 184 | }; 185 | 63F381C71ACAC6A8001ECC7E /* Supporting Files */ = { 186 | isa = PBXGroup; 187 | children = ( 188 | 638854761AD70862007277EA /* AssetsLibrary.framework */, 189 | 63C7CDA41ACC0AAC00CD4024 /* AVFoundation.framework */, 190 | 63F381F01ACAC931001ECC7E /* MobileCoreServices.framework */, 191 | 63F381C81ACAC6A8001ECC7E /* Info.plist */, 192 | 63F381C91ACAC6A8001ECC7E /* main.m */, 193 | ); 194 | name = "Supporting Files"; 195 | sourceTree = ""; 196 | }; 197 | 63F381E31ACAC6A8001ECC7E /* VideoCaptureDemoTests */ = { 198 | isa = PBXGroup; 199 | children = ( 200 | 63F381E61ACAC6A8001ECC7E /* VideoCaptureDemoTests.m */, 201 | 63F381E41ACAC6A8001ECC7E /* Supporting Files */, 202 | ); 203 | path = VideoCaptureDemoTests; 204 | sourceTree = ""; 205 | }; 206 | 63F381E41ACAC6A8001ECC7E /* Supporting Files */ = { 207 | isa = PBXGroup; 208 | children = ( 209 | 63F381E51ACAC6A8001ECC7E /* Info.plist */, 210 | ); 211 | name = "Supporting Files"; 212 | sourceTree = ""; 213 | }; 214 | /* End PBXGroup section */ 215 | 216 | /* Begin PBXNativeTarget section */ 217 | 63F381C31ACAC6A8001ECC7E /* VideoCaptureDemo */ = { 218 | isa = PBXNativeTarget; 219 | buildConfigurationList = 63F381EA1ACAC6A8001ECC7E /* Build configuration list for PBXNativeTarget "VideoCaptureDemo" */; 220 | buildPhases = ( 221 | 63F381C01ACAC6A8001ECC7E /* Sources */, 222 | 63F381C11ACAC6A8001ECC7E /* Frameworks */, 223 | 63F381C21ACAC6A8001ECC7E /* Resources */, 224 | ); 225 | buildRules = ( 226 | ); 227 | dependencies = ( 228 | ); 229 | name = VideoCaptureDemo; 230 | productName = VideoCaptureDemo; 231 | productReference = 63F381C41ACAC6A8001ECC7E /* VideoCaptureDemo.app */; 232 | productType = "com.apple.product-type.application"; 233 | }; 234 | 63F381DF1ACAC6A8001ECC7E /* VideoCaptureDemoTests */ = { 235 | isa = PBXNativeTarget; 236 | buildConfigurationList = 63F381ED1ACAC6A8001ECC7E /* Build configuration list for PBXNativeTarget "VideoCaptureDemoTests" */; 237 | buildPhases = ( 238 | 63F381DC1ACAC6A8001ECC7E /* Sources */, 239 | 63F381DD1ACAC6A8001ECC7E /* Frameworks */, 240 | 63F381DE1ACAC6A8001ECC7E /* Resources */, 241 | ); 242 | buildRules = ( 243 | ); 244 | dependencies = ( 245 | 63F381E21ACAC6A8001ECC7E /* PBXTargetDependency */, 246 | ); 247 | name = VideoCaptureDemoTests; 248 | productName = VideoCaptureDemoTests; 249 | productReference = 63F381E01ACAC6A8001ECC7E /* VideoCaptureDemoTests.xctest */; 250 | productType = "com.apple.product-type.bundle.unit-test"; 251 | }; 252 | /* End PBXNativeTarget section */ 253 | 254 | /* Begin PBXProject section */ 255 | 63F381BC1ACAC6A8001ECC7E /* Project object */ = { 256 | isa = PBXProject; 257 | attributes = { 258 | LastUpgradeCheck = 0620; 259 | ORGANIZATIONNAME = Infoding; 260 | TargetAttributes = { 261 | 63F381C31ACAC6A8001ECC7E = { 262 | CreatedOnToolsVersion = 6.2; 263 | }; 264 | 63F381DF1ACAC6A8001ECC7E = { 265 | CreatedOnToolsVersion = 6.2; 266 | TestTargetID = 63F381C31ACAC6A8001ECC7E; 267 | }; 268 | }; 269 | }; 270 | buildConfigurationList = 63F381BF1ACAC6A8001ECC7E /* Build configuration list for PBXProject "VideoCaptureDemo" */; 271 | compatibilityVersion = "Xcode 3.2"; 272 | developmentRegion = English; 273 | hasScannedForEncodings = 0; 274 | knownRegions = ( 275 | en, 276 | Base, 277 | ); 278 | mainGroup = 63F381BB1ACAC6A8001ECC7E; 279 | productRefGroup = 63F381C51ACAC6A8001ECC7E /* Products */; 280 | projectDirPath = ""; 281 | projectRoot = ""; 282 | targets = ( 283 | 63F381C31ACAC6A8001ECC7E /* VideoCaptureDemo */, 284 | 63F381DF1ACAC6A8001ECC7E /* VideoCaptureDemoTests */, 285 | ); 286 | }; 287 | /* End PBXProject section */ 288 | 289 | /* Begin PBXResourcesBuildPhase section */ 290 | 63F381C21ACAC6A8001ECC7E /* Resources */ = { 291 | isa = PBXResourcesBuildPhase; 292 | buildActionMask = 2147483647; 293 | files = ( 294 | 63F381D61ACAC6A8001ECC7E /* Main.storyboard in Resources */, 295 | 63F381DB1ACAC6A8001ECC7E /* LaunchScreen.xib in Resources */, 296 | 63F381D81ACAC6A8001ECC7E /* Images.xcassets in Resources */, 297 | ); 298 | runOnlyForDeploymentPostprocessing = 0; 299 | }; 300 | 63F381DE1ACAC6A8001ECC7E /* Resources */ = { 301 | isa = PBXResourcesBuildPhase; 302 | buildActionMask = 2147483647; 303 | files = ( 304 | ); 305 | runOnlyForDeploymentPostprocessing = 0; 306 | }; 307 | /* End PBXResourcesBuildPhase section */ 308 | 309 | /* Begin PBXSourcesBuildPhase section */ 310 | 63F381C01ACAC6A8001ECC7E /* Sources */ = { 311 | isa = PBXSourcesBuildPhase; 312 | buildActionMask = 2147483647; 313 | files = ( 314 | 63F381CD1ACAC6A8001ECC7E /* AppDelegate.m in Sources */, 315 | 63AC51F81AD6799300E0B680 /* IDCaptureSessionAssetWriterCoordinator.m in Sources */, 316 | 63C7CD9F1ACC068F00CD4024 /* IDImagePickerCoordinator.m in Sources */, 317 | 63AC51F51AD6736300E0B680 /* IDFileManager.m in Sources */, 318 | 63C7CDA31ACC0A4700CD4024 /* IDCaptureSessionCoordinator.m in Sources */, 319 | 63AC51FD1AD6934D00E0B680 /* IDAssetWriterCoordinator.m in Sources */, 320 | 63AC52011AD6C48600E0B680 /* IDPermissionsManager.m in Sources */, 321 | 63AC51F11AD66EE700E0B680 /* IDCaptureSessionMovieFileOutputCoordinator.m in Sources */, 322 | 63F381D31ACAC6A8001ECC7E /* ViewController.m in Sources */, 323 | 63F381CA1ACAC6A8001ECC7E /* main.m in Sources */, 324 | 638854751AD6D9F7007277EA /* IDCaptureSessionPipelineViewController.m in Sources */, 325 | ); 326 | runOnlyForDeploymentPostprocessing = 0; 327 | }; 328 | 63F381DC1ACAC6A8001ECC7E /* Sources */ = { 329 | isa = PBXSourcesBuildPhase; 330 | buildActionMask = 2147483647; 331 | files = ( 332 | 63F381E71ACAC6A8001ECC7E /* VideoCaptureDemoTests.m in Sources */, 333 | ); 334 | runOnlyForDeploymentPostprocessing = 0; 335 | }; 336 | /* End PBXSourcesBuildPhase section */ 337 | 338 | /* Begin PBXTargetDependency section */ 339 | 63F381E21ACAC6A8001ECC7E /* PBXTargetDependency */ = { 340 | isa = PBXTargetDependency; 341 | target = 63F381C31ACAC6A8001ECC7E /* VideoCaptureDemo */; 342 | targetProxy = 63F381E11ACAC6A8001ECC7E /* PBXContainerItemProxy */; 343 | }; 344 | /* End PBXTargetDependency section */ 345 | 346 | /* Begin PBXVariantGroup section */ 347 | 63F381D41ACAC6A8001ECC7E /* Main.storyboard */ = { 348 | isa = PBXVariantGroup; 349 | children = ( 350 | 63F381D51ACAC6A8001ECC7E /* Base */, 351 | ); 352 | name = Main.storyboard; 353 | sourceTree = ""; 354 | }; 355 | 63F381D91ACAC6A8001ECC7E /* LaunchScreen.xib */ = { 356 | isa = PBXVariantGroup; 357 | children = ( 358 | 63F381DA1ACAC6A8001ECC7E /* Base */, 359 | ); 360 | name = LaunchScreen.xib; 361 | sourceTree = ""; 362 | }; 363 | /* End PBXVariantGroup section */ 364 | 365 | /* Begin XCBuildConfiguration section */ 366 | 63F381E81ACAC6A8001ECC7E /* Debug */ = { 367 | isa = XCBuildConfiguration; 368 | buildSettings = { 369 | ALWAYS_SEARCH_USER_PATHS = NO; 370 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 371 | CLANG_CXX_LIBRARY = "libc++"; 372 | CLANG_ENABLE_MODULES = YES; 373 | CLANG_ENABLE_OBJC_ARC = YES; 374 | CLANG_WARN_BOOL_CONVERSION = YES; 375 | CLANG_WARN_CONSTANT_CONVERSION = YES; 376 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 377 | CLANG_WARN_EMPTY_BODY = YES; 378 | CLANG_WARN_ENUM_CONVERSION = YES; 379 | CLANG_WARN_INT_CONVERSION = YES; 380 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 381 | CLANG_WARN_UNREACHABLE_CODE = YES; 382 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 383 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 384 | COPY_PHASE_STRIP = NO; 385 | ENABLE_STRICT_OBJC_MSGSEND = YES; 386 | GCC_C_LANGUAGE_STANDARD = gnu99; 387 | GCC_DYNAMIC_NO_PIC = NO; 388 | GCC_OPTIMIZATION_LEVEL = 0; 389 | GCC_PREPROCESSOR_DEFINITIONS = ( 390 | "DEBUG=1", 391 | "$(inherited)", 392 | ); 393 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 394 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 395 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 396 | GCC_WARN_UNDECLARED_SELECTOR = YES; 397 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 398 | GCC_WARN_UNUSED_FUNCTION = YES; 399 | GCC_WARN_UNUSED_VARIABLE = YES; 400 | IPHONEOS_DEPLOYMENT_TARGET = 8.2; 401 | MTL_ENABLE_DEBUG_INFO = YES; 402 | ONLY_ACTIVE_ARCH = YES; 403 | SDKROOT = iphoneos; 404 | }; 405 | name = Debug; 406 | }; 407 | 63F381E91ACAC6A8001ECC7E /* Release */ = { 408 | isa = XCBuildConfiguration; 409 | buildSettings = { 410 | ALWAYS_SEARCH_USER_PATHS = NO; 411 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 412 | CLANG_CXX_LIBRARY = "libc++"; 413 | CLANG_ENABLE_MODULES = YES; 414 | CLANG_ENABLE_OBJC_ARC = YES; 415 | CLANG_WARN_BOOL_CONVERSION = YES; 416 | CLANG_WARN_CONSTANT_CONVERSION = YES; 417 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 418 | CLANG_WARN_EMPTY_BODY = YES; 419 | CLANG_WARN_ENUM_CONVERSION = YES; 420 | CLANG_WARN_INT_CONVERSION = YES; 421 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 422 | CLANG_WARN_UNREACHABLE_CODE = YES; 423 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 424 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 425 | COPY_PHASE_STRIP = NO; 426 | ENABLE_NS_ASSERTIONS = NO; 427 | ENABLE_STRICT_OBJC_MSGSEND = YES; 428 | GCC_C_LANGUAGE_STANDARD = gnu99; 429 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 430 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 431 | GCC_WARN_UNDECLARED_SELECTOR = YES; 432 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 433 | GCC_WARN_UNUSED_FUNCTION = YES; 434 | GCC_WARN_UNUSED_VARIABLE = YES; 435 | IPHONEOS_DEPLOYMENT_TARGET = 8.2; 436 | MTL_ENABLE_DEBUG_INFO = NO; 437 | SDKROOT = iphoneos; 438 | VALIDATE_PRODUCT = YES; 439 | }; 440 | name = Release; 441 | }; 442 | 63F381EB1ACAC6A8001ECC7E /* Debug */ = { 443 | isa = XCBuildConfiguration; 444 | buildSettings = { 445 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 446 | INFOPLIST_FILE = VideoCaptureDemo/Info.plist; 447 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 448 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 449 | PRODUCT_NAME = "$(TARGET_NAME)"; 450 | }; 451 | name = Debug; 452 | }; 453 | 63F381EC1ACAC6A8001ECC7E /* Release */ = { 454 | isa = XCBuildConfiguration; 455 | buildSettings = { 456 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 457 | INFOPLIST_FILE = VideoCaptureDemo/Info.plist; 458 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 459 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 460 | PRODUCT_NAME = "$(TARGET_NAME)"; 461 | }; 462 | name = Release; 463 | }; 464 | 63F381EE1ACAC6A8001ECC7E /* Debug */ = { 465 | isa = XCBuildConfiguration; 466 | buildSettings = { 467 | BUNDLE_LOADER = "$(TEST_HOST)"; 468 | FRAMEWORK_SEARCH_PATHS = ( 469 | "$(SDKROOT)/Developer/Library/Frameworks", 470 | "$(inherited)", 471 | ); 472 | GCC_PREPROCESSOR_DEFINITIONS = ( 473 | "DEBUG=1", 474 | "$(inherited)", 475 | ); 476 | INFOPLIST_FILE = VideoCaptureDemoTests/Info.plist; 477 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 478 | PRODUCT_NAME = "$(TARGET_NAME)"; 479 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/VideoCaptureDemo.app/VideoCaptureDemo"; 480 | }; 481 | name = Debug; 482 | }; 483 | 63F381EF1ACAC6A8001ECC7E /* Release */ = { 484 | isa = XCBuildConfiguration; 485 | buildSettings = { 486 | BUNDLE_LOADER = "$(TEST_HOST)"; 487 | FRAMEWORK_SEARCH_PATHS = ( 488 | "$(SDKROOT)/Developer/Library/Frameworks", 489 | "$(inherited)", 490 | ); 491 | INFOPLIST_FILE = VideoCaptureDemoTests/Info.plist; 492 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 493 | PRODUCT_NAME = "$(TARGET_NAME)"; 494 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/VideoCaptureDemo.app/VideoCaptureDemo"; 495 | }; 496 | name = Release; 497 | }; 498 | /* End XCBuildConfiguration section */ 499 | 500 | /* Begin XCConfigurationList section */ 501 | 63F381BF1ACAC6A8001ECC7E /* Build configuration list for PBXProject "VideoCaptureDemo" */ = { 502 | isa = XCConfigurationList; 503 | buildConfigurations = ( 504 | 63F381E81ACAC6A8001ECC7E /* Debug */, 505 | 63F381E91ACAC6A8001ECC7E /* Release */, 506 | ); 507 | defaultConfigurationIsVisible = 0; 508 | defaultConfigurationName = Release; 509 | }; 510 | 63F381EA1ACAC6A8001ECC7E /* Build configuration list for PBXNativeTarget "VideoCaptureDemo" */ = { 511 | isa = XCConfigurationList; 512 | buildConfigurations = ( 513 | 63F381EB1ACAC6A8001ECC7E /* Debug */, 514 | 63F381EC1ACAC6A8001ECC7E /* Release */, 515 | ); 516 | defaultConfigurationIsVisible = 0; 517 | defaultConfigurationName = Release; 518 | }; 519 | 63F381ED1ACAC6A8001ECC7E /* Build configuration list for PBXNativeTarget "VideoCaptureDemoTests" */ = { 520 | isa = XCConfigurationList; 521 | buildConfigurations = ( 522 | 63F381EE1ACAC6A8001ECC7E /* Debug */, 523 | 63F381EF1ACAC6A8001ECC7E /* Release */, 524 | ); 525 | defaultConfigurationIsVisible = 0; 526 | defaultConfigurationName = Release; 527 | }; 528 | /* End XCConfigurationList section */ 529 | }; 530 | rootObject = 63F381BC1ACAC6A8001ECC7E /* Project object */; 531 | } 532 | --------------------------------------------------------------------------------