├── 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 |
--------------------------------------------------------------------------------