【iOS】视频的自定义拍摄与压缩

作者: 取名有丶难 | 来源:发表于2018-03-29 16:05 被阅读204次

    前言

    本篇还是将之前研究过用过的知识做一个梳理与总结。开发过程总体来说是一个快速解决功能需求的过程,在有限的时间内将侧重点放在实现具体功能上,而在此过程中可能对于遇到的问题并没有时间去做进一步的思考的总结,于是就有了事后的总结。

    运行效果

    话不多说,有图有真相
    运行效果.gif

    基本结构

    很简单的结构,如图所示


    结构.png

    过程

    拍摄

    既然是自定义视频拍摄,那么当然用到的是AVFoundation这个框架的东西,需要用到框架提供的几个类包含有

    AVCaptureSession
    AVCaptureDevice
    AVCaptureVideoPreviewLayer
    AVCaptureDeviceInput
    AVCaptureConnection
    AVCaptureVideoDataOutput
    AVCaptureAudioDataOutput
    AVAssetWriter
    AVAssetWriterInput

    其实就是session会话管理器,这是个中枢管理类,然后有摄像头设备的抽象类,视频流数据的输入输出类和连接类,预览的Layer类,最后就是写视频文件的相关类。其实这一部分已经有很多人总结过了,用法什么的也有很多人写了相关内容,我就不多说了。具体的用法可看这位老哥的总结:传送门在此

    压缩

    事实上拍摄和压缩是同步进行的,如果拍完再压缩那就没什么意义了。说白了压缩就是对视频写入的数据在写入之前先处理一下,由于写入是子线程中的异步任务也不会阻塞UI。直接上代码:
    //设置写入视频属性,在这里进行压缩
    - (void)setUpWriterSettings
    {
        NSError *error = nil;
        self.assetWriter = [AVAssetWriter assetWriterWithURL:self.writeVideoUrl fileType:AVFileTypeMPEG4 error:&error];
        //写入视频大小
        NSInteger numPixels = self.videoSize.width * self.videoSize.height;
        //每像素比特
        CGFloat bitsPerPixel = 6.0;
        NSInteger bitsPerSecond = numPixels * bitsPerPixel;
        
        // 码率和帧率设置
        NSDictionary *compressionProperties = @{ AVVideoAverageBitRateKey : @(bitsPerSecond),
                                                 AVVideoExpectedSourceFrameRateKey : @(30),
                                                 AVVideoMaxKeyFrameIntervalKey : @(30),
                                                 AVVideoProfileLevelKey : AVVideoProfileLevelH264BaselineAutoLevel };
        
        //视频属性
        self.videoCompressionSettings = @{ AVVideoCodecKey : AVVideoCodecTypeH264,
                                           AVVideoScalingModeKey : AVVideoScalingModeResizeAspectFill,
                                           AVVideoWidthKey : @(self.videoSize.width*2),
                                           AVVideoHeightKey : @(self.videoSize.height*2),
                                           AVVideoCompressionPropertiesKey : compressionProperties };
        
        _assetWriterVideoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:self.videoCompressionSettings];
        //expectsMediaDataInRealTime 必须设为yes,需要从capture session 实时获取数据
        _assetWriterVideoInput.expectsMediaDataInRealTime = YES;
        _assetWriterVideoInput.transform = CGAffineTransformMakeTranslation(M_PI_2, M_PI_2);
        _assetWriterVideoInput.transform  = CGAffineTransformScale(_assetWriterVideoInput.transform, -1, 1);
        
        // 音频设置
        self.audioCompressionSettings = @{ AVEncoderBitRatePerChannelKey : @(28000),
                                           AVFormatIDKey : @(kAudioFormatMPEG4AAC),
                                           AVNumberOfChannelsKey : @(1),
                                           AVSampleRateKey : @(22050) };
        
        
        _assetWriterAudioInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:self.audioCompressionSettings];
        _assetWriterAudioInput.expectsMediaDataInRealTime = YES;
        
        
        if ([_assetWriter canAddInput:_assetWriterVideoInput]) {
            [_assetWriter addInput:_assetWriterVideoInput];
        }else {
            NSLog(@"AssetWriter error");
            [self resetAsset];
        }
        if ([_assetWriter canAddInput:_assetWriterAudioInput]) {
            [_assetWriter addInput:_assetWriterAudioInput];
        }else {
            NSLog(@"AssetWriter error");
            [self resetAsset];
        }
        
    }
    
    在上面的代码中对视频质量相关的属性(帧率,码率,像素)进行设置,这些属性的组合设置既要保持视频的流畅,在清晰度上也要有一定的保证,毕竟不能压缩的太过分,如果都最后出来的视频都看不清了那就有点扯蛋了。因此在这些属性的值的设置上需要一定的考究,以便在同等压缩率下保持相对最好的清晰度。上面的相关参数设置之后在 AVCaptureSessionPresetHigh 高质量录制下会有10倍左右的压缩倍率,而且清晰度还保留的相当不错。如果大家还有更高效率的参数设置方案也欢迎一起学习讨论。

    一些小坑

    由于这个功能会动用手机的硬件设备,在初始化录制管理器的时候会实例化很多个的AVFoundation提供的相关类的对象,这个过程据我测试会有一个等待的时间,根据手机性能的不同最低也会有将近一秒的等待。因此建议将这些类的实例化放在异步进行,不要阻塞UI,这样用户体验也会好一点,我在demo中也是这样做的。
        //在异步加载录制管理器对象,然后刷新视图
        dispatch_async(dispatch_get_main_queue(), ^{
            _videoRecord = [[CWVideoRecord alloc]initWithPreset:AVCaptureSessionPresetHigh writePath:NSTemporaryDirectory()];
            _videoRecord.delegate = self;
            //展示预览层
            [_videoRecord displayRecordLayer:_videoView];
            //刷新视图
            [_videoView reloadRecord:CWRecordReady];
        });
        
    
    事实上系统的相机也应该是这个做法,系统相机点开时会先进入控制器视图,此时会在黑屏状态下等待一段时间才会出来预览界面。
    在视频写入的过程中全程都是在子线程中执行,此时要注意在写入和完成写入的代码上加上线程锁,否则会崩到怀疑人生,另外在写入开启时
    [self.assetWriter startWriting];
    
    会有几率报错崩溃,这个错误很奇葩,出现的几率也不高,但是一旦出现就是崩溃,这就不能忍了,我只能用@try @catch的方式捕获一下以免直接崩溃。在捕获之后可以进一步处理,或是重新开启录制写入,处理的好基本可以做到让用户难以察觉就是了。如果大家有什么办法避免也可以留言告诉我一下。

    完结

    不得不说,作为一个程序员与文字打交道比与代码打交道痛苦的多啊,不过我也会一直坚持写下去,毕竟每一次的归纳总结也是一次更加深入的学习。

    PS:附上demo传送门

    相关文章

      网友评论

        本文标题:【iOS】视频的自定义拍摄与压缩

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