iOS 仿微信录制小视频

作者: 郑嘉成_ | 来源:发表于2017-09-29 14:08 被阅读481次

    Demo 地址
    JCVideoRecord

    最近有个需求涉及到录制一个小视频上传服务器,所以写了这个Demo,在这记录下,供学习用,主要使用的是AVFoundation 框架。

    IMB_AX2vxc.GIF

    使用

    - (void)present{
        _recordView = [[JCVideoRecordView alloc]initWithFrame:[UIScreen mainScreen].bounds];
        _recordView.cancelBlock = ^{
    
        };
        _recordView.completionBlock = ^(NSURL *fileUrl) {
            
        };
        [_recordView present];
    }
    

    简单介绍

    1.目录结构

    屏幕快照 2017-09-29 下午1.29.07.png

    2. 流程

    创建JCVideoRecordView时,初始化JCVideoRecordManager, JCVideoRecordManager负责管理视频采集,录制,压缩等操作,录制结束后创建JCRecordPlayerView 来循环播放录制的视频,点击确定回调给VCL。

    $ 主要代码

    JCVideoRecordManager.h

    @protocol JCVideoRecordManagerDelegate <NSObject>
    //录制结束
    - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error;
    
    //录制时间
    - (void)recordTimeCurrentTime:(CGFloat)currentTime totalTime:(CGFloat)totalTime;
    @end
    
    @interface JCVideoRecordManager : NSObject
    @property (nonatomic, weak) id<JCVideoRecordManagerDelegate> delegate;
    //摄像头视图层
    @property (nonatomic, strong) AVCaptureVideoPreviewLayer *preViewLayer;
    
    // 准备录制
    - (void)prepareForRecord;
    
    // 开始录制
    - (void)startRecordToFile:(NSURL *)outPutFile;
    
    // 停止录制
    - (void)stopCurrentVideoRecording;
    
    // 切换摄像头
    - (void)switchCamera;
    
    // 设置对焦
    - (void)setFoucusWithPoint:(CGPoint)point;
    
    //压缩视频
    - (void)compressVideo:(NSURL *)inputFileURL complete:(void(^)(BOOL success, NSURL* outputUrl))complete;
    

    JCVideoRecordManager.m

    - (instancetype)init{
        self = [super init];
        if (self) {
            self.captureSession = [[AVCaptureSession alloc]init];
            self.movieFileOutput = [[AVCaptureMovieFileOutput alloc]init];
            //后台播放音频时需要注意加以下代码,否则会获取音频设备失败
            [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
            [[AVAudioSession sharedInstance] setMode:AVAudioSessionModeVideoRecording error:nil];
            [[AVAudioSession sharedInstance] setActive:YES withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil];
            [self prepareForRecord];
        }
        return self;
    }
    
    #pragma mark - 获取权限
    + (void)getCameraAuth:(void(^)(BOOL boolValue, NSString *tipText))isAuthorized{
        __weak typeof(self) instance = self;
        //获取视频权限
        AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
        if (authStatus == AVAuthorizationStatusNotDetermined){
            [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo
                                     completionHandler:^(BOOL granted) {
                                         if (granted) {
                                             [instance getCameraAuth:isAuthorized];
                                         }else{
                                             isAuthorized(NO, @"没有获得相机权限");
                                         }
                                     }];
        }else if (authStatus == AVAuthorizationStatusAuthorized){
            [[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) {
                isAuthorized(granted,  @"没有获得麦克风权限");
            }];
        }else{
            isAuthorized(NO, @"没有获得相机权限");
        }
    }
    
    #pragma mark - 摄像头视图层
    - (AVCaptureVideoPreviewLayer *)preViewLayer{
        if (!_preViewLayer) {
            _preViewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession];
            _preViewLayer.masksToBounds = YES;
            _preViewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
        }
        return _preViewLayer;
    }
    
    #pragma mark 视频输入
    - (AVCaptureDeviceInput *)mediaDeviceInput{
        if (!_mediaDeviceInput) {
            __block AVCaptureDevice *backCamera  = nil;
            NSArray *cameras = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
            [cameras enumerateObjectsUsingBlock:^(AVCaptureDevice *camera, NSUInteger idx, BOOL * _Nonnull stop) {
                if(camera.position == AVCaptureDevicePositionBack){
                    backCamera = camera;
                }
            }];
            [self setExposureModeWithDevice:backCamera];
            _mediaDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:backCamera error:nil];
        }
        return _mediaDeviceInput;
    }
    
    #pragma mark 音频输入
    - (AVCaptureDeviceInput *)audioDeviceInput{
        if (!_audioDeviceInput) {
            NSError *error;
            _audioDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio] error:&error];
        }
        return _audioDeviceInput;
    }
    
    #pragma mark - 输入输出对象连接
    - (AVCaptureConnection *)captureConnection{
        return _captureConnection = _captureConnection ? : [self.movieFileOutput connectionWithMediaType:AVMediaTypeVideo];
    }
    
    #pragma mark 配置曝光模式 设置持续曝光模式
    - (void)setExposureModeWithDevice:(AVCaptureDevice *)device{
        //注意改变设备属性前一定要首先调用lockForConfiguration:调用完之后使用unlockForConfiguration方法解锁
        NSError *error = nil;
        [device lockForConfiguration:&error];
        if ([device isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]){
            [device setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
        }
        [device unlockForConfiguration];
    }
    
    #pragma mark 计时器相关
    - (NSTimer *)timer{
        if (!_timer){
            _timer = [NSTimer scheduledTimerWithTimeInterval:KTimerInterval target:self selector:@selector(fire:) userInfo:nil repeats:YES];
        }
        return _timer;
    }
    
    - (void)fire:(NSTimer *)timer{
        self.recordTime += KTimerInterval;
        if ([self.delegate respondsToSelector:@selector(recordTimeCurrentTime:totalTime:)]) {
            [self.delegate recordTimeCurrentTime:self.recordTime totalTime:KMaxRecordTime];
        }
        if(_recordTime >= KMaxRecordTime){
            [self stopCurrentVideoRecording];
        }
    }
    
    - (void)startTimer{
        [self.timer invalidate];
        self.timer = nil;
        self.recordTime = 0;
        [self.timer fire];
    }
    
    - (void)stopTimer{
        [self.timer invalidate];
        self.timer = nil;
    }
    
    #pragma mark 准备录制
    - (void)prepareForRecord{
        [self.captureSession beginConfiguration];
        
        //视频采集质量
        [self.captureSession canSetSessionPreset:AVCaptureSessionPresetHigh] ? [self.captureSession setSessionPreset:AVCaptureSessionPresetHigh] : nil;
        
        //添加input
        [self.captureSession canAddInput:self.mediaDeviceInput] ? [self.captureSession addInput:self.mediaDeviceInput] : nil;
        [self.captureSession canAddInput:self.audioDeviceInput] ? [self.captureSession addInput:self.audioDeviceInput] : nil;
        
        //添加output
        [self.captureSession canAddOutput:self.movieFileOutput] ? [self.captureSession addOutput:self.movieFileOutput] : nil;
    
        [self.captureSession commitConfiguration];
        
        // 防抖功能
        if ([self.captureConnection isVideoStabilizationSupported] && self.captureConnection.activeVideoStabilizationMode == AVCaptureVideoStabilizationModeOff){
            self.captureConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
        }
        
        [self.captureSession startRunning];
    }
    
    #pragma mark 切换摄像头
    - (void)switchCamera{
        [_captureSession beginConfiguration];
        [_captureSession removeInput:_mediaDeviceInput];
        AVCaptureDevice *swithToDevice = [self switchCameraDevice];
        [swithToDevice lockForConfiguration:nil];
        [self setExposureModeWithDevice:swithToDevice];
        self.mediaDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:swithToDevice error:nil];
        [_captureSession addInput:_mediaDeviceInput];
        [_captureSession commitConfiguration];
    }
    
    #pragma mark 获取切换时的摄像头
    - (AVCaptureDevice *)switchCameraDevice{
        AVCaptureDevice *currentDevice = [self.mediaDeviceInput device];
        AVCaptureDevicePosition currentPosition = [currentDevice position];
        BOOL isUnspecifiedOrFront = (currentPosition == AVCaptureDevicePositionUnspecified || currentPosition ==AVCaptureDevicePositionFront );
        AVCaptureDevicePosition swithToPosition = isUnspecifiedOrFront ? AVCaptureDevicePositionBack:AVCaptureDevicePositionFront;
        NSArray *cameras = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
        __block AVCaptureDevice *swithCameraDevice = nil;
        [cameras enumerateObjectsUsingBlock:^(AVCaptureDevice *camera, NSUInteger idx, BOOL * _Nonnull stop) {
            if (camera.position == swithToPosition){
                swithCameraDevice = camera;
                *stop = YES;
            };
        }];
        return swithCameraDevice;
    }
    
    #pragma mark 开始录制
    - (void)startRecordToFile:(NSURL *)outPutFile{
        
        if (!self.captureConnection) {
            return;
        }
        
        if ([self.movieFileOutput isRecording]) {
            return;
        }
        if ([self.captureConnection isVideoOrientationSupported]){
            self.captureConnection.videoOrientation =[self.preViewLayer connection].videoOrientation;
        }
        [_movieFileOutput startRecordingToOutputFileURL:outPutFile recordingDelegate:self];
    }
    
    #pragma mark  停止录制
    - (void)stopCurrentVideoRecording{
        if (self.movieFileOutput.isRecording) {
            [self stopTimer];
            [_movieFileOutput stopRecording];
        }
    }
    
    #pragma mark 设置对焦
    - (void)setFoucusWithPoint:(CGPoint)point{
        CGPoint cameraPoint= [self.preViewLayer captureDevicePointOfInterestForPoint:point];
        [self focusWithMode:AVCaptureFocusModeContinuousAutoFocus exposureMode:AVCaptureExposureModeContinuousAutoExposure atPoint:cameraPoint];
    }
    
    -(void)focusWithMode:(AVCaptureFocusMode)focusMode exposureMode:(AVCaptureExposureMode)exposureMode atPoint:(CGPoint)point{
        [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
            
            if ([captureDevice isFocusModeSupported:focusMode]) {
                [captureDevice setFocusMode:focusMode];
            }
           
            if ([captureDevice isFocusPointOfInterestSupported]) {
                [captureDevice setFocusPointOfInterest:point];
            }
            
            if ([captureDevice isExposureModeSupported:exposureMode]) {
                [captureDevice setExposureMode:exposureMode];
            }
           
            if ([captureDevice isExposurePointOfInterestSupported]) {
                [captureDevice setExposurePointOfInterest:point];
            }
        }];
    }
    #pragma mark - 改变设备属性方法
    - (void)changeDeviceProperty:(void (^)(id obj))propertyChange
    {
        AVCaptureDevice *captureDevice = [self.mediaDeviceInput device];
        NSError *error;
        
        if ([captureDevice lockForConfiguration:&error]) {
            propertyChange(captureDevice);
            [captureDevice unlockForConfiguration];
        }else{
            
        }
    }
    
    #pragma mark - AVCaptureFileOutputRecordignDelegate method
    
    // 录制开始
    - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections{
        [self startTimer];
    }
    
    // 录制结束
    -(void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error{
        [self stopTimer];
        if (self.delegate && [self.delegate respondsToSelector:@selector(captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:)]) {
            [self.delegate captureOutput:captureOutput didFinishRecordingToOutputFileAtURL:outputFileURL fromConnections:connections error:error];
        }
        [self.captureSession stopRunning];
    }
    
    #pragma mark - 压缩视频
    - (void)compressVideo:(NSURL *)inputFileURL complete:(void(^)(BOOL success, NSURL* outputUrl))complete{
        NSURL *outPutUrl = [NSURL fileURLWithPath:[JCVideoRecordManager cacheFilePath:NO]];
        [self convertVideoQuailtyWithInputURL:inputFileURL outputURL:outPutUrl completeHandler:^(AVAssetExportSession *exportSession) {
            complete(exportSession.status == AVAssetExportSessionStatusCompleted, outPutUrl);
        }];
    }
    
    - (void)convertVideoQuailtyWithInputURL:(NSURL*)inputURL
                                   outputURL:(NSURL*)outputURL
                             completeHandler:(void (^)(AVAssetExportSession*))handler{
        AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:inputURL options:nil];
        
        AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:AVAssetExportPresetMediumQuality];
        
        exportSession.outputURL = outputURL;
        exportSession.outputFileType = AVFileTypeMPEG4;
        exportSession.shouldOptimizeForNetworkUse= YES;
        [exportSession exportAsynchronouslyWithCompletionHandler:^(void){
             handler(exportSession);
        }];
    }
    

    写在最后

    demo还有很多需要完善的地方,例如视频剪裁,设备方向,闪光灯等,日后有时间继续完善。

    Demo 地址
    JCVideoRecord

    参考

    ios视频录制

    相关文章

      本文标题: iOS 仿微信录制小视频

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