自定義相機採集及視訊編輯(1)-短視訊錄製

weixin_33890499發表於2018-02-26

自定義相機採集及視訊編輯

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/

相關文章