//
|
// LXFCameraController.m
|
//
|
//
|
// Created by xf-ling on 2017/6/1.
|
// Copyright © 2017年 LXF. All rights reserved.
|
//
|
|
#import "LXFCameraController.h"
|
#import <AVFoundation/AVFoundation.h>
|
#import <AssetsLibrary/AssetsLibrary.h>
|
#import "LXFCameraButton.h"
|
#import "LXFPhotoLibraryManager.h"
|
#import <Photos/Photos.h>
|
#import <CoreMotion/CoreMotion.h>
|
|
#define kScreenWidth [UIScreen mainScreen].bounds.size.width
|
#define kScreenHeight [UIScreen mainScreen].bounds.size.height
|
#define SafeViewBottomHeight (kScreenHeight == 812.0 ? 34.0 : 0.0)
|
#define VIDEO_FILEPATH @"video"
|
#define TIMER_INTERVAL 0.01f // 定时器记录视频间隔
|
#define VIDEO_RECORDER_MAX_TIME 10.0f // 视频最大时长 (单位/秒)
|
#define VIDEO_RECORDER_MIN_TIME 1.0f // 最短视频时长 (单位/秒)
|
#define START_VIDEO_ANIMATION_DURATION 0.3f // 录制视频前的动画时间
|
#define DEFAULT_VIDEO_ZOOM_FACTOR 3.0f // 默认放大倍数
|
|
typedef void(^PropertyChangeBlock)(AVCaptureDevice *captureDevice);
|
|
@interface LXFCameraController() <AVCapturePhotoCaptureDelegate, AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAudioDataOutputSampleBufferDelegate, UIGestureRecognizerDelegate>
|
|
@property (nonatomic, strong) dispatch_queue_t videoQueue;
|
|
@property (strong, nonatomic) AVCaptureSession *captureSession; //负责输入和输出设备之间的数据传递
|
|
@property (nonatomic, strong) AVCaptureDeviceInput *videoInput; //视频输入
|
@property (nonatomic, strong) AVCaptureDeviceInput *audioInput; //声音输入
|
@property (nonatomic, strong) AVCaptureVideoDataOutput *videoOutput;
|
@property (nonatomic, strong) AVCaptureAudioDataOutput *audioOutput;
|
|
@property (strong, nonatomic) AVCapturePhotoOutput *capturePhotoOutput; //照片输出流
|
|
@property (nonatomic, strong) AVAssetWriter *assetWriter;
|
@property (nonatomic, strong) AVAssetWriterInput *assetWriterVideoInput;
|
@property (nonatomic, strong) AVAssetWriterInput *assetWriterAudioInput;
|
@property (nonatomic, strong) NSDictionary *videoCompressionSettings;
|
@property (nonatomic, strong) NSDictionary *audioCompressionSettings;
|
@property (nonatomic, assign) BOOL canWrite;
|
|
@property (strong, nonatomic) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer; //预览图层
|
|
@property (nonatomic, strong) NSTimer *timer; //记录录制时间
|
|
@property (weak, nonatomic) IBOutlet UIView *viewContainer;
|
@property (weak, nonatomic) IBOutlet UIButton *rotateCameraButton;
|
@property (weak, nonatomic) IBOutlet UIButton *takeButton; //拍摄按钮
|
@property (weak, nonatomic) IBOutlet UIButton *closeButton;
|
@property (weak, nonatomic) IBOutlet UILabel *tipLabel;
|
@property (strong, nonatomic) LXFCameraButton *cameraButton; //拍摄按钮
|
|
@property (weak, nonatomic) IBOutlet UIImageView *focusImageView; //聚焦视图
|
@property (assign, nonatomic) Boolean isFocusing; //镜头正在聚焦
|
@property (assign, nonatomic) Boolean isShooting; //正在拍摄
|
@property (assign, nonatomic) Boolean isRotatingCamera; //正在旋转摄像头
|
|
//捏合缩放摄像头
|
@property (nonatomic,assign) CGFloat beginGestureScale; //记录开始的缩放比例
|
@property (nonatomic,assign) CGFloat effectiveScale; //最后的缩放比例
|
|
// 拍照摄像后的预览模块
|
@property (weak, nonatomic) IBOutlet UIButton *cancelButton;
|
@property (weak, nonatomic) IBOutlet UIButton *confirmButton;
|
@property (strong, nonatomic) UIView *photoPreviewContainerView; //相片预览ContainerView
|
@property (strong, nonatomic) UIImageView *photoPreviewImageView; //相片预览ImageView
|
@property (strong, nonatomic) UIView *videoPreviewContainerView; //视频预览View
|
@property (strong, nonatomic) NSURL *videoURL; //视频文件地址
|
@property (strong, nonatomic) AVPlayerLayer *playerLayer;
|
@property (strong, nonatomic) AVPlayer *player;
|
@property (strong, nonatomic) AVPlayerItem *playerItem;
|
@property (assign, nonatomic) CGFloat currentVideoTimeLength; //当前小视频总时长
|
|
@property (assign, nonatomic) UIDeviceOrientation shootingOrientation; //拍摄中的手机方向
|
@property (strong, nonatomic) CMMotionManager *motionManager;
|
|
@end
|
|
@implementation LXFCameraController{
|
|
CGFloat timeLength; //时间长度
|
|
}
|
|
#pragma mark - 工厂方法
|
|
+ (instancetype)defaultCameraController
|
{
|
LXFCameraController *cameraController = [[LXFCameraController alloc] initWithNibName:NSStringFromClass([LXFCameraController class]) bundle:nil];
|
|
return cameraController;
|
}
|
|
#pragma mark - 控制器方法
|
|
- (void)viewDidLoad
|
{
|
[super viewDidLoad];
|
|
_isFocusing = NO;
|
_isShooting = NO;
|
_isRotatingCamera = NO;
|
_canWrite = NO;
|
_beginGestureScale = 1.0f;
|
_effectiveScale = 1.0f;
|
}
|
|
- (void)viewWillAppear:(BOOL)animated
|
{
|
[super viewWillAppear:animated];
|
|
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
|
if (authStatus == AVAuthorizationStatusRestricted || authStatus == AVAuthorizationStatusDenied)
|
{
|
[self requestAuthorizationForVideo];
|
}
|
|
//判断用户是否允许访问麦克风权限
|
authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
|
if (authStatus == AVAuthorizationStatusRestricted || authStatus == AVAuthorizationStatusDenied)
|
{
|
[self requestAuthorizationForVideo];
|
}
|
[self requestAuthorizationForPhotoLibrary];
|
|
[self initAVCaptureSession];
|
|
[self configDefaultUIDisplay];
|
|
[self addTapGenstureRecognizerForCamera];
|
|
}
|
|
- (void)viewDidAppear:(BOOL)animated
|
{
|
[super viewDidAppear:animated];
|
|
[self startSession];
|
|
[self setFocusCursorWithPoint:self.viewContainer.center];
|
|
[self tipLabelAnimation];
|
|
}
|
|
- (void)viewWillDisappear:(BOOL)animated
|
{
|
[super viewWillDisappear:animated];
|
}
|
|
- (void)viewDidDisappear:(BOOL)animated
|
{
|
[super viewDidDisappear:animated];
|
|
[self stopSession];
|
|
[self stopUpdateAccelerometer];
|
}
|
|
- (void)dealloc
|
{
|
NSLog(@"dealloc");
|
}
|
|
- (void)didReceiveMemoryWarning
|
{
|
[super didReceiveMemoryWarning];
|
}
|
|
- (BOOL)prefersStatusBarHidden {
|
return YES;
|
}
|
|
#pragma mark - 控件方法
|
|
/**
|
* 关闭当前界面
|
*/
|
- (IBAction)closeBtnFunc:(id)sender
|
{
|
[self dismissViewControllerAnimated:YES completion:nil];
|
}
|
|
/**
|
* 切换前后摄像头
|
*/
|
- (IBAction)rotateCameraBtnFunc:(id)sender
|
{
|
_isRotatingCamera = YES;
|
|
AVCaptureDevice *currentDevice = [self.videoInput device];
|
AVCaptureDevicePosition currentPosition = [currentDevice position];
|
|
AVCaptureDevice *toChangeDevice;
|
AVCaptureDevicePosition toChangePosition = AVCaptureDevicePositionFront;
|
if (currentPosition == AVCaptureDevicePositionUnspecified || currentPosition == AVCaptureDevicePositionFront)
|
{
|
toChangePosition = AVCaptureDevicePositionBack;
|
}
|
toChangeDevice = [self getCameraDeviceWithPosition:toChangePosition];
|
|
//获得要调整的设备输入对象
|
AVCaptureDeviceInput *toChangeDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:toChangeDevice error:nil];
|
|
//改变会话的配置前一定要先开启配置,配置完成后提交配置改变
|
[self.captureSession beginConfiguration];
|
//移除原有输入对象
|
[self.captureSession removeInput:self.videoInput];
|
//添加新的输入对象
|
if ([self.captureSession canAddInput:toChangeDeviceInput])
|
{
|
[self.captureSession addInput:toChangeDeviceInput];
|
self.videoInput = toChangeDeviceInput;
|
}
|
|
//提交会话配置
|
[self.captureSession commitConfiguration];
|
|
_isRotatingCamera = NO;
|
}
|
|
- (IBAction)cancelBtnfunc:(id)sender
|
{
|
[self removePlayerItemNotification];
|
|
[self startAnimationGroup];
|
}
|
|
/**
|
* 确认按钮并返回代理
|
*/
|
- (IBAction)confirmBtnFunc:(id)sender
|
{
|
__weak typeof(self) weakSelf = self;
|
if (self.photoPreviewImageView)
|
{
|
UIImage *finalImage = [self cutImageWithView:self.photoPreviewImageView];
|
|
[LXFPhotoLibraryManager savePhotoWithImage:finalImage andAssetCollectionName:self.assetCollectionName withCompletion:^(UIImage *image, NSError *error) {
|
|
if (self.takePhotosCompletionBlock)
|
{
|
if (error)
|
{
|
NSLog(@"保存照片失败!");
|
weakSelf.takePhotosCompletionBlock(nil, error);
|
}
|
else
|
{
|
NSLog(@"保存照片成功!");
|
weakSelf.takePhotosCompletionBlock(image, nil);
|
}
|
}
|
|
}];
|
|
self.confirmButton.userInteractionEnabled = NO;
|
|
}
|
else
|
{
|
[weakSelf cropWithVideoUrlStr:weakSelf.videoURL start:0 end:weakSelf.currentVideoTimeLength completion:^(NSURL *outputURL, Float64 videoDuration, BOOL isSuccess) {
|
|
if (isSuccess)
|
{
|
[LXFPhotoLibraryManager saveVideoWithVideoUrl:outputURL andAssetCollectionName:nil withCompletion:^(NSURL *videoUrl, NSError *error) {
|
|
if (self.shootCompletionBlock)
|
{
|
if (error)
|
{
|
NSLog(@"保存视频失败!");
|
weakSelf.shootCompletionBlock(nil, 0, nil, error);
|
}
|
else
|
{
|
NSLog(@"保存视频成功!");
|
|
// 获取视频的第一帧图片
|
UIImage *image = [weakSelf thumbnailImageRequestWithVideoUrl:videoUrl andTime:0.01f];
|
|
weakSelf.shootCompletionBlock(videoUrl, videoDuration, image, nil);
|
|
[[NSFileManager defaultManager] removeItemAtURL:weakSelf.videoURL error:nil];
|
weakSelf.videoURL = nil;
|
}
|
}
|
|
weakSelf.confirmButton.userInteractionEnabled = NO;
|
|
}];
|
}
|
else
|
{
|
NSLog(@"保存视频失败!");
|
[[NSFileManager defaultManager] removeItemAtURL:weakSelf.videoURL error:nil];
|
weakSelf.videoURL = nil;
|
[[NSFileManager defaultManager] removeItemAtURL:outputURL error:nil];
|
}
|
|
|
}];
|
|
}
|
}
|
|
#pragma mark - 懒加载
|
- (AVCaptureSession *)captureSession
|
{
|
if (_captureSession == nil)
|
{
|
_captureSession = [[AVCaptureSession alloc] init];
|
|
if ([_captureSession canSetSessionPreset:AVCaptureSessionPresetHigh])
|
{
|
_captureSession.sessionPreset = AVCaptureSessionPresetHigh;
|
}
|
}
|
|
return _captureSession;
|
}
|
|
- (dispatch_queue_t)videoQueue
|
{
|
if (!_videoQueue)
|
{
|
_videoQueue = dispatch_queue_create("XFCameraController", DISPATCH_QUEUE_SERIAL); // dispatch_get_main_queue();
|
}
|
|
return _videoQueue;
|
}
|
|
- (CMMotionManager *)motionManager
|
{
|
if (!_motionManager)
|
{
|
_motionManager = [[CMMotionManager alloc] init];
|
}
|
return _motionManager;
|
}
|
|
#pragma mark - 私有方法
|
|
/**
|
* 初始化AVCapture会话
|
*/
|
- (void)initAVCaptureSession
|
{
|
//1、添加 "视频" 与 "音频" 输入流到session
|
[self setupVideo];
|
|
[self setupAudio];
|
|
//2、添加图片,movie输出流到session
|
[self setupCaptureStillImageOutput];
|
|
//3、创建视频预览层,用于实时展示摄像头状态
|
[self setupCaptureVideoPreviewLayer];
|
|
//设置静音状态也可播放声音
|
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
|
[audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
|
}
|
|
/**
|
* 设置视频输入
|
*/
|
- (void)setupVideo
|
{
|
AVCaptureDevice *captureDevice = [self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];
|
if (!captureDevice)
|
{
|
NSLog(@"取得后置摄像头时出现问题.");
|
|
return;
|
}
|
|
NSError *error = nil;
|
self.videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:captureDevice error:&error];
|
if (error)
|
{
|
NSLog(@"取得设备输入videoInput对象时出错,错误原因:%@", error);
|
|
return;
|
}
|
|
//3、将设备输出添加到会话中
|
if ([self.captureSession canAddInput:self.videoInput])
|
{
|
[self.captureSession addInput:self.videoInput];
|
}
|
|
self.videoOutput = [[AVCaptureVideoDataOutput alloc] init];
|
self.videoOutput.alwaysDiscardsLateVideoFrames = YES; //立即丢弃旧帧,节省内存,默认YES
|
[self.videoOutput setSampleBufferDelegate:self queue:self.videoQueue];
|
if ([self.captureSession canAddOutput:self.videoOutput])
|
{
|
[self.captureSession addOutput:self.videoOutput];
|
}
|
}
|
|
/**
|
* 设置音频录入
|
*/
|
- (void)setupAudio
|
{
|
NSError *error = nil;
|
self.audioInput = [[AVCaptureDeviceInput alloc] initWithDevice:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio] error:&error];
|
if (error)
|
{
|
NSLog(@"取得设备输入audioInput对象时出错,错误原因:%@", error);
|
|
return;
|
}
|
if ([self.captureSession canAddInput:self.audioInput])
|
{
|
[self.captureSession addInput:self.audioInput];
|
}
|
|
self.audioOutput = [[AVCaptureAudioDataOutput alloc] init];
|
[self.audioOutput setSampleBufferDelegate:self queue:self.videoQueue];
|
if([self.captureSession canAddOutput:self.audioOutput])
|
{
|
[self.captureSession addOutput:self.audioOutput];
|
}
|
}
|
|
/**
|
* 设置图片输出
|
*/
|
- (void)setupCaptureStillImageOutput
|
{
|
self.capturePhotoOutput = [[AVCapturePhotoOutput alloc] init];
|
|
if ([self.captureSession canAddOutput:self.capturePhotoOutput])
|
{
|
[self.captureSession addOutput:self.capturePhotoOutput];
|
}
|
}
|
|
/**
|
* 设置预览layer
|
*/
|
- (void)setupCaptureVideoPreviewLayer
|
{
|
_captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.captureSession];
|
|
CALayer *layer = self.viewContainer.layer;
|
|
_captureVideoPreviewLayer.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight);
|
_captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspect; //填充模式
|
|
[layer addSublayer:_captureVideoPreviewLayer];
|
}
|
|
/**
|
* 开启会话
|
*/
|
- (void)startSession
|
{
|
if (![self.captureSession isRunning])
|
{
|
[self.captureSession startRunning];
|
}
|
}
|
|
/**
|
* 停止会话
|
*/
|
- (void)stopSession
|
{
|
if ([self.captureSession isRunning])
|
{
|
[self.captureSession stopRunning];
|
}
|
}
|
|
/**
|
* 开始拍照录像动画组合
|
*/
|
- (void)startAnimationGroup
|
{
|
[self configDefaultUIDisplay];
|
|
[self setFocusCursorWithPoint:self.viewContainer.center];
|
|
[self tipLabelAnimation];
|
}
|
|
/**
|
* 配置默认UI信息
|
*/
|
- (void)configDefaultUIDisplay
|
{
|
if (self.photoPreviewImageView)
|
{
|
[self.photoPreviewImageView removeFromSuperview];
|
[self.photoPreviewContainerView removeFromSuperview];
|
self.photoPreviewImageView = nil;
|
self.photoPreviewContainerView = nil;
|
}
|
if (self.videoPreviewContainerView)
|
{
|
[self.player pause];
|
self.player = nil;
|
self.playerItem = nil;
|
[self.playerLayer removeFromSuperlayer];
|
self.playerLayer = nil;
|
self.cameraButton.progressPercentage = 0.0f;
|
[self.videoPreviewContainerView removeFromSuperview];
|
self.videoPreviewContainerView = nil;
|
[[NSFileManager defaultManager] removeItemAtURL:self.videoURL error:nil];
|
self.videoURL = nil;
|
}
|
|
[self.view bringSubviewToFront:self.rotateCameraButton];
|
[self.view bringSubviewToFront:self.closeButton];
|
[self.rotateCameraButton setHidden:NO];
|
[self.closeButton setHidden:NO];
|
|
[self.view bringSubviewToFront:self.tipLabel];
|
[self.tipLabel setAlpha:0];
|
|
[self.cancelButton setHidden:YES];
|
[self.confirmButton setHidden:YES];
|
|
// 设置拍照按钮
|
if (_cameraButton == nil)
|
{
|
LXFCameraButton *cameraButton = [LXFCameraButton defaultCameraButton];
|
_cameraButton = cameraButton;
|
|
[self.view addSubview:cameraButton];
|
CGFloat cameraBtnX = (kScreenWidth - cameraButton.bounds.size.width) / 2;
|
CGFloat cameraBtnY = kScreenHeight - cameraButton.bounds.size.height - 60 - SafeViewBottomHeight; //距离底部60
|
cameraButton.frame = CGRectMake(cameraBtnX, cameraBtnY, cameraButton.bounds.size.width, cameraButton.bounds.size.height);
|
[self.view bringSubviewToFront:cameraButton];
|
|
// 设置拍照按钮点击事件
|
__weak typeof(self) weakSelf = self;
|
// 配置拍照方法
|
[cameraButton configureTapCameraButtonEventWithBlock:^(UITapGestureRecognizer *tapGestureRecognizer) {
|
[weakSelf takePhotos:tapGestureRecognizer];
|
}];
|
// 配置拍摄方法
|
[cameraButton configureLongPressCameraButtonEventWithBlock:^(UILongPressGestureRecognizer *longPressGestureRecognizer) {
|
[weakSelf longPressCameraButtonFunc:longPressGestureRecognizer];
|
}];
|
}
|
[self.cameraButton setHidden:NO];
|
[self.view bringSubviewToFront:self.cameraButton];
|
|
// 对焦imageView
|
[self.view bringSubviewToFront:self.focusImageView];
|
[self.focusImageView setAlpha:0];
|
|
// 监听屏幕方向
|
[self startUpdateAccelerometer];
|
}
|
|
/**
|
* 提示语动画
|
*/
|
- (void)tipLabelAnimation
|
{
|
[self.view bringSubviewToFront:self.tipLabel];
|
|
__weak typeof(self) weakSelf = self;
|
[UIView animateWithDuration:1.0f delay:0.5f options:UIViewAnimationOptionCurveEaseInOut animations:^{
|
|
[weakSelf.tipLabel setAlpha:1];
|
|
} completion:^(BOOL finished) {
|
|
[UIView animateWithDuration:1.0f delay:3.0f options:UIViewAnimationOptionCurveEaseInOut animations:^{
|
|
[weakSelf.tipLabel setAlpha:0];
|
|
} completion:nil];
|
|
}];
|
}
|
|
/**
|
* 取得指定位置的摄像头
|
*
|
* @param position 摄像头位置
|
*
|
* @return 摄像头设备
|
*/
|
- (AVCaptureDevice *)getCameraDeviceWithPosition:(AVCaptureDevicePosition )position
|
{
|
AVCaptureDeviceDiscoverySession *deviceDiscoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera] mediaType:AVMediaTypeVideo position:position];
|
for (AVCaptureDevice *device in deviceDiscoverySession.devices)
|
{
|
if ([device position] == position)
|
{
|
return device;
|
}
|
}
|
return nil;
|
}
|
|
/**
|
* 改变设备属性的统一操作方法
|
*
|
* @param propertyChange 属性改变操作
|
*/
|
- (void)changeDeviceProperty:(PropertyChangeBlock)propertyChange
|
{
|
AVCaptureDevice *captureDevice = [self.videoInput device];
|
NSError *error;
|
|
//注意改变设备属性前一定要首先调用lockForConfiguration:调用完之后使用unlockForConfiguration方法解锁
|
if ([captureDevice lockForConfiguration:&error])
|
{
|
propertyChange(captureDevice);
|
[captureDevice unlockForConfiguration];
|
}
|
else
|
{
|
NSLog(@"设置设备属性过程发生错误,错误信息:%@",error.localizedDescription);
|
}
|
}
|
|
#pragma mark - 拍照功能
|
|
/**
|
* 拍照方法
|
*/
|
- (void)takePhotos:(UITapGestureRecognizer *)tapGestureRecognizer
|
{
|
//根据设备输出获得连接
|
// AVCaptureConnection *captureConnection = [self.capturePhotoOutput connectionWithMediaType:AVMediaTypeVideo];
|
|
// [captureConnection setVideoScaleAndCropFactor:self.effectiveScale];
|
|
//根据连接取得设备输出的数据
|
AVCapturePhotoSettings *settings = [AVCapturePhotoSettings photoSettings];
|
// [settings];
|
[self.capturePhotoOutput capturePhotoWithSettings:settings delegate:self];
|
}
|
|
/**
|
* 预览图片
|
*/
|
- (void)previewPhotoWithImage:(UIImage *)image
|
{
|
[self stopUpdateAccelerometer];
|
|
[self.cameraButton setHidden:YES];
|
[self.closeButton setHidden:YES];
|
[self.rotateCameraButton setHidden:YES];
|
|
UIImage *finalImage = nil;
|
if (self.shootingOrientation == UIDeviceOrientationLandscapeRight)
|
{
|
finalImage = [self rotateImage:image withOrientation:UIImageOrientationDown];
|
}
|
else if (self.shootingOrientation == UIDeviceOrientationLandscapeLeft)
|
{
|
finalImage = [self rotateImage:image withOrientation:UIImageOrientationUp];
|
}
|
else if (self.shootingOrientation == UIDeviceOrientationPortraitUpsideDown)
|
{
|
finalImage = [self rotateImage:image withOrientation:UIImageOrientationLeft];
|
}
|
else
|
{
|
finalImage = [self rotateImage:image withOrientation:UIImageOrientationRight];
|
}
|
// NSLog(@"image %@",NSStringFromCGSize(image.size));
|
// NSLog(@"finalImage %@",NSStringFromCGSize(finalImage.size));
|
|
self.photoPreviewImageView = [[UIImageView alloc] init];
|
float videoRatio = finalImage.size.width / finalImage.size.height; //得到的图片 高/宽
|
if (self.shootingOrientation == UIDeviceOrientationLandscapeRight || self.shootingOrientation == UIDeviceOrientationLandscapeLeft)
|
{
|
CGFloat height = kScreenWidth * videoRatio;
|
CGFloat y = (kScreenHeight - height) / 2;
|
[self.photoPreviewImageView setFrame:CGRectMake(0, y, kScreenWidth, height)];
|
}
|
else
|
{
|
[self.photoPreviewImageView setFrame:CGRectMake(0, 0, kScreenWidth, kScreenWidth*videoRatio)];
|
}
|
self.photoPreviewImageView.image = finalImage;
|
|
self.photoPreviewContainerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, kScreenHeight)];
|
self.photoPreviewContainerView.backgroundColor = [UIColor blackColor];
|
[self.photoPreviewContainerView addSubview:self.photoPreviewImageView];
|
[self.view addSubview:self.photoPreviewContainerView];
|
self.photoPreviewImageView.center = self.view.center;
|
[self.view bringSubviewToFront:self.photoPreviewImageView];
|
[self.view bringSubviewToFront:self.cancelButton];
|
[self.view bringSubviewToFront:self.confirmButton];
|
[self.cancelButton setHidden:NO];
|
[self.confirmButton setHidden:NO];
|
}
|
|
- (UIImage *)cutImageWithView:(UIView *)view
|
{
|
UIGraphicsBeginImageContextWithOptions(view.frame.size, NO, 0);
|
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
|
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
UIGraphicsEndImageContext();
|
|
return image;
|
}
|
|
#pragma mark - 视频录制
|
|
/**
|
* 录制视频方法
|
*/
|
- (void)longPressCameraButtonFunc:(UILongPressGestureRecognizer *)sender
|
{
|
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
|
if (authStatus == AVAuthorizationStatusRestricted || authStatus ==AVAuthorizationStatusDenied)
|
{
|
return;
|
}
|
|
//判断用户是否允许访问麦克风权限
|
authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
|
if (authStatus == AVAuthorizationStatusRestricted || authStatus ==AVAuthorizationStatusDenied)
|
{
|
return;
|
}
|
|
switch (sender.state) {
|
case UIGestureRecognizerStateBegan:
|
[self startVideoRecorder];
|
break;
|
case UIGestureRecognizerStateCancelled:
|
[self stopVideoRecorder];
|
break;
|
case UIGestureRecognizerStateEnded:
|
[self stopVideoRecorder];
|
break;
|
case UIGestureRecognizerStateFailed:
|
[self stopVideoRecorder];
|
break;
|
default:
|
break;
|
}
|
|
}
|
|
/**
|
* 开始录制视频
|
*/
|
- (void)startVideoRecorder
|
{
|
_isShooting = YES;
|
|
[self stopUpdateAccelerometer];
|
|
[self.cameraButton startShootAnimationWithDuration:START_VIDEO_ANIMATION_DURATION];
|
|
__weak typeof(self) weakSelf = self;
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(START_VIDEO_ANIMATION_DURATION * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
NSURL *url = [NSURL fileURLWithPath:[weakSelf createVideoFilePath]];
|
self.videoURL = url;
|
|
[self setUpWriter];
|
|
[weakSelf timerFired];
|
|
});
|
}
|
|
/**
|
* 结束录制视频
|
*/
|
- (void)stopVideoRecorder
|
{
|
if (_isShooting)
|
{
|
_isShooting = NO;
|
self.cameraButton.progressPercentage = 0.0f;
|
[self.cameraButton stopShootAnimation];
|
[self timerStop];
|
|
__weak __typeof(self)weakSelf = self;
|
if(_assetWriter && _assetWriter.status == AVAssetWriterStatusWriting)
|
{
|
// dispatch_async(self.videoQueue, ^{
|
[_assetWriter finishWritingWithCompletionHandler:^{
|
weakSelf.canWrite = NO;
|
weakSelf.assetWriter = nil;
|
weakSelf.assetWriterAudioInput = nil;
|
weakSelf.assetWriterVideoInput = nil;
|
}];
|
// });
|
}
|
|
if (timeLength < VIDEO_RECORDER_MIN_TIME)
|
{
|
return;
|
}
|
|
[self.cameraButton setHidden:YES];
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
[weakSelf previewVideoAfterShoot];
|
|
});
|
}
|
else
|
{
|
// nothing
|
}
|
}
|
|
/**
|
* 设置写入视频属性
|
*/
|
- (void)setUpWriter
|
{
|
if (self.videoURL == nil)
|
{
|
return;
|
}
|
|
self.assetWriter = [AVAssetWriter assetWriterWithURL:self.videoURL fileType:AVFileTypeMPEG4 error:nil];
|
//写入视频大小
|
NSInteger numPixels = kScreenWidth * kScreenHeight;
|
|
//每像素比特
|
CGFloat bitsPerPixel = 12.0;
|
NSInteger bitsPerSecond = numPixels * bitsPerPixel;
|
|
// 码率和帧率设置
|
NSDictionary *compressionProperties = @{ AVVideoAverageBitRateKey : @(bitsPerSecond),
|
AVVideoExpectedSourceFrameRateKey : @(15),
|
AVVideoMaxKeyFrameIntervalKey : @(15),
|
AVVideoProfileLevelKey : AVVideoProfileLevelH264BaselineAutoLevel };
|
CGFloat width = kScreenHeight;
|
CGFloat height = kScreenWidth;
|
//视频属性
|
if (@available(iOS 11.0, *)) {
|
self.videoCompressionSettings = @{ AVVideoCodecKey : AVVideoCodecTypeH264,
|
AVVideoWidthKey : @(width * 2),
|
AVVideoHeightKey : @(height * 2),
|
AVVideoScalingModeKey : AVVideoScalingModeResizeAspect,
|
AVVideoCompressionPropertiesKey : compressionProperties };
|
} else {
|
// Fallback on earlier versions
|
}
|
|
_assetWriterVideoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:self.videoCompressionSettings];
|
//expectsMediaDataInRealTime 必须设为yes,需要从capture session 实时获取数据
|
_assetWriterVideoInput.expectsMediaDataInRealTime = YES;
|
|
if (self.shootingOrientation == UIDeviceOrientationLandscapeRight)
|
{
|
_assetWriterVideoInput.transform = CGAffineTransformMakeRotation(M_PI);
|
}
|
else if (self.shootingOrientation == UIDeviceOrientationLandscapeLeft)
|
{
|
_assetWriterVideoInput.transform = CGAffineTransformMakeRotation(0);
|
}
|
else if (self.shootingOrientation == UIDeviceOrientationPortraitUpsideDown)
|
{
|
_assetWriterVideoInput.transform = CGAffineTransformMakeRotation(M_PI + (M_PI / 2.0));
|
}
|
else
|
{
|
_assetWriterVideoInput.transform = CGAffineTransformMakeRotation(M_PI / 2.0);
|
}
|
|
// 音频设置
|
self.audioCompressionSettings = @{ AVEncoderBitRatePerChannelKey : @(28000),
|
AVFormatIDKey : @(kAudioFormatMPEG4AAC),
|
AVNumberOfChannelsKey : @(1),
|
AVSampleRateKey : @(22050) };
|
|
_assetWriterAudioInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:self.audioCompressionSettings];
|
_assetWriterAudioInput.expectsMediaDataInRealTime = YES;
|
|
if ([_assetWriter canAddInput:_assetWriterVideoInput])
|
{
|
[_assetWriter addInput:_assetWriterVideoInput];
|
}
|
else
|
{
|
NSLog(@"AssetWriter videoInput append Failed");
|
}
|
|
if ([_assetWriter canAddInput:_assetWriterAudioInput])
|
{
|
[_assetWriter addInput:_assetWriterAudioInput];
|
}
|
else
|
{
|
NSLog(@"AssetWriter audioInput Append Failed");
|
}
|
|
_canWrite = NO;
|
}
|
|
- (NSString *)createVideoFilePath
|
{
|
// 创建视频文件的存储路径
|
NSString *filePath = [self createVideoFolderPath];
|
if (filePath == nil)
|
{
|
return nil;
|
}
|
|
NSString *videoType = @".mp4";
|
NSString *videoDestDateString = [self createFileNamePrefix];
|
NSString *videoFileName = [videoDestDateString stringByAppendingString:videoType];
|
|
NSUInteger idx = 1;
|
/*We only allow 10000 same file name*/
|
NSString *finalPath = [NSString stringWithFormat:@"%@/%@", filePath, videoFileName];
|
|
while (idx % 10000 && [[NSFileManager defaultManager] fileExistsAtPath:finalPath])
|
{
|
finalPath = [NSString stringWithFormat:@"%@/%@_(%lu)%@", filePath, videoDestDateString, (unsigned long)idx++, videoType];
|
}
|
|
return finalPath;
|
}
|
|
- (NSString *)createVideoFolderPath
|
{
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
NSString *homePath = NSHomeDirectory();
|
|
NSString *tmpFilePath;
|
|
if (homePath.length > 0)
|
{
|
NSString *documentPath = [homePath stringByAppendingString:@"/Documents"];
|
if ([fileManager fileExistsAtPath:documentPath isDirectory:NULL] == YES)
|
{
|
BOOL success = NO;
|
|
NSArray *paths = [fileManager contentsOfDirectoryAtPath:documentPath error:nil];
|
|
//offline file folder
|
tmpFilePath = [documentPath stringByAppendingString:[NSString stringWithFormat:@"/%@", VIDEO_FILEPATH]];
|
if ([paths containsObject:VIDEO_FILEPATH] == NO)
|
{
|
success = [fileManager createDirectoryAtPath:tmpFilePath withIntermediateDirectories:YES attributes:nil error:nil];
|
if (!success)
|
{
|
tmpFilePath = nil;
|
}
|
}
|
return tmpFilePath;
|
}
|
}
|
|
return false;
|
}
|
|
/**
|
* 创建文件名
|
*/
|
- (NSString *)createFileNamePrefix
|
{
|
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
|
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss zzz"];
|
|
NSString *destDateString = [dateFormatter stringFromDate:[NSDate date]];
|
destDateString = [destDateString stringByReplacingOccurrencesOfString:@" " withString:@"-"];
|
destDateString = [destDateString stringByReplacingOccurrencesOfString:@"+" withString:@"-"];
|
destDateString = [destDateString stringByReplacingOccurrencesOfString:@":" withString:@"-"];
|
|
return destDateString;
|
}
|
|
/**
|
* 开启定时器
|
*/
|
- (void)timerFired
|
{
|
timeLength = 0;
|
self.timer = [NSTimer scheduledTimerWithTimeInterval:TIMER_INTERVAL target:self selector:@selector(timerRecord) userInfo:nil repeats:YES];
|
}
|
|
/**
|
* 绿色转圈百分比计算
|
*/
|
- (void)timerRecord
|
{
|
if (!_isShooting)
|
{
|
[self timerStop];
|
return ;
|
}
|
|
// 时间大于VIDEO_RECORDER_MAX_TIME则停止录制
|
if (timeLength > VIDEO_RECORDER_MAX_TIME)
|
{
|
[self stopVideoRecorder];
|
}
|
|
timeLength += TIMER_INTERVAL;
|
|
// NSLog(@"%lf", timeLength / VIDEO_RECORDER_MAX_TIME);
|
|
self.cameraButton.progressPercentage = timeLength / VIDEO_RECORDER_MAX_TIME;
|
|
}
|
|
/**
|
* 停止定时器
|
*/
|
- (void)timerStop
|
{
|
if ([self.timer isValid])
|
{
|
[self.timer invalidate];
|
self.timer = nil;
|
}
|
}
|
|
/**
|
* 预览录制的视频
|
*/
|
- (void)previewVideoAfterShoot
|
{
|
if (self.videoURL == nil || self.videoPreviewContainerView != nil)
|
{
|
return;
|
}
|
|
AVURLAsset *asset = [AVURLAsset assetWithURL:self.videoURL];
|
|
//获取视频总时长
|
Float64 duration = CMTimeGetSeconds(asset.duration);
|
|
self.currentVideoTimeLength = duration;
|
|
// 初始化AVPlayer
|
self.videoPreviewContainerView = [[UIView alloc] init];
|
self.videoPreviewContainerView.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight);
|
self.videoPreviewContainerView.backgroundColor = [UIColor blackColor];
|
|
self.playerItem = [AVPlayerItem playerItemWithAsset:asset];
|
self.player = [[AVPlayer alloc] initWithPlayerItem:_playerItem];
|
|
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
|
self.playerLayer.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight);
|
|
self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
|
|
[self.videoPreviewContainerView.layer addSublayer:self.playerLayer];
|
|
// 其余UI布局设置
|
[self.viewContainer addSubview:self.videoPreviewContainerView];
|
[self.viewContainer bringSubviewToFront:self.videoPreviewContainerView];
|
[self.viewContainer bringSubviewToFront:self.cancelButton];
|
[self.viewContainer bringSubviewToFront:self.confirmButton];
|
[self.cameraButton setHidden:YES];
|
[self.closeButton setHidden:YES];
|
[self.rotateCameraButton setHidden:YES];
|
[self.cancelButton setHidden:NO];
|
[self.confirmButton setHidden:NO];
|
|
// 重复播放预览视频
|
[self addNotificationWithPlayerItem];
|
|
// 开始播放
|
[self.player play];
|
}
|
|
/**
|
* 截取指定时间的视频缩略图
|
*
|
* @param timeBySecond 时间点,单位:s
|
*/
|
- (UIImage *)thumbnailImageRequestWithVideoUrl:(NSURL *)videoUrl andTime:(CGFloat)timeBySecond
|
{
|
if (self.videoURL == nil)
|
{
|
return nil;
|
}
|
|
AVURLAsset *urlAsset = [AVURLAsset assetWithURL:videoUrl];
|
|
//根据AVURLAsset创建AVAssetImageGenerator
|
AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:urlAsset];
|
/*截图
|
* requestTime:缩略图创建时间
|
* actualTime:缩略图实际生成的时间
|
*/
|
NSError *error = nil;
|
CMTime requestTime = CMTimeMakeWithSeconds(timeBySecond, 10); //CMTime是表示电影时间信息的结构体,第一个参数表示是视频第几秒,第二个参数表示每秒帧数.(如果要活的某一秒的第几帧可以使用CMTimeMake方法)
|
CMTime actualTime;
|
CGImageRef cgImage = [imageGenerator copyCGImageAtTime:requestTime actualTime:&actualTime error:&error];
|
if(error)
|
{
|
NSLog(@"截取视频缩略图时发生错误,错误信息:%@", error.localizedDescription);
|
return nil;
|
}
|
|
CMTimeShow(actualTime);
|
UIImage *image = [UIImage imageWithCGImage:cgImage];
|
CGImageRelease(cgImage);
|
|
UIImage *finalImage = nil;
|
if (self.shootingOrientation == UIDeviceOrientationLandscapeRight)
|
{
|
finalImage = [self rotateImage:image withOrientation:UIImageOrientationDown];
|
}
|
else if (self.shootingOrientation == UIDeviceOrientationLandscapeLeft)
|
{
|
finalImage = [self rotateImage:image withOrientation:UIImageOrientationUp];
|
}
|
else if (self.shootingOrientation == UIDeviceOrientationPortraitUpsideDown)
|
{
|
finalImage = [self rotateImage:image withOrientation:UIImageOrientationLeft];
|
}
|
else
|
{
|
finalImage = [self rotateImage:image withOrientation:UIImageOrientationRight];
|
}
|
|
return finalImage;
|
}
|
|
/**
|
* 图片旋转
|
*/
|
- (UIImage *)rotateImage:(UIImage *)image withOrientation:(UIImageOrientation)orientation
|
{
|
long double rotate = 0.0;
|
CGRect rect;
|
float translateX = 0;
|
float translateY = 0;
|
float scaleX = 1.0;
|
float scaleY = 1.0;
|
|
switch (orientation)
|
{
|
case UIImageOrientationLeft:
|
rotate = M_PI_2;
|
rect = CGRectMake(0, 0, image.size.height, image.size.width);
|
translateX = 0;
|
translateY = -rect.size.width;
|
scaleY = rect.size.width/rect.size.height;
|
scaleX = rect.size.height/rect.size.width;
|
break;
|
case UIImageOrientationRight:
|
rotate = 3 * M_PI_2;
|
rect = CGRectMake(0, 0, image.size.height, image.size.width);
|
translateX = -rect.size.height;
|
translateY = 0;
|
scaleY = rect.size.width/rect.size.height;
|
scaleX = rect.size.height/rect.size.width;
|
break;
|
case UIImageOrientationDown:
|
rotate = M_PI;
|
rect = CGRectMake(0, 0, image.size.width, image.size.height);
|
translateX = -rect.size.width;
|
translateY = -rect.size.height;
|
break;
|
default:
|
rotate = 0.0;
|
rect = CGRectMake(0, 0, image.size.width, image.size.height);
|
translateX = 0;
|
translateY = 0;
|
break;
|
}
|
|
UIGraphicsBeginImageContext(rect.size);
|
CGContextRef context = UIGraphicsGetCurrentContext();
|
//做CTM变换
|
CGContextTranslateCTM(context, 0.0, rect.size.height);
|
CGContextScaleCTM(context, 1.0, -1.0);
|
CGContextRotateCTM(context, rotate);
|
CGContextTranslateCTM(context, translateX, translateY);
|
|
CGContextScaleCTM(context, scaleX, scaleY);
|
//绘制图片
|
CGContextDrawImage(context, CGRectMake(0, 0, rect.size.width, rect.size.height), image.CGImage);
|
|
UIImage *newPic = UIGraphicsGetImageFromCurrentImageContext();
|
|
return newPic;
|
}
|
|
#pragma mark - 截取视频方法
|
|
- (void)cropWithVideoUrlStr:(NSURL *)videoUrl start:(CGFloat)startTime end:(CGFloat)endTime completion:(void (^)(NSURL *outputURL, Float64 videoDuration, BOOL isSuccess))completionHandle
|
{
|
AVURLAsset *asset =[[AVURLAsset alloc] initWithURL:videoUrl options:nil];
|
|
//获取视频总时长
|
Float64 duration = CMTimeGetSeconds(asset.duration);
|
|
if (duration > VIDEO_RECORDER_MAX_TIME)
|
{
|
duration = VIDEO_RECORDER_MAX_TIME;
|
}
|
|
startTime = 0;
|
endTime = duration;
|
|
NSString *outputFilePath = [self createVideoFilePath];
|
NSURL *outputFileUrl = [NSURL fileURLWithPath:outputFilePath];
|
|
NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:asset];
|
if ([compatiblePresets containsObject:AVAssetExportPresetMediumQuality])
|
{
|
|
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]
|
initWithAsset:asset presetName:AVAssetExportPresetPassthrough];
|
|
NSURL *outputURL = outputFileUrl;
|
|
exportSession.outputURL = outputURL;
|
exportSession.outputFileType = AVFileTypeMPEG4;
|
exportSession.shouldOptimizeForNetworkUse = YES;
|
|
CMTime start = CMTimeMakeWithSeconds(startTime, asset.duration.timescale);
|
CMTime duration = CMTimeMakeWithSeconds(endTime - startTime,asset.duration.timescale);
|
CMTimeRange range = CMTimeRangeMake(start, duration);
|
exportSession.timeRange = range;
|
|
[exportSession exportAsynchronouslyWithCompletionHandler:^{
|
switch ([exportSession status]) {
|
case AVAssetExportSessionStatusFailed:
|
{
|
NSLog(@"合成失败:%@", [[exportSession error] description]);
|
completionHandle(outputURL, endTime, NO);
|
}
|
break;
|
case AVAssetExportSessionStatusCancelled:
|
{
|
completionHandle(outputURL, endTime, NO);
|
}
|
break;
|
case AVAssetExportSessionStatusCompleted:
|
{
|
completionHandle(outputURL, endTime, YES);
|
}
|
break;
|
default:
|
{
|
completionHandle(outputURL, endTime, NO);
|
} break;
|
}
|
}];
|
}
|
}
|
|
#pragma mark - 预览视频通知
|
/**
|
* 添加播放器通知
|
*/
|
-(void)addNotificationWithPlayerItem
|
{
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playVideoFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.playerItem];
|
}
|
|
-(void)removePlayerItemNotification
|
{
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
}
|
|
/**
|
* 播放完成通知
|
*
|
* @param notification 通知对象
|
*/
|
-(void)playVideoFinished:(NSNotification *)notification
|
{
|
// NSLog(@"视频播放完成.");
|
|
// 播放完成后重复播放
|
// 跳到最新的时间点开始播放
|
[self.player seekToTime:CMTimeMake(0, 1)];
|
[self.player play];
|
}
|
|
#pragma mark - AVCapturePhotoCaptureDelegate
|
|
- (void)captureOutput:(AVCapturePhotoOutput *)output didFinishProcessingPhoto:(AVCapturePhoto *)photo error:(NSError *)error API_AVAILABLE(ios(11.0)){
|
if (!error) {
|
NSData *data = [photo fileDataRepresentation];
|
UIImage *image = [UIImage imageWithData:data];
|
[self previewPhotoWithImage:image];
|
}
|
}
|
|
#pragma mark - AVCaptureVideoDataOutputSampleBufferDelegate AVCaptureAudioDataOutputSampleBufferDelegate
|
|
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
|
{
|
if (_isRotatingCamera)
|
{
|
return;
|
}
|
|
@autoreleasepool
|
{
|
//视频
|
if (connection == [self.videoOutput connectionWithMediaType:AVMediaTypeVideo])
|
{
|
@synchronized(self)
|
{
|
if (_isShooting)
|
{
|
[self appendSampleBuffer:sampleBuffer ofMediaType:AVMediaTypeVideo];
|
}
|
}
|
}
|
|
//音频
|
if (connection == [self.audioOutput connectionWithMediaType:AVMediaTypeAudio])
|
{
|
@synchronized(self)
|
{
|
if (_isShooting)
|
{
|
[self appendSampleBuffer:sampleBuffer ofMediaType:AVMediaTypeAudio];
|
}
|
}
|
}
|
}
|
}
|
|
|
/**
|
* 开始写入数据
|
*/
|
- (void)appendSampleBuffer:(CMSampleBufferRef)sampleBuffer ofMediaType:(NSString *)mediaType
|
{
|
if (sampleBuffer == NULL)
|
{
|
NSLog(@"empty sampleBuffer");
|
return;
|
}
|
|
// CFRetain(sampleBuffer);
|
// dispatch_async(self.videoQueue, ^{
|
@autoreleasepool
|
{
|
if (!self.canWrite && mediaType == AVMediaTypeVideo)
|
{
|
[self.assetWriter startWriting];
|
[self.assetWriter startSessionAtSourceTime:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
|
self.canWrite = YES;
|
}
|
|
//写入视频数据
|
if (mediaType == AVMediaTypeVideo)
|
{
|
if (self.assetWriterVideoInput.readyForMoreMediaData)
|
{
|
BOOL success = [self.assetWriterVideoInput appendSampleBuffer:sampleBuffer];
|
if (!success)
|
{
|
@synchronized (self)
|
{
|
[self stopVideoRecorder];
|
}
|
}
|
}
|
}
|
|
//写入音频数据
|
if (mediaType == AVMediaTypeAudio)
|
{
|
if (self.assetWriterAudioInput.readyForMoreMediaData)
|
{
|
BOOL success = [self.assetWriterAudioInput appendSampleBuffer:sampleBuffer];
|
if (!success)
|
{
|
@synchronized (self)
|
{
|
[self stopVideoRecorder];
|
}
|
}
|
}
|
}
|
// CFRelease(sampleBuffer);
|
}
|
// });
|
}
|
|
#pragma mark - 摄像头聚焦,与缩放
|
|
/**
|
* 添加点按手势
|
*/
|
- (void)addTapGenstureRecognizerForCamera
|
{
|
UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchGesture:)];
|
|
pinchGesture.delegate = self;
|
|
[self.viewContainer addGestureRecognizer:pinchGesture];
|
}
|
|
/**
|
* 点击屏幕,聚焦事件
|
*/
|
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
|
{
|
// 不聚焦的情况:聚焦中,旋转摄像头中,查看录制的视频中,查看照片中
|
if (_isFocusing || touches.count == 0 || _isRotatingCamera || _videoPreviewContainerView || _photoPreviewImageView)
|
{
|
return;
|
}
|
|
UITouch *touch = nil;
|
|
for (UITouch *t in touches)
|
{
|
touch = t;
|
break;
|
}
|
|
CGPoint point = [touch locationInView:self.viewContainer];;
|
|
if (point.y > CGRectGetMaxY(self.tipLabel.frame))
|
{
|
return;
|
}
|
|
[self setFocusCursorWithPoint:point];
|
}
|
|
/**
|
* 设置聚焦光标位置
|
*
|
* @param point 光标位置
|
*/
|
- (void)setFocusCursorWithPoint:(CGPoint)point
|
{
|
self.isFocusing = YES;
|
|
self.focusImageView.center = point;
|
self.focusImageView.transform = CGAffineTransformMakeScale(1.5, 1.5);
|
self.focusImageView.alpha = 1;
|
|
//将UI坐标转化为摄像头坐标
|
CGPoint cameraPoint = [self.captureVideoPreviewLayer captureDevicePointOfInterestForPoint:point];
|
[self focusWithPoint:cameraPoint];
|
|
__weak typeof(self) weakSelf = self;
|
[UIView animateWithDuration:1.0 animations:^{
|
|
weakSelf.focusImageView.transform = CGAffineTransformIdentity;
|
|
} completion:^(BOOL finished) {
|
|
weakSelf.focusImageView.alpha = 0;
|
weakSelf.isFocusing = NO;
|
|
}];
|
}
|
|
/**
|
* 设置聚焦点
|
*
|
* @param point 聚焦点
|
*/
|
-(void)focusWithPoint:(CGPoint)point
|
{
|
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice)
|
{
|
// 聚焦
|
if ([captureDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus])
|
{
|
[captureDevice setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
|
}
|
if ([captureDevice isFocusPointOfInterestSupported])
|
{
|
[captureDevice setFocusPointOfInterest:point];
|
}
|
// 曝光
|
if ([captureDevice isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure])
|
{
|
[captureDevice setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
|
}
|
if ([captureDevice isExposurePointOfInterestSupported])
|
{
|
[captureDevice setExposurePointOfInterest:point];
|
}
|
}];
|
}
|
|
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)recognizer
|
{
|
if (_isShooting)
|
{
|
return;
|
}
|
|
BOOL allTouchesAreOnTheCaptureVideoPreviewLayer = YES;
|
|
NSUInteger numTouches = [recognizer numberOfTouches], i;
|
for ( i = 0; i < numTouches; ++i)
|
{
|
CGPoint location = [recognizer locationOfTouch:i inView:self.viewContainer];
|
CGPoint convertedLocation = [self.captureVideoPreviewLayer convertPoint:location fromLayer:self.captureVideoPreviewLayer.superlayer];
|
if (![self.captureVideoPreviewLayer containsPoint:convertedLocation])
|
{
|
allTouchesAreOnTheCaptureVideoPreviewLayer = NO;
|
break;
|
}
|
}
|
|
if (allTouchesAreOnTheCaptureVideoPreviewLayer)
|
{
|
CGFloat videoMaxZoomFactor = self.videoInput.device.activeFormat.videoMaxZoomFactor;
|
CGFloat maxScaleAndCropFactor = videoMaxZoomFactor<DEFAULT_VIDEO_ZOOM_FACTOR?videoMaxZoomFactor:DEFAULT_VIDEO_ZOOM_FACTOR;
|
CGFloat currentScale = self.beginGestureScale * recognizer.scale;
|
if ((currentScale > 1.0f) && (currentScale < maxScaleAndCropFactor))
|
{
|
self.effectiveScale = self.beginGestureScale * recognizer.scale;
|
if ((self.effectiveScale < videoMaxZoomFactor) && (self.effectiveScale > 1.0f))
|
{
|
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
|
[captureDevice rampToVideoZoomFactor:self.effectiveScale withRate:10.0f];
|
}];
|
}
|
}
|
}
|
}
|
|
#pragma mark - UIGestureRecognizerDelegate
|
|
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
|
{
|
if ([gestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]])
|
{
|
self.beginGestureScale = self.effectiveScale;
|
}
|
|
return YES;
|
}
|
|
#pragma mark - 重力感应相关
|
|
/**
|
* 开始监听屏幕方向
|
*/
|
- (void)startUpdateAccelerometer
|
{
|
if ([self.motionManager isAccelerometerAvailable] == YES)
|
{
|
__weak typeof(self) weakSelf = self;
|
//回调会一直调用,建议获取到就调用下面的停止方法,需要再重新开始,当然如果需求是实时不间断的话可以等离开页面之后再stop
|
[self.motionManager setAccelerometerUpdateInterval:1.0];
|
[self.motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue currentQueue] withHandler:^(CMAccelerometerData *accelerometerData, NSError *error)
|
{
|
double x = accelerometerData.acceleration.x;
|
double y = accelerometerData.acceleration.y;
|
if ((fabs(y) + 0.1f) >= fabs(x))
|
{
|
// NSLog(@"y:%lf", y);
|
if (y >= 0.1f)
|
{
|
// Down
|
NSLog(@"Down");
|
weakSelf.shootingOrientation = UIDeviceOrientationPortraitUpsideDown;
|
}
|
else
|
{
|
// Portrait
|
NSLog(@"Portrait");
|
weakSelf.shootingOrientation = UIDeviceOrientationPortrait;
|
}
|
}
|
else
|
{
|
// NSLog(@"x:%lf", x);
|
if (x >= 0.1f)
|
{
|
// Right
|
NSLog(@"Right");
|
weakSelf.shootingOrientation = UIDeviceOrientationLandscapeRight;
|
}
|
else if (x <= 0.1f)
|
{
|
// Left
|
NSLog(@"Left");
|
weakSelf.shootingOrientation = UIDeviceOrientationLandscapeLeft;
|
}
|
else
|
{
|
// Portrait
|
NSLog(@"Portrait");
|
weakSelf.shootingOrientation = UIDeviceOrientationPortrait;
|
}
|
}
|
}];
|
}
|
}
|
|
/**
|
* 停止监听屏幕方向
|
*/
|
- (void)stopUpdateAccelerometer
|
{
|
if ([self.motionManager isAccelerometerActive] == YES)
|
{
|
[self.motionManager stopAccelerometerUpdates];
|
_motionManager = nil;
|
}
|
}
|
|
#pragma mark - 判断是否有权限
|
|
/**
|
* 请求权限
|
*/
|
- (void)requestAuthorizationForVideo
|
{
|
__weak typeof(self) weakSelf = self;
|
|
// 请求相机权限
|
AVAuthorizationStatus videoAuthStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
|
if (videoAuthStatus != AVAuthorizationStatusAuthorized)
|
{
|
NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
|
|
NSString *appName = [infoDictionary objectForKey:@"CFBundleDisplayName"];
|
if (appName == nil)
|
{
|
appName = @"APP";
|
}
|
NSString *message = [NSString stringWithFormat:@"允许%@访问你的相机?", appName];
|
|
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"警告" message:message preferredStyle:UIAlertControllerStyleAlert];
|
|
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"好的" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
[weakSelf dismissViewControllerAnimated:YES completion:nil];
|
}];
|
|
UIAlertAction *setAction = [UIAlertAction actionWithTitle:@"设置" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
|
if ([[UIApplication sharedApplication] canOpenURL:url])
|
{
|
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
|
}
|
[weakSelf dismissViewControllerAnimated:YES completion:nil];
|
}];
|
|
[alertController addAction:okAction];
|
[alertController addAction:setAction];
|
|
[self presentViewController:alertController animated:YES completion:nil];
|
}
|
|
// 请求麦克风权限
|
AVAuthorizationStatus audioAuthStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
|
if (audioAuthStatus != AVAuthorizationStatusAuthorized)
|
{
|
NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
|
|
NSString *appName = [infoDictionary objectForKey:@"CFBundleDisplayName"];
|
if (appName == nil)
|
{
|
appName = @"APP";
|
}
|
NSString *message = [NSString stringWithFormat:@"允许%@访问你的麦克风?", appName];
|
|
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"警告" message:message preferredStyle:UIAlertControllerStyleAlert];
|
|
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"好的" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
[weakSelf dismissViewControllerAnimated:YES completion:nil];
|
}];
|
|
UIAlertAction *setAction = [UIAlertAction actionWithTitle:@"设置" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
|
if ([[UIApplication sharedApplication] canOpenURL:url])
|
{
|
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
|
}
|
[weakSelf dismissViewControllerAnimated:YES completion:nil];
|
}];
|
|
[alertController addAction:okAction];
|
[alertController addAction:setAction];
|
|
[self presentViewController:alertController animated:YES completion:nil];
|
}
|
|
|
}
|
|
- (void)requestAuthorizationForPhotoLibrary
|
{
|
__weak typeof(self) weakSelf = self;
|
|
// 请求照片权限
|
[LXFPhotoLibraryManager requestALAssetsLibraryAuthorizationWithCompletion:^(Boolean isAuth) {
|
|
if (!isAuth)
|
{
|
NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
|
|
NSString *appName = [infoDictionary objectForKey:@"CFBundleDisplayName"];
|
if (appName == nil)
|
{
|
appName = @"APP";
|
}
|
NSString *message = [NSString stringWithFormat:@"允许%@访问你的相册?", appName];
|
|
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"警告" message:message preferredStyle:UIAlertControllerStyleAlert];
|
|
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"好的" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
[weakSelf dismissViewControllerAnimated:YES completion:nil];
|
}];
|
|
UIAlertAction *setAction = [UIAlertAction actionWithTitle:@"设置" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
|
if ([[UIApplication sharedApplication] canOpenURL:url])
|
{
|
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
|
}
|
[weakSelf dismissViewControllerAnimated:YES completion:nil];
|
}];
|
|
[alertController addAction:okAction];
|
[alertController addAction:setAction];
|
|
[weakSelf presentViewController:alertController animated:YES completion:nil];
|
|
}
|
}];
|
}
|
|
@end
|