├── architecture.png
├── SCCamera
├── .DS_Store
├── Assets.xcassets
│ ├── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── ViewController.h
├── Camera
│ ├── View
│ │ ├── SCFocusView.h
│ │ ├── SCPermissionsView.h
│ │ ├── SCVideoPreviewView.h
│ │ ├── SCFocusView.m
│ │ ├── SCCameraView.h
│ │ ├── SCVideoPreviewView.m
│ │ ├── SCPermissionsView.m
│ │ ├── SCCameraView.m
│ │ └── SCCameraView.xib
│ ├── Controller
│ │ ├── SCCameraResultController.h
│ │ ├── SCCameraController.h
│ │ ├── SCShowMovieController.h
│ │ ├── SCFaceDetectionDelegate.h
│ │ ├── SCCameraResultController.m
│ │ ├── SCShowMovieController.m
│ │ ├── SCCameraResultController.xib
│ │ └── SCCameraController.m
│ ├── Category
│ │ ├── UIViewController+SCCamera.h
│ │ ├── AVMetadataFaceObject+Transform.h
│ │ ├── UIView+SCCategory.h
│ │ ├── AVCaptureDevice+SCCategory.h
│ │ ├── UIImage+SCCamera.h
│ │ ├── UIViewController+SCCamera.m
│ │ ├── PHPhotoLibrary+Save.h
│ │ ├── AVMetadataFaceObject+Transform.m
│ │ ├── UIView+SCCategory.m
│ │ ├── PHPhotoLibrary+Save.m
│ │ ├── AVCaptureDevice+SCCategory.m
│ │ └── UIImage+SCCamera.m
│ ├── Model
│ │ ├── SCFaceModel.h
│ │ └── SCFaceModel.m
│ └── Manager
│ │ ├── SCStableCheckTool.h
│ │ ├── SCStillPhotoManager.h
│ │ ├── SCMovieManager.h
│ │ ├── SCPhotoManager.h
│ │ ├── SCMovieFileOutManager.h
│ │ ├── SCCameraManager.h
│ │ ├── SCStableCheckTool.m
│ │ ├── SCStillPhotoManager.m
│ │ ├── SCMovieFileOutManager.m
│ │ ├── SCMovieManager.m
│ │ ├── SCCameraManager.m
│ │ └── SCPhotoManager.m
├── AppDelegate.h
├── main.m
├── ViewController.m
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── Info.plist
└── AppDelegate.m
├── SCCamera.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── project.pbxproj
├── .gitignore
└── README.md
/architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeacenLiu/SCCamera/HEAD/architecture.png
--------------------------------------------------------------------------------
/SCCamera/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeacenLiu/SCCamera/HEAD/SCCamera/.DS_Store
--------------------------------------------------------------------------------
/SCCamera/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/SCCamera.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SCCamera/ViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.h
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/2.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface ViewController : UIViewController
12 |
13 |
14 | @end
15 |
16 |
--------------------------------------------------------------------------------
/SCCamera.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SCCamera/Camera/View/SCFocusView.h:
--------------------------------------------------------------------------------
1 | //
2 | // SCFocusView.h
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/10.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface SCFocusView : UIView
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/SCCamera/AppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.h
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/2.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface AppDelegate : UIResponder
12 |
13 | @property (strong, nonatomic) UIWindow *window;
14 |
15 |
16 | @end
17 |
18 |
--------------------------------------------------------------------------------
/SCCamera/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/2.
6 | // Copyright © 2019 SeacenLiu. 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 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Controller/SCCameraResultController.h:
--------------------------------------------------------------------------------
1 | //
2 | // SCCameraResultController.h
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/4.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface SCCameraResultController : UIViewController
14 | @property (nonatomic, strong) UIImage *img;
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Controller/SCCameraController.h:
--------------------------------------------------------------------------------
1 | //
2 | // SCCameraController.h
3 | // Detector
4 | //
5 | // Created by SeacenLiu on 2019/3/8.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "SCFaceDetectionDelegate.h"
11 |
12 | NS_ASSUME_NONNULL_BEGIN
13 |
14 | @interface SCCameraController : UIViewController
15 |
16 | @property (nonatomic, weak) id faceDetectionDelegate;
17 |
18 | @end
19 |
20 | NS_ASSUME_NONNULL_END
21 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Category/UIViewController+SCCamera.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIViewController+SCCamera.h
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/4.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface UIViewController (SCCamera)
14 |
15 | - (void)showAlertView:(NSString*)message ok:(void(^)(UIAlertAction *action))ok cancel:(void(^)(UIAlertAction *action))cancel;
16 |
17 | @end
18 |
19 | NS_ASSUME_NONNULL_END
20 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Controller/SCShowMovieController.h:
--------------------------------------------------------------------------------
1 | //
2 | // SCShowMovieController.h
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/26.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface SCShowMovieController : UIViewController
14 |
15 | - (instancetype)initWithFileURL:(NSURL*)fileURL;
16 |
17 | + (instancetype)showMovieControllerWithFileURL:(NSURL*)fileURL;
18 |
19 | @end
20 |
21 | NS_ASSUME_NONNULL_END
22 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Model/SCFaceModel.h:
--------------------------------------------------------------------------------
1 | //
2 | // SCFaceModel.h
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/10.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface SCFaceModel : NSObject
14 | @property (nonatomic, assign) NSInteger faceId;
15 | @property (nonatomic, assign) NSInteger count;
16 |
17 | - (instancetype)initWithFaceId:(NSInteger)faceId;
18 | + (instancetype)faceModelWithFaceId:(NSInteger)faceId;
19 | @end
20 |
21 | NS_ASSUME_NONNULL_END
22 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Controller/SCFaceDetectionDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // SCFaceDetectionDelegate.h
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/19.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #ifndef SCFaceDetectionDelegate_h
10 | #define SCFaceDetectionDelegate_h
11 | #import
12 |
13 | @protocol SCFaceDetectionDelegate
14 | - (void)faceDetectionDidDetectFaces:(NSArray*)faces connection:(AVCaptureConnection*)connection;
15 | @end
16 |
17 | #endif /* SCFaceDetectionDelegate_h */
18 |
--------------------------------------------------------------------------------
/SCCamera/Camera/View/SCPermissionsView.h:
--------------------------------------------------------------------------------
1 | //
2 | // SCPermissionsView.h
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/11.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @class SCPermissionsView;
12 | @protocol SCPermissionsViewDelegate
13 | - (void)permissionsViewDidHasAllPermissions:(SCPermissionsView*)pv;
14 | @end
15 |
16 | NS_ASSUME_NONNULL_BEGIN
17 |
18 | @interface SCPermissionsView : UIView
19 | @property (nonatomic, weak) id delegate;
20 | @end
21 |
22 | NS_ASSUME_NONNULL_END
23 |
--------------------------------------------------------------------------------
/SCCamera/ViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.m
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/2.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import "ViewController.h"
10 | #import "SCCameraController.h"
11 |
12 | @interface ViewController ()
13 |
14 | @end
15 |
16 | @implementation ViewController
17 |
18 | - (IBAction)openCamera:(id)sender {
19 | SCCameraController *cc = [SCCameraController new];
20 | [self presentViewController:cc animated:YES completion:nil];
21 | }
22 |
23 | - (void)viewDidLoad {
24 | [super viewDidLoad];
25 | }
26 |
27 |
28 | @end
29 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Controller/SCCameraResultController.m:
--------------------------------------------------------------------------------
1 | //
2 | // SCCameraResultController.m
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/4.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import "SCCameraResultController.h"
10 |
11 | @interface SCCameraResultController ()
12 | @property (weak, nonatomic) IBOutlet UIImageView *imgView;
13 | @end
14 |
15 | @implementation SCCameraResultController
16 |
17 | - (IBAction)closeClick:(id)sender {
18 | [self dismissViewControllerAnimated:YES completion:nil];
19 | }
20 |
21 | - (void)viewDidLoad {
22 | [super viewDidLoad];
23 | self.imgView.image = self.img;
24 | }
25 |
26 | @end
27 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Manager/SCStableCheckTool.h:
--------------------------------------------------------------------------------
1 | //
2 | // SCStableCheckTool.h
3 | // StaticCheckTool
4 | //
5 | // Created by SeacenLiu on 2019/3/21.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface SCStableCheckTool : NSObject
14 |
15 | @property (nonatomic, assign) BOOL isStable;
16 |
17 | @property (nonatomic, assign) NSTimeInterval updateInterval;
18 |
19 | @property (nonatomic, assign) double revMax;
20 |
21 | /** 指定构造函数 */
22 | + (instancetype)stableCheckToolWithRevMax:(double)revMax;
23 |
24 | /** 需要手动开始检测 */
25 | - (void)start;
26 |
27 | /** 手动停止检测 */
28 | - (void)stop;
29 |
30 | @end
31 |
32 | NS_ASSUME_NONNULL_END
33 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Category/AVMetadataFaceObject+Transform.h:
--------------------------------------------------------------------------------
1 | //
2 | // AVMetadataFaceObject+Transform.h
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/19.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface AVMetadataFaceObject (Transform)
14 |
15 | /**
16 | @abstract
17 | 斜倾角转变换矩阵
18 |
19 | @return CATransform3D
20 | */
21 | - (CATransform3D)transformFromRollAngle;
22 |
23 | /**
24 | @abstract
25 | 偏转角转变换矩阵
26 | @discussion
27 | 支持屏幕多方向,若仅支持HOME键在底部的情况,需要做二次转换纠正
28 |
29 | @return CATransform3D
30 | */
31 | - (CATransform3D)transformFromYawAngle;
32 |
33 | @end
34 |
35 | NS_ASSUME_NONNULL_END
36 |
--------------------------------------------------------------------------------
/SCCamera/Camera/View/SCVideoPreviewView.h:
--------------------------------------------------------------------------------
1 | //
2 | // SCVideoPreviewView.h
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/3.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 | #import "SCFaceDetectionDelegate.h"
12 |
13 | NS_ASSUME_NONNULL_BEGIN
14 |
15 | @interface SCVideoPreviewView : UIView
16 |
17 | @property (nonatomic, readonly) AVCaptureVideoPreviewLayer *videoPreviewLayer;
18 | @property (nonatomic, strong) AVCaptureSession *captureSession;
19 | @property (nonatomic, assign, readonly) AVCaptureVideoOrientation videoOrientation;
20 |
21 | - (CGPoint)captureDevicePointForPoint:(CGPoint)point;
22 |
23 | @end
24 |
25 | NS_ASSUME_NONNULL_END
26 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Category/UIView+SCCategory.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+SCCategory.h
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/9.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface UIView (SCCategory)
14 |
15 | @property (nonatomic) CGFloat top;
16 | @property (nonatomic) CGFloat left;
17 | @property (nonatomic) CGFloat right;
18 | @property (nonatomic) CGFloat bottom;
19 |
20 | @property (nonatomic) CGFloat width;
21 | @property (nonatomic) CGFloat height;
22 |
23 | @property (nonatomic) CGFloat centerX;
24 | @property (nonatomic) CGFloat centerY;
25 |
26 | @property (nonatomic) CGPoint origin;
27 | @property (nonatomic) CGSize size;
28 |
29 | - (UIViewController *)viewController;
30 |
31 | @end
32 |
33 | NS_ASSUME_NONNULL_END
34 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Model/SCFaceModel.m:
--------------------------------------------------------------------------------
1 | //
2 | // SCFaceModel.m
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/10.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import "SCFaceModel.h"
10 |
11 | @implementation SCFaceModel
12 |
13 | - (instancetype)initWithFaceId:(NSInteger)faceId {
14 | if (self = [super init]) {
15 | self.faceId = faceId;
16 | self.count = 0;
17 | }
18 | return self;
19 | }
20 |
21 | + (instancetype)faceModelWithFaceId:(NSInteger)faceId {
22 | return [[self alloc] initWithFaceId:faceId];
23 | }
24 |
25 | - (NSUInteger)hash {
26 | return self.faceId;
27 | }
28 |
29 | - (BOOL)isEqual:(id)object {
30 | if ([object isKindOfClass:[SCFaceModel class]]) {
31 | SCFaceModel *obj = (SCFaceModel*)object;
32 | return self.faceId == obj.faceId;
33 | }
34 | return [super isEqual:object];
35 | }
36 |
37 | @end
38 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Manager/SCStillPhotoManager.h:
--------------------------------------------------------------------------------
1 | //
2 | // SCStillPhotoManager.h
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/9.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 | #import
12 |
13 | NS_ASSUME_NONNULL_BEGIN
14 |
15 | @interface SCStillPhotoManager : NSObject
16 |
17 | /// 拍照
18 | - (void)takePhoto:(AVCaptureVideoPreviewLayer*)previewLayer
19 | stillImageOutput:(AVCaptureStillImageOutput*)stillImageOutput
20 | handle:(void (^)(UIImage *originImage, UIImage *scaledImage, UIImage *croppedImage))handle;
21 |
22 | /// 保存到相册
23 | - (void)saveImageToCameraRoll:(UIImage*)image
24 | authHandle:(void(^)(BOOL success, PHAuthorizationStatus status))authHandle
25 | completion:(void(^)(BOOL success, NSError * _Nullable error))completion;
26 |
27 | @end
28 |
29 | NS_ASSUME_NONNULL_END
30 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Manager/SCMovieManager.h:
--------------------------------------------------------------------------------
1 | //
2 | // SCMovieManager.h
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/9.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 | #import
12 |
13 | NS_ASSUME_NONNULL_BEGIN
14 |
15 | @interface SCMovieManager : NSObject
16 |
17 | @property (nonatomic, assign, getter=isRecording) BOOL recording;
18 |
19 | - (instancetype)initWithDispatchQueue:(dispatch_queue_t)dispatchQueue;
20 |
21 | - (void)startRecordWithVideoSettings:(NSDictionary*)videoSettings
22 | audioSettings:(NSDictionary*)audioSettings
23 | handle:(void(^_Nullable)(NSError *error))handle;
24 |
25 | - (void)recordSampleBuffer:(CMSampleBufferRef)sampleBuffer;
26 |
27 | - (void)stopRecordWithCompletion:(void(^)(BOOL success, NSURL* _Nullable fileURL))completion;
28 |
29 | @end
30 |
31 | NS_ASSUME_NONNULL_END
32 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Controller/SCShowMovieController.m:
--------------------------------------------------------------------------------
1 | //
2 | // SCShowMovieController.m
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/26.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import "SCShowMovieController.h"
10 | #import
11 |
12 | @interface SCShowMovieController ()
13 | @property (nonatomic, strong) NSURL *fileURL;
14 | @property (nonatomic, strong) AVPlayerViewController *playerController;
15 | @end
16 |
17 | @implementation SCShowMovieController
18 |
19 | - (instancetype)initWithFileURL:(NSURL*)fileURL {
20 | if (self = [super init]) {
21 | self.fileURL = fileURL;
22 | }
23 | return self;
24 | }
25 |
26 | + (instancetype)showMovieControllerWithFileURL:(NSURL*)fileURL {
27 | return [[self alloc] initWithFileURL:fileURL];
28 | }
29 |
30 | - (void)viewDidLoad {
31 | [super viewDidLoad];
32 | self.playerController = [[AVPlayerViewController alloc] init];
33 | self.playerController.player = [AVPlayer playerWithURL:self.fileURL];
34 | self.playerController.view.frame = self.view.frame;
35 | [self.view addSubview:self.playerController.view];
36 | }
37 |
38 | @end
39 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Category/AVCaptureDevice+SCCategory.h:
--------------------------------------------------------------------------------
1 | //
2 | // AVCaptureDevice+SCCategory.h
3 | // Detector
4 | //
5 | // Created by SeacenLiu on 2019/3/29.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface SCQualityOfService : NSObject
12 | @property (nonatomic, strong) AVCaptureDeviceFormat *format;
13 | @property (nonatomic, strong) AVFrameRateRange *frameRateRange;
14 |
15 | /** 构造函数 */
16 | + (instancetype)qosWithFormat:(AVCaptureDeviceFormat *)format
17 | frameRateRange:(AVFrameRateRange *)frameRateRange;
18 |
19 | /** 是否支持高帧率 30fps */
20 | - (BOOL)isHighFrameRate;
21 |
22 | @end
23 |
24 | @interface AVCaptureDevice (SCCategory)
25 |
26 | /** 设备专用队列 */
27 | @property (nonatomic, strong) dispatch_queue_t deviceQueue;
28 |
29 | /** device 设置用 */
30 | - (void)settingWithConfig:(void(^)(AVCaptureDevice* device, NSError* error))config;
31 |
32 | /** 是否支持高帧率 30fps */ // 30 60 120
33 | - (BOOL)supportsHighFrameRateCapture;
34 |
35 | /** 开启高帧率 */
36 | - (BOOL)enableMaxFrameRateCapture:(NSError **)error;
37 |
38 | /** 用来获取前置摄像头/后置摄像头 */
39 | + (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position;
40 |
41 | @end
42 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Manager/SCPhotoManager.h:
--------------------------------------------------------------------------------
1 | //
2 | // SCPhotoManager.h
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/20.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 |
12 | typedef void(^SCPhotoManagerStillImageCompletion)(UIImage *originImage, UIImage *scaledImage, UIImage *croppedImage, NSError* _Nullable error);
13 | typedef void(^SCPhotoManagerLiveImageCompletion)(NSURL *liveURL, NSData *liveData, NSError* _Nullable error);
14 |
15 | NS_ASSUME_NONNULL_BEGIN
16 |
17 | API_AVAILABLE(ios(10.0))
18 | @interface SCPhotoManager : NSObject
19 |
20 | @property (nonatomic, strong) AVCapturePhotoOutput *photoOutput;
21 |
22 | - (instancetype)initWithPhotoOutput:(AVCapturePhotoOutput*)photoOutput;
23 | + (instancetype)photoManager:(AVCapturePhotoOutput*)photoOutput;
24 |
25 | /// 静态照片拍摄
26 | - (void)takeStillPhoto:(AVCaptureVideoPreviewLayer*)previewLayer
27 | completion:(SCPhotoManagerStillImageCompletion)completion;
28 |
29 | /// 动态照片拍摄
30 | - (void)takeLivePhoto:(AVCaptureVideoPreviewLayer*)previewLayer
31 | completion:(SCPhotoManagerLiveImageCompletion)completion;
32 |
33 | @end
34 |
35 | NS_ASSUME_NONNULL_END
36 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Category/UIImage+SCCamera.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIImage+SCCamera.h
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/3.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 |
12 | NS_ASSUME_NONNULL_BEGIN
13 |
14 | @interface UIImage (SCCamera)
15 |
16 | /**
17 | 通过抽样缓存数据创建一个UIImage对象
18 |
19 | @param sampleBuffer 帧数据
20 | @return UIImage
21 | */
22 | + (instancetype)imageFromSampleBuffer:(CMSampleBufferRef)sampleBuffer;
23 |
24 | - (UIImage *)simpleResizeTo:(CGSize)newSize;
25 |
26 | - (UIImage *)croppedImage:(CGRect)bounds;
27 |
28 | - (UIImage *)resizedImage:(CGSize)newSize
29 | interpolationQuality:(CGInterpolationQuality)quality;
30 |
31 | - (UIImage *)resizedImageWithContentMode:(UIViewContentMode)contentMode
32 | size:(CGSize)size
33 | interpolationQuality:(CGInterpolationQuality)quality;
34 |
35 | - (UIImage *)resizedImage:(CGSize)newSize
36 | transform:(CGAffineTransform)transform
37 | interpolationQuality:(CGInterpolationQuality)quality;
38 |
39 | - (CGAffineTransform)transformForOrientation:(CGSize)newSize;
40 |
41 | - (UIImage *)fixOrientation;
42 |
43 | - (UIImage *)rotatedByDegrees:(CGFloat)degrees;
44 |
45 | @end
46 |
47 | NS_ASSUME_NONNULL_END
48 |
--------------------------------------------------------------------------------
/SCCamera/Camera/View/SCFocusView.m:
--------------------------------------------------------------------------------
1 | //
2 | // SCFocusView.m
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/10.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import "SCFocusView.h"
10 |
11 | @interface SCFocusView ()
12 | @property (nonatomic, assign) IBInspectable CGFloat lineWidth;
13 | @end
14 |
15 | IB_DESIGNABLE
16 | @implementation SCFocusView
17 |
18 | - (instancetype)initWithFrame:(CGRect)frame {
19 | if (self = [super initWithFrame:frame]) {
20 | [self setupUI];
21 | }
22 | return self;
23 | }
24 |
25 | - (void)awakeFromNib {
26 | [super awakeFromNib];
27 | [self setupUI];
28 | }
29 |
30 | - (void)prepareForInterfaceBuilder {
31 | [super prepareForInterfaceBuilder];
32 | [self setupUI];
33 | }
34 |
35 | - (void)drawRect:(CGRect)rect {
36 | CGFloat x = _lineWidth * 0.5;
37 | CGFloat y = x;
38 | CGFloat w = rect.size.width - _lineWidth;
39 | CGFloat h = rect.size.height - _lineWidth;
40 | UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(x, y, w, h)];
41 | [path setLineWidth:_lineWidth];
42 | [path setLineCapStyle:kCGLineCapRound];
43 | [path setLineJoinStyle:kCGLineJoinRound];
44 | [[UIColor yellowColor] setStroke];
45 | [path stroke];
46 | }
47 |
48 | - (void)setupUI {
49 | _lineWidth = 1;
50 | self.backgroundColor = [UIColor clearColor];
51 | }
52 |
53 | @end
54 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Category/UIViewController+SCCamera.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIViewController+SCCamera.m
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/4.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import "UIViewController+SCCamera.h"
10 |
11 | @implementation UIViewController (SCCamera)
12 |
13 | - (void)showAlertView:(NSString*)message ok:(void(^)(UIAlertAction *action))ok cancel:(void(^)(UIAlertAction *action))cancel {
14 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil
15 | message:message
16 | preferredStyle:UIAlertControllerStyleAlert];
17 | if (cancel) {
18 | UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
19 | !cancel ? : cancel(action) ;
20 | }];
21 | [alertController addAction:cancelAction];
22 | }
23 | if (ok) {
24 | UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
25 | !ok ? : ok(action) ;
26 | }];
27 | [alertController addAction:okAction];
28 | }
29 | [self presentViewController:alertController animated:YES completion:nil];
30 | }
31 |
32 | @end
33 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Manager/SCMovieFileOutManager.h:
--------------------------------------------------------------------------------
1 | //
2 | // SCMovieFileOutManager.h
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/12.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 |
12 | NS_ASSUME_NONNULL_BEGIN
13 |
14 | @class SCMovieFileOutManager;
15 | @protocol SCMovieFileOutManagerDelegate
16 |
17 | /// 完成录制
18 | - (void)movieFileOutManagerDidFinishRecord:(SCMovieFileOutManager*)manager outputFileURL:(NSURL*)outputFileURL;
19 |
20 | /// 错误处理
21 | - (void)movieFileOutManagerHandleError:(SCMovieFileOutManager*)manager error:(nullable NSError*)error;
22 |
23 | @end
24 |
25 | /*!
26 | @class SCMovieFileOutManager
27 | @abstract
28 | 封装 AVCaptureMovieFileOutput 的使用
29 | @disscussion
30 | 需要在代理中处理完成和错误处理
31 | */
32 | @interface SCMovieFileOutManager : NSObject
33 |
34 | @property (nonatomic, strong) AVCaptureMovieFileOutput *movieFileOutput;
35 | @property (nonatomic, weak) id delegate;
36 |
37 | /// 开始录制
38 | - (void)start:(AVCaptureVideoOrientation)orientation;
39 |
40 | /// 停止录制
41 | - (void)stop;
42 |
43 | /// 保存到相册
44 | - (void)saveMovieToCameraRoll:(NSURL *)url
45 | authHandle:(void(^)(BOOL success, PHAuthorizationStatus status))authHandle
46 | completion:(void(^)(BOOL success, NSError * _Nullable error))completion;
47 |
48 | @end
49 |
50 | NS_ASSUME_NONNULL_END
51 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | # CocoaPods
32 | #
33 | # We recommend against adding the Pods directory to your .gitignore. However
34 | # you should judge for yourself, the pros and cons are mentioned at:
35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
36 | #
37 | # Pods/
38 |
39 | # Carthage
40 | #
41 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
42 | # Carthage/Checkouts
43 |
44 | Carthage/Build
45 |
46 | # fastlane
47 | #
48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
49 | # screenshots whenever they are needed.
50 | # For more information about the recommended setup visit:
51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
52 |
53 | fastlane/report.xml
54 | fastlane/Preview.html
55 | fastlane/screenshots/**/*.png
56 | fastlane/test_output
57 |
58 | # Code Injection
59 | #
60 | # After new code Injection tools there's a generated folder /iOSInjectionProject
61 | # https://github.com/johnno1962/injectionforxcode
62 |
63 | iOSInjectionProject/
64 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Manager/SCCameraManager.h:
--------------------------------------------------------------------------------
1 | //
2 | // SCCameraManager.h
3 | // Detector
4 | //
5 | // Created by SeacenLiu on 2019/3/8.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 |
12 | NS_ASSUME_NONNULL_BEGIN
13 | typedef void(^_Nullable CameraHandleError)(NSError * _Nullable error);
14 |
15 | @interface SCCameraManager : NSObject
16 | - (void)switchCamera:(AVCaptureSession *)session
17 | old:(AVCaptureDeviceInput *)oldInput
18 | new:(AVCaptureDeviceInput *)newInput
19 | handle:(CameraHandleError)handle;
20 |
21 | - (void)focusWithMode:(AVCaptureFocusMode)focusMode
22 | exposeWithMode:(AVCaptureExposureMode)exposureMode
23 | device:(AVCaptureDevice*)device
24 | atDevicePoint:(CGPoint)point
25 | monitorSubjectAreaChange:(BOOL)monitorSubjectAreaChange
26 | handle:(CameraHandleError)handle;
27 |
28 | - (void)iso:(AVCaptureDevice *)device factor:(CGFloat)factor handle:(CameraHandleError)handle;
29 |
30 | - (void)changeFlash:(AVCaptureDevice*)device mode:(AVCaptureFlashMode)mode handle:(CameraHandleError)handle;
31 |
32 | - (void)changeTorch:(AVCaptureDevice*)device mode:(AVCaptureTorchMode)mode handle:(CameraHandleError)handle;
33 |
34 | - (void)zoom:(AVCaptureDevice*)device factor:(CGFloat)factor handle:(CameraHandleError)handle;
35 |
36 | - (void)whiteBalance:(AVCaptureDevice*)device mode:(AVCaptureWhiteBalanceMode)mode handle:(CameraHandleError)handle;
37 |
38 | - (void)resetFocusAndExpose:(AVCaptureDevice*)device handle:(CameraHandleError)handle;
39 | @end
40 |
41 | NS_ASSUME_NONNULL_END
42 |
--------------------------------------------------------------------------------
/SCCamera/Base.lproj/LaunchScreen.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 |
25 |
26 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Category/PHPhotoLibrary+Save.h:
--------------------------------------------------------------------------------
1 | //
2 | // PHPhotoLibrary+Save.h
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/25.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | typedef void(^ _Nullable SCPhotosSaveAuthHandle)(BOOL success, PHAuthorizationStatus status);
14 | typedef void(^ _Nullable SCPhotosSaveCompletion)(BOOL success, NSError * _Nullable error);
15 |
16 | typedef NS_ENUM(NSInteger, SCImageType) {
17 | SCImageTypeJPEG,
18 | SCImageTypePNG
19 | };
20 |
21 | @interface PHPhotoLibrary (Save)
22 |
23 | /// 图片数据保存
24 | + (void)saveImageDataToCameraRool:(NSData *)imageData
25 | authHandle:(SCPhotosSaveAuthHandle)authHandle
26 | completion:(SCPhotosSaveCompletion)completion;
27 |
28 | /// 图片保存
29 | + (void)saveImageToCameraRool:(UIImage *)image
30 | imageType:(SCImageType)type
31 | compressionQuality:(CGFloat)quality
32 | authHandle:(SCPhotosSaveAuthHandle)authHandle
33 | completion:(SCPhotosSaveCompletion)completion;
34 |
35 | /// 动态图片保存
36 | + (void)saveLivePhotoToCameraRool:(NSData *)imageData
37 | shortFilm:(NSURL *)filmURL
38 | authHandle:(SCPhotosSaveAuthHandle)authHandle
39 | completion:(SCPhotosSaveCompletion)completion;
40 |
41 |
42 | /// 视频保存
43 | + (void)saveMovieFileToCameraRoll:(NSURL *)fileURL
44 | authHandle:(SCPhotosSaveAuthHandle)authHandle
45 | completion:(SCPhotosSaveCompletion)completion;
46 |
47 | /// 自定义保存
48 | + (void)customSaveWithChangeBlock:(dispatch_block_t)changeBlock
49 | authHandle:(SCPhotosSaveAuthHandle)authHandle
50 | completion:(SCPhotosSaveCompletion)completion;
51 |
52 | @end
53 |
54 | NS_ASSUME_NONNULL_END
55 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SCCamera
2 | Custom camera by AVFoundation.
3 |
4 | - session queue problem
5 |
6 | ## Architecture
7 | > MVC
8 |
9 | 
10 |
11 | ## SCMovieFileOutManager 使用
12 | ```
13 | // 创建 SCMovieFileOutManager
14 | - (SCMovieFileOutManager *)movieFileManager {
15 | if (_movieFileManager == nil) {
16 | _movieFileManager = [SCMovieFileOutManager new];
17 | _movieFileManager.movieFileOutput = self.movieFileOutput;
18 | _movieFileManager.delegate = self;
19 | }
20 | return _movieFileManager;
21 | }
22 |
23 | #pragma mark - 录制视频
24 | // 开始录像视频
25 | - (void)startRecordVideoAction:(SCCameraView *)cameraView {
26 | [self.movieFileManager start:self.cameraView.previewView.videoOrientation];
27 | }
28 |
29 | // 停止录像视频
30 | - (void)stopRecordVideoAction:(SCCameraView *)cameraView {
31 | [self.movieFileManager stop];
32 | }
33 |
34 | // movieFileOut 错误处理
35 | - (void)movieFileOutManagerHandleError:(SCMovieFileOutManager *)manager error:(NSError *)error {
36 | [self.view showError:error];
37 | }
38 |
39 | // movieFileOut 录制完成处理
40 | - (void)movieFileOutManagerDidFinishRecord:(SCMovieFileOutManager *)manager outputFileURL:(NSURL *)outputFileURL {
41 | // 保存视频
42 | [self.view showLoadHUD:@"保存中..."];
43 | [self.movieFileManager saveMovieToCameraRoll:outputFileURL authHandle:^(BOOL success, PHAuthorizationStatus status) {
44 | // TODO: - 权限处理问题
45 | } completion:^(BOOL success, NSError * _Nullable error) {
46 | [self.view hideHUD];
47 | success?:[self.view showError:error];
48 | }];
49 | }
50 | ```
51 |
52 | PS: `AVCaptureMovieFileOutput`不能与`AVCaptureVideoDataOutput`或`AVCaptureAudioDataOutput`共用
53 |
54 | [Simultaneous AVCaptureVideoDataOutput and AVCaptureMovieFileOutput](https://stackoverflow.com/questions/3968879/simultaneous-avcapturevideodataoutput-and-avcapturemoviefileoutput)
55 |
56 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Category/AVMetadataFaceObject+Transform.m:
--------------------------------------------------------------------------------
1 | //
2 | // AVMetadataFaceObject+Transform.m
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/19.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import "AVMetadataFaceObject+Transform.h"
10 | #import
11 |
12 | @implementation AVMetadataFaceObject (Transform)
13 |
14 | // Rotate around Z-axis
15 | - (CATransform3D)transformFromRollAngle {
16 | if (!self.hasRollAngle)
17 | return CATransform3DIdentity;
18 | CGFloat rollAngleInRadians = SCDegreesToRadians(self.rollAngle);
19 | // 绕 z 轴正方向进行旋转
20 | return CATransform3DMakeRotation(rollAngleInRadians, 0.0f, 0.0f, 1.0f);
21 | }
22 |
23 | // Rotate around Y-axis
24 | - (CATransform3D)transformFromYawAngle {
25 | if (!self.hasYawAngle)
26 | return CATransform3DIdentity;
27 | CGFloat yawAngleInRadians = SCDegreesToRadians(self.yawAngle);
28 | // 绕 y 轴反方向进行旋转
29 | return CATransform3DMakeRotation(yawAngleInRadians, 0.0f, -1.0f, 0.0f);
30 | // 仅支持垂直界面方向
31 | // return CATransform3DConcat(yawTransform, [self orientationTransform]);
32 | }
33 |
34 | /// 仅支持HOME键在底部的二次纠正变换
35 | - (CATransform3D)orientationTransform {
36 | CGFloat angle = 0.0;
37 | switch ([UIDevice currentDevice].orientation) {
38 | case UIDeviceOrientationPortraitUpsideDown:
39 | angle = M_PI;
40 | break;
41 | case UIDeviceOrientationLandscapeRight:
42 | angle = -M_PI / 2.0f;
43 | break;
44 | case UIDeviceOrientationLandscapeLeft:
45 | angle = M_PI / 2.0f;
46 | break;
47 | default: // as UIDeviceOrientationPortrait
48 | angle = 0.0;
49 | break;
50 | }
51 | // 绕 z 轴正方向进行旋转
52 | return CATransform3DMakeRotation(angle, 0.0f, 0.0f, 1.0f);
53 | }
54 |
55 | /// 角度制转弧度制
56 | static CGFloat SCDegreesToRadians(CGFloat degrees) {
57 | return degrees * M_PI / 180;
58 | }
59 |
60 | @end
61 |
--------------------------------------------------------------------------------
/SCCamera/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | NSCameraUsageDescription
24 | App需要您的同意,才能访问相机
25 | NSMicrophoneUsageDescription
26 | App需要您的同意,才能访问麦克风
27 | NSPhotoLibraryAddUsageDescription
28 | App需要您的同意,才能添加图片
29 | NSPhotoLibraryUsageDescription
30 | App需要您的同意,才能访问相册
31 | UILaunchStoryboardName
32 | LaunchScreen
33 | UIMainStoryboardFile
34 | Main
35 | UIRequiredDeviceCapabilities
36 |
37 | armv7
38 |
39 | UISupportedInterfaceOrientations
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationLandscapeLeft
43 | UIInterfaceOrientationLandscapeRight
44 | UIInterfaceOrientationPortraitUpsideDown
45 |
46 | UISupportedInterfaceOrientations~ipad
47 |
48 | UIInterfaceOrientationPortrait
49 | UIInterfaceOrientationPortraitUpsideDown
50 | UIInterfaceOrientationLandscapeLeft
51 | UIInterfaceOrientationLandscapeRight
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/SCCamera/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/SCCamera/AppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.m
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/2.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import "AppDelegate.h"
10 | #import
11 |
12 | @interface AppDelegate ()
13 |
14 | @end
15 |
16 | @implementation AppDelegate
17 |
18 |
19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
20 | // Override point for customization after application launch.
21 | return YES;
22 | }
23 |
24 |
25 | - (void)applicationWillResignActive:(UIApplication *)application {
26 | // 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.
27 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
28 | }
29 |
30 |
31 | - (void)applicationDidEnterBackground:(UIApplication *)application {
32 | // 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.
33 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
34 | }
35 |
36 |
37 | - (void)applicationWillEnterForeground:(UIApplication *)application {
38 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
39 | }
40 |
41 |
42 | - (void)applicationDidBecomeActive:(UIApplication *)application {
43 | // 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.
44 | }
45 |
46 |
47 | - (void)applicationWillTerminate:(UIApplication *)application {
48 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
49 | }
50 |
51 |
52 | @end
53 |
--------------------------------------------------------------------------------
/SCCamera/Camera/View/SCCameraView.h:
--------------------------------------------------------------------------------
1 | //
2 | // SCCameraView.h
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/8.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @class SCCameraView;
14 | @protocol SCCameraViewDelegate
15 | @optional;
16 |
17 | #pragma mark - 相机操作
18 | /// 闪光灯
19 | - (void)flashLightAction:(SCCameraView *)cameraView isOn:(BOOL)isOn handle:(void(^)(NSError *error))handle;
20 | /// 补光
21 | - (void)torchLightAction:(SCCameraView *)cameraView isOn:(BOOL)isOn handle:(void(^)(NSError *error))handle;
22 | /// 转换摄像头
23 | - (void)switchCameraAction:(SCCameraView *)cameraView isFront:(BOOL)isFront handle:(void(^)(NSError *error))handle;
24 | /// 聚焦&曝光
25 | - (void)focusAndExposeAction:(SCCameraView *)cameraView point:(CGPoint)point handle:(void(^)(NSError *error))handle;
26 | /// 调整感光度
27 | - (void)isoAction:(SCCameraView *)cameraView factor:(CGFloat)factor handle:(void(^)(NSError *error))handle;
28 | /// 缩放
29 | - (void)zoomAction:(SCCameraView *)cameraView factor:(CGFloat)factor handle:(void(^)(NSError *error))handle;
30 |
31 | /// 重置聚焦&曝光
32 | - (void)resetFocusAndExposeAction:(SCCameraView *)cameraView handle:(void(^)(NSError *error))handle;
33 |
34 | #pragma mark - 拍照
35 | /// 拍照
36 | - (void)takeStillPhotoAction:(SCCameraView *)cameraView;
37 |
38 | /// 拍摄 Live Photo
39 | - (void)takeLivePhotoAction:(SCCameraView *)cameraView;
40 |
41 | #pragma mark - 录制视频
42 | /// 开始录制视频
43 | - (void)startRecordVideoAction:(SCCameraView *)cameraView;
44 | /// 停止录制视频
45 | - (void)stopRecordVideoAction:(SCCameraView *)cameraView;
46 |
47 | #pragma mark - 其他
48 | /// 改变拍摄类型 1:拍照 2:视频
49 | - (void)didChangeTypeAction:(SCCameraView *)cameraView type:(NSInteger)type;
50 | /// 取消
51 | - (void)cancelAction:(SCCameraView *)cameraView;
52 |
53 | @end
54 |
55 | @class SCVideoPreviewView;
56 | @interface SCCameraView : UIView
57 |
58 | @property (nonatomic, weak) id delegate;
59 | @property (weak, nonatomic) IBOutlet SCVideoPreviewView *previewView;
60 | @property (weak, nonatomic) IBOutlet UIButton *cancelBtn;
61 |
62 | /// 指定构造函数
63 | + (instancetype)cameraView:(CGRect)frame;
64 |
65 | - (void)runFocusAnimation:(CGPoint)center;
66 |
67 | @end
68 |
69 | NS_ASSUME_NONNULL_END
70 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Manager/SCStableCheckTool.m:
--------------------------------------------------------------------------------
1 | //
2 | // SCStableCheckTool.m
3 | // StaticCheckTool
4 | //
5 | // Created by SeacenLiu on 2019/3/21.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import "SCStableCheckTool.h"
10 | #import
11 |
12 | @interface SCStableCheckTool ()
13 | @property (nonatomic, strong) dispatch_queue_t stableQueue;
14 | @property (nonatomic, strong) CMMotionManager *motionManager;
15 | @property (nonatomic, strong) NSOperationQueue *motionQueue;
16 | @end
17 |
18 | @implementation SCStableCheckTool
19 | @synthesize isStable = _isStable;
20 |
21 | - (instancetype)init {
22 | if (self = [super init]) {
23 | _updateInterval = 1;
24 | _stableQueue = dispatch_queue_create("com.seacen.stableQueue", DISPATCH_QUEUE_CONCURRENT);
25 | _motionManager = [CMMotionManager new];
26 | _motionQueue = [NSOperationQueue new];
27 | _revMax = 0.05;
28 | }
29 | return self;
30 | }
31 |
32 | + (instancetype)stableCheckToolWithRevMax:(double)revMax {
33 | SCStableCheckTool *tool = [[self alloc] init];
34 | tool.revMax = revMax;
35 | return tool;
36 | }
37 |
38 | - (void)start {
39 | if ([_motionManager isGyroAvailable]) {
40 | [_motionManager setGyroUpdateInterval:_updateInterval];
41 | [_motionManager startGyroUpdatesToQueue:_motionQueue withHandler:^(CMGyroData * _Nullable gyroData, NSError * _Nullable error) {
42 | if (error != nil) {
43 | NSLog(@"%@", error);
44 | [self.motionManager stopGyroUpdates];
45 | return;
46 | }
47 | // NSString *string = [NSString stringWithFormat:@"x轴转速: %.2f, y轴转速: %.2f, z轴转速: %.2f", gyroData.rotationRate.x, gyroData.rotationRate.y, gyroData.rotationRate.z];
48 | // NSLog(@"%@", string);
49 | double rev = sqrt(gyroData.rotationRate.x*gyroData.rotationRate.x + gyroData.rotationRate.y*gyroData.rotationRate.y + gyroData.rotationRate.z*gyroData.rotationRate.z);
50 | BOOL flag = rev <= self.revMax;
51 | NSLog(@"转速向量大小: %f and %d", rev, flag);
52 | self.isStable = flag;
53 | }];
54 | } else {
55 | NSLog(@"陀螺仪不可用");
56 | }
57 | }
58 |
59 | - (void)dealloc {
60 | NSLog(@"SCStableCheckTool dealloc");
61 | }
62 |
63 | - (void)stop {
64 | if ([_motionManager isGyroActive]) {
65 | [_motionManager stopGyroUpdates];
66 | }
67 | }
68 |
69 | - (void)setIsStable:(BOOL)isStable {
70 | dispatch_barrier_async(self.stableQueue, ^{
71 | self->_isStable = isStable;
72 | // FIXME: - 提示手机不稳定
73 | if (isStable == false) {
74 |
75 | } else {
76 |
77 | }
78 | });
79 | }
80 |
81 | - (BOOL)isStable {
82 | __block BOOL tmp;
83 | dispatch_sync(self.stableQueue, ^{
84 | tmp = self->_isStable;
85 | });
86 | return tmp;
87 | }
88 |
89 | @end
90 |
--------------------------------------------------------------------------------
/SCCamera/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 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Category/UIView+SCCategory.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+SCCategory.m
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/9.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import "UIView+SCCategory.h"
10 |
11 | @implementation UIView (SCCategory)
12 |
13 | - (CGFloat)top {
14 | return self.frame.origin.y;
15 | }
16 |
17 | - (void)setTop:(CGFloat)y {
18 | CGRect frame = self.frame;
19 | frame.origin.y = y;
20 | self.frame = frame;
21 | }
22 |
23 | - (CGFloat)left {
24 | return self.frame.origin.x;
25 | }
26 |
27 | - (void)setLeft:(CGFloat)x {
28 | CGRect frame = self.frame;
29 | frame.origin.x = x;
30 | self.frame = frame;
31 | }
32 |
33 | - (CGFloat)right {
34 | return self.frame.origin.x + self.frame.size.width;
35 | }
36 |
37 | - (void)setRight:(CGFloat)right {
38 | CGRect frame = self.frame;
39 | frame.origin.x = right - frame.size.width;
40 | self.frame = frame;
41 | }
42 |
43 | - (CGFloat)bottom {
44 | return self.frame.origin.y + self.frame.size.height;
45 | }
46 |
47 | - (void)setBottom:(CGFloat)bottom {
48 | CGRect frame = self.frame;
49 | frame.origin.y = bottom - frame.size.height;
50 | self.frame = frame;
51 | }
52 |
53 | - (CGFloat)centerX {
54 | return self.center.x;
55 | }
56 |
57 | - (void)setCenterX:(CGFloat)centerX {
58 | self.center = CGPointMake(centerX, self.center.y);
59 | }
60 |
61 | - (CGFloat)centerY {
62 | return self.center.y;
63 | }
64 |
65 | - (void)setCenterY:(CGFloat)centerY {
66 | self.center = CGPointMake(self.center.x, centerY);
67 | }
68 |
69 | - (CGFloat)width {
70 | return self.frame.size.width;
71 | }
72 |
73 | - (void)setWidth:(CGFloat)width {
74 | CGRect frame = self.frame;
75 | frame.size.width = width;
76 | self.frame = frame;
77 | }
78 |
79 | - (CGFloat)height {
80 | return self.frame.size.height;
81 | }
82 |
83 | - (void)setHeight:(CGFloat)height {
84 | CGRect frame = self.frame;
85 | frame.size.height = height;
86 | self.frame = frame;
87 | }
88 |
89 | - (CGPoint)origin {
90 | return self.frame.origin;
91 | }
92 |
93 | - (void)setOrigin:(CGPoint)origin {
94 | CGRect frame = self.frame;
95 | frame.origin = origin;
96 | self.frame = frame;
97 | }
98 |
99 | - (CGSize)size {
100 | return self.frame.size;
101 | }
102 |
103 | - (void)setSize:(CGSize)size {
104 | CGRect frame = self.frame;
105 | frame.size = size;
106 | self.frame = frame;
107 | }
108 |
109 | - (UIViewController *)viewController
110 | {
111 | if ([[self nextResponder] isKindOfClass:[UIViewController class]]) {
112 | return (UIViewController *)[self nextResponder];
113 | }
114 |
115 | for (UIView* next = [self superview]; next; next = next.superview)
116 | {
117 | UIResponder *nextResponder = [next nextResponder];
118 | if ([nextResponder isKindOfClass:[UIViewController class]])
119 | {
120 | return (UIViewController *)nextResponder;
121 | }
122 | }
123 | return nil;
124 | }
125 |
126 | @end
127 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Manager/SCStillPhotoManager.m:
--------------------------------------------------------------------------------
1 | //
2 | // SCStillPhotoManager.m
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/9.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import "SCStillPhotoManager.h"
10 | #import "UIImage+SCCamera.h"
11 |
12 | @implementation SCStillPhotoManager
13 |
14 | - (void)takePhoto:(AVCaptureVideoPreviewLayer*)previewLayer
15 | stillImageOutput:(AVCaptureStillImageOutput*)stillImageOutput
16 | handle:(void (^)(UIImage *, UIImage *, UIImage *))handle {
17 | AVCaptureConnection* stillImageConnection = [stillImageOutput connectionWithMediaType:AVMediaTypeVideo];
18 | AVCaptureVideoOrientation videoOrientation = previewLayer.connection.videoOrientation;
19 | if (stillImageConnection.supportsVideoOrientation) {
20 | stillImageConnection.videoOrientation = videoOrientation;
21 | }
22 | void (^completionHandler)(CMSampleBufferRef, NSError *) = ^(CMSampleBufferRef _Nullable imageDataSampleBuffer, NSError * _Nullable error){
23 | if (!imageDataSampleBuffer) {
24 | return;
25 | }
26 | // 1. 获取 originImage
27 | NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
28 | UIImage *originImage = [[UIImage alloc] initWithData:imageData];
29 | originImage = [originImage fixOrientation];
30 | // 2. 获取 scaledImage
31 | CGFloat width = previewLayer.bounds.size.width;
32 | CGFloat height = previewLayer.bounds.size.height;
33 | CGFloat scale = [[UIScreen mainScreen] scale];
34 | CGSize size = CGSizeMake(width*scale, height*scale);
35 | UIImage *scaledImage = [originImage resizedImageWithContentMode:UIViewContentModeScaleAspectFill size:size interpolationQuality:kCGInterpolationHigh];
36 | // 3. 获取 croppedImage
37 | CGRect cropFrame = CGRectMake((scaledImage.size.width - size.width) * 0.5, (scaledImage.size.height - size.height) * 0.5, size.width, size.height);
38 | UIImage *croppedImage = [scaledImage croppedImage:cropFrame];
39 | // 4. 回调
40 | dispatch_async(dispatch_get_main_queue(), ^{
41 | handle(originImage, scaledImage, croppedImage);
42 | });
43 | };
44 | [stillImageOutput captureStillImageAsynchronouslyFromConnection:stillImageConnection completionHandler: completionHandler];
45 | }
46 |
47 | - (void)saveImageToCameraRoll:(UIImage*)image
48 | authHandle:(void(^)(BOOL success, PHAuthorizationStatus status))authHandle
49 | completion:(void(^)(BOOL success, NSError * _Nullable error))completion {
50 | [PHPhotoLibrary requestAuthorization:^( PHAuthorizationStatus status ) {
51 | if (status != PHAuthorizationStatusAuthorized) {
52 | authHandle(false, status);
53 | return;
54 | }
55 | [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
56 | PHAssetCreationRequest *imageRequest = [PHAssetCreationRequest creationRequestForAsset];
57 | [imageRequest addResourceWithType:PHAssetResourceTypePhoto data:UIImageJPEGRepresentation(image, 1) options:nil];
58 | } completionHandler:^( BOOL success, NSError * _Nullable error ) {
59 | dispatch_sync(dispatch_get_main_queue(), ^{
60 | completion(success, error);
61 | });
62 | }];
63 | }];
64 | }
65 |
66 | @end
67 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Manager/SCMovieFileOutManager.m:
--------------------------------------------------------------------------------
1 | //
2 | // SCMovieFileOutManager.m
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/12.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import "SCMovieFileOutManager.h"
10 |
11 | @interface SCMovieFileOutManager ()
12 | @property (nonatomic, strong) NSURL *movieURL;
13 | @end
14 |
15 | @implementation SCMovieFileOutManager
16 |
17 | - (instancetype)init
18 | {
19 | self = [super init];
20 | if (self) {
21 | _movieURL = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@%@", NSTemporaryDirectory(), @"movie.mov"]];
22 | }
23 | return self;
24 | }
25 |
26 | /// 录制状态
27 | - (BOOL)isRecording {
28 | return self.movieFileOutput.isRecording;
29 | }
30 |
31 |
32 | /// 开始录制
33 | - (void)start:(AVCaptureVideoOrientation)orientation {
34 | NSAssert(self.movieFileOutput, @"必须给movieFileOutput赋值");
35 | if (!self.isRecording) {
36 | AVCaptureConnection *videoConnection = [self.movieFileOutput connectionWithMediaType:AVMediaTypeVideo];
37 | if (videoConnection.supportsVideoStabilization) {
38 | videoConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
39 | }
40 | if (videoConnection.supportsVideoOrientation) {
41 | videoConnection.videoOrientation = orientation;
42 | }
43 | [self.movieFileOutput startRecordingToOutputFileURL:self.movieURL recordingDelegate:self];
44 | }
45 | }
46 |
47 | /// 停止录制
48 | - (void)stop{
49 | if (self.isRecording) {
50 | [self.movieFileOutput stopRecording];
51 | }
52 | }
53 |
54 | #pragma mark - AVCaptureFileOutputRecordingDelegate
55 | - (void)captureOutput:(nonnull AVCaptureFileOutput *)output didFinishRecordingToOutputFileAtURL:(nonnull NSURL *)outputFileURL fromConnections:(nonnull NSArray *)connections error:(nullable NSError *)error {
56 | if (error) {
57 | if ([_delegate respondsToSelector:@selector(movieFileOutManagerHandleError:error:)]) {
58 | [_delegate movieFileOutManagerHandleError:self error:error];
59 | }
60 | } else {
61 | if ([_delegate respondsToSelector:@selector(movieFileOutManagerDidFinishRecord:outputFileURL:)]) {
62 | [_delegate movieFileOutManagerDidFinishRecord:self outputFileURL:outputFileURL];
63 | }
64 | }
65 | }
66 |
67 | /// 保存到相册
68 | - (void)saveMovieToCameraRoll:(NSURL *)url
69 | authHandle:(void(^)(BOOL success, PHAuthorizationStatus status))authHandle
70 | completion:(void(^)(BOOL success, NSError * _Nullable error))completion {
71 | [PHPhotoLibrary requestAuthorization:^( PHAuthorizationStatus status ) {
72 | if (status != PHAuthorizationStatusAuthorized) {
73 | authHandle(false, status);
74 | return;
75 | }
76 | [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
77 | PHAssetCreationRequest *videoRequest = [PHAssetCreationRequest creationRequestForAsset];
78 | [videoRequest addResourceWithType:PHAssetResourceTypeVideo fileURL:url options:nil];
79 | } completionHandler:^( BOOL success, NSError * _Nullable error ) {
80 | dispatch_sync(dispatch_get_main_queue(), ^{
81 | completion(success, error);
82 | });
83 | }];
84 | }];
85 | }
86 |
87 | @end
88 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Category/PHPhotoLibrary+Save.m:
--------------------------------------------------------------------------------
1 | //
2 | // PHPhotoLibrary+Save.m
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/25.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import "PHPhotoLibrary+Save.h"
10 |
11 | @implementation PHPhotoLibrary (Save)
12 |
13 | #pragma mark - still photo
14 | + (void)saveImageToCameraRool:(UIImage *)image
15 | imageType:(SCImageType)type
16 | compressionQuality:(CGFloat)quality
17 | authHandle:(SCPhotosSaveAuthHandle)authHandle
18 | completion:(SCPhotosSaveCompletion)completion {
19 | NSData *data;
20 | switch (type) {
21 | case SCImageTypeJPEG:
22 | data = UIImageJPEGRepresentation(image, quality);
23 | break;
24 | case SCImageTypePNG:
25 | data = UIImagePNGRepresentation(image);
26 | break;
27 | }
28 | [self saveImageDataToCameraRool:data authHandle:authHandle completion:completion];
29 | }
30 |
31 | + (void)saveImageDataToCameraRool:(NSData *)imageData
32 | authHandle:(SCPhotosSaveAuthHandle)authHandle
33 | completion:(SCPhotosSaveCompletion)completion {
34 | [self customSaveWithChangeBlock:^{
35 | PHAssetCreationRequest *imageRequest = [PHAssetCreationRequest creationRequestForAsset];
36 | [imageRequest addResourceWithType:PHAssetResourceTypePhoto data:imageData options:nil];
37 | } authHandle:authHandle completion:completion];
38 | }
39 |
40 | #pragma mark - live photo
41 | + (void)saveLivePhotoToCameraRool:(NSData *)imageData
42 | shortFilm:(NSURL *)filmURL
43 | authHandle:(SCPhotosSaveAuthHandle)authHandle
44 | completion:(SCPhotosSaveCompletion)completion {
45 | [self customSaveWithChangeBlock:^{
46 | PHAssetCreationRequest* creationRequest = [PHAssetCreationRequest creationRequestForAsset];
47 | [creationRequest addResourceWithType:PHAssetResourceTypePhoto data:imageData options:nil];
48 | PHAssetResourceCreationOptions* resourceOptions = [[PHAssetResourceCreationOptions alloc] init];
49 | resourceOptions.shouldMoveFile = YES;
50 | [creationRequest addResourceWithType:PHAssetResourceTypePairedVideo fileURL:filmURL options:resourceOptions];
51 | } authHandle:authHandle completion:completion];
52 | }
53 |
54 |
55 | #pragma mark - movie
56 | + (void)saveMovieFileToCameraRoll:(NSURL *)fileURL
57 | authHandle:(SCPhotosSaveAuthHandle)authHandle
58 | completion:(SCPhotosSaveCompletion)completion {
59 | [self customSaveWithChangeBlock:^{
60 | PHAssetCreationRequest *videoRequest = [PHAssetCreationRequest creationRequestForAsset];
61 | PHAssetResourceCreationOptions* resourceOptions = [[PHAssetResourceCreationOptions alloc] init];
62 | resourceOptions.shouldMoveFile = YES;
63 | [videoRequest addResourceWithType:PHAssetResourceTypeVideo fileURL:fileURL options:resourceOptions];
64 | } authHandle:authHandle completion:completion];
65 | }
66 |
67 |
68 | #pragma mark - private
69 | + (void)customSaveWithChangeBlock:(dispatch_block_t)changeBlock
70 | authHandle:(SCPhotosSaveAuthHandle)authHandle
71 | completion:(SCPhotosSaveCompletion)completion {
72 | [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
73 | if (status != PHAuthorizationStatusAuthorized) {
74 | dispatch_async(dispatch_get_main_queue(), ^{
75 | if (authHandle)
76 | authHandle(false, status);
77 | });
78 | return;
79 | }
80 | [[PHPhotoLibrary sharedPhotoLibrary]
81 | performChanges: changeBlock
82 | completionHandler:^(BOOL success, NSError * _Nullable error) {
83 | dispatch_async(dispatch_get_main_queue(), ^{
84 | if (completion)
85 | completion(success, error);
86 | });
87 | }];
88 | }];
89 | }
90 |
91 | @end
92 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Controller/SCCameraResultController.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Category/AVCaptureDevice+SCCategory.m:
--------------------------------------------------------------------------------
1 | //
2 | // AVCaptureDevice+SCCategory.m
3 | // Detector
4 | //
5 | // Created by SeacenLiu on 2019/3/29.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import "AVCaptureDevice+SCCategory.h"
10 | #import
11 |
12 | #pragma mark - 辅助类
13 | @implementation SCQualityOfService
14 |
15 | + (instancetype)qosWithFormat:(AVCaptureDeviceFormat *)format
16 | frameRateRange:(AVFrameRateRange *)frameRateRange {
17 |
18 | return [[self alloc] initWithFormat:format frameRateRange:frameRateRange];
19 | }
20 |
21 | - (instancetype)initWithFormat:(AVCaptureDeviceFormat *)format
22 | frameRateRange:(AVFrameRateRange *)frameRateRange {
23 | self = [super init];
24 | if (self) {
25 | _format = format;
26 | _frameRateRange = frameRateRange;
27 | }
28 | return self;
29 | }
30 |
31 | /** 大于30就看成高帧率 */
32 | - (BOOL)isHighFrameRate {
33 | return self.frameRateRange.maxFrameRate > 30.0f;
34 | }
35 |
36 | @end
37 |
38 | #pragma mark - 分类
39 | @implementation AVCaptureDevice (SCCategory)
40 | @dynamic deviceQueue;
41 |
42 | + (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition) position {
43 | NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
44 | for (AVCaptureDevice *device in devices) {
45 | if ([device position] == position) {
46 | return device;
47 | }
48 | }
49 | return nil;
50 | }
51 |
52 | - (void)settingWithConfig:(void(^)(AVCaptureDevice* device, NSError* error))config {
53 | NSError *error;
54 | if ([self lockForConfiguration:&error]) {
55 | config(self, nil);
56 | [self unlockForConfiguration];
57 | }
58 | if (error) {
59 | config(nil, error);
60 | }
61 | }
62 |
63 | - (BOOL)supportsHighFrameRateCapture {
64 | if ([self hasMediaType:AVMediaTypeVideo] == false) {
65 | return NO;
66 | }
67 | return [self findHighestQualityOfService].isHighFrameRate;
68 | }
69 |
70 | - (SCQualityOfService*)findHighestQualityOfService {
71 | AVCaptureDeviceFormat *maxFormat = nil;
72 | AVFrameRateRange *maxFrameRateRange = nil;
73 | for (AVCaptureDeviceFormat *format in self.formats) {
74 | FourCharCode codeType = CMVideoFormatDescriptionGetCodecType(format.formatDescription);
75 | if (codeType == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) {
76 | NSArray *frameRateRanges = format.videoSupportedFrameRateRanges;
77 | for (AVFrameRateRange *range in frameRateRanges) {
78 | if (range.maxFrameRate > maxFrameRateRange.maxFrameRate) {
79 | maxFormat = format;
80 | maxFrameRateRange = range;
81 | }
82 | }
83 | }
84 | }
85 | // NSLog(@"%@ %@", maxFormat, maxFrameRateRange);
86 | return [SCQualityOfService qosWithFormat:maxFormat frameRateRange:maxFrameRateRange];
87 | }
88 |
89 | - (BOOL)enableMaxFrameRateCapture:(NSError **)error {
90 |
91 | SCQualityOfService *qos = [self findHighestQualityOfService];
92 |
93 | if (qos.isHighFrameRate == false) {
94 | if (error) {
95 | NSString *message = @"Device does not support high FPS capture";
96 | NSDictionary *userInfo = @{NSLocalizedDescriptionKey : message};
97 | // FIXME: - 错误码
98 | NSUInteger code = 1000;
99 | *error = [NSError errorWithDomain:@"com.seacen"
100 | code:code
101 | userInfo:userInfo];
102 | }
103 | return NO;
104 | }
105 |
106 | if ([self lockForConfiguration:error]) {
107 | CMTime minFrameDuration = qos.frameRateRange.minFrameDuration;
108 |
109 | self.activeFormat = qos.format;
110 | self.activeVideoMinFrameDuration = minFrameDuration;
111 | self.activeVideoMaxFrameDuration = minFrameDuration;
112 |
113 | [self unlockForConfiguration];
114 | return YES;
115 | }
116 | return NO;
117 | }
118 |
119 | #pragma mark - 关联对象
120 | const char* deviceQueueKey = "com.seacen.deviceQueueKey";
121 | - (void)setDeviceQueue:(dispatch_queue_t)deviceQueue {
122 | objc_setAssociatedObject(self, deviceQueueKey, deviceQueue, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
123 | }
124 |
125 | - (dispatch_queue_t)deviceQueue {
126 | dispatch_queue_t queue = objc_getAssociatedObject(self, deviceQueueKey);
127 | if (queue != nil) { return queue; }
128 | queue = dispatch_queue_create("com.seacen.device.queue", DISPATCH_QUEUE_SERIAL);
129 | self.deviceQueue = queue;
130 | return queue;
131 | }
132 |
133 | @end
134 |
--------------------------------------------------------------------------------
/SCCamera/Camera/View/SCVideoPreviewView.m:
--------------------------------------------------------------------------------
1 | //
2 | // SCVideoPreviewView.m
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/3.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import "SCVideoPreviewView.h"
10 | #import "AVMetadataFaceObject+Transform.h"
11 |
12 | @interface SCVideoPreviewView ()
13 | @property (nonatomic, strong) CALayer *overlayLayer;
14 | @property (nonatomic, strong) NSMutableDictionary *faceLayers;
15 | @property (nonatomic, strong) NSMutableDictionary *faceShowes;
16 | @end
17 |
18 | @implementation SCVideoPreviewView
19 |
20 | + (Class)layerClass {
21 | return [AVCaptureVideoPreviewLayer class];
22 | }
23 |
24 | - (AVCaptureVideoPreviewLayer*)videoPreviewLayer {
25 | return (AVCaptureVideoPreviewLayer *)self.layer;
26 | }
27 |
28 | - (void)awakeFromNib {
29 | [super awakeFromNib];
30 | [self prepare];
31 | }
32 |
33 | - (instancetype)initWithFrame:(CGRect)frame {
34 | if (self = [super initWithFrame:frame]) {
35 | [self prepare];
36 | }
37 | return self;
38 | }
39 |
40 | - (void)prepare {
41 | [self.videoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
42 | [self setupFaceDetect];
43 | }
44 |
45 | - (CGPoint)captureDevicePointForPoint:(CGPoint)point {
46 | AVCaptureVideoPreviewLayer *layer = (AVCaptureVideoPreviewLayer *)self.layer;
47 | return [layer captureDevicePointOfInterestForPoint:point];
48 | }
49 |
50 | - (AVCaptureSession*)captureSession {
51 | return self.videoPreviewLayer.session;
52 | }
53 |
54 | - (void)setCaptureSession:(AVCaptureSession*)captureSession {
55 | self.videoPreviewLayer.session = captureSession;
56 | // 根据状态栏位置设置视频方向
57 | UIInterfaceOrientation statusBarOrientation = [UIApplication sharedApplication].statusBarOrientation;
58 | AVCaptureVideoOrientation initialVideoOrientation = AVCaptureVideoOrientationPortrait;
59 | if (statusBarOrientation != UIInterfaceOrientationUnknown) {
60 | initialVideoOrientation = (AVCaptureVideoOrientation)statusBarOrientation;
61 | }
62 | // videoPreviewLayer 也有 connection
63 | self.videoPreviewLayer.connection.videoOrientation = initialVideoOrientation;
64 | }
65 |
66 | - (AVCaptureVideoOrientation)videoOrientation {
67 | return self.videoPreviewLayer.connection.videoOrientation;
68 | }
69 |
70 | #pragma mark - face detect
71 | - (void)layoutSubviews {
72 | [super layoutSubviews];
73 | // 界面旋转后需要重新设置
74 | self.overlayLayer.frame = self.layer.frame;
75 | self.overlayLayer.sublayerTransform = CATransform3DMakePerspective(1000);
76 | }
77 |
78 | - (void)setupFaceDetect {
79 | self.faceLayers = [NSMutableDictionary dictionaryWithCapacity:2];
80 | self.overlayLayer = [CALayer layer];
81 | self.overlayLayer.frame = self.videoPreviewLayer.frame;
82 | self.overlayLayer.sublayerTransform = CATransform3DMakePerspective(1000);
83 | [self.videoPreviewLayer addSublayer:self.overlayLayer];
84 | }
85 |
86 | - (void)faceDetectionDidDetectFaces:(NSArray *)faces connection:(AVCaptureConnection *)connection {
87 | NSArray *transformedFaces = [self transformedFaces:faces];
88 |
89 | NSMutableArray *lostFaces = [self.faceLayers.allKeys mutableCopy];
90 | for (AVMetadataFaceObject *face in transformedFaces) {
91 | NSNumber *faceID = @(face.faceID);
92 | [lostFaces removeObject:faceID];
93 |
94 | CALayer *layer = self.faceLayers[faceID];
95 | if (!layer) {
96 | layer = [self makeFaceLayer];
97 | [self.overlayLayer addSublayer:layer];
98 | self.faceLayers[faceID] = layer;
99 | }
100 |
101 | // 重置 transform
102 | layer.transform = CATransform3DIdentity;
103 | layer.frame = face.bounds;
104 |
105 | // 显示倾斜角
106 | if (face.hasRollAngle) {
107 | CATransform3D t = [face transformFromRollAngle];
108 | layer.transform = CATransform3DConcat(layer.transform, t);
109 | }
110 |
111 | // 显示偏转角
112 | if (face.hasYawAngle) {
113 | CATransform3D t = [face transformFromYawAngle];
114 | layer.transform = CATransform3DConcat(layer.transform, t);
115 | }
116 | }
117 |
118 | for (NSNumber *faceID in lostFaces) {
119 | CALayer *layer = self.faceLayers[faceID];
120 | [layer removeFromSuperlayer];
121 | [self.faceLayers removeObjectForKey:faceID];
122 | }
123 | }
124 |
125 | - (CALayer*)makeFaceLayer {
126 | CALayer *layer = [CALayer layer];
127 | layer.borderWidth = 5.0f;
128 | layer.borderColor = [UIColor yellowColor].CGColor;
129 | return layer;
130 | }
131 |
132 | - (NSArray*)transformedFaces:(NSArray*)faces {
133 | NSMutableArray *mArr = [NSMutableArray arrayWithCapacity:faces.count];
134 | for (AVMetadataFaceObject* face in faces) {
135 | AVMetadataObject *transfromedFace = [self.videoPreviewLayer transformedMetadataObjectForMetadataObject:face];
136 | [mArr addObject:transfromedFace];
137 | }
138 | return [mArr copy];
139 | }
140 |
141 | static CATransform3D CATransform3DMakePerspective(CGFloat eyePosition) {
142 | CATransform3D transform = CATransform3DIdentity;
143 | transform.m34 = -1.0 / eyePosition;
144 | return transform;
145 | }
146 |
147 | @end
148 |
--------------------------------------------------------------------------------
/SCCamera/Camera/View/SCPermissionsView.m:
--------------------------------------------------------------------------------
1 | //
2 | // SCPermissionsView.m
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/11.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import "SCPermissionsView.h"
10 | #import
11 | #import "UIView+SCCategory.h"
12 |
13 | @interface SCPermissionsView ()
14 | @property (nonatomic, strong) UIButton *cameraBtn;
15 | @property (nonatomic, strong) UIButton *microphoneBtn;
16 | @end
17 |
18 | @implementation SCPermissionsView
19 |
20 | - (instancetype)initWithFrame:(CGRect)frame {
21 | if (self = [super initWithFrame:frame]) {
22 | [self setupUI];
23 | [self setupCurStatus];
24 | }
25 | return self;
26 | }
27 |
28 | - (void)setupCurStatus {
29 | /*
30 | AVAuthorizationStatusNotDetermined = 0,
31 | AVAuthorizationStatusRestricted = 1,
32 | AVAuthorizationStatusDenied = 2,
33 | AVAuthorizationStatusAuthorized = 3,
34 | */
35 | AVAuthorizationStatus cameraStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
36 | BOOL cameraGranted = cameraStatus == AVAuthorizationStatusAuthorized;
37 | [self btnChange:self.cameraBtn granted:cameraGranted];
38 | AVAuthorizationStatus microphoneStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
39 | BOOL microphoneGranted = microphoneStatus == AVAuthorizationStatusAuthorized;
40 | [self btnChange:self.microphoneBtn granted:microphoneGranted];
41 | }
42 |
43 | - (void)btnChange:(UIButton*)btn granted:(BOOL)granted {
44 | [btn setEnabled:!granted];
45 | // 判断权限都获取了的状态
46 | if (!self.cameraBtn.enabled && !self.microphoneBtn.enabled) {
47 | if ([self.delegate respondsToSelector:@selector(permissionsViewDidHasAllPermissions:)]) {
48 | [self.delegate permissionsViewDidHasAllPermissions:self];
49 | }
50 | }
51 | }
52 |
53 | - (void)obtainPermission:(UIButton*)sender {
54 | AVMediaType type = AVMediaTypeVideo;
55 | if (sender == self.microphoneBtn)
56 | type = AVMediaTypeAudio;
57 | switch ([AVCaptureDevice authorizationStatusForMediaType:type]) {
58 | case AVAuthorizationStatusNotDetermined: {
59 | // 手动询问
60 | [AVCaptureDevice requestAccessForMediaType:type completionHandler:^(BOOL granted) {
61 | dispatch_async(dispatch_get_main_queue(), ^{
62 | [self btnChange:sender granted:granted];
63 | });
64 | }];
65 | break;
66 | }
67 | case AVAuthorizationStatusDenied:
68 | // 跳转设置界面
69 | [self openSetting];
70 | break;
71 | default:
72 | break;
73 | }
74 | }
75 |
76 | - (void)openSetting {
77 | NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
78 | if ([[UIApplication sharedApplication] canOpenURL:url]) {
79 | [[UIApplication sharedApplication] openURL:url];
80 | }
81 | }
82 |
83 | - (void)setupUI {
84 | self.backgroundColor = [UIColor blackColor];
85 | [self addSubview:self.cameraBtn];
86 | [self addSubview:self.microphoneBtn];
87 | [_cameraBtn setTranslatesAutoresizingMaskIntoConstraints:NO];
88 | [_microphoneBtn setTranslatesAutoresizingMaskIntoConstraints:NO];
89 | [self addConstraint:[NSLayoutConstraint constraintWithItem:_cameraBtn attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]];
90 | [self addConstraint:[NSLayoutConstraint constraintWithItem:_cameraBtn attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:0.8 constant:0]];
91 | [self addConstraint:[NSLayoutConstraint constraintWithItem:_microphoneBtn attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]];
92 | [self addConstraint:[NSLayoutConstraint constraintWithItem:_microphoneBtn attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0]];
93 | }
94 |
95 | #pragma mark - lazy
96 | - (UIButton *)cameraBtn {
97 | if (_cameraBtn == nil) {
98 | _cameraBtn = [UIButton buttonWithType:UIButtonTypeCustom];
99 | [_cameraBtn setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
100 | [_cameraBtn setTitle:@"允许访问相机" forState:UIControlStateNormal];
101 | [_cameraBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateDisabled];
102 | [_cameraBtn setTitle:@"相机访问权限已启用" forState:UIControlStateDisabled];
103 | [_cameraBtn addTarget:self action:@selector(obtainPermission:) forControlEvents:UIControlEventTouchUpInside];
104 | [_cameraBtn sizeToFit];
105 | }
106 | return _cameraBtn;
107 | }
108 |
109 | - (UIButton *)microphoneBtn {
110 | if (_microphoneBtn == nil) {
111 | _microphoneBtn = [UIButton buttonWithType:UIButtonTypeCustom];
112 | [_microphoneBtn setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
113 | [_microphoneBtn setTitle:@"允许访问麦克风" forState:UIControlStateNormal];
114 | [_microphoneBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateDisabled];
115 | [_microphoneBtn setTitle:@"麦克风权限已启用" forState:UIControlStateDisabled];
116 | [_microphoneBtn addTarget:self action:@selector(obtainPermission:) forControlEvents:UIControlEventTouchUpInside];
117 | [_microphoneBtn sizeToFit];
118 | }
119 | return _microphoneBtn;
120 | }
121 |
122 | @end
123 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Manager/SCMovieManager.m:
--------------------------------------------------------------------------------
1 | //
2 | // SCMovieManager.m
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/9.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import "SCMovieManager.h"
10 |
11 | static NSString *const SCMovieFileName = @"movie.mov";
12 |
13 | @interface SCMovieManager ()
14 | @property (nonatomic, assign) dispatch_queue_t movieQueue;
15 | @property (nonatomic, strong) NSURL *movieURL;
16 | @property (nonatomic, strong) AVAssetWriter *movieWriter;
17 | @property (nonatomic, strong) AVAssetWriterInput *movieVideoInput;
18 | @property (nonatomic, strong) AVAssetWriterInput *movieAudioInput;
19 |
20 | @property (nonatomic, assign, getter=isFirstSample) BOOL firstSample;
21 | @end
22 |
23 | @implementation SCMovieManager
24 |
25 | #pragma mark - public method
26 | - (instancetype)initWithDispatchQueue:(dispatch_queue_t)dispatchQueue {
27 | if (self = [super init]) {
28 | _movieQueue = dispatchQueue;
29 | _movieURL = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@%@", NSTemporaryDirectory(), @"movie.mov"]];
30 | }
31 | return self;
32 | }
33 |
34 | - (void)startRecordWithVideoSettings:(NSDictionary *)videoSettings
35 | audioSettings:(NSDictionary *)audioSettings
36 | handle:(void (^ _Nullable)(NSError * _Nonnull))handle {
37 | dispatch_async(self.movieQueue, ^{
38 | NSError *error;
39 | self.movieWriter = [AVAssetWriter assetWriterWithURL:self.movieURL fileType:AVFileTypeQuickTimeMovie error:&error];
40 | if (!self.movieWriter || error) {
41 | NSLog(@"movieWriter error.");
42 | if (handle) handle(error);
43 | return;
44 | }
45 | // 创建视频输入
46 | self.movieVideoInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];
47 | // 针对实时性进行优化
48 | self.movieVideoInput.expectsMediaDataInRealTime = YES;
49 |
50 | // TODO: - 如果应用程序只支持一个方向就需要做图像旋转转换
51 | // self.movieVideoInput.transform =
52 |
53 | if ([self.movieWriter canAddInput:self.movieVideoInput]) {
54 | [self.movieWriter addInput:self.movieVideoInput];
55 | } else {
56 | NSLog(@"Unable to add video input.");
57 | }
58 |
59 | // 创建音频输入
60 | self.movieAudioInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:audioSettings];
61 | // 针对实时性进行优化
62 | self.movieAudioInput.expectsMediaDataInRealTime = YES;
63 | if ([self.movieWriter canAddInput:self.movieAudioInput]) {
64 | [self.movieWriter addInput:self.movieAudioInput];
65 | } else {
66 | NSLog(@"Unable to add audio input.");
67 | }
68 |
69 | self.recording = YES;
70 | self.firstSample = YES;
71 | });
72 | }
73 |
74 | - (void)recordSampleBuffer:(CMSampleBufferRef)sampleBuffer {
75 | if (!self.isRecording) {
76 | return;
77 | }
78 | CMFormatDescriptionRef formatDesc = CMSampleBufferGetFormatDescription(sampleBuffer);
79 | CMMediaType mediaType = CMFormatDescriptionGetMediaType(formatDesc);
80 |
81 | if (mediaType == kCMMediaType_Video) {
82 | // 视频数据处理
83 | CMTime timestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
84 | if (self.isFirstSample) {
85 | if ([self.movieWriter startWriting]) {
86 | [self.movieWriter startSessionAtSourceTime: timestamp];
87 | } else {
88 | NSLog(@"Failed to start writing.");
89 | }
90 | self.firstSample = NO;
91 | }
92 | if (self.movieVideoInput.readyForMoreMediaData) {
93 | if (![self.movieVideoInput appendSampleBuffer:sampleBuffer]) {
94 | NSLog(@"Error appending video sample buffer.");
95 | }
96 | }
97 | } else if (!self.firstSample && mediaType == kCMMediaType_Audio) {
98 | // 音频数据处理(已处理至少一个视频数据)
99 | if (self.movieAudioInput.readyForMoreMediaData) {
100 | if (![self.movieAudioInput appendSampleBuffer:sampleBuffer]) {
101 | NSLog(@"Error appending audio sample buffer.");
102 | }
103 | }
104 | }
105 | }
106 |
107 | - (void)stopRecordWithCompletion:(void (^)(BOOL, NSURL * _Nullable))completion {
108 | self.recording = NO;
109 | dispatch_async(self.movieQueue, ^{
110 | [self.movieWriter finishWritingWithCompletionHandler:^{
111 | switch (self.movieWriter.status) {
112 | case AVAssetWriterStatusCompleted:{
113 | self.firstSample = YES;
114 | NSURL *fileURL = [self.movieWriter outputURL];
115 | completion(YES, fileURL);
116 | break;
117 | }
118 | default:
119 | NSLog(@"Failed to write movie: %@", self.movieWriter.error);
120 | break;
121 | }
122 | }];
123 | });
124 | }
125 |
126 | #pragma mark - setter/getter
127 | - (NSURL *)movieURL {
128 | NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:SCMovieFileName];
129 | NSURL *fileURL = [NSURL fileURLWithPath:filePath];
130 | if ([[NSFileManager defaultManager] fileExistsAtPath:fileURL.path]) {
131 | [[NSFileManager defaultManager] removeItemAtURL:fileURL error:nil];
132 | }
133 | return fileURL;
134 | }
135 |
136 | #pragma mark - test
137 | /// 保存视频
138 | - (void)saveMovieToCameraRoll:(NSURL *)url
139 | authHandle:(void (^)(BOOL, PHAuthorizationStatus))authHandle
140 | completion:(void (^)(BOOL, NSError * _Nullable))completion {
141 | [PHPhotoLibrary requestAuthorization:^( PHAuthorizationStatus status ) {
142 | if (status != PHAuthorizationStatusAuthorized) {
143 | authHandle(false, status);
144 | return;
145 | }
146 | [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
147 | PHAssetCreationRequest *videoRequest = [PHAssetCreationRequest creationRequestForAsset];
148 | [videoRequest addResourceWithType:PHAssetResourceTypeVideo fileURL:url options:nil];
149 | } completionHandler:^( BOOL success, NSError * _Nullable error ) {
150 | dispatch_sync(dispatch_get_main_queue(), ^{
151 | completion(success, error);
152 | });
153 | }];
154 | }];
155 | }
156 |
157 | @end
158 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Manager/SCCameraManager.m:
--------------------------------------------------------------------------------
1 | //
2 | // SCCameraManager.m
3 | // Detector
4 | //
5 | // Created by SeacenLiu on 2019/3/8.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import "SCCameraManager.h"
10 | #import
11 | #import "UIImage+SCCamera.h"
12 | #import "AVCaptureDevice+SCCategory.h"
13 |
14 | // TODO: - 判断权限
15 | @interface SCCameraManager ()
16 | @property (nonatomic, assign) float autoISO;
17 | @end
18 |
19 | @implementation SCCameraManager
20 |
21 | #pragma mark - init
22 | - (instancetype)init {
23 | if (self = [super init]) {
24 |
25 | }
26 | return self;
27 | }
28 |
29 | - (void)dealloc {
30 | NSLog(@"SCCameraManager dealloc");
31 | }
32 |
33 | /// 转换摄像头
34 | - (void)switchCamera:(AVCaptureSession *)session
35 | old:(AVCaptureDeviceInput *)oldInput
36 | new:(AVCaptureDeviceInput *)newInput
37 | handle:(CameraHandleError)handle {
38 | [session beginConfiguration];
39 | [session removeInput:oldInput];
40 | if ([session canAddInput:newInput]) {
41 | [session addInput:newInput];
42 | } else {
43 | [session addInput:oldInput];
44 | }
45 | [session commitConfiguration];
46 | }
47 |
48 | /// 缩放
49 | - (void)zoom:(AVCaptureDevice *)device factor:(CGFloat)factor handle:(CameraHandleError)handle {
50 | [device settingWithConfig:^(AVCaptureDevice *device, NSError *error) {
51 | if (error) {
52 | handle(error);
53 | return;
54 | }
55 | if (device.activeFormat.videoMaxZoomFactor > factor && factor >= 1.0) {
56 | [device rampToVideoZoomFactor:factor withRate:4.0];
57 | }
58 | }];
59 | }
60 |
61 | /// 聚焦&曝光
62 | - (void)focusWithMode:(AVCaptureFocusMode)focusMode
63 | exposeWithMode:(AVCaptureExposureMode)exposureMode
64 | device:(AVCaptureDevice*)device
65 | atDevicePoint:(CGPoint)point
66 | monitorSubjectAreaChange:(BOOL)monitorSubjectAreaChange
67 | handle:(CameraHandleError)handle {
68 | [device settingWithConfig:^(AVCaptureDevice *device, NSError *error) {
69 | if (error) {
70 | handle(error);
71 | return;
72 | }
73 | // 聚焦
74 | if (device.isFocusPointOfInterestSupported && [device isFocusModeSupported:focusMode]) {
75 | device.focusPointOfInterest = point;
76 | // 需要设置 focusMode 才应用 focusPointOfInterest
77 | device.focusMode = focusMode;
78 | }
79 | // 曝光
80 | if (device.isExposurePointOfInterestSupported && [device isExposureModeSupported:exposureMode]) {
81 | device.exposurePointOfInterest = point;
82 | device.exposureMode = exposureMode;
83 | }
84 | device.subjectAreaChangeMonitoringEnabled = monitorSubjectAreaChange;
85 | // NSLog(@"min: %f max: %f cur: %f", device.activeFormat.minISO, device.activeFormat.maxISO, device.ISO);
86 | self.autoISO = device.ISO;
87 | }];
88 | }
89 |
90 | /// 曝光ISO设置
91 | - (void)iso:(AVCaptureDevice *)device factor:(CGFloat)factor handle:(CameraHandleError)handle {
92 | // 0.5 对应的是 self.autoISO
93 | float margin = MIN(self.autoISO-device.activeFormat.minISO, device.activeFormat.maxISO-self.autoISO);
94 | float min = self.autoISO - margin;
95 | float max = self.autoISO + margin;
96 | float val = min + (max-min) * factor;
97 | [device settingWithConfig:^(AVCaptureDevice *device, NSError *error) {
98 | if (error) {
99 | handle(error);
100 | return;
101 | }
102 | __weak typeof(device) weakDevice = device;
103 | [device setExposureModeCustomWithDuration:device.exposureDuration ISO:val completionHandler:^(CMTime syncTime) {
104 | [weakDevice settingWithConfig:^(AVCaptureDevice *device, NSError *error) {
105 | [device setExposureMode:AVCaptureExposureModeCustom];
106 | }];
107 | }];
108 | }];
109 | }
110 |
111 | /// 闪光灯
112 | - (void)changeFlash:(AVCaptureDevice *)device mode:(AVCaptureFlashMode)mode handle:(CameraHandleError)handle {
113 | [device settingWithConfig:^(AVCaptureDevice *device, NSError *error) {
114 | if (error) {
115 | handle(error);
116 | return;
117 | }
118 | if ([device isFlashModeSupported:mode]) {
119 | device.flashMode = mode;
120 | } else {
121 | // TODO: - 抛出错误 handle(error)
122 | }
123 | }];
124 | }
125 |
126 | /// 补光
127 | - (void)changeTorch:(AVCaptureDevice *)device mode:(AVCaptureTorchMode)mode handle:(CameraHandleError)handle {
128 | [device settingWithConfig:^(AVCaptureDevice *device, NSError *error) {
129 | if (error) {
130 | handle(error);
131 | return;
132 | }
133 | if ([device isTorchModeSupported:mode]) {
134 | device.torchMode = mode;
135 | } else {
136 | // TODO: - 抛出错误 handle(error)
137 | }
138 | }];
139 | }
140 |
141 | /// 自动白平衡
142 | - (void)whiteBalance:(AVCaptureDevice*)device mode:(AVCaptureWhiteBalanceMode)mode handle:(CameraHandleError)handle {
143 | [device settingWithConfig:^(AVCaptureDevice *device, NSError *error) {
144 | if (error) {
145 | handle(error);
146 | return;
147 | }
148 | if ([device isWhiteBalanceModeSupported:mode]) {
149 | [device setWhiteBalanceMode:mode];
150 | } else {
151 | // TODO: - 抛出错误 handle(error)
152 | }
153 | }];
154 | }
155 |
156 | /// 重置聚焦&曝光
157 | - (void)resetFocusAndExpose:(AVCaptureDevice*)device handle:(CameraHandleError)handle {
158 | AVCaptureFocusMode focusMode = AVCaptureFocusModeContinuousAutoFocus;
159 | AVCaptureExposureMode exposureMode = AVCaptureExposureModeContinuousAutoExposure;
160 | BOOL canResetFocus = [device isFocusPointOfInterestSupported] &&
161 | [device isFocusModeSupported:focusMode];
162 | BOOL canResetExposure = [device isExposurePointOfInterestSupported] &&
163 | [device isExposureModeSupported:exposureMode];
164 | CGPoint centerPoint = CGPointMake(0.5f, 0.5f);
165 | [device settingWithConfig:^(AVCaptureDevice *device, NSError *error) {
166 | if (error) {
167 | handle(error);
168 | return;
169 | }
170 | if (canResetFocus) {
171 | device.focusPointOfInterest = centerPoint;
172 | device.focusMode = focusMode;
173 | }
174 | if (canResetExposure) {
175 | device.exposurePointOfInterest = centerPoint;
176 | device.exposureMode = exposureMode;
177 | }
178 | self.autoISO = device.ISO;
179 | }];
180 | }
181 |
182 | @end
183 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Manager/SCPhotoManager.m:
--------------------------------------------------------------------------------
1 | //
2 | // SCPhotoManager.m
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/20.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import "SCPhotoManager.h"
10 | #import "UIImage+SCCamera.h"
11 |
12 | @interface SCPhotoManager ()
13 | @property (nonatomic, copy) SCPhotoManagerStillImageCompletion stillPhotoCompletion;
14 | @property (nonatomic, copy) SCPhotoManagerLiveImageCompletion livePhotoCompletion;
15 | @property (nonatomic, assign) CGRect currentPreviewFrame;
16 | @property (nonatomic, strong) NSData *photoData;
17 | @end
18 |
19 | @implementation SCPhotoManager
20 |
21 | #pragma mark - init
22 | - (instancetype)initWithPhotoOutput:(AVCapturePhotoOutput*)photoOutput {
23 | if (self = [super init]) {
24 | self.photoOutput = photoOutput;
25 | }
26 | return self;
27 | }
28 |
29 | + (instancetype)photoManager:(AVCapturePhotoOutput*)photoOutput {
30 | return [[self alloc] initWithPhotoOutput:photoOutput];
31 | }
32 |
33 | #pragma mark - public method
34 | - (void)takeStillPhoto:(AVCaptureVideoPreviewLayer*)previewLayer
35 | completion:(SCPhotoManagerStillImageCompletion)completion {
36 | NSAssert(self.photoOutput, @"photoOutput 不可为空");
37 | self.currentPreviewFrame = previewLayer.frame;
38 | self.stillPhotoCompletion = completion;
39 |
40 | AVCaptureConnection *connection = [self.photoOutput connectionWithMediaType:AVMediaTypeVideo];
41 | if (connection.supportsVideoOrientation) {
42 | connection.videoOrientation = previewLayer.connection.videoOrientation;
43 | }
44 |
45 | AVCapturePhotoSettings *photoSettings = [AVCapturePhotoSettings photoSettings];
46 | if ([self.photoOutput.availablePhotoCodecTypes containsObject:AVVideoCodecJPEG]) {
47 | NSDictionary *format = @{AVVideoCodecKey: AVVideoCodecJPEG};
48 | photoSettings = [AVCapturePhotoSettings photoSettingsWithFormat:format];
49 | }
50 |
51 | photoSettings.autoStillImageStabilizationEnabled = YES;
52 |
53 | [self.photoOutput capturePhotoWithSettings:photoSettings delegate:self];
54 | }
55 |
56 | - (void)takeLivePhoto:(AVCaptureVideoPreviewLayer*)previewLayer
57 | completion:(SCPhotoManagerLiveImageCompletion)completion {
58 | NSAssert(self.photoOutput, @"photoOutput 不可为空");
59 | NSAssert(self.photoOutput.livePhotoCaptureEnabled, @"需要在捕捉会话启动前设置 livePhotoCaptureEnabled");
60 | self.currentPreviewFrame = previewLayer.frame;
61 | self.livePhotoCompletion = completion;
62 |
63 | AVCaptureConnection *connection = [self.photoOutput connectionWithMediaType:AVMediaTypeVideo];
64 | if (connection.supportsVideoOrientation) {
65 | connection.videoOrientation = previewLayer.connection.videoOrientation;
66 | }
67 |
68 | AVCapturePhotoSettings *photoSettings = [AVCapturePhotoSettings photoSettings];
69 | // 暂时不用 AVVideoCodecHEVC
70 | NSString *livePhotoMovieFileName = [[NSUUID UUID] UUIDString];
71 | NSString *livePhotoMovieFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[livePhotoMovieFileName stringByAppendingPathExtension:@"mov"]];
72 | photoSettings.livePhotoMovieFileURL = [NSURL fileURLWithPath:livePhotoMovieFilePath];
73 |
74 | [self.photoOutput capturePhotoWithSettings:photoSettings delegate:self];
75 | }
76 |
77 | #pragma mark - AVCapturePhotoCaptureDelegate
78 | - (void)captureOutput:(AVCapturePhotoOutput *)output willBeginCaptureForResolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings {
79 | // 拍摄准备完毕
80 | }
81 |
82 | - (void)captureOutput:(AVCapturePhotoOutput *)output willCapturePhotoForResolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings {
83 | // 曝光开始
84 | }
85 |
86 | - (void)captureOutput:(AVCapturePhotoOutput *)output didCapturePhotoForResolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings {
87 | // 曝光结束
88 | }
89 |
90 | /// ios(11.0) 才有用的
91 | //- (void) captureOutput:(AVCapturePhotoOutput*)captureOutput didFinishProcessingPhoto:(AVCapturePhoto*)photo error:(nullable NSError*)error API_AVAILABLE(ios(11.0)) {}
92 |
93 | - (void)captureOutput:(AVCapturePhotoOutput *)output didFinishProcessingPhotoSampleBuffer:(nullable CMSampleBufferRef)photoSampleBuffer previewPhotoSampleBuffer:(nullable CMSampleBufferRef)previewPhotoSampleBuffer resolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings bracketSettings:(nullable AVCaptureBracketedStillImageSettings *)bracketSettings error:(nullable NSError *)error API_DEPRECATED("Use -captureOutput:didFinishProcessingPhoto:error: instead.", ios(10.0, 11.0)) {
94 | if (error) {
95 | if (self.stillPhotoCompletion) {
96 | self.stillPhotoCompletion(nil, nil, nil, error);
97 | self.stillPhotoCompletion = nil;
98 | }
99 | if (self.livePhotoCompletion) {
100 | self.livePhotoCompletion(nil, nil, error);
101 | self.livePhotoCompletion = nil;
102 | }
103 | return;
104 | }
105 | // 1. 获取 originImage
106 | NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:photoSampleBuffer];
107 | self.photoData = imageData;
108 | // 静态图片处理
109 | if (self.stillPhotoCompletion) {
110 | UIImage *originImage = [[UIImage alloc] initWithData:imageData];
111 | originImage = [originImage fixOrientation];
112 | // 2. 获取 scaledImage
113 | CGFloat width = self.currentPreviewFrame.size.width;
114 | CGFloat height = self.currentPreviewFrame.size.height;
115 | CGFloat scale = [[UIScreen mainScreen] scale];
116 | CGSize size = CGSizeMake(width*scale, height*scale);
117 | UIImage *scaledImage = [originImage resizedImageWithContentMode:UIViewContentModeScaleAspectFill size:size interpolationQuality:kCGInterpolationHigh];
118 | // 3. 获取 croppedImage
119 | CGRect cropFrame = CGRectMake((scaledImage.size.width - size.width) * 0.5, (scaledImage.size.height - size.height) * 0.5, size.width, size.height);
120 | UIImage *croppedImage = [scaledImage croppedImage:cropFrame];
121 | // 4. 回调
122 | dispatch_async(dispatch_get_main_queue(), ^{
123 | self.stillPhotoCompletion(originImage, scaledImage, croppedImage, nil);
124 | self.stillPhotoCompletion = nil;
125 | });
126 | }
127 | }
128 |
129 | - (void)captureOutput:(AVCapturePhotoOutput *)output didFinishRecordingLivePhotoMovieForEventualFileAtURL:(NSURL *)outputFileURL resolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings {
130 | // 完成 Live Photo 停止拍摄
131 | }
132 |
133 | - (void)captureOutput:(AVCapturePhotoOutput *)output didFinishProcessingLivePhotoToMovieFileAtURL:(NSURL *)outputFileURL duration:(CMTime)duration photoDisplayTime:(CMTime)photoDisplayTime resolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings error:(nullable NSError *)error {
134 | if (error) {
135 | if (self.livePhotoCompletion) {
136 | self.livePhotoCompletion(nil, nil, error);
137 | self.livePhotoCompletion = nil;
138 | }
139 | return;
140 | }
141 | if (self.livePhotoCompletion) {
142 | self.livePhotoCompletion(outputFileURL, self.photoData, nil);
143 | self.livePhotoCompletion = nil;
144 | }
145 | }
146 |
147 | - (void) captureOutput:(AVCapturePhotoOutput*)captureOutput didFinishCaptureForResolvedSettings:(AVCaptureResolvedPhotoSettings*)resolvedSettings error:(NSError*)error {
148 | // 完成拍摄,可以在此处保存
149 | }
150 |
151 | #pragma mark - private method
152 |
153 |
154 | #pragma mark - setter/getter
155 | - (void)setPhotoOutput:(AVCapturePhotoOutput *)photoOutput {
156 | _photoOutput = photoOutput;
157 | }
158 |
159 | @end
160 |
161 |
--------------------------------------------------------------------------------
/SCCamera/Camera/View/SCCameraView.m:
--------------------------------------------------------------------------------
1 | //
2 | // SCCameraView.m
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/8.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import "SCCameraView.h"
10 | #import "SCVideoPreviewView.h"
11 | #import "UIView+SCCategory.h"
12 |
13 | typedef NS_ENUM(NSInteger, SCCameraType) {
14 | SCCameraTypePhoto,
15 | SCCameraTypeMovie
16 | };
17 |
18 | @interface SCCameraView ()
19 |
20 | @property (nonatomic, assign) SCCameraType currentType;
21 | @property (weak, nonatomic) IBOutlet UIButton *photoBtn;
22 | @property (weak, nonatomic) IBOutlet UIButton *typeSelBtn;
23 | @property (weak, nonatomic) IBOutlet UIButton *liveBtn;
24 |
25 | /// 聚焦动画 view
26 | @property (nonatomic, weak) IBOutlet UIView *focusView;
27 | /// 显示缩放比
28 | @property(nonatomic, weak) IBOutlet UISlider *zoomSlider;
29 | /// 亮度调节
30 | @property (weak, nonatomic) IBOutlet UISlider *isoSlider;
31 | @end
32 |
33 | @implementation SCCameraView
34 |
35 | + (instancetype)cameraView:(CGRect)frame {
36 | SCCameraView *view = (SCCameraView*)[[UINib nibWithNibName:@"SCCameraView" bundle:nil] instantiateWithOwner:self options:nil][0];
37 | view.frame = frame;
38 | [view addGestureRecognizers];
39 | [view setupUI];
40 | return view;
41 | }
42 |
43 | - (void)setupUI {
44 | [self addSubview:self.focusView];
45 | [self addSubview:self.zoomSlider];
46 | self.zoomSlider.transform = CGAffineTransformMakeRotation(-M_PI_2);
47 | self.isoSlider.transform = CGAffineTransformMakeRotation(-M_PI_2);
48 | }
49 |
50 | #pragma mark - 手势添加
51 | - (void)addGestureRecognizers {
52 | // 单击 -> 自动聚焦&曝光
53 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(focusingTapClcik:)];
54 | [self.previewView addGestureRecognizer:tap];
55 | // 捏合 -> 缩放
56 | UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action: @selector(pinchAction:)];
57 | [self.previewView addGestureRecognizer:pinch];
58 | // 双指单击 -> 重置聚焦&曝光
59 | UITapGestureRecognizer *tap2 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(resetTapClcik:)];
60 | tap2.numberOfTouchesRequired = 2;
61 | [self.previewView addGestureRecognizer:tap2];
62 | }
63 |
64 | #pragma mark - 相机操作
65 | - (IBAction)isoSliderAction:(UISlider*)sender {
66 | if ([_delegate respondsToSelector:@selector(isoAction:factor:handle:)]) {
67 | [_delegate isoAction:self factor:sender.value handle:^(NSError * _Nonnull error) {
68 | // TODO: - handle error
69 | }];
70 | }
71 | }
72 |
73 | - (IBAction)zoomSliderAction:(UISlider*)sender {
74 | if ([_delegate respondsToSelector:@selector(zoomAction:factor:handle:)]) {
75 | [_delegate zoomAction:self factor: powf(5, sender.value) handle:^(NSError * _Nonnull error) {
76 | // TODO: - handle error
77 | }];
78 | }
79 | }
80 |
81 | - (void)pinchAction:(UIPinchGestureRecognizer *)pinch {
82 | if ([_delegate respondsToSelector:@selector(zoomAction:factor:handle:)]) {
83 | switch (pinch.state) {
84 | case UIGestureRecognizerStateBegan:
85 | break;
86 | case UIGestureRecognizerStateChanged:
87 | // 根据捏合速度来做
88 | if (pinch.velocity > 0) {
89 | _zoomSlider.value += pinch.velocity/500;
90 | } else {
91 | _zoomSlider.value += pinch.velocity/200;
92 | }
93 | [_delegate zoomAction:self factor: powf(5, _zoomSlider.value) handle:^(NSError * _Nonnull error) {
94 | // TODO: - handle error
95 | }];
96 | break;
97 | case UIGestureRecognizerStateEnded:
98 | break;
99 | default:
100 | break;
101 | }
102 | }
103 | }
104 |
105 | - (void)focusingTapClcik:(UITapGestureRecognizer *)tap {
106 | if ([_delegate respondsToSelector:@selector(focusAndExposeAction:point:handle:)]) {
107 | CGPoint point = [tap locationInView:self.previewView];
108 | [_delegate focusAndExposeAction:self point:point handle:^(NSError * _Nonnull error) {
109 | // TODO: - handle error
110 | }];
111 | self.isoSlider.value = 0.5;
112 | }
113 | }
114 |
115 | - (void)resetTapClcik:(UITapGestureRecognizer *)tap {
116 | if ([_delegate respondsToSelector:@selector(resetFocusAndExposeAction:handle:)]) {
117 | [self runFocusAnimation:self.previewView.center];
118 | [_delegate resetFocusAndExposeAction:self handle:^(NSError * _Nonnull error) {
119 | // TODO: - handle error
120 | }];
121 | self.isoSlider.value = 0.5;
122 | }
123 | }
124 |
125 | - (IBAction)flashLightClick:(UIButton*)sender {
126 | if ([self.delegate respondsToSelector:@selector(flashLightAction:isOn:handle:)]) {
127 | [sender setSelected:!sender.isSelected];
128 | [_delegate flashLightAction:self isOn:sender.isSelected handle:^(NSError * _Nonnull error) {
129 | // TODO: - handle error
130 | }];
131 | }
132 | }
133 |
134 | - (IBAction)torchLightClick:(UIButton*)sender {
135 | if ([self.delegate respondsToSelector:@selector(torchLightAction:isOn:handle:)]) {
136 | [sender setSelected:!sender.isSelected];
137 | [_delegate torchLightAction:self isOn:sender.isSelected handle:^(NSError * _Nonnull error) {
138 | // TODO: - handle error
139 | }];
140 | }
141 | }
142 |
143 | - (IBAction)switchCameraClick:(UIButton*)sender {
144 | if ([_delegate respondsToSelector:@selector(switchCameraAction:isFront:handle:)]) {
145 | [sender setSelected:!sender.isSelected];
146 | [_delegate switchCameraAction:self isFront:sender.isSelected handle:^(NSError * _Nonnull error) {
147 | // TODO: - handle error
148 | }];
149 | }
150 | }
151 |
152 | - (IBAction)cancelClick:(id)sender {
153 | if ([_delegate respondsToSelector:@selector(cancelAction:)]) {
154 | [_delegate cancelAction:self];
155 | }
156 | }
157 |
158 | - (IBAction)cameraTypeClick:(UIButton*)sender {
159 | // 如果是 photoBtn 被选中(正在录像)则不做事
160 | if (self.photoBtn.isSelected) {
161 | return;
162 | }
163 | switch (self.currentType) {
164 | case SCCameraTypePhoto:
165 | [self.photoBtn setTitle:@"开始" forState:UIControlStateNormal];
166 | [self.photoBtn setTitle:@"停止" forState:UIControlStateSelected];
167 | [self.typeSelBtn setTitle:@"录像" forState:UIControlStateNormal];
168 | self.currentType = SCCameraTypeMovie;
169 | break;
170 | case SCCameraTypeMovie:
171 | [self.photoBtn setSelected:NO];
172 | [self.photoBtn setTitle:@"拍照" forState:UIControlStateNormal];
173 | [self.photoBtn setTitle:@"拍照" forState:UIControlStateSelected];
174 | [self.typeSelBtn setTitle:@"拍照" forState:UIControlStateNormal];
175 | self.currentType = SCCameraTypePhoto;
176 | break;
177 | }
178 | }
179 |
180 | - (IBAction)liveBtnClick:(UIButton*)sender {
181 | [sender setSelected:!sender.isSelected];
182 | }
183 |
184 | - (IBAction)photoClick:(UIButton*)sender {
185 | switch (self.currentType) {
186 | case SCCameraTypePhoto:
187 | if (self.liveBtn.selected) {
188 | // Live Photo
189 | if ([_delegate respondsToSelector:@selector(takeLivePhotoAction:)]) {
190 | [_delegate takeLivePhotoAction:self];
191 | }
192 | } else {
193 | // Still Photo
194 | if ([_delegate respondsToSelector:@selector(takeStillPhotoAction:)]) {
195 | [_delegate takeStillPhotoAction:self];
196 | }
197 | }
198 | break;
199 | case SCCameraTypeMovie:
200 | [sender setSelected:!sender.isSelected];
201 | if (sender.isSelected) {
202 | // 开始录制
203 | if ([_delegate respondsToSelector:@selector(startRecordVideoAction:)]) {
204 | [_delegate startRecordVideoAction:self];
205 | }
206 | } else {
207 | // 停止录制
208 | if ([_delegate respondsToSelector:@selector(stopRecordVideoAction:)]) {
209 | [_delegate stopRecordVideoAction:self];
210 | }
211 | }
212 | break;
213 | }
214 | }
215 |
216 | #pragma mark - Animation
217 | - (void)runFocusAnimation:(CGPoint)center {
218 | [self runFocusAnimation:self.focusView point:center];
219 | }
220 |
221 | /// 聚焦、曝光动画
222 | - (void)runFocusAnimation:(UIView *)view point:(CGPoint)point {
223 | view.center = point;
224 | view.hidden = NO;
225 | [UIView animateWithDuration:0.15f delay:0.0f options:UIViewAnimationOptionCurveEaseInOut animations:^{
226 | view.layer.transform = CATransform3DMakeScale(0.5, 0.5, 1.0);
227 | } completion:^(BOOL complete) {
228 | double delayInSeconds = 0.5f;
229 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
230 | dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
231 | view.hidden = YES;
232 | view.transform = CGAffineTransformIdentity;
233 | });
234 | }];
235 | }
236 |
237 | @end
238 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Category/UIImage+SCCamera.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIImage+SCCamera.m
3 | // SCCamera
4 | //
5 | // Created by SeacenLiu on 2019/4/3.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import "UIImage+SCCamera.h"
10 |
11 | @implementation UIImage (SCCamera)
12 |
13 | + (instancetype)imageFromSampleBuffer:(CMSampleBufferRef)sampleBuffer {
14 | CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
15 | CVPixelBufferLockBaseAddress(imageBuffer, 0);
16 |
17 | void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
18 |
19 | size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
20 | size_t width = CVPixelBufferGetWidth(imageBuffer);
21 | size_t height = CVPixelBufferGetHeight(imageBuffer);
22 |
23 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
24 |
25 | CGContextRef context = CGBitmapContextCreate(baseAddress, width, height, 8,
26 | bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
27 | CGImageRef quartzImage = CGBitmapContextCreateImage(context);
28 | CVPixelBufferUnlockBaseAddress(imageBuffer,0);
29 |
30 | CGContextRelease(context);
31 | CGColorSpaceRelease(colorSpace);
32 |
33 | UIImage *image = [UIImage imageWithCGImage:quartzImage scale:1.0f orientation:UIImageOrientationRight];
34 |
35 | CGImageRelease(quartzImage);
36 |
37 | return (image);
38 | }
39 |
40 | // Return a copy of image that simple resize
41 | // ignore scale
42 | - (UIImage *)simpleResizeTo:(CGSize)newSize {
43 | CGFloat scale = [[UIScreen mainScreen]scale];
44 | //UIGraphicsBeginImageContext(newSize);
45 | UIGraphicsBeginImageContextWithOptions(newSize, NO, scale);
46 | [self drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
47 | UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
48 | UIGraphicsEndImageContext();
49 | return newImage;
50 | }
51 |
52 | // Returns a copy of this image that is cropped to the given bounds.
53 | // The bounds will be adjusted using CGRectIntegral.
54 | // This method ignores the image's imageOrientation setting.
55 | - (UIImage *)croppedImage:(CGRect)bounds
56 | {
57 | CGImageRef imageRef = CGImageCreateWithImageInRect([self CGImage], bounds);
58 | UIImage *croppedImage = [UIImage imageWithCGImage:imageRef];
59 | CGImageRelease(imageRef);
60 | return croppedImage;
61 | }
62 |
63 | // Returns a rescaled copy of the image, taking into account its orientation
64 | // The image will be scaled disproportionately if necessary to fit the bounds specified by the parameter
65 | - (UIImage *)resizedImage:(CGSize)newSize interpolationQuality:(CGInterpolationQuality)quality
66 | {
67 | // 不需要 drawTransposed
68 | CGAffineTransform transform = CGAffineTransformIdentity;
69 | transform = [self transformForOrientation:newSize];
70 | return [self resizedImage:newSize transform:transform interpolationQuality:quality];
71 | }
72 |
73 | // Resizes the image according to the given content mode, taking into account the image's orientation
74 | - (UIImage *)resizedImageWithContentMode:(UIViewContentMode)contentMode
75 | size:(CGSize)size
76 | interpolationQuality:(CGInterpolationQuality)quality
77 | {
78 | CGFloat horizontalRatio = size.width / self.size.width;
79 | CGFloat verticalRatio = size.height / self.size.height;
80 | CGFloat ratio;
81 |
82 | switch(contentMode) {
83 | case UIViewContentModeScaleAspectFill:
84 | ratio = MAX(horizontalRatio, verticalRatio);
85 | break;
86 |
87 | case UIViewContentModeScaleAspectFit:
88 | ratio = MIN(horizontalRatio, verticalRatio);
89 | break;
90 |
91 | default:
92 | [NSException raise:NSInvalidArgumentException format:@"Unsupported content mode: %ld", (long)contentMode];
93 | }
94 |
95 | CGSize newSize = CGSizeMake(self.size.width * ratio, self.size.height * ratio);
96 |
97 | return [self resizedImage:newSize interpolationQuality:quality];
98 | }
99 |
100 |
101 | #pragma mark - fix orientation
102 | - (UIImage *)fixOrientation
103 | {
104 | // No-op if the orientation is already correct
105 | if (self.imageOrientation == UIImageOrientationUp) return self;
106 |
107 | // We need to calculate the proper transformation to make the image upright.
108 | // We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored.
109 | CGAffineTransform transform = CGAffineTransformIdentity;
110 |
111 | switch (self.imageOrientation) {
112 | case UIImageOrientationDown:
113 | case UIImageOrientationDownMirrored:
114 | transform = CGAffineTransformTranslate(transform, self.size.width, self.size.height);
115 | transform = CGAffineTransformRotate(transform, M_PI);
116 | break;
117 |
118 | case UIImageOrientationLeft:
119 | case UIImageOrientationLeftMirrored:
120 | transform = CGAffineTransformTranslate(transform, self.size.width, 0);
121 | transform = CGAffineTransformRotate(transform, M_PI_2);
122 | break;
123 |
124 | case UIImageOrientationRight:
125 | case UIImageOrientationRightMirrored:
126 | transform = CGAffineTransformTranslate(transform, 0, self.size.height);
127 | transform = CGAffineTransformRotate(transform, -M_PI_2);
128 | break;
129 | case UIImageOrientationUp:
130 | case UIImageOrientationUpMirrored:
131 | break;
132 | }
133 |
134 | switch (self.imageOrientation) {
135 | case UIImageOrientationUpMirrored:
136 | case UIImageOrientationDownMirrored:
137 | transform = CGAffineTransformTranslate(transform, self.size.width, 0);
138 | transform = CGAffineTransformScale(transform, -1, 1);
139 | break;
140 |
141 | case UIImageOrientationLeftMirrored:
142 | case UIImageOrientationRightMirrored:
143 | transform = CGAffineTransformTranslate(transform, self.size.height, 0);
144 | transform = CGAffineTransformScale(transform, -1, 1);
145 | break;
146 | case UIImageOrientationUp:
147 | case UIImageOrientationDown:
148 | case UIImageOrientationLeft:
149 | case UIImageOrientationRight:
150 | break;
151 | }
152 |
153 | // Now we draw the underlying CGImage into a new context, applying the transform
154 | // calculated above.
155 | CGContextRef ctx = CGBitmapContextCreate(NULL, self.size.width, self.size.height,
156 | CGImageGetBitsPerComponent(self.CGImage), 0,
157 | CGImageGetColorSpace(self.CGImage),
158 | CGImageGetBitmapInfo(self.CGImage));
159 | CGContextConcatCTM(ctx, transform);
160 | switch (self.imageOrientation) {
161 | case UIImageOrientationLeft:
162 | case UIImageOrientationLeftMirrored:
163 | case UIImageOrientationRight:
164 | case UIImageOrientationRightMirrored:
165 | // Grr...
166 | CGContextDrawImage(ctx, CGRectMake(0,0,self.size.height,self.size.width), self.CGImage);
167 | break;
168 |
169 | default:
170 | CGContextDrawImage(ctx, CGRectMake(0,0,self.size.width,self.size.height), self.CGImage);
171 | break;
172 | }
173 |
174 | // And now we just create a new UIImage from the drawing context
175 | CGImageRef cgimg = CGBitmapContextCreateImage(ctx);
176 | UIImage *img = [UIImage imageWithCGImage:cgimg];
177 | CGContextRelease(ctx);
178 | CGImageRelease(cgimg);
179 | return img;
180 | }
181 |
182 | static inline CGFloat DegreesToRadians(CGFloat degrees)
183 | {
184 | return M_PI * (degrees / 180.0);
185 | }
186 |
187 | - (UIImage *)rotatedByDegrees:(CGFloat)degrees
188 | {
189 | // calculate the size of the rotated view's containing box for our drawing space
190 | UIView *rotatedViewBox = [[UIView alloc] initWithFrame:CGRectMake(0,0,self.size.width, self.size.height)];
191 | CGAffineTransform t = CGAffineTransformMakeRotation(DegreesToRadians(degrees));
192 | rotatedViewBox.transform = t;
193 | CGSize rotatedSize = rotatedViewBox.frame.size;
194 |
195 | // Create the bitmap context
196 | UIGraphicsBeginImageContext(rotatedSize);
197 | CGContextRef bitmap = UIGraphicsGetCurrentContext();
198 |
199 | // Move the origin to the middle of the image so we will rotate and scale around the center.
200 | CGContextTranslateCTM(bitmap, rotatedSize.width/2, rotatedSize.height/2);
201 |
202 | // // Rotate the image context
203 | CGContextRotateCTM(bitmap, DegreesToRadians(degrees));
204 |
205 | // Now, draw the rotated/scaled image into the context
206 | CGContextScaleCTM(bitmap, 1.0, -1.0);
207 | CGContextDrawImage(bitmap, CGRectMake(-self.size.width / 2, -self.size.height / 2, self.size.width, self.size.height), [self CGImage]);
208 |
209 | UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
210 | UIGraphicsEndImageContext();
211 | return newImage;
212 | }
213 |
214 | // Returns a copy of the image that has been transformed using the given affine transform and scaled to the new size
215 | // The new image's orientation will be UIImageOrientationUp, regardless of the current image's orientation
216 | // If the new size is not integral, it will be rounded up
217 | - (UIImage *)resizedImage:(CGSize)newSize
218 | transform:(CGAffineTransform)transform
219 | interpolationQuality:(CGInterpolationQuality)quality
220 | {
221 | CGRect newRect = CGRectIntegral(CGRectMake(0, 0, newSize.width, newSize.height));
222 | // Delete
223 | // CGRect transposedRect = CGRectMake(0, 0, newRect.size.height, newRect.size.width);
224 | CGImageRef imageRef = self.CGImage;
225 |
226 | // Fix for a colorspace / transparency issue that affects some types of
227 | // images. See here: http://vocaro.com/trevor/blog/2009/10/12/resize-a-uiimage-the-right-way/comment-page-2/#comment-39951
228 |
229 | CGContextRef bitmap = CGBitmapContextCreate(NULL,
230 | newRect.size.width,
231 | newRect.size.height,
232 | 8,
233 | 0,
234 | CGImageGetColorSpace(imageRef),
235 | kCGImageAlphaNoneSkipLast);
236 |
237 | // Rotate and/or flip the image if required by its orientation
238 | CGContextConcatCTM(bitmap, transform);
239 |
240 | // Set the quality level to use when rescaling
241 | CGContextSetInterpolationQuality(bitmap, quality);
242 |
243 | // Draw into the context; this scales the image
244 | // Delete
245 | // CGContextDrawImage(bitmap, transpose ? transposedRect : newRect, imageRef);
246 | CGContextDrawImage(bitmap, newRect, imageRef);
247 |
248 | // Get the resized image from the context and a UIImage
249 | CGImageRef newImageRef = CGBitmapContextCreateImage(bitmap);
250 | UIImage *newImage = [UIImage imageWithCGImage:newImageRef];
251 |
252 | // Clean up
253 | CGContextRelease(bitmap);
254 | CGImageRelease(newImageRef);
255 |
256 | return newImage;
257 | }
258 |
259 | // Returns an affine transform that takes into account the image orientation when drawing a scaled image
260 | - (CGAffineTransform)transformForOrientation:(CGSize)newSize
261 | {
262 | CGAffineTransform transform = CGAffineTransformIdentity;
263 |
264 | switch(self.imageOrientation) {
265 | case UIImageOrientationDown: // EXIF = 3
266 | case UIImageOrientationDownMirrored: // EXIF = 4
267 | transform = CGAffineTransformTranslate(transform, newSize.width, newSize.height);
268 | transform = CGAffineTransformRotate(transform, M_PI);
269 | break;
270 |
271 | case UIImageOrientationLeft: // EXIF = 6
272 | case UIImageOrientationLeftMirrored: // EXIF = 5
273 | transform = CGAffineTransformTranslate(transform, newSize.width, 0);
274 | transform = CGAffineTransformRotate(transform, M_PI_2);
275 | break;
276 |
277 | case UIImageOrientationRight: // EXIF = 8
278 | case UIImageOrientationRightMirrored: // EXIF = 7
279 | transform = CGAffineTransformTranslate(transform, 0, newSize.height);
280 | transform = CGAffineTransformRotate(transform, -M_PI_2);
281 | break;
282 | default:
283 | break;
284 | }
285 |
286 | switch(self.imageOrientation) {
287 | case UIImageOrientationUpMirrored: // EXIF = 2
288 | case UIImageOrientationDownMirrored: // EXIF = 4
289 | transform = CGAffineTransformTranslate(transform, newSize.width, 0);
290 | transform = CGAffineTransformScale(transform, -1, 1);
291 | break;
292 |
293 | case UIImageOrientationLeftMirrored: // EXIF = 5
294 | case UIImageOrientationRightMirrored: // EXIF = 7
295 | transform = CGAffineTransformTranslate(transform, newSize.height, 0);
296 | transform = CGAffineTransformScale(transform, -1, 1);
297 | break;
298 | default:
299 | break;
300 | }
301 |
302 | return transform;
303 | }
304 |
305 | @end
306 |
--------------------------------------------------------------------------------
/SCCamera/Camera/View/SCCameraView.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
34 |
41 |
51 |
65 |
75 |
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 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
--------------------------------------------------------------------------------
/SCCamera/Camera/Controller/SCCameraController.m:
--------------------------------------------------------------------------------
1 | //
2 | // SCCameraController.m
3 | // Detector
4 | //
5 | // Created by SeacenLiu on 2019/3/8.
6 | // Copyright © 2019 SeacenLiu. All rights reserved.
7 | //
8 |
9 | #import "SCCameraController.h"
10 | #import "SCVideoPreviewView.h"
11 | #import "SCCameraResultController.h"
12 | #import "SCShowMovieController.h"
13 | #import "SCCameraView.h"
14 | #import "AVCaptureDevice+SCCategory.h"
15 | #import
16 | #import "SCPermissionsView.h"
17 |
18 | #import "SCCameraManager.h"
19 | #import "SCStillPhotoManager.h"
20 | #import "SCMovieManager.h"
21 | #import "SCPhotoManager.h"
22 |
23 | #import "PHPhotoLibrary+save.h"
24 |
25 | // TODO: - 待处理
26 | // - 视频录制过程中,补光失效
27 |
28 | API_AVAILABLE(ios(10.0))
29 | @interface SCCameraController ()
30 | @property (nonatomic) dispatch_queue_t sessionQueue;
31 | @property (nonatomic) dispatch_queue_t metaQueue;
32 | @property (nonatomic) dispatch_queue_t captureQueue;
33 | // 会话
34 | @property (nonatomic, strong) AVCaptureSession *session;
35 | // 输入
36 | @property (nonatomic, strong) AVCaptureDeviceInput *backCameraInput;
37 | @property (nonatomic, strong) AVCaptureDeviceInput *frontCameraInput;
38 | @property (nonatomic, strong) AVCaptureDeviceInput *currentCameraInput;
39 | // 输出
40 | @property (nonatomic, strong) AVCaptureVideoDataOutput *videoOutput;
41 | @property (nonatomic, strong) AVCaptureAudioDataOutput *audioOutput;
42 | @property (nonatomic, strong) AVCaptureMetadataOutput *metaOutput;
43 | @property (nonatomic, strong) AVCapturePhotoOutput *photoOutput;
44 | //@property (nonatomic, strong) AVCaptureStillImageOutput *stillImageOutput; // iOS10 AVCapturePhotoOutput
45 | //@property (nonatomic, strong) AVCaptureMovieFileOutput *movieFileOutput;
46 |
47 | @property (nonatomic, strong) SCCameraView *cameraView;
48 | @property (nonatomic, strong) SCPermissionsView *permissionsView;
49 |
50 | @property (nonatomic, strong) SCCameraManager *cameraManager;
51 | @property (nonatomic, strong) SCStillPhotoManager *stillPhotoManager;
52 | @property (nonatomic, strong) SCMovieManager *movieManager;
53 | @property (nonatomic, strong) SCPhotoManager *photoManager;
54 |
55 | /// 有相机和麦克风的权限(必须调用getter方法)
56 | @property (nonatomic, assign, readonly) BOOL hasAllPermissions;
57 | @end
58 |
59 | @implementation SCCameraController
60 |
61 | #pragma mark - view life cycle
62 | - (void)viewDidLoad {
63 | [super viewDidLoad];
64 | [self setupUI];
65 | if (!self.hasAllPermissions) { // 没有权限
66 | [self setupPermissionsView];
67 | } else { // 有权限
68 | dispatch_async(self.sessionQueue, ^{
69 | NSError *error;
70 | [self configureSession:&error];
71 | // TODO: - 处理配置会话错误情况
72 | });
73 | }
74 | self.faceDetectionDelegate = self.cameraView.previewView;
75 | }
76 |
77 | - (void)permissionsViewDidHasAllPermissions:(SCPermissionsView *)pv {
78 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
79 | [UIView animateWithDuration:0.25 animations:^{
80 | pv.alpha = 0;
81 | } completion:^(BOOL finished) {
82 | [self.permissionsView removeFromSuperview];
83 | self.permissionsView = nil;
84 | }];
85 | });
86 | dispatch_async(self.sessionQueue, ^{
87 | [self configureSession:nil];
88 | [self.session startRunning];
89 | });
90 | }
91 |
92 | - (void)viewWillAppear:(BOOL)animated {
93 | [super viewWillAppear:animated];
94 | dispatch_async(self.sessionQueue, ^{
95 | if (self.hasAllPermissions && !self.session.isRunning) {
96 | [self.session startRunning];
97 | }
98 | });
99 | }
100 |
101 | - (void)viewDidDisappear:(BOOL)animated {
102 | [super viewDidDisappear:animated];
103 | dispatch_async(self.sessionQueue, ^{
104 | if (self.session.isRunning) {
105 | [self.session stopRunning];
106 | }
107 | });
108 | }
109 |
110 | - (void)setupPermissionsView {
111 | [self.cameraView addSubview:self.permissionsView];
112 | [self.cameraView bringSubviewToFront:self.cameraView.cancelBtn];
113 | [self.permissionsView setTranslatesAutoresizingMaskIntoConstraints:NO];
114 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.permissionsView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]];
115 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.permissionsView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0]];
116 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.permissionsView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]];
117 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.permissionsView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0 constant:0]];
118 | }
119 |
120 | - (void)setupUI {
121 | [self.view addSubview:self.cameraView];
122 | [self.cameraView setTranslatesAutoresizingMaskIntoConstraints:NO];
123 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.cameraView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]];
124 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.cameraView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0]];
125 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.cameraView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]];
126 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.cameraView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0 constant:0]];
127 | }
128 |
129 | #pragma mark - 会话配置
130 | /** 配置会话 */
131 | - (void)configureSession:(NSError**)error {
132 | [self.session beginConfiguration];
133 | // Live Photo 不支持 AVCaptureSessionPresetHigh !!!
134 | self.session.sessionPreset = AVCaptureSessionPresetPhoto;
135 | [self setupSessionInput:error];
136 | dispatch_async(dispatch_get_main_queue(), ^{
137 | // 在添加视频输入后就可以设置
138 | self.cameraView.previewView.captureSession = self.session;
139 | });
140 | [self setupSessionOutput:error];
141 | [self.session commitConfiguration];
142 | }
143 |
144 | /** 配置输入 */
145 | - (void)setupSessionInput:(NSError**)error {
146 | // 视频输入(默认是后置摄像头)
147 | // 创建Input时候可能会有错误
148 | if ([_session canAddInput:self.backCameraInput]) {
149 | [_session addInput:self.backCameraInput];
150 | } else {
151 | NSLog(@"Failed backCameraInput");
152 | }
153 | self.currentCameraInput = _backCameraInput;
154 |
155 | // 音频输入
156 | AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
157 | AVCaptureDeviceInput *audioInput = [[AVCaptureDeviceInput alloc] initWithDevice:audioDevice error:error];
158 | if ([_session canAddInput:audioInput]){
159 | [_session addInput:audioInput];
160 | }
161 | }
162 |
163 | /** 配置输出 */
164 | - (void)setupSessionOutput:(NSError**)error {
165 | // 添加视频输出
166 | _videoOutput = [AVCaptureVideoDataOutput new];
167 | NSDictionary *rgbOutputSettings = [NSDictionary dictionaryWithObject:
168 | [NSNumber numberWithInt:kCMPixelFormat_32BGRA] forKey:(id)kCVPixelBufferPixelFormatTypeKey];
169 | [_videoOutput setVideoSettings:rgbOutputSettings];
170 | [_videoOutput setSampleBufferDelegate:self queue:self.captureQueue];
171 | // 代理方法会提供额外的时间用于处理样本,但会增加内存消耗
172 | [_videoOutput setAlwaysDiscardsLateVideoFrames:NO];
173 | if ([_session canAddOutput:_videoOutput]) {
174 | [_session addOutput:_videoOutput];
175 | }
176 |
177 | // 音频输出
178 | _audioOutput = [[AVCaptureAudioDataOutput alloc] init];
179 | [_audioOutput setSampleBufferDelegate:self queue:self.captureQueue];
180 | if ([_session canAddOutput:_audioOutput]){
181 | [_session addOutput:_audioOutput];
182 | }
183 |
184 | // 添加元素输出(识别)
185 | _metaOutput = [AVCaptureMetadataOutput new];
186 | if ([_session canAddOutput:_metaOutput]) {
187 | [_session addOutput:_metaOutput];
188 | // 需要先 addOutput 后面在 setMetadataObjectTypes
189 | [_metaOutput setMetadataObjectTypes:@[AVMetadataObjectTypeFace]];
190 | [_metaOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
191 | }
192 |
193 | // 照片输出
194 | _photoOutput = [AVCapturePhotoOutput new];
195 | if ([_session canAddOutput:_photoOutput]) {
196 | [_session addOutput:_photoOutput];
197 | // 配置高分辨率静态拍摄
198 | _photoOutput.highResolutionCaptureEnabled = YES;
199 | if (_photoOutput.livePhotoCaptureSupported) {
200 | // 自动修建 Live Photo 避免过度移动
201 | _photoOutput.livePhotoAutoTrimmingEnabled = YES;
202 | _photoOutput.livePhotoCaptureEnabled = YES;
203 | } else {
204 | NSLog(@"不支持 livePhotoCaptureEnabled");
205 | }
206 | }
207 |
208 | // // 静态图片输出
209 | // _stillImageOutput = [AVCaptureStillImageOutput new];
210 | // // 设置编解码
211 | // _stillImageOutput.outputSettings = @{AVVideoCodecKey: AVVideoCodecJPEG};
212 | // if ([_session canAddOutput:_stillImageOutput]) {
213 | // [_session addOutput:_stillImageOutput];
214 | // }
215 |
216 | // 视频文件输出
217 | // _movieFileOutput = [AVCaptureMovieFileOutput new];
218 | // if ([_session canAddOutput:_movieFileOutput]) {
219 | // [_session addOutput:_movieFileOutput];
220 | // }
221 | }
222 |
223 | #pragma mark - 相机操作
224 | /// 转换镜头
225 | - (void)switchCameraAction:(SCCameraView *)cameraView isFront:(BOOL)isFront handle:(void(^)(NSError *error))handle {
226 | dispatch_async(self.sessionQueue, ^{
227 | // 重新正确修正 connection orientation
228 | AVCaptureVideoOrientation orientation = [self.videoOutput connectionWithMediaType:AVMediaTypeVideo].videoOrientation;
229 | AVCaptureDeviceInput *old = isFront ? self.backCameraInput : self.frontCameraInput;
230 | AVCaptureDeviceInput *new = isFront ? self.frontCameraInput : self.backCameraInput;
231 | [self.cameraManager switchCamera:self.session old:old new:new handle:handle];
232 | self.currentCameraInput = new;
233 | [self.videoOutput connectionWithMediaType:AVMediaTypeVideo].videoOrientation = orientation;
234 | });
235 | }
236 |
237 | /// 缩放
238 | - (void)zoomAction:(SCCameraView *)cameraView factor:(CGFloat)factor handle:(void(^)(NSError *error))handle {
239 | dispatch_async(self.sessionQueue, ^{
240 | [self.cameraManager zoom:self.currentCameraInput.device factor:factor handle:handle];
241 | });
242 | }
243 |
244 | /// 聚焦&曝光操作
245 | - (void)focusAndExposeAction:(SCCameraView *)cameraView point:(CGPoint)point handle:(void (^)(NSError * _Nonnull))handle {
246 | // instestPoint 只能在主线程获取
247 | CGPoint instestPoint = [cameraView.previewView captureDevicePointForPoint:point];
248 | dispatch_async(self.sessionQueue, ^{
249 | dispatch_async(dispatch_get_main_queue(), ^{
250 | [cameraView runFocusAnimation:point];
251 | });
252 | [self.cameraManager focusWithMode:AVCaptureFocusModeAutoFocus
253 | exposeWithMode:AVCaptureExposureModeAutoExpose
254 | device:self.currentCameraInput.device
255 | atDevicePoint:instestPoint
256 | monitorSubjectAreaChange:YES
257 | handle:handle];
258 | });
259 | }
260 |
261 | /// 调整ISO
262 | - (void)isoAction:(SCCameraView *)cameraView factor:(CGFloat)factor handle:(void(^)(NSError *error))handle {
263 | dispatch_async(self.sessionQueue, ^{
264 | [self.cameraManager iso:self.currentCameraInput.device factor:factor handle:handle];
265 | });
266 | }
267 |
268 | /// 重置聚焦&曝光
269 | - (void)resetFocusAndExposeAction:(SCCameraView *)cameraView handle:(void(^)(NSError *error))handle {
270 | dispatch_async(self.sessionQueue, ^{
271 | [self.cameraManager resetFocusAndExpose:self.currentCameraInput.device handle:handle];
272 | });
273 | }
274 |
275 | /// 闪光灯
276 | - (void)flashLightAction:(SCCameraView *)cameraView isOn:(BOOL)isOn handle:(void(^)(NSError *error))handle {
277 | dispatch_async(self.sessionQueue, ^{
278 | AVCaptureFlashMode mode = isOn?AVCaptureFlashModeOn:AVCaptureFlashModeOff;
279 | [self.cameraManager changeFlash:self.currentCameraInput.device mode:mode handle:handle];
280 | });
281 | }
282 |
283 | /// 补光
284 | - (void)torchLightAction:(SCCameraView *)cameraView isOn:(BOOL)isOn handle:(void(^)(NSError *error))handle {
285 | dispatch_async(self.sessionQueue, ^{
286 | AVCaptureTorchMode mode = isOn?AVCaptureTorchModeOn:AVCaptureTorchModeOff;
287 | [self.cameraManager changeTorch:self.currentCameraInput.device mode:mode handle:handle];
288 | });
289 | }
290 |
291 | /// 取消
292 | - (void)cancelAction:(SCCameraView *)cameraView {
293 | [self dismissViewControllerAnimated:YES completion:nil];
294 | }
295 |
296 | #pragma mark - AVCaptureMetadataOutputObjectsDelegate
297 | - (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
298 | // 强转 AVMetadataFaceObject
299 | if ([self.faceDetectionDelegate respondsToSelector:@selector(faceDetectionDidDetectFaces:connection:)]) {
300 | [self.faceDetectionDelegate faceDetectionDidDetectFaces:metadataObjects connection:connection];
301 | }
302 | }
303 |
304 | #pragma mark - 拍照
305 | /// 静态拍照
306 | - (void)takeStillPhotoAction:(SCCameraView *)cameraView {
307 | // photoManager 使用
308 | [self.photoManager takeStillPhoto:self.cameraView.previewView.videoPreviewLayer completion:^(UIImage *originImage, UIImage *scaledImage, UIImage *croppedImage, NSError * _Nullable error) {
309 | SCCameraResultController *rc = [SCCameraResultController new];
310 | rc.img = croppedImage;
311 | [PHPhotoLibrary saveImageToCameraRool:croppedImage
312 | imageType:SCImageTypePNG
313 | compressionQuality:1.0
314 | authHandle:nil
315 | completion:^(BOOL success, NSError * _Nullable error) {
316 | if (error) {
317 | NSLog(@"%@", error);
318 | return;
319 | }
320 | NSLog(@"图片保存成功");
321 | }];
322 | [self presentViewController:rc animated:YES completion:nil];
323 | }];
324 |
325 | /** stillPhotoManager 使用
326 | [self.stillPhotoManager takePhoto:self.cameraView.previewView.videoPreviewLayer stillImageOutput:self.stillImageOutput handle:^(UIImage * _Nonnull originImage, UIImage * _Nonnull scaleImage, UIImage * _Nonnull cropImage) {
327 | NSLog(@"take photo success.");
328 | }];
329 | */
330 | }
331 |
332 | /// 动态拍照
333 | - (void)takeLivePhotoAction:(SCCameraView *)cameraView {
334 | [self.photoManager takeLivePhoto:self.cameraView.previewView.videoPreviewLayer completion:^(NSURL *liveURL, NSData *liveData, NSError * _Nullable error) {
335 | if (error) {
336 | NSLog(@"%@", error);
337 | return;
338 | }
339 | [PHPhotoLibrary saveLivePhotoToCameraRool:liveData shortFilm:liveURL authHandle:nil completion:^(BOOL success, NSError * _Nullable error) {
340 | if (error) {
341 | NSLog(@"%@", error);
342 | return;
343 | }
344 | NSLog(@"Live Photo保存完毕");
345 | }];
346 | }];
347 | }
348 |
349 | #pragma mark - 录制视频
350 | /// 开始录像视频
351 | - (void)startRecordVideoAction:(SCCameraView *)cameraView {
352 | // 注意 input --> connection --> output
353 | // 转换镜头的时候 connection 会变!!!!!
354 | AVCaptureConnection *currentConnection = [self.videoOutput connectionWithMediaType:AVMediaTypeVideo];
355 | currentConnection.videoOrientation = self.cameraView.previewView.videoOrientation;
356 | NSString *fileType = AVFileTypeQuickTimeMovie;
357 | NSDictionary *videoSettings = [self.videoOutput recommendedVideoSettingsForAssetWriterWithOutputFileType:fileType];
358 | NSDictionary *audioSettings = [self.audioOutput recommendedAudioSettingsForAssetWriterWithOutputFileType:fileType];
359 | [self.movieManager startRecordWithVideoSettings:videoSettings
360 | audioSettings:audioSettings
361 | handle:nil];
362 | }
363 |
364 | /// AVCaptureVideoDataOutputSampleBufferDelegate & AVCaptureAudioDataOutputSampleBufferDelegate
365 | - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection{
366 | if (self.movieManager.isRecording) {
367 | [self.movieManager recordSampleBuffer:sampleBuffer];
368 | }
369 | }
370 |
371 | /// 停止录像视频
372 | - (void)stopRecordVideoAction:(SCCameraView *)cameraView {
373 | [self.movieManager stopRecordWithCompletion:^(BOOL success, NSURL * _Nullable fileURL) {
374 | if (success && fileURL) {
375 | // [PHPhotoLibrary saveMovieFileToCameraRoll:fileURL authHandle:nil completion:^(BOOL success, NSError * _Nullable error) {
376 | // if (error) {
377 | // NSLog(@"%@", error);
378 | // return;
379 | // }
380 | // NSLog(@"视频保存完成");
381 | // }];
382 | // 影片预览
383 | SCShowMovieController *mc = [SCShowMovieController showMovieControllerWithFileURL:fileURL];
384 | [self presentViewController:mc animated:YES completion:nil];
385 | }
386 | }];
387 | }
388 |
389 | #pragma mark - 方向变化处理
390 | /// 禁用自动旋转
391 | - (BOOL) shouldAutorotate {
392 | return !self.movieManager.isRecording;
393 | }
394 |
395 | - (UIInterfaceOrientationMask)supportedInterfaceOrientations {
396 | return UIInterfaceOrientationMaskAll;
397 | }
398 |
399 | - (void) viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator {
400 | [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
401 | UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
402 | if (UIDeviceOrientationIsPortrait(deviceOrientation) || UIDeviceOrientationIsLandscape(deviceOrientation)) {
403 | self.cameraView.previewView.videoPreviewLayer.connection.videoOrientation = (AVCaptureVideoOrientation)deviceOrientation;
404 | }
405 | }
406 |
407 | #pragma mark - getter/setter
408 | - (BOOL)hasAllPermissions {
409 | return [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] == AVAuthorizationStatusAuthorized
410 | && [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio] == AVAuthorizationStatusAuthorized;
411 | }
412 |
413 | - (void)setCurrentCameraInput:(AVCaptureDeviceInput *)currentCameraInput {
414 | _currentCameraInput = currentCameraInput;
415 | [self.cameraManager whiteBalance:currentCameraInput.device mode:AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance handle:nil];
416 | }
417 |
418 | #pragma mark - lazy
419 | // Views
420 | - (SCCameraView *)cameraView {
421 | if (_cameraView == nil) {
422 | _cameraView = [SCCameraView cameraView:self.view.frame];
423 | _cameraView.delegate = self;
424 | }
425 | return _cameraView;
426 | }
427 |
428 | - (SCPermissionsView *)permissionsView {
429 | if (_permissionsView == nil) {
430 | _permissionsView = [[SCPermissionsView alloc] initWithFrame:self.view.bounds];
431 | _permissionsView.delegate = self;
432 | }
433 | return _permissionsView;
434 | }
435 |
436 | // Managers
437 | - (SCCameraManager *)cameraManager {
438 | if (_cameraManager == nil) {
439 | _cameraManager = [SCCameraManager new];
440 | }
441 | return _cameraManager;
442 | }
443 |
444 | - (SCStillPhotoManager *)stillPhotoManager {
445 | if (_stillPhotoManager == nil) {
446 | _stillPhotoManager = [SCStillPhotoManager new];
447 | }
448 | return _stillPhotoManager;
449 | }
450 |
451 | - (SCMovieManager *)movieManager {
452 | if (_movieManager == nil) {
453 | _movieManager = [[SCMovieManager alloc] initWithDispatchQueue:self.captureQueue];
454 | }
455 | return _movieManager;
456 | }
457 |
458 | - (SCPhotoManager *)photoManager API_AVAILABLE(ios(10.0)){
459 | if (_photoManager == nil) {
460 | _photoManager = [[SCPhotoManager alloc] initWithPhotoOutput:self.photoOutput];
461 | }
462 | return _photoManager;
463 | }
464 |
465 | // AVFoundation
466 | - (AVCaptureSession *)session {
467 | if (_session == nil) {
468 | _session = [AVCaptureSession new];
469 | }
470 | return _session;
471 | }
472 |
473 | - (AVCaptureDeviceInput *)backCameraInput {
474 | if (_backCameraInput == nil) {
475 | NSError *error;
476 | _backCameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self backCamera] error:&error];
477 | if (error) {
478 | NSLog(@"获取后置摄像头失败~");
479 | }
480 | }
481 | return _backCameraInput;
482 | }
483 |
484 | - (AVCaptureDeviceInput *)frontCameraInput {
485 | if (_frontCameraInput == nil) {
486 | NSError *error;
487 | _frontCameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self frontCamera] error:&error];
488 | if (error) {
489 | NSLog(@"获取前置摄像头失败~");
490 | }
491 | }
492 | return _frontCameraInput;
493 | }
494 |
495 | - (AVCaptureDevice *)frontCamera {
496 | return [AVCaptureDevice cameraWithPosition:AVCaptureDevicePositionFront];
497 | }
498 |
499 | - (AVCaptureDevice *)backCamera {
500 | return [AVCaptureDevice cameraWithPosition:AVCaptureDevicePositionBack];
501 | }
502 |
503 | // 队列懒加载
504 | - (dispatch_queue_t)sessionQueue {
505 | if (_sessionQueue == NULL) {
506 | _sessionQueue = dispatch_queue_create("com.seacen.sessionQueue", DISPATCH_QUEUE_SERIAL);
507 | }
508 | return _sessionQueue;
509 | }
510 |
511 | - (dispatch_queue_t)metaQueue {
512 | if (_metaQueue == NULL) {
513 | _metaQueue = dispatch_queue_create("com.seacen.metaQueue", DISPATCH_QUEUE_SERIAL);
514 | }
515 | return _metaQueue;
516 | }
517 |
518 | - (dispatch_queue_t)captureQueue {
519 | if (_captureQueue == NULL) {
520 | _captureQueue = dispatch_queue_create("com.seacen.captureQueue", DISPATCH_QUEUE_SERIAL);
521 | }
522 | return _captureQueue;
523 | }
524 |
525 | @end
526 |
527 |
--------------------------------------------------------------------------------
/SCCamera.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1412718E225DD4E6006873FC /* SCFocusView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1412718D225DD4E5006873FC /* SCFocusView.m */; };
11 | 1413CBB6225B7C590052E78A /* SCCameraView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1413CBB5225B7C590052E78A /* SCCameraView.m */; };
12 | 1413CBB8225B7C860052E78A /* SCCameraView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1413CBB7225B7C860052E78A /* SCCameraView.xib */; };
13 | 1413CBBB225C946A0052E78A /* UIView+SCCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = 1413CBBA225C946A0052E78A /* UIView+SCCategory.m */; };
14 | 1413CBC1225C9B620052E78A /* SCStillPhotoManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1413CBC0225C9B620052E78A /* SCStillPhotoManager.m */; };
15 | 1413CBCF225CDE760052E78A /* SCMovieManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1413CBCE225CDE760052E78A /* SCMovieManager.m */; };
16 | 1429266B2253B5AC0056D569 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1429266A2253B5AC0056D569 /* AppDelegate.m */; };
17 | 1429266E2253B5AC0056D569 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1429266D2253B5AC0056D569 /* ViewController.m */; };
18 | 142926712253B5AC0056D569 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1429266F2253B5AC0056D569 /* Main.storyboard */; };
19 | 142926732253B5AE0056D569 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 142926722253B5AE0056D569 /* Assets.xcassets */; };
20 | 142926762253B5AE0056D569 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 142926742253B5AE0056D569 /* LaunchScreen.storyboard */; };
21 | 142926792253B5AE0056D569 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 142926782253B5AE0056D569 /* main.m */; };
22 | 142926852253B68F0056D569 /* SCCameraController.m in Sources */ = {isa = PBXBuildFile; fileRef = 142926832253B68F0056D569 /* SCCameraController.m */; };
23 | 142926922253B6F60056D569 /* AVCaptureDevice+SCCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = 142926872253B6F50056D569 /* AVCaptureDevice+SCCategory.m */; };
24 | 142926942253B6F60056D569 /* SCStableCheckTool.m in Sources */ = {isa = PBXBuildFile; fileRef = 1429268A2253B6F50056D569 /* SCStableCheckTool.m */; };
25 | 142926962253B6F60056D569 /* SCCameraManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1429268E2253B6F60056D569 /* SCCameraManager.m */; };
26 | 142926B82254A10A0056D569 /* UIImage+SCCamera.m in Sources */ = {isa = PBXBuildFile; fileRef = 142926B72254A10A0056D569 /* UIImage+SCCamera.m */; };
27 | 142926BC2254B1510056D569 /* SCVideoPreviewView.m in Sources */ = {isa = PBXBuildFile; fileRef = 142926BB2254B1510056D569 /* SCVideoPreviewView.m */; };
28 | 142926C0225510280056D569 /* SCCameraResultController.m in Sources */ = {isa = PBXBuildFile; fileRef = 142926BE225510280056D569 /* SCCameraResultController.m */; };
29 | 142926C1225510280056D569 /* SCCameraResultController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 142926BF225510280056D569 /* SCCameraResultController.xib */; };
30 | 143B9A972255FA520038C15A /* UIViewController+SCCamera.m in Sources */ = {isa = PBXBuildFile; fileRef = 143B9A962255FA520038C15A /* UIViewController+SCCamera.m */; };
31 | 14A1723422696BCC009BF03D /* AVMetadataFaceObject+Transform.m in Sources */ = {isa = PBXBuildFile; fileRef = 14A1723322696BCC009BF03D /* AVMetadataFaceObject+Transform.m */; };
32 | 14A17237226AAD86009BF03D /* SCPhotoManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14A17236226AAD86009BF03D /* SCPhotoManager.m */; };
33 | 14C543E22271831500CC5959 /* PHPhotoLibrary+Save.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C543E12271831500CC5959 /* PHPhotoLibrary+Save.m */; };
34 | 14C543E52273305D00CC5959 /* SCShowMovieController.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C543E42273305D00CC5959 /* SCShowMovieController.m */; };
35 | 14C940C72260C485005B0AF7 /* SCMovieFileOutManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C940C62260C485005B0AF7 /* SCMovieFileOutManager.m */; };
36 | 14E4E862225E29A900EC58E4 /* SCFaceModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 14E4E861225E29A900EC58E4 /* SCFaceModel.m */; };
37 | 14E4E86B225F8D9100EC58E4 /* SCPermissionsView.m in Sources */ = {isa = PBXBuildFile; fileRef = 14E4E86A225F8D9100EC58E4 /* SCPermissionsView.m */; };
38 | /* End PBXBuildFile section */
39 |
40 | /* Begin PBXFileReference section */
41 | 1412718C225DD4E5006873FC /* SCFocusView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCFocusView.h; sourceTree = ""; };
42 | 1412718D225DD4E5006873FC /* SCFocusView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCFocusView.m; sourceTree = ""; };
43 | 1413CBB4225B7C590052E78A /* SCCameraView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCCameraView.h; sourceTree = ""; };
44 | 1413CBB5225B7C590052E78A /* SCCameraView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCCameraView.m; sourceTree = ""; };
45 | 1413CBB7225B7C860052E78A /* SCCameraView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SCCameraView.xib; sourceTree = ""; };
46 | 1413CBB9225C946A0052E78A /* UIView+SCCategory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+SCCategory.h"; sourceTree = ""; };
47 | 1413CBBA225C946A0052E78A /* UIView+SCCategory.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+SCCategory.m"; sourceTree = ""; };
48 | 1413CBBF225C9B620052E78A /* SCStillPhotoManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCStillPhotoManager.h; sourceTree = ""; };
49 | 1413CBC0225C9B620052E78A /* SCStillPhotoManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCStillPhotoManager.m; sourceTree = ""; };
50 | 1413CBCD225CDE760052E78A /* SCMovieManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCMovieManager.h; sourceTree = ""; };
51 | 1413CBCE225CDE760052E78A /* SCMovieManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCMovieManager.m; sourceTree = ""; };
52 | 142926662253B5AC0056D569 /* SCCamera.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SCCamera.app; sourceTree = BUILT_PRODUCTS_DIR; };
53 | 142926692253B5AC0056D569 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
54 | 1429266A2253B5AC0056D569 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
55 | 1429266C2253B5AC0056D569 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; };
56 | 1429266D2253B5AC0056D569 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; };
57 | 142926702253B5AC0056D569 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
58 | 142926722253B5AE0056D569 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
59 | 142926752253B5AE0056D569 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
60 | 142926772253B5AE0056D569 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
61 | 142926782253B5AE0056D569 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
62 | 142926812253B68F0056D569 /* SCCameraController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SCCameraController.h; sourceTree = ""; };
63 | 142926832253B68F0056D569 /* SCCameraController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SCCameraController.m; sourceTree = ""; };
64 | 142926872253B6F50056D569 /* AVCaptureDevice+SCCategory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "AVCaptureDevice+SCCategory.m"; sourceTree = ""; };
65 | 142926882253B6F50056D569 /* SCCameraManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SCCameraManager.h; sourceTree = ""; };
66 | 1429268A2253B6F50056D569 /* SCStableCheckTool.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SCStableCheckTool.m; sourceTree = ""; };
67 | 1429268D2253B6F60056D569 /* SCStableCheckTool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SCStableCheckTool.h; sourceTree = ""; };
68 | 1429268E2253B6F60056D569 /* SCCameraManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SCCameraManager.m; sourceTree = ""; };
69 | 142926902253B6F60056D569 /* AVCaptureDevice+SCCategory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "AVCaptureDevice+SCCategory.h"; sourceTree = ""; };
70 | 142926B62254A10A0056D569 /* UIImage+SCCamera.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIImage+SCCamera.h"; sourceTree = ""; };
71 | 142926B72254A10A0056D569 /* UIImage+SCCamera.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIImage+SCCamera.m"; sourceTree = ""; };
72 | 142926BA2254B1510056D569 /* SCVideoPreviewView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCVideoPreviewView.h; sourceTree = ""; };
73 | 142926BB2254B1510056D569 /* SCVideoPreviewView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCVideoPreviewView.m; sourceTree = ""; };
74 | 142926BD225510270056D569 /* SCCameraResultController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCCameraResultController.h; sourceTree = ""; };
75 | 142926BE225510280056D569 /* SCCameraResultController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCCameraResultController.m; sourceTree = ""; };
76 | 142926BF225510280056D569 /* SCCameraResultController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SCCameraResultController.xib; sourceTree = ""; };
77 | 143B9A952255FA520038C15A /* UIViewController+SCCamera.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIViewController+SCCamera.h"; sourceTree = ""; };
78 | 143B9A962255FA520038C15A /* UIViewController+SCCamera.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+SCCamera.m"; sourceTree = ""; };
79 | 14A1722E2268E539009BF03D /* SCFaceDetectionDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCFaceDetectionDelegate.h; sourceTree = ""; };
80 | 14A1723222696BCC009BF03D /* AVMetadataFaceObject+Transform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AVMetadataFaceObject+Transform.h"; sourceTree = ""; };
81 | 14A1723322696BCC009BF03D /* AVMetadataFaceObject+Transform.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "AVMetadataFaceObject+Transform.m"; sourceTree = ""; };
82 | 14A17235226AAD86009BF03D /* SCPhotoManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCPhotoManager.h; sourceTree = ""; };
83 | 14A17236226AAD86009BF03D /* SCPhotoManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCPhotoManager.m; sourceTree = ""; };
84 | 14C543E02271831500CC5959 /* PHPhotoLibrary+Save.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "PHPhotoLibrary+Save.h"; sourceTree = ""; };
85 | 14C543E12271831500CC5959 /* PHPhotoLibrary+Save.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "PHPhotoLibrary+Save.m"; sourceTree = ""; };
86 | 14C543E32273305D00CC5959 /* SCShowMovieController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCShowMovieController.h; sourceTree = ""; };
87 | 14C543E42273305D00CC5959 /* SCShowMovieController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCShowMovieController.m; sourceTree = ""; };
88 | 14C940C52260C485005B0AF7 /* SCMovieFileOutManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCMovieFileOutManager.h; sourceTree = ""; };
89 | 14C940C62260C485005B0AF7 /* SCMovieFileOutManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCMovieFileOutManager.m; sourceTree = ""; };
90 | 14E4E860225E29A900EC58E4 /* SCFaceModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCFaceModel.h; sourceTree = ""; };
91 | 14E4E861225E29A900EC58E4 /* SCFaceModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCFaceModel.m; sourceTree = ""; };
92 | 14E4E869225F8D9100EC58E4 /* SCPermissionsView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCPermissionsView.h; sourceTree = ""; };
93 | 14E4E86A225F8D9100EC58E4 /* SCPermissionsView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCPermissionsView.m; sourceTree = ""; };
94 | /* End PBXFileReference section */
95 |
96 | /* Begin PBXFrameworksBuildPhase section */
97 | 142926632253B5AC0056D569 /* Frameworks */ = {
98 | isa = PBXFrameworksBuildPhase;
99 | buildActionMask = 2147483647;
100 | files = (
101 | );
102 | runOnlyForDeploymentPostprocessing = 0;
103 | };
104 | /* End PBXFrameworksBuildPhase section */
105 |
106 | /* Begin PBXGroup section */
107 | 1429265D2253B5AC0056D569 = {
108 | isa = PBXGroup;
109 | children = (
110 | 142926682253B5AC0056D569 /* SCCamera */,
111 | 142926672253B5AC0056D569 /* Products */,
112 | );
113 | sourceTree = "";
114 | };
115 | 142926672253B5AC0056D569 /* Products */ = {
116 | isa = PBXGroup;
117 | children = (
118 | 142926662253B5AC0056D569 /* SCCamera.app */,
119 | );
120 | name = Products;
121 | sourceTree = "";
122 | };
123 | 142926682253B5AC0056D569 /* SCCamera */ = {
124 | isa = PBXGroup;
125 | children = (
126 | 142926802253B65F0056D569 /* Camera */,
127 | 142926692253B5AC0056D569 /* AppDelegate.h */,
128 | 1429266A2253B5AC0056D569 /* AppDelegate.m */,
129 | 1429266C2253B5AC0056D569 /* ViewController.h */,
130 | 1429266D2253B5AC0056D569 /* ViewController.m */,
131 | 1429266F2253B5AC0056D569 /* Main.storyboard */,
132 | 142926722253B5AE0056D569 /* Assets.xcassets */,
133 | 142926742253B5AE0056D569 /* LaunchScreen.storyboard */,
134 | 142926772253B5AE0056D569 /* Info.plist */,
135 | 142926782253B5AE0056D569 /* main.m */,
136 | );
137 | path = SCCamera;
138 | sourceTree = "";
139 | };
140 | 142926802253B65F0056D569 /* Camera */ = {
141 | isa = PBXGroup;
142 | children = (
143 | 14E4E863225E29B100EC58E4 /* Model */,
144 | 142926B92254B11B0056D569 /* View */,
145 | 142926862253B6DD0056D569 /* Manager */,
146 | 142926992253B73A0056D569 /* Controller */,
147 | 142926982253B6FC0056D569 /* Category */,
148 | );
149 | path = Camera;
150 | sourceTree = "";
151 | };
152 | 142926862253B6DD0056D569 /* Manager */ = {
153 | isa = PBXGroup;
154 | children = (
155 | 1429268D2253B6F60056D569 /* SCStableCheckTool.h */,
156 | 1429268A2253B6F50056D569 /* SCStableCheckTool.m */,
157 | 142926882253B6F50056D569 /* SCCameraManager.h */,
158 | 1429268E2253B6F60056D569 /* SCCameraManager.m */,
159 | 14A17235226AAD86009BF03D /* SCPhotoManager.h */,
160 | 14A17236226AAD86009BF03D /* SCPhotoManager.m */,
161 | 1413CBCD225CDE760052E78A /* SCMovieManager.h */,
162 | 1413CBCE225CDE760052E78A /* SCMovieManager.m */,
163 | 1413CBBF225C9B620052E78A /* SCStillPhotoManager.h */,
164 | 1413CBC0225C9B620052E78A /* SCStillPhotoManager.m */,
165 | 14C940C52260C485005B0AF7 /* SCMovieFileOutManager.h */,
166 | 14C940C62260C485005B0AF7 /* SCMovieFileOutManager.m */,
167 | );
168 | path = Manager;
169 | sourceTree = "";
170 | };
171 | 142926982253B6FC0056D569 /* Category */ = {
172 | isa = PBXGroup;
173 | children = (
174 | 142926902253B6F60056D569 /* AVCaptureDevice+SCCategory.h */,
175 | 142926872253B6F50056D569 /* AVCaptureDevice+SCCategory.m */,
176 | 142926B62254A10A0056D569 /* UIImage+SCCamera.h */,
177 | 142926B72254A10A0056D569 /* UIImage+SCCamera.m */,
178 | 143B9A952255FA520038C15A /* UIViewController+SCCamera.h */,
179 | 143B9A962255FA520038C15A /* UIViewController+SCCamera.m */,
180 | 1413CBB9225C946A0052E78A /* UIView+SCCategory.h */,
181 | 1413CBBA225C946A0052E78A /* UIView+SCCategory.m */,
182 | 14A1723222696BCC009BF03D /* AVMetadataFaceObject+Transform.h */,
183 | 14A1723322696BCC009BF03D /* AVMetadataFaceObject+Transform.m */,
184 | 14C543E02271831500CC5959 /* PHPhotoLibrary+Save.h */,
185 | 14C543E12271831500CC5959 /* PHPhotoLibrary+Save.m */,
186 | );
187 | path = Category;
188 | sourceTree = "";
189 | };
190 | 142926992253B73A0056D569 /* Controller */ = {
191 | isa = PBXGroup;
192 | children = (
193 | 14A1722E2268E539009BF03D /* SCFaceDetectionDelegate.h */,
194 | 142926812253B68F0056D569 /* SCCameraController.h */,
195 | 142926832253B68F0056D569 /* SCCameraController.m */,
196 | 142926BD225510270056D569 /* SCCameraResultController.h */,
197 | 142926BE225510280056D569 /* SCCameraResultController.m */,
198 | 142926BF225510280056D569 /* SCCameraResultController.xib */,
199 | 14C543E32273305D00CC5959 /* SCShowMovieController.h */,
200 | 14C543E42273305D00CC5959 /* SCShowMovieController.m */,
201 | );
202 | path = Controller;
203 | sourceTree = "";
204 | };
205 | 142926B92254B11B0056D569 /* View */ = {
206 | isa = PBXGroup;
207 | children = (
208 | 142926BA2254B1510056D569 /* SCVideoPreviewView.h */,
209 | 142926BB2254B1510056D569 /* SCVideoPreviewView.m */,
210 | 1413CBB4225B7C590052E78A /* SCCameraView.h */,
211 | 1413CBB5225B7C590052E78A /* SCCameraView.m */,
212 | 1413CBB7225B7C860052E78A /* SCCameraView.xib */,
213 | 1412718C225DD4E5006873FC /* SCFocusView.h */,
214 | 1412718D225DD4E5006873FC /* SCFocusView.m */,
215 | 14E4E869225F8D9100EC58E4 /* SCPermissionsView.h */,
216 | 14E4E86A225F8D9100EC58E4 /* SCPermissionsView.m */,
217 | );
218 | path = View;
219 | sourceTree = "";
220 | };
221 | 14E4E863225E29B100EC58E4 /* Model */ = {
222 | isa = PBXGroup;
223 | children = (
224 | 14E4E860225E29A900EC58E4 /* SCFaceModel.h */,
225 | 14E4E861225E29A900EC58E4 /* SCFaceModel.m */,
226 | );
227 | path = Model;
228 | sourceTree = "";
229 | };
230 | /* End PBXGroup section */
231 |
232 | /* Begin PBXNativeTarget section */
233 | 142926652253B5AC0056D569 /* SCCamera */ = {
234 | isa = PBXNativeTarget;
235 | buildConfigurationList = 1429267C2253B5AE0056D569 /* Build configuration list for PBXNativeTarget "SCCamera" */;
236 | buildPhases = (
237 | 142926622253B5AC0056D569 /* Sources */,
238 | 142926632253B5AC0056D569 /* Frameworks */,
239 | 142926642253B5AC0056D569 /* Resources */,
240 | );
241 | buildRules = (
242 | );
243 | dependencies = (
244 | );
245 | name = SCCamera;
246 | productName = SCCamera;
247 | productReference = 142926662253B5AC0056D569 /* SCCamera.app */;
248 | productType = "com.apple.product-type.application";
249 | };
250 | /* End PBXNativeTarget section */
251 |
252 | /* Begin PBXProject section */
253 | 1429265E2253B5AC0056D569 /* Project object */ = {
254 | isa = PBXProject;
255 | attributes = {
256 | LastUpgradeCheck = 1010;
257 | ORGANIZATIONNAME = SeacenLiu;
258 | TargetAttributes = {
259 | 142926652253B5AC0056D569 = {
260 | CreatedOnToolsVersion = 10.1;
261 | };
262 | };
263 | };
264 | buildConfigurationList = 142926612253B5AC0056D569 /* Build configuration list for PBXProject "SCCamera" */;
265 | compatibilityVersion = "Xcode 9.3";
266 | developmentRegion = en;
267 | hasScannedForEncodings = 0;
268 | knownRegions = (
269 | en,
270 | Base,
271 | );
272 | mainGroup = 1429265D2253B5AC0056D569;
273 | productRefGroup = 142926672253B5AC0056D569 /* Products */;
274 | projectDirPath = "";
275 | projectRoot = "";
276 | targets = (
277 | 142926652253B5AC0056D569 /* SCCamera */,
278 | );
279 | };
280 | /* End PBXProject section */
281 |
282 | /* Begin PBXResourcesBuildPhase section */
283 | 142926642253B5AC0056D569 /* Resources */ = {
284 | isa = PBXResourcesBuildPhase;
285 | buildActionMask = 2147483647;
286 | files = (
287 | 142926C1225510280056D569 /* SCCameraResultController.xib in Resources */,
288 | 1413CBB8225B7C860052E78A /* SCCameraView.xib in Resources */,
289 | 142926762253B5AE0056D569 /* LaunchScreen.storyboard in Resources */,
290 | 142926732253B5AE0056D569 /* Assets.xcassets in Resources */,
291 | 142926712253B5AC0056D569 /* Main.storyboard in Resources */,
292 | );
293 | runOnlyForDeploymentPostprocessing = 0;
294 | };
295 | /* End PBXResourcesBuildPhase section */
296 |
297 | /* Begin PBXSourcesBuildPhase section */
298 | 142926622253B5AC0056D569 /* Sources */ = {
299 | isa = PBXSourcesBuildPhase;
300 | buildActionMask = 2147483647;
301 | files = (
302 | 1413CBBB225C946A0052E78A /* UIView+SCCategory.m in Sources */,
303 | 142926962253B6F60056D569 /* SCCameraManager.m in Sources */,
304 | 1429266E2253B5AC0056D569 /* ViewController.m in Sources */,
305 | 14C940C72260C485005B0AF7 /* SCMovieFileOutManager.m in Sources */,
306 | 142926792253B5AE0056D569 /* main.m in Sources */,
307 | 14C543E52273305D00CC5959 /* SCShowMovieController.m in Sources */,
308 | 142926852253B68F0056D569 /* SCCameraController.m in Sources */,
309 | 142926942253B6F60056D569 /* SCStableCheckTool.m in Sources */,
310 | 1413CBCF225CDE760052E78A /* SCMovieManager.m in Sources */,
311 | 14A1723422696BCC009BF03D /* AVMetadataFaceObject+Transform.m in Sources */,
312 | 142926C0225510280056D569 /* SCCameraResultController.m in Sources */,
313 | 143B9A972255FA520038C15A /* UIViewController+SCCamera.m in Sources */,
314 | 142926922253B6F60056D569 /* AVCaptureDevice+SCCategory.m in Sources */,
315 | 1429266B2253B5AC0056D569 /* AppDelegate.m in Sources */,
316 | 1412718E225DD4E6006873FC /* SCFocusView.m in Sources */,
317 | 142926B82254A10A0056D569 /* UIImage+SCCamera.m in Sources */,
318 | 14C543E22271831500CC5959 /* PHPhotoLibrary+Save.m in Sources */,
319 | 142926BC2254B1510056D569 /* SCVideoPreviewView.m in Sources */,
320 | 1413CBB6225B7C590052E78A /* SCCameraView.m in Sources */,
321 | 14E4E86B225F8D9100EC58E4 /* SCPermissionsView.m in Sources */,
322 | 14E4E862225E29A900EC58E4 /* SCFaceModel.m in Sources */,
323 | 14A17237226AAD86009BF03D /* SCPhotoManager.m in Sources */,
324 | 1413CBC1225C9B620052E78A /* SCStillPhotoManager.m in Sources */,
325 | );
326 | runOnlyForDeploymentPostprocessing = 0;
327 | };
328 | /* End PBXSourcesBuildPhase section */
329 |
330 | /* Begin PBXVariantGroup section */
331 | 1429266F2253B5AC0056D569 /* Main.storyboard */ = {
332 | isa = PBXVariantGroup;
333 | children = (
334 | 142926702253B5AC0056D569 /* Base */,
335 | );
336 | name = Main.storyboard;
337 | sourceTree = "";
338 | };
339 | 142926742253B5AE0056D569 /* LaunchScreen.storyboard */ = {
340 | isa = PBXVariantGroup;
341 | children = (
342 | 142926752253B5AE0056D569 /* Base */,
343 | );
344 | name = LaunchScreen.storyboard;
345 | sourceTree = "";
346 | };
347 | /* End PBXVariantGroup section */
348 |
349 | /* Begin XCBuildConfiguration section */
350 | 1429267A2253B5AE0056D569 /* Debug */ = {
351 | isa = XCBuildConfiguration;
352 | buildSettings = {
353 | ALWAYS_SEARCH_USER_PATHS = NO;
354 | CLANG_ANALYZER_NONNULL = YES;
355 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
356 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
357 | CLANG_CXX_LIBRARY = "libc++";
358 | CLANG_ENABLE_MODULES = YES;
359 | CLANG_ENABLE_OBJC_ARC = YES;
360 | CLANG_ENABLE_OBJC_WEAK = YES;
361 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
362 | CLANG_WARN_BOOL_CONVERSION = YES;
363 | CLANG_WARN_COMMA = YES;
364 | CLANG_WARN_CONSTANT_CONVERSION = YES;
365 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
366 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
367 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
368 | CLANG_WARN_EMPTY_BODY = YES;
369 | CLANG_WARN_ENUM_CONVERSION = YES;
370 | CLANG_WARN_INFINITE_RECURSION = YES;
371 | CLANG_WARN_INT_CONVERSION = YES;
372 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
373 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
374 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
375 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
376 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
377 | CLANG_WARN_STRICT_PROTOTYPES = YES;
378 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
379 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
380 | CLANG_WARN_UNREACHABLE_CODE = YES;
381 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
382 | CODE_SIGN_IDENTITY = "iPhone Developer";
383 | COPY_PHASE_STRIP = NO;
384 | DEBUG_INFORMATION_FORMAT = dwarf;
385 | ENABLE_STRICT_OBJC_MSGSEND = YES;
386 | ENABLE_TESTABILITY = YES;
387 | GCC_C_LANGUAGE_STANDARD = gnu11;
388 | GCC_DYNAMIC_NO_PIC = NO;
389 | GCC_NO_COMMON_BLOCKS = YES;
390 | GCC_OPTIMIZATION_LEVEL = 0;
391 | GCC_PREPROCESSOR_DEFINITIONS = (
392 | "DEBUG=1",
393 | "$(inherited)",
394 | );
395 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
396 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
397 | GCC_WARN_UNDECLARED_SELECTOR = YES;
398 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
399 | GCC_WARN_UNUSED_FUNCTION = YES;
400 | GCC_WARN_UNUSED_VARIABLE = YES;
401 | IPHONEOS_DEPLOYMENT_TARGET = 12.1;
402 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
403 | MTL_FAST_MATH = YES;
404 | ONLY_ACTIVE_ARCH = YES;
405 | SDKROOT = iphoneos;
406 | };
407 | name = Debug;
408 | };
409 | 1429267B2253B5AE0056D569 /* Release */ = {
410 | isa = XCBuildConfiguration;
411 | buildSettings = {
412 | ALWAYS_SEARCH_USER_PATHS = NO;
413 | CLANG_ANALYZER_NONNULL = YES;
414 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
415 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
416 | CLANG_CXX_LIBRARY = "libc++";
417 | CLANG_ENABLE_MODULES = YES;
418 | CLANG_ENABLE_OBJC_ARC = YES;
419 | CLANG_ENABLE_OBJC_WEAK = YES;
420 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
421 | CLANG_WARN_BOOL_CONVERSION = YES;
422 | CLANG_WARN_COMMA = YES;
423 | CLANG_WARN_CONSTANT_CONVERSION = YES;
424 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
425 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
426 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
427 | CLANG_WARN_EMPTY_BODY = YES;
428 | CLANG_WARN_ENUM_CONVERSION = YES;
429 | CLANG_WARN_INFINITE_RECURSION = YES;
430 | CLANG_WARN_INT_CONVERSION = YES;
431 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
432 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
433 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
434 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
435 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
436 | CLANG_WARN_STRICT_PROTOTYPES = YES;
437 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
438 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
439 | CLANG_WARN_UNREACHABLE_CODE = YES;
440 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
441 | CODE_SIGN_IDENTITY = "iPhone Developer";
442 | COPY_PHASE_STRIP = NO;
443 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
444 | ENABLE_NS_ASSERTIONS = NO;
445 | ENABLE_STRICT_OBJC_MSGSEND = YES;
446 | GCC_C_LANGUAGE_STANDARD = gnu11;
447 | GCC_NO_COMMON_BLOCKS = YES;
448 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
449 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
450 | GCC_WARN_UNDECLARED_SELECTOR = YES;
451 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
452 | GCC_WARN_UNUSED_FUNCTION = YES;
453 | GCC_WARN_UNUSED_VARIABLE = YES;
454 | IPHONEOS_DEPLOYMENT_TARGET = 12.1;
455 | MTL_ENABLE_DEBUG_INFO = NO;
456 | MTL_FAST_MATH = YES;
457 | SDKROOT = iphoneos;
458 | VALIDATE_PRODUCT = YES;
459 | };
460 | name = Release;
461 | };
462 | 1429267D2253B5AE0056D569 /* Debug */ = {
463 | isa = XCBuildConfiguration;
464 | buildSettings = {
465 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
466 | CODE_SIGN_STYLE = Automatic;
467 | DEVELOPMENT_TEAM = 6426K9AWKV;
468 | INFOPLIST_FILE = SCCamera/Info.plist;
469 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
470 | LD_RUNPATH_SEARCH_PATHS = (
471 | "$(inherited)",
472 | "@executable_path/Frameworks",
473 | );
474 | PRODUCT_BUNDLE_IDENTIFIER = com.cppteam.SCCamera;
475 | PRODUCT_NAME = "$(TARGET_NAME)";
476 | TARGETED_DEVICE_FAMILY = "1,2";
477 | };
478 | name = Debug;
479 | };
480 | 1429267E2253B5AE0056D569 /* Release */ = {
481 | isa = XCBuildConfiguration;
482 | buildSettings = {
483 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
484 | CODE_SIGN_STYLE = Automatic;
485 | DEVELOPMENT_TEAM = 6426K9AWKV;
486 | INFOPLIST_FILE = SCCamera/Info.plist;
487 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
488 | LD_RUNPATH_SEARCH_PATHS = (
489 | "$(inherited)",
490 | "@executable_path/Frameworks",
491 | );
492 | PRODUCT_BUNDLE_IDENTIFIER = com.cppteam.SCCamera;
493 | PRODUCT_NAME = "$(TARGET_NAME)";
494 | TARGETED_DEVICE_FAMILY = "1,2";
495 | };
496 | name = Release;
497 | };
498 | /* End XCBuildConfiguration section */
499 |
500 | /* Begin XCConfigurationList section */
501 | 142926612253B5AC0056D569 /* Build configuration list for PBXProject "SCCamera" */ = {
502 | isa = XCConfigurationList;
503 | buildConfigurations = (
504 | 1429267A2253B5AE0056D569 /* Debug */,
505 | 1429267B2253B5AE0056D569 /* Release */,
506 | );
507 | defaultConfigurationIsVisible = 0;
508 | defaultConfigurationName = Release;
509 | };
510 | 1429267C2253B5AE0056D569 /* Build configuration list for PBXNativeTarget "SCCamera" */ = {
511 | isa = XCConfigurationList;
512 | buildConfigurations = (
513 | 1429267D2253B5AE0056D569 /* Debug */,
514 | 1429267E2253B5AE0056D569 /* Release */,
515 | );
516 | defaultConfigurationIsVisible = 0;
517 | defaultConfigurationName = Release;
518 | };
519 | /* End XCConfigurationList section */
520 | };
521 | rootObject = 1429265E2253B5AC0056D569 /* Project object */;
522 | }
523 |
--------------------------------------------------------------------------------