自定义相机采集及视频编辑
iOS调用系统的相册(显示中文的标题)
在info.plist加上这一条这样就可以使我们调出来的相册显示出中文了:
Localized resources can be mixed 设置为 YES。
1、相册访问权限
权限状态说明
相册、相机、通讯录等授权状态目前都可以对应以下几种状态:
AuthorizationStatusNotDetermined // 用户从未进行过授权等处理,首次访问相应内容会提示用户进行授权
AuthorizationStatusAuthorized = 0, // 用户已授权,允许访问
AuthorizationStatusDenied, // 用户拒绝访问
AuthorizationStatusRestricted, // 应用没有相关权限,且当前用户无法改变这个权限,比如:家长控制
// 判断相册访问权限
- (BOOL)achiveAuthorizationStatus{
/*
* PHAuthorizationStatusNotDetermined = 0, 用户未对这个应用程序的权限做出选择
* PHAuthorizationStatusRestricted, 此应用程序没有被授权访问的照片数据。可能是家长控制权限。
* PHAuthorizationStatusDenied, 用户已经明确拒绝了此应用程序访问照片数据.
* PHAuthorizationStatusAuthorized, 用户已授权此应用访问照片数据.
*/
PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
if (status == PHAuthorizationStatusDenied || status == PHAuthorizationStatusRestricted) {
// 没权限
UIAlertController *authorizationAlert = [UIAlertController alertControllerWithTitle:@"提示" message:@"没有照片的访问权限,请在设置中开启" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:NULL];
[authorizationAlert addAction:cancel];
[self presentViewController:authorizationAlert animated:YES completion:nil];
return NO;
} else {
return YES;
}
}
// 判断设备是否有摄像头
- (BOOL) isCameraAvailable{
return [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera];
}
// 前面的摄像头是否可用
- (BOOL) isFrontCameraAvailable{
return [UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront];
}
// 后面的摄像头是否可用
- (BOOL) isRearCameraAvailable{
return [UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceRear];
}
视频录制相关的类及作用:
AVCaptureSession
AVCaptureSession:媒体(音、视频)捕获会话,负责把捕获的音视频数据输出到输出设备中。一个AVCaptureSession可以有多个输入输出。
AVCaptureDevice :输入设备,包括麦克风、摄像头,通过该对象可以设置物理设备的一些属性(例如相机聚焦、白平衡等)。
AVCaptureDeviceInput :设备输入数据管理对象,可以根据AVCaptureDevice创建对应的AVCaptureDeviceInput对象,该对象将会被添加到AVCaptureSession中管理。
AVCaptureVideoPreviewLayer :相机拍摄预览图层,是CALayer的子类,使用该对象可以实时查看拍照或视频录制效果,创建该对象需要指定对应的 AVCaptureSession对象。
AVCaptureOutput :输出数据管理对象,用于接收各类输出数据,通常使用对应的子类AVCaptureAudioDataOutput、AVCaptureStillImageOutput、
AVCaptureVideoDataOutput、AVCaptureFileOutput, 该对象将会被添加到AVCaptureSession中管理。
注意:前面几个对象的输出数据都是NSData类型,而AVCaptureFileOutput代表数据以文件形式输出,类似的,AVCcaptureFileOutput也不会直接创建使用,通常会使用其子类:
AVCaptureAudioFileOutput、AVCaptureMovieFileOutput。当把一个输入或者输出添加到AVCaptureSession之后AVCaptureSession就会在所有相符的输入、输出设备之间
建立连接(AVCaptionConnection)。
iOS中在系统相册中创建自己App的自定义相册:
要创建自己App的自定义相册,首先要获取系统中的所有自定义相册,看这些自定义相册中是否已经包含了我们自己要创建的自定义相册,如果已经包含自然不用再次创建,如果还没有那么就需要我们自己进行创建。注意:iOS中在创建自定义相册之后并不会给我们返回一个相册的对象,还需要我们自己根据一个标识去系统中获取我们创建的自定义相册。
代码:
// 创建自己要创建的自定义相册
- (PHAssetCollection * )createCollection{
// 创建一个新的相册
// 查看所有的自定义相册
// 先查看是否有自己要创建的自定义相册
// 如果没有自己要创建的自定义相册那么我们就进行创建
NSString * title = [NSBundle mainBundle].infoDictionary[(NSString *)kCFBundleNameKey];
PHFetchResult<PHAssetCollection *> *collections = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
PHAssetCollection * createCollection = nil; // 最终要获取的自己创建的相册
for (PHAssetCollection * collection in collections) {
if ([collection.localizedTitle isEqualToString:title]) { // 如果有自己要创建的相册
createCollection = collection;
break;
}
}
if (createCollection == nil) { // 如果没有自己要创建的相册
// 创建自己要创建的相册
NSError * error1 = nil;
__block NSString * createCollectionID = nil;
[[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
NSString * title = [NSBundle mainBundle].infoDictionary[(NSString *)kCFBundleNameKey];
createCollectionID = [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:title].placeholderForCreatedAssetCollection.localIdentifier;
} error:&error1];
if (error1) {
NSLog(@"创建相册失败...");
}
// 创建相册之后我们还要获取此相册 因为我们要往进存储相片
createCollection = [PHAssetCollection fetchAssetCollectionsWithLocalIdentifiers:@[createCollectionID] options:nil].firstObject;
}
return createCollection;
}
设置相机
-(void)setupCamera{
self.cameraMode = CameraModePhoto;
// 获取摄像头输入设备
if ([self cameraWithPosition:AVCaptureDevicePositionBack]) {
self.captureDevice = [self cameraWithPosition:AVCaptureDevicePositionBack];
}else if ([self cameraWithPosition:AVCaptureDevicePositionFront]){
self.captureDevice = [self cameraWithPosition:AVCaptureDevicePositionFront];
}else{
[MBProgressHUD showError:@"相机不可用"];
return;
}
NSError * error;
WEAKSELF
// 视频输入
self.captureInput = [AVCaptureDeviceInput deviceInputWithDevice:self.captureDevice error:&error];
if (error) {
UIAlertController *alertContr = [UIAlertController alertControllerWithTitle:@"提示" message:[error localizedDescription] preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancleAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
[weakSelf.navigationController dismissViewControllerAnimated:YES completion:nil];
}];
[alertContr addAction:cancleAction];
[self presentViewController:alertContr animated:YES completion:nil];
}
//音频设备
AVCaptureDevice *audioCaptureDevice=[[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];
//音频输入
AVCaptureDeviceInput *audioCaptureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:audioCaptureDevice error:&error];
if (error) {
WEAKSELF
UIAlertController *alertContr = [UIAlertController alertControllerWithTitle:@"提示" message:[error localizedDescription] preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancleAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
[weakSelf.navigationController dismissViewControllerAnimated:YES completion:nil];
}];
[alertContr addAction:cancleAction];
[self presentViewController:alertContr animated:YES completion:nil];
return;
}
//初始化输出数据管理对象,如果要拍照就初始化AVCaptureStillImageOutput对象;如果拍摄视频就初始化AVCaptureMovieFileOutput对象。
// 拍照图片输出
self.captureStillImageOutput = [[AVCaptureStillImageOutput alloc]init];
NSDictionary *outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG};
[self.captureStillImageOutput setOutputSettings:outputSettings];
//视频输出
self.captureMovieFileOutput = [[AVCaptureMovieFileOutput alloc]init];
self.captureMovieFileOutput.movieFragmentInterval = kCMTimeInvalid;
AVCaptureConnection *captureConnection = [self.captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
if ([captureConnection isVideoStabilizationSupported ]) {
captureConnection.preferredVideoStabilizationMode=AVCaptureVideoStabilizationModeAuto;
}
// 初始化会话对象
self.captureSession = [[AVCaptureSession alloc]init];
//设置分辨率 (设备支持的最高分辨率)
[self.captureSession setSessionPreset:AVCaptureSessionPresetHigh];
//给设备添加输入
if ([self.captureSession canAddInput:self.captureInput])
{
[self.captureSession addInput:self.captureInput];//视频输出
[self.captureSession addInput:audioCaptureDeviceInput];//音频输出
}
//将图片输出添加到会话中
if ([self.captureSession canAddOutput:self.captureStillImageOutput])
{
[self.captureSession addOutput:self.captureStillImageOutput];
}
//将音频输出添加到会话中
if ([weakSelf.captureSession canAddOutput:weakSelf.captureMovieFileOutput]) {
[weakSelf.captureSession addOutput:weakSelf.captureMovieFileOutput];
AVCaptureConnection *captureConnection=[weakSelf.captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
if ([captureConnection isVideoStabilizationSupported ]) {
captureConnection.preferredVideoStabilizationMode=AVCaptureVideoStabilizationModeAuto;
}
}
// 通过会话 (AVCaptureSession) 创建预览层
self.preview = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession];
self.preview.videoGravity=AVLayerVideoGravityResizeAspect;//填充模式
if([Helper checkCameraAuthorizationStatus] == NO){
return;
}
}
开始录制
-(void)videoStart{
if([Helper checkCameraAuthorizationStatus] == NO){
return;
}
if([Helper checkMicAuthorizationStatus] == NO){
return;
}
self.isRecording = YES;
//修改UI显示
self.closeBtn.hidden = YES;
self.cameraRotationBtn.hidden = YES;
self.flashLightBtn.hidden = YES;
//开始录制
AVCaptureConnection *captureConnection=[self.captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
//预览图层和视频方向保持一致
captureConnection.videoOrientation = [self.preview connection].videoOrientation;
NSString *outputFielPath=[NSTemporaryDirectory() stringByAppendingString:@"myMovie.mov"];
if ([[NSFileManager defaultManager] fileExistsAtPath:outputFielPath]) {
[[NSFileManager defaultManager] removeItemAtPath:outputFielPath error:nil];
}
NSURL *fileUrl=[NSURL fileURLWithPath:outputFielPath];
[self.captureMovieFileOutput startRecordingToOutputFileURL:fileUrl recordingDelegate:self];
//如果支持多任务则则开始多任务
if ([[UIDevice currentDevice] isMultitaskingSupported]) {
self.backgroundTaskIdentifier=[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
}
}
结束录制
-(void)videoEnd{
if (self.isRecording == YES) {
self.isRecording = NO;
[_captureMovieFileOutput stopRecording];
}
}
视频输出代理
-(void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections{
NSLog(@"开始录制");
}
-(void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error{
WEAKSELF
[self.videoTimer invalidate];
self.videoTimer = nil;
self.closeBtn.hidden = NO;
self.cameraRotationBtn.hidden = NO;
self.flashLightBtn.hidden = NO;
if (self.isCancelVideo == YES) {
self.isCancelVideo = NO;
NSLog(@"取消录制 finish");
return;
}
AVURLAsset *avUrl = [AVURLAsset assetWithURL:outputFileURL];
CMTime time = [avUrl duration];
CGFloat duration = CMTimeGetSeconds(time);
NSLog(@"duration:%f",duration);
if(self.isTimeTooShort){
self.isTimeTooShort = NO;
return;
}
//视频文件转换后存到本地
NSDate * now = [NSDate date];
NSString * fileName = [NSString stringWithFormat:@"%zd.mp4",[now timeIntervalSince1970] * 1000];
NSString * thumbFileName = [NSString stringWithFormat:@"%zd.jpg",[now timeIntervalSince1970] * 1000];
NSFileManager * fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString * diskCachePath = [paths[0] stringByAppendingPathComponent:@"ecamera"];
if (![fileManager fileExistsAtPath:diskCachePath]) {
[fileManager createDirectoryAtPath:diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
}
NSString * filePath =[NSString stringWithFormat:@"%@/%@",diskCachePath,fileName] ;
NSString * thumbFilePath = [NSString stringWithFormat:@"%@/%@",diskCachePath,thumbFileName];
[ToolFunction transMovToMP4:outputFileURL.absoluteString Output:filePath exportStatusHandler:^(AVAssetExportSessionStatus exportStatus) {
switch (exportStatus) {
case AVAssetExportSessionStatusFailed:
[weakSelf.recordView videoExportFailHandle];
break;
case AVAssetExportSessionStatusCancelled:
NSLog(@"导出视频被终了");
break;
case AVAssetExportSessionStatusCompleted:
if ([fileManager fileExistsAtPath:filePath]) {
NSLog(@"导出视频成功");
}else{
[weakSelf.recordView videoExportFailHandle];
}
break;
default:
break;
}
}];
NSLog(@"transMovToMP4:%f",duration);
self.filePath = filePath;
//写数据库
ECameraMediaModel * media = [[ECameraMediaModel alloc]init];
media.type = ECameraMediaTypeVideo;
media.fileName = fileName;
media.thumbFileName = thumbFileName;
media.thumbFilePath = thumbFilePath;
media.dateInfo = now;
media.phoneNum = @"110";
media.duration = duration;
self.tmpMedia = media;
}
通过AVAssetExportSeeion 这个类对视频进行压缩
// 压缩视频
- (IBAction)compressVideo:(id)sender
{
NSString *cachePath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *savePath=[cachePath stringByAppendingPathComponent:MOVIEPATH];
NSURL *saveUrl=[NSURL fileURLWithPath:savePath];
// 通过文件的 url 获取到这个文件的资源
AVURLAsset *avAsset = [[AVURLAsset alloc] initWithURL:saveUrl options:nil];
// 用 AVAssetExportSession 这个类来导出资源中的属性
NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset];
// 压缩视频
if ([compatiblePresets containsObject:AVAssetExportPresetLowQuality]) { // 导出属性是否包含低分辨率
// 通过资源(AVURLAsset)来定义 AVAssetExportSession,得到资源属性来重新打包资源 (AVURLAsset, 将某一些属性重新定义
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:AVAssetExportPresetLowQuality];
// 设置导出文件的存放路径
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd-HH:mm:ss"];
NSDate *date = [[NSDate alloc] init];
NSString *outPutPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true) lastObject] stringByAppendingPathComponent:[NSString stringWithFormat:@"output-%@.mp4",[formatter stringFromDate:date]]];
exportSession.outputURL = [NSURL fileURLWithPath:outPutPath];
// 是否对网络进行优化
exportSession.shouldOptimizeForNetworkUse = true;
// 转换成MP4格式
exportSession.outputFileType = AVFileTypeMPEG4;
// 开始导出,导出后执行完成的block
[exportSession exportAsynchronouslyWithCompletionHandler:^{
// 如果导出的状态为完成
if ([exportSession status] == AVAssetExportSessionStatusCompleted) {
dispatch_async(dispatch_get_main_queue(), ^{
// 更新一下显示包的大小
self.videoSize.text = [NSString stringWithFormat:@"%f MB",[self getfileSize:outPutPath]];
});
}
}];
}
}
拍照
-(void)photoBtnClick{
if([Helper checkCameraAuthorizationStatus] == NO){
return;
}
//根据设备输出获得连接
AVCaptureConnection *captureConnection=[self.captureStillImageOutput connectionWithMediaType:AVMediaTypeVideo];
//根据连接取得设备输出的数据
WEAKSELF
[self.captureStillImageOutput captureStillImageAsynchronouslyFromConnection:captureConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
if (imageDataSampleBuffer) {
NSData *imageData=[AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
UIImage *imageTemp = [UIImage imageWithData:imageData];
UIImage *image = [ToolFunction cropImage:imageTemp Rect:weakSelf.view.frame];
image = [image fixOrientation];
if([weakSelf.preview connection].videoOrientation == AVCaptureVideoOrientationLandscapeLeft){
image = [UIImage image:image rotation:UIImageOrientationRight];
}else if([weakSelf.preview connection].videoOrientation== AVCaptureVideoOrientationLandscapeRight){
image = [UIImage image:image rotation:UIImageOrientationLeft];
}else if([weakSelf.preview connection].videoOrientation == AVCaptureVideoOrientationPortraitUpsideDown){
image = [UIImage image:image rotation:UIImageOrientationDown];
}
//保存到相册
//UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
//写入文件
NSDate * now = [NSDate date];
NSString * fileName = [NSString stringWithFormat:@"%zd.jpg",[now timeIntervalSince1970] * 1000];
NSFileManager * fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString * diskCachePath = [paths[0] stringByAppendingPathComponent:@"ecamera"];
if (![fileManager fileExistsAtPath:diskCachePath]) {
[fileManager createDirectoryAtPath:diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
}
NSString * filePath =[NSString stringWithFormat:@"%@/%@",diskCachePath,fileName] ;
[UIImageJPEGRepresentation(image, 0.75) writeToFile:filePath atomically:YES];
//写入数据库
ECameraMediaModel * media = [[ECameraMediaModel alloc]init];
media.type = ECameraMediaTypeImage;
media.fileName = fileName;
media.dateInfo = now;
media.phoneNum = @"110";
[ECameraSQL insertEasyCameraMediaWith:media];
//保存到自定义相册
[ToolFunction saveToAlbumWithMetadata:nil imageData:UIImagePNGRepresentation(image) customAlbumName:@"乐鱼" completionBlock:^{
//[MBProgressHUD showSuccess:@"保存成功"];
} failureBlock:^(NSError *error) {
[MBProgressHUD showError:@"保存失败"];
}];
weakSelf.photoView.thumbnailImgV.image = image;
weakSelf.recordView.thumbImgV.image = image;
CGFloat kAnimationDuration = 0.3f;
CAGradientLayer *contentLayer = (CAGradientLayer *)weakSelf.photoView.thumbnailImgV.layer;
CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
scaleAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0, 0, 1)];
scaleAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 1)];
scaleAnimation.duration = kAnimationDuration;
scaleAnimation.cumulative = NO;
scaleAnimation.repeatCount = 1;
[scaleAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]];
[contentLayer addAnimation: scaleAnimation forKey:@"myScale"];
}
}];
}
参考过的文章:https://www.jianshu.com/p/7c57c58c253d
http://kayosite.com/ios-development-and-detail-of-photo-framework-part-two.html
https://123sunxiaolin.github.io/2016/08/27/iOS%E4%B8%AD%EF%BC%8C%E7%B3%BB%E7%BB%9F%E7%9B%B8%E5%86%8C%E7%9A%84%E9%82%A3%E4%BA%9B%E4%BA%8B/
网友评论