美文网首页视图控件
自定义相机采集及视频编辑(1)-短视频录制

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

作者: WSGNSLog | 来源:发表于2018-02-26 15:17 被阅读18次

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

    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/

    相关文章

      网友评论

        本文标题:自定义相机采集及视频编辑(1)-短视频录制

        本文链接:https://www.haomeiwen.com/subject/ymsyxftx.html