zoukankan      html  css  js  c++  java
  • 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
    
  • 相关阅读:
    20191208浙江自然博物馆寒武纪迸发专题展
    Spring Crest-英特尔出品的神经网络训练场景加速卡
    20191203动物园玉皇山凤凰山南宋皇城遗址凤凰山玉皇山动物园
    博观而约取-观展攻略
    20191130周六浙江美术馆纤维艺术特展
    git clone下载速度很慢的解决方法
    Jupyter notebook使用技巧积累
    (转)Python--matplotlib绘图可视化知识点整理
    Pygame一些不错教程平时收集....
    pygame.Surface.get_at
  • 原文地址:https://www.cnblogs.com/hualuoshuijia/p/11549165.html
Copyright © 2011-2022 走看看