OC AVFoundation視頻錄制相關(AVCaptureSession+ AVCaptureMovieFileOutput+ AVCaptureVideoPreviewLayer)


參考的博客不過里面添加了一些我自己的總結,

#import "LittleVideoController.h"
#import <AVKit/AVKit.h>
#import <AVFoundation/AVFoundation.h>

@interface LittleVideoController ()<AVCaptureFileOutputRecordingDelegate>


@property(nonatomic,strong) dispatch_source_t timer;
@property (weak, nonatomic) IBOutlet UIButton * startButton;//開始錄制
@property(nonatomic,strong) UIButton * videoButton;//播放
@property(nonatomic,strong) AVCaptureSession *  captureSession;//捕捉會話對象
@property(nonatomic,strong) AVCaptureMovieFileOutput * captureMovieFileOutput;//
@property(nonatomic,strong) AVCaptureVideoPreviewLayer * captureVideoPreviewLayer;

@property(nonatomic,strong) NSString * videoPath;

@end

@implementation LittleVideoController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
        
    [self.startButton addTarget:self action:@selector(startCaptureWithSession) forControlEvents:UIControlEventTouchUpInside];
    
    /*
     下面是一個有趣的寫法,上網搜到這樣一段話,補充說明之后能理解這種寫法:
     這個問題嚴格上講和Objective-C沒什么太大的關系,這個是GNU C的對C的擴展語法 
     
     在理解一下什么是GNU C ,下面是百度給的定義:
     GNU C 函式庫(GNU C Library,又稱為glibc)是一種按照LGPL許可協議發布的,公開源代碼的,免費的,方便從網絡下載的C的編譯程序。 GNU C運行期庫,是一種C函式庫,是程序運行時使用到的一些API集合,它們一般是已預先編譯好,以二進制代碼形式存 在Linux類系統中,GNU C運行期庫,通常作為GNU C編譯程序的一個部分發布。 它最初是自由軟件基金會為其GNU操作系統所寫,但目前最主要的應用是配合Linux內核,成為GNU/Linux操作系統一個重要的組成部分。
     
     繼續解釋:
     Xcode采用的Clang編譯,Clang作為GCC(GCC的初衷是為GNU操作系統專門編寫的一款編譯器)的替代品,和GCC一樣對於GNU C語法完全支持
     
     你可能知道if(condition)后面只能根一條語句,多條語句必須用{}闊起來,這個語法擴展即將一條(多條要用到{})語句外面加一個括號(),
     這樣的話你就可以在表達式中應用循環、判斷甚至本地變量等。
     表達式()最后一行應該一個能夠計算結果的子表達式加上一個分號(;),這個子表達式作為整個結構的返回結果
     
     這個擴展在代碼中最常見的用處在於宏定義中
     */

    
    
    
    self.captureSession = ({
        // 分辨率設置
        AVCaptureSession *session = [[AVCaptureSession alloc] init];
        // 先判斷這個設備是否支持設置你要設置的分辨率
        if ([session canSetSessionPreset:AVCaptureSessionPresetMedium]) {
                
                /*
                 下面是對你能設置的預設圖片的質量和分辨率的說明
                 AVCaptureSessionPresetHigh      High 最高的錄制質量,每台設備不同
                 AVCaptureSessionPresetMedium    Medium 基於無線分享的,實際值可能會改變
                 AVCaptureSessionPresetLow       LOW 基於3g分享的
                 AVCaptureSessionPreset640x480   640x480 VGA
                 AVCaptureSessionPreset1280x720  1280x720 720p HD
                 AVCaptureSessionPresetPhoto     Photo 完整的照片分辨率,不支持視頻輸出
                 */
                [session setSessionPreset:AVCaptureSessionPresetMedium];
        }
        session;
    });
    
    
    // 初始化一個拍攝輸出對象
    self.captureMovieFileOutput = ({
        
        //輸出一個電影文件
        /*
         a.AVCaptureMovieFileOutput  輸出一個電影文件
         b.AVCaptureVideoDataOutput  輸出處理視頻幀被捕獲
         c.AVCaptureAudioDataOutput  輸出音頻數據被捕獲
         d.AVCaptureStillImageOutput 捕獲元數據
         */
        
        AVCaptureMovieFileOutput * output = [[AVCaptureMovieFileOutput alloc]init];
        
        /*
         一個ACCaptureConnection可以控制input到output的數據傳輸。
         */
        AVCaptureConnection * connection = [output connectionWithMediaType:AVMediaTypeVideo];
        
        if ([connection isVideoMirroringSupported]) {

                
            /*
             
             視頻防抖 是在 iOS 6 和 iPhone 4S 發布時引入的功能。到了 iPhone 6,增加了更強勁和流暢的防抖模式,被稱為影院級的視頻防抖動。相關的 API 也有所改動 (目前為止並沒有在文檔中反映出來,不過可以查看頭文件)。防抖並不是在捕獲設備上配置的,而是在 AVCaptureConnection 上設置。由於不是所有的設備格式都支持全部的防抖模式,所以在實際應用中應事先確認具體的防抖模式是否支持:
             
             typedef NS_ENUM(NSInteger, AVCaptureVideoStabilizationMode) {
             AVCaptureVideoStabilizationModeOff       = 0,
             AVCaptureVideoStabilizationModeStandard  = 1,
             AVCaptureVideoStabilizationModeCinematic = 2,
             AVCaptureVideoStabilizationModeAuto      = -1,  自動
             } NS_AVAILABLE_IOS(8_0) __TVOS_PROHIBITED;
             */
            connection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
            //預覽圖層和視頻方向保持一致
            connection.videoOrientation = [self.captureVideoPreviewLayer connection].videoOrientation;
        }
        
        if ([self.captureSession canAddOutput:output]) {
            
            [self.captureSession addOutput:output];
        }
        
        output;
    });
    
    /*
     用於展示制的畫面
     */
    self.captureVideoPreviewLayer = ({
        
        AVCaptureVideoPreviewLayer * preViewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:self.captureSession];
        preViewLayer.frame = CGRectMake(10, 50, 355, 355);
        
        /*
         AVLayerVideoGravityResizeAspect:保留長寬比,未填充部分會有黑邊
         AVLayerVideoGravityResizeAspectFill:保留長寬比,填充所有的區域
         AVLayerVideoGravityResize:拉伸填滿所有的空間
        */
        preViewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
        [self.view.layer addSublayer:preViewLayer];
        self.view.layer.masksToBounds = YES;
        preViewLayer;
    });
    
    
    //如果這塊代碼寫在前面,則打開captureVideoPreviewLayer就顯示攝像頭的內容,和微信小程序一樣
    if ([self SetSessioninputs:nil]) {
         [self.captureSession startRunning];
    }
    
}

#pragma mark 開始錄制
-(void)startCaptureWithSession{
    
    // 先刪除之前的視頻文件
    if ([[NSFileManager defaultManager] fileExistsAtPath:self.videoPath]) {
                
        [[NSFileManager defaultManager] removeItemAtURL:[NSURL fileURLWithPath:self.videoPath] error:NULL];
    }
        
//    NSError * error = nil;
//    if ([self SetSessioninputs:error]) {
//
        // 開始錄制
       
        [self startRecordSession];
        
//    }else{
//        
//        [self.captureSession stopRunning];
//        NSLog(@"錄制失敗:%@",error);
//    }
}


-(BOOL)SetSessioninputs:(NSError *)error{
    
    // capture 捕捉 捕獲
    /*
       視頻輸入類
       AVCaptureDevice 捕獲設備類
       AVCaptureDeviceInput 捕獲設備輸入類
     */
    AVCaptureDevice * captureDevice   = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    AVCaptureDeviceInput * videoInput = [AVCaptureDeviceInput deviceInputWithDevice: captureDevice error: &error];
    
    if (!videoInput) {
        return NO;
    }
    
    // 給捕獲會話類添加輸入捕獲設備
    if ([self.captureSession canAddInput:videoInput]) {
        
        [self.captureSession addInput:videoInput];
    }else{
        return NO;
    }
    
    
    
//      添加音頻捕獲設備
    /*
     __block AVCaptureDevice *backCamera  = nil;
     NSArray *cameras = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
     [cameras enumerateObjectsUsingBlock:^(AVCaptureDevice *camera, NSUInteger idx, BOOL * _Nonnull stop) {

     //AVCaptureDevicePositionFront 前攝像頭
     //AVCaptureDevicePositionBack:后攝像頭
     //AVCaptureDevicePositionUnspecified不指定前值或者后置,用系統當前的
       if(camera.position == AVCaptureDevicePositionBack){
           backCamera = camera;
       }
    }];
     
     
     // 配置曝光模式 設置持續曝光模式
     //注意改變設備屬性前一定要首先調用lockForConfiguration:調用完之后使用unlockForConfiguration方法解鎖
     NSError *error = nil;
     [backCamera lockForConfiguration:&error];
     //AVCaptureExposureModeLocked 直接用當前值就好,不指定
     //AVCaptureExposureModeAutoExpose 自動調整曝光一次,然后用系統的
     //AVCaptureExposureModeContinuousAutoExposure 需要的時候自行調節
     //AVCaptureExposureModeCustom 自定義,需要自己手動設置
     
    if ([backCamera isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]){
        [backCamera setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
    }
    [backCamera unlockForConfiguration];
     
     
     //閃光燈設計  AVCaptureFlashMode
     
     //手電筒開關--其實就是相機的閃光燈 AVCaptureTorchMode
     [backCamera lockForConfiguration:&error];
     if([backCamera isTorchModeSupported:AVCaptureTorchModeOn]){
     [backCamera setTorchMode:AVCaptureTorchModeOn];
     }
     [backCamera unlockForConfiguration];
     
     //焦距模式調整AVCaptureFocusMode
     //曝光量調節AVCaptureExposureMode
     //白平衡  AVCaptureWhiteBalanceMode
     //距離調整 AVCaptureAutoFocusRangeRestriction
     
    */

    //如果需要指定的攝像頭,
    AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
    //

    AVCaptureDeviceInput * audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error];
    
    if (!audioDevice) {
        
        return NO;
    }
    
    if ([self.captureSession canAddInput:audioInput]) {
        
        [self.captureSession addInput:audioInput];
    }
    return YES;
}



-(void)startRuningWithSession{
        
        __block int time = 0;
        _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
        dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
        dispatch_source_set_event_handler(_timer, ^{
                
                time++;
                NSLog(@"錄制的時長:%d",time);
                if (time == 10) {
                        
                        NSLog(@"錄制的時長限制在10秒以內");
                        [self.captureMovieFileOutput stopRecording];
                        [self.captureSession stopRunning];
                        dispatch_source_cancel(_timer);
                }
        });
        dispatch_resume(_timer);
}


#pragma mark --
#pragma mark -- AVCaptureMovieFileOutput 錄制視頻
-(void)startRecordSession{

        
    [self.captureMovieFileOutput startRecordingToOutputFileURL:({
        
        NSURL * url  = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"zhangxu.mov"]];
        NSLog(@"視頻想要緩存的地址:%@",url);
        if ([[NSFileManager defaultManager]fileExistsAtPath:url.path]) {
            
            [[NSFileManager defaultManager] removeItemAtURL:url error:nil];
        }
        url;
    }) recordingDelegate:self];
}
#pragma mark AVCaptureFileOutputRecordingDelegate 代理方法
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL
      fromConnections:(NSArray *)connections{
        
        //Recording started
        NSLog(@"視頻錄制開始!!");
        [self startRuningWithSession]; // 開始計時
}

- (void)captureOutput:(AVCaptureFileOutput *)output didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray<AVCaptureConnection *> *)connections error:(nullable NSError *)error{
    
        NSLog(@"視頻錄制結束!!");
        NSLog(@"視頻緩存地址:%@",outputFileURL);
    
        NSLog(@"視頻壓縮前大小 %f M",  [self getFileSize:[outputFileURL path]]);
    
    
        BOOL recordedSuccessfully = YES;
        id captureResult = [[error userInfo]objectForKey:AVErrorRecordingSuccessfullyFinishedKey];
        if (captureResult) {
                
                recordedSuccessfully = [captureResult boolValue];
        }
        
        if (recordedSuccessfully) {
                
            [self compressVideoWithFileUrl:outputFileURL];
            
        }
}

//此⽅方法可以獲取視頻⽂文件的大小。
- (CGFloat) getFileSize:(NSString *)path {
    
    NSData * data = [NSData dataWithContentsOfFile:path];
    float dataSize = (float)data.length/1024/1024;
    NSLog(@"視頻壓縮qian大小 %f M", dataSize);
    return dataSize;
    
//    NSLog(@"%@",path);
//    NSFileManager *fileManager = [NSFileManager defaultManager];
//    float filesize = -1.0;
//    if ([fileManager fileExistsAtPath:path]) {
//        NSDictionary *fileDic = [fileManager attributesOfItemAtPath:path error:nil];//獲取⽂文件的屬性
//        unsigned long long size = [[fileDic objectForKey:NSFileSize] longLongValue];
//        filesize = 1.0*size/1024/1024;
//    }else{ NSLog(@"找不不到⽂文件");
//
//
//    }
//    return filesize;
    
}

#pragma mark --
#pragma mark -- 視頻壓縮方法
-(void)compressVideoWithFileUrl:(NSURL *)fileUrl{
    
    /*
      這里需要注意的一點就是在重復的路徑上保存文件是不行的,可以選擇在點擊開始的時候刪除之前的
      也可以這樣按照時間命名不同的文件保存
      在后面的AVAssetWriter也要注意這一點
     */
    // 壓縮后的視頻的方法命名
    NSDateFormatter * formatter = [[NSDateFormatter alloc]init];
    [formatter setDateFormat:@"yyyy-MM-dd-HH:mm:ss"];
        
    // 壓縮后的文件路徑
    self.videoPath = [
                      NSString stringWithFormat:@"%@/%@.mov",NSTemporaryDirectory(),[formatter stringFromDate:[NSDate date]]];
    
    
    // 先根據你傳入的文件的路徑穿件一個AVAsset
    AVAsset * asset = [AVAsset  assetWithURL:fileUrl];
    /*
     
     根據urlAsset創建AVAssetExportSession壓縮類
     第二個參數的意義:常用 壓縮中等質量  AVAssetExportPresetMediumQuality
     AVF_EXPORT NSString *const AVAssetExportPresetLowQuality        NS_AVAILABLE_IOS(4_0);
     AVF_EXPORT NSString *const AVAssetExportPresetMediumQuality     NS_AVAILABLE_IOS(4_0);
     AVF_EXPORT NSString *const AVAssetExportPresetHighestQuality    NS_AVAILABLE_IOS(4_0);
    
    */
    AVAssetExportSession * exportSession = [[AVAssetExportSession alloc]initWithAsset:asset presetName:AVAssetExportPresetMediumQuality];
    
    // 優化壓縮,這個屬性能使壓縮的質量更好
    exportSession.shouldOptimizeForNetworkUse = YES;
    // 到處的文件的路徑
    exportSession.outputURL =  [NSURL fileURLWithPath:self.videoPath];
    // 導出的文件格式
    /*!
      @constant  AVFileTypeMPEG4  mp4格式的   AVFileTypeQuickTimeMovie mov格式的
      @abstract A UTI for the MPEG-4 file format.
      @discussion
      The value of this UTI is @"public.mpeg-4".
      Files are identified with the .mp4 extension.
      可以看看這個outputFileType格式,比如AVFileTypeMPEG4也可以寫成public.mpeg-4,其他類似
    */
    exportSession.outputFileType = AVFileTypeQuickTimeMovie;
    
    NSLog(@"視頻壓縮后的presetName: %@",exportSession.presetName);
    // 壓縮的方法  export 導出  Asynchronously 異步
    [exportSession exportAsynchronouslyWithCompletionHandler:^{
        
        /*
         exportSession.status 枚舉屬性
         typedef NS_ENUM(NSInteger, AVAssetExportSessionStatus) {
         AVAssetExportSessionStatusUnknown,
         AVAssetExportSessionStatusWaiting,
         AVAssetExportSessionStatusExporting,
         AVAssetExportSessionStatusCompleted,
         AVAssetExportSessionStatusFailed,
         AVAssetExportSessionStatusCancelled
         };
         */
        int exportStatus = exportSession.status;
        switch (exportStatus) {
            case AVAssetExportSessionStatusFailed:
                
                NSLog(@"壓縮失敗");
                break;
            case AVAssetExportSessionStatusCompleted:
            {
                /*
                 壓縮后的大小
                 也可以利用exportSession的progress屬性,隨時監測壓縮的進度
                 */
                NSData * data = [NSData dataWithContentsOfFile:self.videoPath];
                float dataSize = (float)data.length/1024/1024;
                NSLog(@"視頻壓縮后大小 %f M", dataSize);
                
                
                
                
               
            }
                break;
            default:
                break;
        }
    }];
}



- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM