内容字号:默认大号超大号

段落设置:段首缩进取消段首缩进

字体设置:切换到微软雅黑切换到宋体

使用AVFoundation录制视频以及碰到的一些问题

2018-05-14 17:50 出处:清屏网 人气: 评论(0

最近在项目中碰到一个需求:

播放一段事先录制好的音频文件,客户回答音频里面出现的问题,整个过程使用手机录制视频,完成后上传至服务器。

这里面有几个需求点:

  • 录制的时候不能锁屏
  • 能够手动对焦
  • 能够切换摄像头(我只完成了在录制前能够摄像头)
  • 横拍或者竖排,视频要求都是正的

在这里我是使用 AVFoundation 框架的 AVCaptureSession + AVCaptureMovieFileOutput 来录制视频。好了,下面我会介绍一下自己项目里面的部分代码,并说明下我碰到过的一些麻烦,让有需要的同学们能少踩点坑。。。

遇到的坑

此文档篇幅有点长,怕没有耐心的同学直接走了。。。所有我现在这里放上本文的Demo地址: Demo地址 ,以及说明遇到的坑

设置AVCaptureMovieFileOutput,需要设置它的movieFragmentInterval属性为kCMTimeInvalid,不然录制的视频超过10秒就会没有声音

使用CMMotionManager时,需要将其设置为全局变量或者私有属性。在调用它的方法 - (void)startAccelerometerUpdatesToQueue:(NSOperationQueue *)queue withHandler:(CMAccelerometerHandler)handler __TVOS_PROHIBITED; 时,在block里面的self需设置为__weak修饰的self,不然会引起循环引用导致无法dealloc。

[self.motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue currentQueue] withHandler:^(CMAccelerometerData * _Nullable accelerometerData, NSError * _Nullable error) {
        __strong typeof(weakSelf) strongSelf = weakSelf;
        
        if (!error) {
            [strongSelf outputData:accelerometerData.acceleration];
        } else {
            NSLog(@"error = %@",error);
        }
    }];

可以使用NSTimer的 [_recordTimer setFireDate:[NSDate distantPast]];[_recordTimer setFireDate:[NSDate distantFuture]]; ,实现定时器的暂停与继续

几个类

首先导入头文件 #import <AVFoundation/AVFoundation.h>

导入头文件后我们需要创建几个相机必须的属性

//AVCaptureSession对象来执行输入设备和输出设备之间的数据传递
@property (nonatomic, strong) AVCaptureSession *captureSession;
//输入设备源 
@property (nonatomic, strong) AVCaptureDeviceInput *captureDeviceInput; 
//视频输出流
@property (nonatomic, strong) AVCaptureMovieFileOutput *captureMovieFileOutput;    
//相机拍摄预览图层
@property (nonatomic, strong) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;

AVCaptureSession: 控制输入和输出设备之间的数据传递

AVCaptureDeviceInput: 调用所有的输入硬件。例如摄像头和麦克风

AVCaptureMovieFileOutput: 用于输出视频

AVCaptureVideoPreviewLayer:镜头捕捉到得预览图层

一个AVCaptureSession对象可以管理多个输入输出设备,如下图所示

输入输出设备之间的关系

接下来就是初始化所有的对象,可以放在viewDidLoad方法中进行

AVCaptureSession

- (AVCaptureSession *)captureSession
{
    // 录制5秒钟视频 高画质10M,压缩成中画质 0.5M
    // 录制5秒钟视频 中画质0.5M,压缩成中画质 0.5M
    // 录制5秒钟视频 低画质0.1M,压缩成中画质 0.1M
    if (_captureSession == nil) {
        //设置分辨率
        _captureSession = [[AVCaptureSession alloc] init];
        if ([_captureSession canSetSessionPreset:AVCaptureSessionPresetHigh]) {
            _captureSession.sessionPreset =AVCaptureSessionPresetHigh;
        }
    }
    return _captureSession;
}

AVCaptureDevice(相机以及麦克风)

//获得输入设备
    //取得后置摄像头
    AVCaptureDevice *captureDevice = [self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];
    if (!captureDevice) {
        NSLog(@"取得后置摄像头时出现问题");
        return;
    }

    //获取音频输入设备
    AVCaptureDevice *audioCaptureDevice = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];

AVCaptureDeviceInput

使用输入设备AVCaptureDevice初始化AVCaptureDeviceInput对象。

NSError *error = nil;
    //根据输入设备初始化设备输入对象,用于获得输入数据
    _captureDeviceInput = [[AVCaptureDeviceInput alloc]initWithDevice:captureDevice error:&error];
    if (error) {
        NSLog(@"取得设备输入对象时出错,错误原因:%@",error.localizedDescription);
        return;
    }
    
    //获取音频输入设备
    AVCaptureDevice *audioCaptureDevice = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];
    //创建音频输入源
    NSError *tError;
    AVCaptureDeviceInput *audioCaptureDeviceInput = [[AVCaptureDeviceInput alloc]initWithDevice:audioCaptureDevice error:&tError];
    if (tError) {
        NSLog(@"取得设备输入对象时出错,错误原因:%@",tError.localizedDescription);
        return;
    }

AVCaptureMovieFileOutput

初始化输出数据管理对象,如果要拍照就初始化AVCaptureStillImageOutput对象;如果拍摄视频就初始化AVCaptureMovieFileOutput对象。

需要注意的是:

需要设置movieFragmentInterval属性为kCMTimeInvalid,不然录制的视频超过十秒就会没有声音

![](https://upload-images.jianshu.io/upload_images/1929756-833746eb5d9c73b1.jpeg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

//初始化设备输出对象,用于获得输出数据
    _captureMovieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
    //需要设置movieFragmentInterval,不然视频超过十秒就会没有声音
    _captureMovieFileOutput.movieFragmentInterval = kCMTimeInvalid;

添加输入源,输出源到会话中

//将视频输入添加到会话中
    if ([self.captureSession canAddInput:_captureDeviceInput]) {
        [_captureSession addInput:_captureDeviceInput];
    }
//将音频输入源添加到会话中
    if ([self.captureSession canAddInput:audioCaptureDeviceInput]) {
        [_captureSession addInput:audioCaptureDeviceInput];
    }
    AVCaptureConnection *captureConnection = [_captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
    //视频防抖 是在 iOS 6 和 iPhone 4S 发布时引入的功能。到了 iPhone 6,增加了更强劲和流畅的防抖模式,被称为影院级的视频防抖动。相关的 API 也有所改动 (目前为止并没有在文档中反映出来,不过可以查看头文件)。防抖并不是在捕获设备上配置的,而是在 AVCaptureConnection 上设置。由于不是所有的设备格式都支持全部的防抖模式,所以在实际应用中应事先确认具体的防抖模式是否支持
    if ([captureConnection isVideoStabilizationSupported ]) {
        captureConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
    }
    //将设备输出添加到会话中
    if ([_captureSession canAddOutput:_captureMovieFileOutput]) {
        [_captureSession addOutput:_captureMovieFileOutput];
    }

AVCaptureVideoPreviewLayer

- (AVCaptureVideoPreviewLayer *)captureVideoPreviewLayer
{
    if (_captureVideoPreviewLayer == nil) {
        _captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.captureSession];
        //填充模式
        _captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
        _captureVideoPreviewLayer.frame = self.view.bounds;
    }
    return _captureVideoPreviewLayer;
}
    //创建视频预览层,用于实时展示摄像头状态
    //将视频预览层添加到界面中
    [self.view.layer insertSublayer:self.captureVideoPreviewLayer atIndex:0];

开始录制

在viewWillAppear,viewDidDisappear方法里开启和关闭session, 捕捉摄像内容到 AVCaptureVideoPreviewLayer

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:YES];
    [self.session startRunning];
}


- (void)viewDidDisappear:(BOOL)animated
{
   [super viewDidDisappear:YES];
   [self.session stopRunning];
}

将录制的视频输出到指定文件

//开始结束录制
- (void)startOrEndAction:(UIButton *)sender
{
    sender.selected = !sender.selected;
    
    if (sender.selected) {
        //录制状态
        if (!self.captureMovieFileOutput.isRecording) {
            //开始定时器
            [self startTimeLabel];
            [sender setTitle:@"结束录制" forState:UIControlStateNormal];
            
            //将视频的输出方向与设备方向保持一致
            AVCaptureConnection *connection = [self.captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
            connection.videoOrientation = self.videoOrientation;
            
            [self.captureMovieFileOutput startRecordingToOutputFileURL:self.fileUrl recordingDelegate:self];
        }
    } else {
        //结束录制状态
        [sender setTitle:@"开始录制" forState:UIControlStateNormal];
        //停止录制
        [self.captureMovieFileOutput stopRecording];
        //关闭定时器
        [self.recordTimer invalidate];
        self.recordTimer = nil;
    }
}

代理

当我们开始和结束录制都是有监听方法的, AVCaptureFileOutputRecordingDelegate 这个代理里面就有我们想要做的。

#pragma mark - AVCaptureFileOutputRecordingDelegate

-(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 stopTimeLabel];
    
    NSFileManager *fileManager = [[NSFileManager alloc] init] ;
    float filesize = -1.0;
    if ([fileManager fileExistsAtPath:self.fileUrl.path]) {
        NSDictionary *fileDic = [fileManager attributesOfItemAtPath:self.fileUrl.path error:nil];//获取文件的属性
        unsigned long long size = fileDic.fileSize;
        filesize = 1*size;
    }

    NSLog(@"视频大小 %lfM", filesize / 1024 / 1024);
}

至此相机的拍照功能已经完成~

私有方法

/**
 *  取得指定位置的摄像头
 *
 *  @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 sender 按钮
 */
- (void)switchCameraClicked:(UIButton *)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];
    if (!toChangeDevice) {
        NSLog(@"切换摄像头失败");
        return;
    }
    [self addNotificationToCaptureDevice:toChangeDevice];
    //获得要调整的设备输入对象
    AVCaptureDeviceInput *toChangeDeviceInput = [[AVCaptureDeviceInput alloc]initWithDevice:toChangeDevice error:nil];
    
    //改变会话的配置前一定要先开启配置,配置完成后提交配置改变
    [self.captureSession beginConfiguration];
    //移除原有输入对象
    [self.captureSession removeInput:self.captureDeviceInput];
    //添加新的输入对象
    if ([self.captureSession canAddInput:toChangeDeviceInput]) {
        [self.captureSession addInput:toChangeDeviceInput];
        self.captureDeviceInput = toChangeDeviceInput;
    }else{
        [self.captureSession addInput:self.captureDeviceInput];
    }
    
    //提交会话配置
    [self.captureSession commitConfiguration];
}

/**
 *  改变设备属性的统一操作方法
 *
 *  @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 focusMode 聚焦模式
 */
-(void)setFocusMode:(AVCaptureFocusMode )focusMode{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        if ([captureDevice isFocusModeSupported:focusMode]) {
            [captureDevice setFocusMode:focusMode];
        }
    }];
}


/**
 *  设置聚焦点
 *
 *  @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.focusImage.center = point;
    self.focusImage.transform = CGAffineTransformMakeScale(1.5, 1.5);
    self.focusImage.alpha = 1.0;
    [UIView animateWithDuration:.2 animations:^{
        self.focusImage.transform = CGAffineTransformIdentity;
    } completion:^(BOOL finished) {
        [UIView animateWithDuration:1.0 animations:^{
            self.focusImage.alpha = 0;
        }];
    }];
}

结尾

到这主要的基本都讲了了,详细点说的话代码有点多会显得很乱。所以如果对录制视频感兴趣的同学,可以在我的github上下载这个Demo: Demo地址

好久没写东西了,写的不好见谅有错误请指出哈~~~

分享给小伙伴们:
本文标签: AVFoundation

相关文章

发表评论愿您的每句评论,都能给大家的生活添色彩,带来共鸣,带来思索,带来快乐。

CopyRight © 2015-2016 QingPingShan.com , All Rights Reserved.

清屏网 版权所有 豫ICP备15026204号