美文网首页iOS开发总结
iOS录制视频遇到的那些坎

iOS录制视频遇到的那些坎

作者: 刘书亚的天堂之路 | 来源:发表于2016-04-21 14:01 被阅读2666次

    先说一下我的视频录制的思路:

    1.首先创建一个VC专门用来渲染和显示视频的控制器

    2.在viewWillAppear中做好如下初始化准备录音步骤:

    #import "RecordVideoViewController.h"
    //音视频
    #import <AVFoundation/AVFoundation.h>
    #import <AssetsLibrary/AssetsLibrary.h>
    //文件
    #import "FCFileManager.h"
    //网络
    #import "DRDAPIPostCall.h"
    //提示
    #import "UIView+Toast.h"
    
    #define RGBA_COLOR(R, G, B, A) [UIColor colorWithRed:((R) / 255.0f) green:((G) / 255.0f) blue:((B) / 255.0f) alpha:A]
    
    typedef void(^PropertyChangeBlock)(AVCaptureDevice *captureDevice);
    
    @interface RecordVideoViewController ()<AVCaptureFileOutputRecordingDelegate>     //视频文件输出代理
    @property (strong,nonatomic) AVCaptureSession *captureSession;                    //负责输入和输出设备之间的数据传递
    @property (strong,nonatomic) AVCaptureDeviceInput *captureDeviceInput;            //负责从AVCaptureDevice获得输入数据
    @property (strong,nonatomic) AVCaptureMovieFileOutput *captureMovieFileOutput;    //视频输出流
    @property (strong,nonatomic) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;//相机拍摄预览图层
    @property (assign,nonatomic) BOOL enableRotation;                                //是否允许旋转(注意在视频录制过程中禁止屏幕旋转)
    @property (assign,nonatomic) CGRect *lastBounds;                                 //旋转的前大小
    @property (assign,nonatomic) UIBackgroundTaskIdentifier backgroundTaskIdentifier;//后台任务标识
    @property (weak, nonatomic) IBOutlet UIView *viewContainer;                      //本控制器
    @property (weak, nonatomic) IBOutlet UIImageView *focusCursor;                   //聚焦光标
    @property (weak, nonatomic) IBOutlet UIProgressView *videoProgress;              //加载进度条
    @property (weak, nonatomic) IBOutlet UIButton *recordBtn;                        //录屏按钮
    @property (weak, nonatomic) IBOutlet UILabel *recoderTipsLabel;                  //录屏时间显示标签
    @property (strong,nonatomic) NSTimer *mTimerForRecord;                           //录屏定时器
    
    @end
    
    @implementation RecordVideoViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view from its nib.
        
        //文字颜色
        [_recoderTipsLabel setTextColor:RGBA_COLOR(32, 192, 227, 1)];
    }
    - (IBAction)backButtonAction:(id)sender {
        //停止录视频
        [self.captureMovieFileOutput stopRecording];//停止录制
        //关闭监听
        [self removeNotification];
        //关闭定时器
        [_mTimerForRecord invalidate];
        _mTimerForRecord = nil;
        [self dismissViewControllerAnimated:YES completion:nil];
    }
    
    -(void)viewWillAppear:(BOOL)animated{
        [super viewWillAppear:animated];
        //初始化会话
        _captureSession=[[AVCaptureSession alloc]init];
        if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset1920x1080]) {//设置分辨率
            _captureSession.sessionPreset=AVCaptureSessionPreset1920x1080;
        }
        //获得输入设备
        AVCaptureDevice *captureDevice=[self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];//取得后置摄像头
        if (!captureDevice) {
            NSLog(@"取得后置摄像头时出现问题.");
            return;
        }
        //添加一个音频输入设备
        AVCaptureDevice *audioCaptureDevice=[[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];
        
        
        NSError *error=nil;
        //根据输入设备初始化设备输入对象,用于获得输入数据
        _captureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:captureDevice error:&error];
        if (error) {
            NSLog(@"取得设备输入对象时出错,错误原因:%@",error.localizedDescription);
            return;
        }
        AVCaptureDeviceInput *audioCaptureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:audioCaptureDevice error:&error];
        if (error) {
            NSLog(@"取得设备输入对象时出错,错误原因:%@",error.localizedDescription);
            return;
        }
        //初始化设备输出对象,用于获得输出数据
        _captureMovieFileOutput=[[AVCaptureMovieFileOutput alloc]init];
        
        //将设备输入添加到会话中
        if ([_captureSession canAddInput:_captureDeviceInput]) {
            [_captureSession addInput:_captureDeviceInput];
            [_captureSession addInput:audioCaptureDeviceInput];
            AVCaptureConnection *captureConnection=[_captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
            if ([captureConnection isVideoStabilizationSupported ]) {
                captureConnection.preferredVideoStabilizationMode=AVCaptureVideoStabilizationModeAuto;
            }
        }
        
        //将设备输出添加到会话中
        if ([_captureSession canAddOutput:_captureMovieFileOutput]) {
            [_captureSession addOutput:_captureMovieFileOutput];
        }
        
        //创建视频预览层,用于实时展示摄像头状态
        _captureVideoPreviewLayer=[[AVCaptureVideoPreviewLayer alloc]initWithSession:self.captureSession];
        NSLog(@"前:%i",[_captureVideoPreviewLayer connection].videoOrientation);
        //新增加
    
        
        [_captureVideoPreviewLayer connection].videoOrientation = (AVCaptureVideoOrientation)[[UIApplication sharedApplication] statusBarOrientation];
    
        NSLog(@"后:%i",[_captureVideoPreviewLayer connection].videoOrientation);
        
        CALayer *layer=self.viewContainer.layer;
        layer.masksToBounds=YES;
        
        _captureVideoPreviewLayer.frame=layer.bounds;
        NSLog(@"%@",NSStringFromCGRect(layer.bounds));
        
        _captureVideoPreviewLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;//填充模式
    
    
        //将视频预览层添加到界面中
        //[layer addSublayer:_captureVideoPreviewLayer];
        [layer insertSublayer:_captureVideoPreviewLayer below:self.focusCursor.layer];
        
        _enableRotation=NO;
        [self addNotificationToCaptureDevice:captureDevice];
        [self addGenstureRecognizer];
    }
    
    #pragma mark - 计时器
    - (void)startTimeLabel
    {
        _recoderTipsLabel.text = @"00:00";
        _mTimerForRecord = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(toSetRecFlags) userInfo:nil repeats:YES];                   //定时改变录像标
        [[NSRunLoop currentRunLoop] addTimer:_mTimerForRecord forMode:NSRunLoopCommonModes];
        [_mTimerForRecord setFireDate:[NSDate distantPast]];
    }
    
    - (void)stopTimeLabel
    {
        [_mTimerForRecord setFireDate:[NSDate distantFuture]];
        [_mTimerForRecord invalidate];
        _mTimerForRecord = nil;
        _recoderTipsLabel.text = @"00:00";
    }
    
    - (void)toSetRecFlags
    {
        NSInteger value = [[[_recoderTipsLabel.text componentsSeparatedByString:@":"] objectAtIndex:0] integerValue]*60 + [[[_recoderTipsLabel.text componentsSeparatedByString:@":"] objectAtIndex:1] integerValue];
        
        value++;
        NSString *minute;
        NSString *second;
        
        if (value/60 > 9)
        {
            minute = [NSString stringWithFormat:@"%d",value/60];
        }
        else
        {
            minute = [NSString stringWithFormat:@"0%d",value/60];
        }
        
        if (value%60 > 9)
        {
            second = [NSString stringWithFormat:@"%d",value%60];
        }
        else
        {
            second = [NSString stringWithFormat:@"0%d",value%60];
        }
        
        [_recoderTipsLabel setTextColor:RGBA_COLOR(32, 192, 227, 1)];//RGBA(32, 192, 227, 1)//RGBA(201, 201, 201, 1)
        [_recoderTipsLabel setText:[NSString stringWithFormat:@"%@:%@",minute,second]];
    }
    
    #pragma mark 上传视频
    - (IBAction)uploadVideoAction:(id)sender
    {
        _videoProgress.hidden = NO;
        NSString *outputFielPath = [FCFileManager pathForTemporaryDirectoryWithPath:@"user_video.mp4"];
            
        DRDAPIPostCall *apiPost = [[DRDAPIPostCall alloc] init];
        [apiPost setApiCompletionHandler:^(id _Nonnull responseObject, NSError * _Nullable error) {
            if (error) {
                [self.view makeToast:[error description] duration:2.0 position:CSToastPositionCenter];
            }
        }];
        [apiPost setApiRequestConstructingBodyBlock:^(id<DRDMultipartFormData> _Nonnull formData) {
            NSData *videoData = [NSData dataWithContentsOfFile:outputFielPath];
            [formData appendPartWithFileData:videoData
                                        name:@"user_video"
                                    fileName:@"user_video"
                                    mimeType:@"video/mp4"];
        }];
        [apiPost setApiProgressBlock:^(NSProgress * _Nullable progress) {
            __weak typeof(self) weakSelf = self;
            dispatch_async(dispatch_get_main_queue(), ^{
                weakSelf.videoProgress.progress =  progress.fractionCompleted;
                weakSelf.recoderTipsLabel.text = [NSString stringWithFormat:@"上传进度: %.2f%%", progress.fractionCompleted * 100];
            });
    
        }];
        [apiPost start];
    }
    
    #pragma mark 录屏
    -(BOOL)shouldAutorotate{
        return NO;
    }
    
    -(void)viewDidAppear:(BOOL)animated{
        [super viewDidAppear:animated];
        [self.captureSession startRunning];
    }
    
    -(void)viewDidDisappear:(BOOL)animated{
        [super viewDidDisappear:animated];
        [self.captureSession stopRunning];
    }
    
    
    
    //屏幕旋转时调整视频预览图层的方向
    //-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
    //    AVCaptureConnection *captureConnection=[self.captureVideoPreviewLayer connection];
    //    captureConnection.videoOrientation=(AVCaptureVideoOrientation)toInterfaceOrientation;
    //}
    //旋转后重新设置大小
    //-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
    //    _captureVideoPreviewLayer.frame=self.viewContainer.bounds;
    //}
    
    -(void)dealloc{
        [self removeNotification];
    }
    //切换摄像头
    - (IBAction)toggleCapture:(id)sender {
        AVCaptureDevice *currentDevice=[self.captureDeviceInput device];
        AVCaptureDevicePosition currentPosition=[currentDevice position];
        [self removeNotificationFromCaptureDevice:currentDevice];
        
        AVCaptureDevice *toChangeDevice;
        AVCaptureDevicePosition toChangePosition=AVCaptureDevicePositionFront;
        if (currentPosition==AVCaptureDevicePositionUnspecified||currentPosition==AVCaptureDevicePositionFront) {
            toChangePosition=AVCaptureDevicePositionBack;
        }
        toChangeDevice=[self getCameraDeviceWithPosition:toChangePosition];
        [self addNotificationToCaptureDevice:toChangeDevice];
        //获得要调整的设备输入对象
        AVCaptureDeviceInput *toChangeDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:toChangeDevice error:nil];
        
        //改变会话的配置前一定要先开启配置,配置完成后提交配置改变
        [self.captureSession beginConfiguration];
        //移除原有输入对象
        [self.captureSession removeInput:self.captureDeviceInput];
        [self.captureSession setSessionPreset:AVCaptureSessionPresetHigh];
        //添加新的输入对象
        BOOL isAdded = [self.captureSession canAddInput:toChangeDeviceInput];
        if (isAdded) {
            [self.captureSession addInput:toChangeDeviceInput];
            self.captureDeviceInput=toChangeDeviceInput;
        }else{
            [self.captureSession addInput:self.captureDeviceInput];
        }
    
        
        //提交会话配置
        [self.captureSession commitConfiguration];
    }
    - (IBAction)startVideo:(id)sender {
        //根据设备输出获得连接
        AVCaptureConnection *captureConnection=[self.captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
    
        //根据连接取得设备输出的数据
        if (![self.captureMovieFileOutput isRecording]) {
            self.enableRotation=NO;
            //开始计时
            [self startTimeLabel];
            //切换UI图标
            [_recordBtn setImage:[UIImage imageNamed:@"RecoderBtn_Selected.png"] forState:UIControlStateNormal];
            //如果支持多任务则则开始多任务
            if ([[UIDevice currentDevice] isMultitaskingSupported]) {
                self.backgroundTaskIdentifier=[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
            }
            //预览图层和视频方向保持一致
            captureConnection.videoOrientation=[self.captureVideoPreviewLayer connection].videoOrientation;
            NSLog(@"%i",[self.captureVideoPreviewLayer connection].videoOrientation);
    
            NSString *outputFielPath=[NSTemporaryDirectory() stringByAppendingString:@"user_video.mp4"];
            if ([FCFileManager existsItemAtPath:outputFielPath]) {
                [FCFileManager removeItemAtPath:outputFielPath];
            }
            NSLog(@"save path is :%@",outputFielPath);
            NSURL *fileUrl=[NSURL fileURLWithPath:outputFielPath];
            [self.captureMovieFileOutput startRecordingToOutputFileURL:fileUrl recordingDelegate:self];
        }
        else{
            [self.captureMovieFileOutput stopRecording];//停止录制
            [_recordBtn setImage:[UIImage imageNamed:@"RecoderBtn_Defalut.png"] forState:UIControlStateNormal];//切换UI图标
            [self stopTimeLabel];//结束计时
        }
    }
    
    
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    #pragma mark - 视频输出代理
    -(void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections{
        NSLog(@"开始录制...");
    }
    -(void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error{
        NSLog(@"视频录制完成.");
        //视频录入完成之后在后台将视频存储到相簿
        self.enableRotation=YES;
        UIBackgroundTaskIdentifier lastBackgroundTaskIdentifier=self.backgroundTaskIdentifier;
        self.backgroundTaskIdentifier=UIBackgroundTaskInvalid;
        ALAssetsLibrary *assetsLibrary=[[ALAssetsLibrary alloc]init];
        [assetsLibrary writeVideoAtPathToSavedPhotosAlbum:outputFileURL completionBlock:^(NSURL *assetURL, NSError *error) {
            if (error) {
                NSLog(@"保存视频到相簿过程中发生错误,错误信息:%@",error.localizedDescription);
            }
            if (lastBackgroundTaskIdentifier!=UIBackgroundTaskInvalid) {
                [[UIApplication sharedApplication] endBackgroundTask:lastBackgroundTaskIdentifier];
            }
            NSLog(@"成功保存视频到相簿.");
        }];
        
    }
    
    - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
    {
        [connection setVideoOrientation:AVCaptureVideoOrientationLandscapeRight];
    }
    
    #pragma mark - 通知
    /**
     *  给输入设备添加通知
     */
    -(void)addNotificationToCaptureDevice:(AVCaptureDevice *)captureDevice{
        //注意添加区域改变捕获通知必须首先设置设备允许捕获
        [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
            captureDevice.subjectAreaChangeMonitoringEnabled=YES;
        }];
        NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
        //捕获区域发生改变
        [notificationCenter addObserver:self selector:@selector(areaChange:) name:AVCaptureDeviceSubjectAreaDidChangeNotification object:captureDevice];
    }
    -(void)removeNotificationFromCaptureDevice:(AVCaptureDevice *)captureDevice{
        NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
        [notificationCenter removeObserver:self name:AVCaptureDeviceSubjectAreaDidChangeNotification object:captureDevice];
    }
    /**
     *  移除所有通知
     */
    -(void)removeNotification{
        NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
        [notificationCenter removeObserver:self];
    }
    
    -(void)addNotificationToCaptureSession:(AVCaptureSession *)captureSession{
        NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
        //会话出错
        [notificationCenter addObserver:self selector:@selector(sessionRuntimeError:) name:AVCaptureSessionRuntimeErrorNotification object:captureSession];
    }
    
    /**
     *  设备连接成功
     *
     *  @param notification 通知对象
     */
    -(void)deviceConnected:(NSNotification *)notification{
        NSLog(@"设备已连接...");
    }
    /**
     *  设备连接断开
     *
     *  @param notification 通知对象
     */
    -(void)deviceDisconnected:(NSNotification *)notification{
        NSLog(@"设备已断开.");
    }
    /**
     *  捕获区域改变
     *
     *  @param notification 通知对象
     */
    -(void)areaChange:(NSNotification *)notification{
        NSLog(@"捕获区域改变...");
    }
    
    /**
     *  会话出错
     *
     *  @param notification 通知对象
     */
    -(void)sessionRuntimeError:(NSNotification *)notification{
        NSLog(@"会话发生错误.");
    }
    
    #pragma mark - 私有方法
    
    /**
     *  取得指定位置的摄像头
     *
     *  @param position 摄像头位置
     *
     *  @return 摄像头设备
     */
    -(AVCaptureDevice *)getCameraDeviceWithPosition:(AVCaptureDevicePosition )position{
        NSArray *cameras= [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
        for (AVCaptureDevice *camera in cameras) {
            if ([camera position]==position) {
                return camera;
            }
        }
        return nil;
    }
    
    /**
     *  改变设备属性的统一操作方法
     *
     *  @param propertyChange 属性改变操作
     */
    -(void)changeDeviceProperty:(PropertyChangeBlock)propertyChange{
        AVCaptureDevice *captureDevice= [self.captureDeviceInput device];
        NSError *error;
        //注意改变设备属性前一定要首先调用lockForConfiguration:调用完之后使用unlockForConfiguration方法解锁
        if ([captureDevice lockForConfiguration:&error]) {
            propertyChange(captureDevice);
            [captureDevice unlockForConfiguration];
        }else{
            NSLog(@"设置设备属性过程发生错误,错误信息:%@",error.localizedDescription);
        }
    }
    
    /**
     *  设置闪光灯模式
     *
     *  @param flashMode 闪光灯模式
     */
    -(void)setFlashMode:(AVCaptureFlashMode )flashMode{
        [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
            if ([captureDevice isFlashModeSupported:flashMode]) {
                [captureDevice setFlashMode:flashMode];
            }
        }];
    }
    /**
     *  设置聚焦模式
     *
     *  @param focusMode 聚焦模式
     */
    -(void)setFocusMode:(AVCaptureFocusMode )focusMode{
        [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
            if ([captureDevice isFocusModeSupported:focusMode]) {
                [captureDevice setFocusMode:focusMode];
            }
        }];
    }
    /**
     *  设置曝光模式
     *
     *  @param exposureMode 曝光模式
     */
    -(void)setExposureMode:(AVCaptureExposureMode)exposureMode{
        [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
            if ([captureDevice isExposureModeSupported:exposureMode]) {
                [captureDevice setExposureMode:exposureMode];
            }
        }];
    }
    /**
     *  设置聚焦点
     *
     *  @param point 聚焦点
     */
    -(void)focusWithMode:(AVCaptureFocusMode)focusMode exposureMode:(AVCaptureExposureMode)exposureMode atPoint:(CGPoint)point{
        [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
            if ([captureDevice isFocusModeSupported:focusMode]) {
                [captureDevice setFocusMode:AVCaptureFocusModeAutoFocus];
            }
            if ([captureDevice isFocusPointOfInterestSupported]) {
                [captureDevice setFocusPointOfInterest:point];
            }
            if ([captureDevice isExposureModeSupported:exposureMode]) {
                [captureDevice setExposureMode:AVCaptureExposureModeAutoExpose];
            }
            if ([captureDevice isExposurePointOfInterestSupported]) {
                [captureDevice setExposurePointOfInterest:point];
            }
        }];
    }
    
    /**
     *  添加点按手势,点按时聚焦
     */
    -(void)addGenstureRecognizer{
        UITapGestureRecognizer *tapGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapScreen:)];
        [self.viewContainer addGestureRecognizer:tapGesture];
    }
    -(void)tapScreen:(UITapGestureRecognizer *)tapGesture{
        CGPoint point= [tapGesture locationInView:self.viewContainer];
        //将UI坐标转化为摄像头坐标
        CGPoint cameraPoint= [self.captureVideoPreviewLayer captureDevicePointOfInterestForPoint:point];
        [self setFocusCursorWithPoint:point];
        [self focusWithMode:AVCaptureFocusModeAutoFocus exposureMode:AVCaptureExposureModeAutoExpose atPoint:cameraPoint];
    }
    
    /**
     *  设置聚焦光标位置
     *
     *  @param point 光标位置
     */
    -(void)setFocusCursorWithPoint:(CGPoint)point{
        self.focusCursor.center=point;
        self.focusCursor.transform=CGAffineTransformMakeScale(1.5, 1.5);
        self.focusCursor.alpha=1.0;
        [UIView animateWithDuration:1.0 animations:^{
            self.focusCursor.transform=CGAffineTransformIdentity;
        } completion:^(BOOL finished) {
            self.focusCursor.alpha=0;
            
        }];
    }
    
    - (UIInterfaceOrientationMask)supportedInterfaceOrientations
    {
        return UIInterfaceOrientationMaskLandscape;
    }
    
    @end
    

    遇到的主要问题:
    我是ipad页面,当我翻转ipad的时候,该页面没有跟着转动,导致当我拿反pad的时候页面是个反的。
    解决的办法就是上面的渲染页面我把它做了以下的操作:

    [_captureVideoPreviewLayer connection].videoOrientation = (AVCaptureVideoOrientation)[[UIApplication sharedApplication] statusBarOrientation];
    

    相关文章

      网友评论

        本文标题:iOS录制视频遇到的那些坎

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