视图控件

自定义相机采集及视频编辑(1)-短视频录制

2018-02-26  本文已影响18人  WSGNSLog

自定义相机采集及视频编辑

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/

上一篇下一篇

猜你喜欢

热点阅读