├── 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 | ![architecture](https://raw.githubusercontent.com/SeacenLiu/SCCamera/master/architecture.png) 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 | 27 | 34 | 41 | 51 | 65 | 75 | 82 | 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 | --------------------------------------------------------------------------------