美文网首页视频开发技术
iOS视频流开发(3)— 录制

iOS视频流开发(3)— 录制

作者: darcy87 | 来源:发表于2018-06-05 17:08 被阅读0次

    AVFoundation 开发

    当接到拍照或者视频录制需求的时候,很多同学会选择UIImagePickerController因为他封装的非常好,几个简单的设置就可以开始使用。但是同MPMoviePlayerController一样,由于它的高度封装性,它的适用性就比较差,比如以下几个场景:

    • 自定义拍照界面,相信我无论PD还是UED他们一定会要求你改掉系统原生界面的
    • 为了使拍出来的照片或者视频更鲜亮,可以使用Torch硬件。但是用ImagePicker就无法控制了,要看系统娘的心情。
    • UIImagePickerController还有一个比较磨人的问题,就是当照相机控制器被压入栈的时候是不会关闭的,如果我们有一些后续操作如图片编辑、打标,这个时候取景框是一直工作的,对内存和电量都是极大的浪费。甚至会导致memory warning。
    • 有时候我们需要的不是一个全屏的取景框,需要的可能只是一个区域,比如在相册列表中,加入一个实时取景框区域表示拍照。

    这个时候我们就需要使用AVFoundation进行视频流的获取。

    AVFoundation框架简介

    !

    AVFoundation是基于CoreAudio CoreMedia CoreAnimation的库,提供一组Objective-C接口,所以支持ARC,不需要程序员管理引用计数。他提供了必要的服务以便于程序员可以在iOS、OS X上进行基于时间的视频音频开发工作。可以方便的对QuickTime影片和MPEG-4等媒体格式文件进行播放、拍摄、编辑和编码工作。
    上文的AVPlayer和AVQueuePlayer也属于AVFoundation框架。

    AVFoundation工作模型

    !

    AVCaptureSession:

    媒体捕获会话,负责将输入的音频、视频数据输出到输出设备中。一个AVCaptureSession可以有多个输入设备和输出设备。实际上是在输入源和输出源之间充当中介者。

    AVCaptureDevice:

    捕获视频、音频信息的设备,如摄像头、麦克风。通过该对象可以对物理设备进行功能设置,如对焦、曝光、白平衡、闪光灯、火炬

    AVCaptureDeviceInput:

    输入源,这个对象需要跟AVCaptureDevice对象绑定,并添加到AVCaptureSession工作。

    AVCaptureOutput:

    输出源,用于接受输出的多媒体数据或文件。主要子类有:

    • AVCaptureVideoDataOutput 获取视频数据
    • AVCaptureAudioDataOutput 获取音频数据
    • AVCaptureStillImageOutput 获取静态图片数据
    • AVCaptureFileOutput 获取多媒体文件
    • AVCaptureMovieFileOutput 获取视频文件,是AVCaptureFileOutput的子类。
    • AVCaptureAudioFileOutput 获取音频文件,是AVCaptureFileOutput的子类。

    AVCaptureConnection:

    当把输入源和输出源添加到AVCaptureSession之后AVCaptureSession就会在所有相符的输入源和输出源之间建立连接。通过连接可以调整视频的方向,视频录制时的稳定模式,音量等属性

    AVCaptureVideoPreviewLayer:

    呈现捕获的视频流的层,是CALayer子类。用于实时取景,这个层上呈现的效果就是最后实际输出的效果。PreviewLayer创建的时候必须绑定一个AVCaptureSession对象。

    视频流捕获编程步骤

    视频流捕获初始化

    • 1、获取照相机访问权限
    • 2、创建session
    • 3、设置session
    • 4、添加输入源到session
    • 5、添加输出源到session
    • 6、设置预览layer

    视频流捕获启动

    • 1、启动session
    • 2、停止session

    摄像设备设置

    • 1、设置曝光
    • 2、设置对焦
    • 3、设置白平衡
    • 4、设置火炬

    多媒体文件输出

    • 1、图片输出
    • 2、视频输出
    • 3、音频输出
    • 4、音频视频混合文件movie输出

    下面将详细介绍视频流捕获的编程工作

    视频流的创建和启动

    无论是拍照还是录制视频都需要创建和启动视频流。创建和启动视频

    1、获取照相机访问权限
    • iOS6.x以及之前的iOS版本(包括iOS6),应用都可以获取照相机不需要用户授权,可以直接进行视频流的初始化和获取工作。
    • iOS7.0开始,APP第一次使用照相机的时候需要用户授权,所以要先进行权限判断,如果没有权限需要获取照相机访问权限。
        //iOS7之后的版本需要照相机访问权限
        if (IsIOS7AndHigher && self.isDeviceAuthorized == NO)
        {
            NSString *mediaType = AVMediaTypeVideo;
            @weakify(self);
            self.handlerBlock = ^(BOOL granted) {
                @strongify(self);
                self.isDeviceAuthorized = granted;
                if (granted)
                {
                    [self initAndRunCameraDeviceWithPosition:self.devicePosition];
                }
                else
                {
                    [self startRunningDidFinish:NO];
                }
            };
            //获取权限
            [AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:self.handlerBlock];
        }
        else //iOS7之前的版本直接进行初始化工作
        {
            self.isDeviceAuthorized = YES;
            [self initAndRunCameraDeviceWithPosition:self.devicePosition];
        }
    
    2、创建session
    AVCaptureSession *session = [[AVCaptureSession alloc] init];
    
    3、设置session

    因为session是可以重用的,所以在设置之前必须确保是stop的。

    //设置session
    if (self.session)
    {
        [self.session stopRunning];
    }
    self.session = session;
            
    //设置session呈现的尺寸
    [self setSessionPresent];
    
    //设置会话呈现的取景尺寸
    - (void)setSessionPresent
    {
        if (self.model == CICameraCaptureModelVideo)
        {
            switch (self.qualityType) {
                case CICameraDeviceVideoQualityTypeHigh:
                    [self.session setSessionPreset:AVCaptureSessionPresetHigh];
                    break;
                case CICameraDeviceVideoQualityTypeMedium:
                    [self.session setSessionPreset:AVCaptureSessionPresetMedium];
                case CICameraDeviceVideoQualityTypeLow:
                    [self.session setSessionPreset:AVCaptureSessionPresetLow];
                default:
                    break;
            }
        }
        else
        {
            [self.session setSessionPreset:AVCaptureSessionPresetPhoto];
        }
    }
    
    4、添加输入源到session
    //设备输入添加到会话
    - (BOOL)addDeviceInputToSession:(AVCaptureDevicePosition)position
    {
        NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
        AVCaptureDevice *videoDevice = [devices firstObject];
        
        for (AVCaptureDevice *device in devices)
        {
            if ([device position] == position)
            {
                videoDevice = device;
                break;
            }
        }
        
        NSError *error = nil;
    
        //添加视频输入
        AVCaptureDeviceInput *videoDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];
        
        if (error)
        {
            NSLog(@"获取视频输入错误:%@", error.localizedDescription);
            return NO;
        }
        
        if ([self.session canAddInput:videoDeviceInput])
        {
            [self.session addInput:videoDeviceInput];
            [self setVideoDeviceInput:videoDeviceInput];
        }
        
        //添加音频输入
        if (self.model == CICameraCaptureModelVideo && self.needRecordAudio)
        {
            AVCaptureDevice *audioDevice = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];
            AVCaptureDeviceInput *audioDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error];
            
            if (error)
            {
                NSLog(@"获取音频输入错误:%@", error.localizedDescription);
                return NO;
            }
            
            if ([self.session canAddInput:audioDeviceInput])
            {
                [self.session addInput:audioDeviceInput];
            }
        }
        
        return YES;
    }
    
    
    5、添加输出源到session
    - (void)addDeviceOutputToSession
    {
    
        if (self.model == CICameraCaptureModelPhoto) //获取静态图片
        {
            AVCaptureStillImageOutput *stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
            if ([self.session canAddOutput:stillImageOutput])
            {
                [stillImageOutput setOutputSettings:@{AVVideoCodecKey : AVVideoCodecJPEG}];
                [self.session addOutput:stillImageOutput];
                [self setStillImageOutput:stillImageOutput];
            }
            
            [self setDeviceModeWithFocusPoint:CGPointMake(0.5, 0.5)];
        }
        else if (self.model == CICameraCaptureModelVideo) //获取视频流
        {
            AVCaptureMovieFileOutput *movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
            if ([self.session canAddOutput:movieFileOutput])
            {
                [self.session addOutput:movieFileOutput];
                
                AVCaptureConnection *videoConnection = [movieFileOutput connectionWithMediaType:AVMediaTypeVideo];
                self.movieFileOutput = movieFileOutput;
                if ([videoConnection isVideoStabilizationSupported]) {
                    videoConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
                }
            }
            
        }
    }
    
    6、设置预览layer
    - (void)setupPreview
    {
        UIView *preview = [self.dataSource cameraDeviceVideoPreview];
        
        AVCaptureVideoPreviewLayer *previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.session];
        previewLayer.frame = preview.bounds;
        previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
        
        WeakSelf
        dispatch_async(dispatch_get_main_queue(), ^{
            StrongSelf
            [strongSelf.previewLayer removeFromSuperlayer];
            strongSelf.previewLayer = previewLayer;
            [preview.layer insertSublayer:strongSelf.previewLayer atIndex:0];
        });
        
    }
    
    7、启动session

    启动session之后预览界面就会有画面呈现,因为启动是个比较耗时的操作,所以一般会加入过渡动画

    [self.session startRunning];
    
    8、停止session

    当不需要实时取景的时候可以关闭session,比如拍照视图被压入视图栈下面,当拍照视图从新到视图栈顶端的时候可以再将session start。这样既不会对内存和电量造成不良影响,也不需要视频流重新初始化。

    [self.session stopRunning];
    

    到此为止一个视频流的创建和启动已经完成了。当然我们最重要捕获视频流,所以要进行视频流的捕获。在此之前,为了获得更好的效果,我们还要进行一些设置,如设置对焦、曝光、白平衡和火炬,以便我们能得到更好的拍摄效果。

    摄像设备的设置

    切换摄像头
    - (void)changeDevicePosition
    {
        if (self.isDeviceAuthorized)
        {
            AVCaptureDevice *currentVideoDevice = [self.videoDeviceInput device];
            AVCaptureDevicePosition preferredPosition = AVCaptureDevicePositionUnspecified;
            AVCaptureDevicePosition currentPosition = [currentVideoDevice position];
            
            switch (currentPosition)
            {
                case AVCaptureDevicePositionUnspecified:
                    preferredPosition = AVCaptureDevicePositionBack;
                    break;
                case AVCaptureDevicePositionBack:
                    preferredPosition = AVCaptureDevicePositionFront;
                    break;
                case AVCaptureDevicePositionFront:
                    preferredPosition = AVCaptureDevicePositionBack;
                    break;
            }
            
            self.devicePosition = preferredPosition;
            [self startDevice];
        }
    }
    
    设置对焦、曝光、白平衡、火炬(torch)

    对这些设备状态的设定可以在session启动之后。在设置之前一定要锁定,lockForConfiguration:。设置完后,调用unlockForConfiguration解除锁定。火炬是Apple一个独特的功能,它会为摄像头提供一个LED光源,光源的亮度是可以调节的,相比闪光灯,torch可以提供持续的光源,对在黑夜录制清晰的视频帮助巨大。

    + (void)setDevice:(AVCaptureDevice *)device deviceMode:(CICameraDeviceMode *)mode
    {
        NSError *error = nil;
        if ([device lockForConfiguration:&error])
        {
            //对焦
            if ([device isFocusPointOfInterestSupported] && [device isFocusModeSupported:mode.focusMode])
            {
                [device setFocusMode:mode.focusMode];
                [device setFocusPointOfInterest:mode.focusPoint];
            }
            
            //曝光
            if ([device isExposurePointOfInterestSupported] && [device isExposureModeSupported:mode.exposureMode])
            {
                [device setExposureMode:mode.exposureMode];
                [device setExposurePointOfInterest:mode.exposurePoint];
            }
            
            //白平衡
            if ([device isWhiteBalanceModeSupported:mode.whiteBalanceMode])
            {
                [device setWhiteBalanceMode:mode.whiteBalanceMode];
            }
            
            //火炬
            if ([device hasTorch] && [device isTorchModeSupported:mode.torchMode])
            {
                [device setTorchMode:mode.torchMode];
            }
            
            [device setSubjectAreaChangeMonitoringEnabled:mode.monitorSubjectAreaChange];
            [device unlockForConfiguration];
        }
        else
        {
            NSLog(@"%@", error);
        }
    }
    

    视频流输出文件

    获取静态图片

    输出时注意connection的方向设置

        if (self.isDeviceAuthorized && self.session.isRunning)
        {
            @weakify(self)
            dispatch_async([self sessionQueue], ^{
                @strongify(self)
                
                AVCaptureConnection *videoConnection = [[self stillImageOutput] connectionWithMediaType:AVMediaTypeVideo];
                
                if ([videoConnection isVideoOrientationSupported]) {
                    [videoConnection setVideoOrientation:[self videoOrientation:[UIDevice currentDevice].orientation]];
                }
                
                [[self stillImageOutput] captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
                    
                    UIImage *image = nil;
                    if (imageDataSampleBuffer)
                    {
                        NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
                        
                        if (imageData)
                        {
                            image = [[UIImage alloc] initWithData:imageData];
                        }
                    }
                    
                    if ([self.delegate respondsToSelector:@selector(snapStillImageDidFinish:)])
                    {
                        [self.delegate snapStillImageDidFinish:image];
                    }
                }];
            });
        }
    

    获取视频

    开始获取视频流

    视频的方向在这个时候设置,在拍摄期间不能更改。
    视频的保存路径也在这个时机决定。

    - (void)startRecordingWithFilePath:(NSString *)filePath
    {
        if (self.isDeviceAuthorized && self.session.isRunning && filePath.length) {
            WeakSelf
            dispatch_async([self sessionQueue], ^{
                StrongSelf
                
                AVCaptureConnection *videoConnection = [strongSelf.movieFileOutput connectionWithMediaType:AVMediaTypeAudio];
                
                videoConnection.videoOrientation = [strongSelf videoOrientation:[UIDevice currentDevice].orientation];
                
                NSURL *fileURL = [NSURL fileURLWithPath:filePath];
                
                [strongSelf.movieFileOutput startRecordingToOutputFileURL:fileURL recordingDelegate:self];
                
                strongSelf.isRecording = YES;
                
            });
        }
    }
    
    结束获取视频流

    通过AVCaptureMovieFileOutput可以获得一个完整的音视频混合movie文件

    - (void)stopRecording
    {
        WeakSelf
        dispatch_async([self sessionQueue], ^{
            StrongSelf
            if ([strongSelf.movieFileOutput isRecording])
            {
                [strongSelf.movieFileOutput stopRecording];
                strongSelf.isRecording = NO;
            }
        });
    }
    
    视频录制delegate

    视频录制相关的代理,可以查看AVCaptureFileOutputRecordingDelegate文件。比较常用的是,开始录制回调和录制完成回调

    - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections
    {
        if ([self.delegate respondsToSelector:@selector(videoRecordDidStarted:)]) {
            [self.delegate videoRecordDidStarted:fileURL];
        }
    }
    
    - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error
    {
        if ([self.delegate respondsToSelector:@selector(videoRecordDidFinished:)] && !error && outputFileURL)
        {
            [self.delegate videoRecordDidFinished:outputFileURL];
        }
    }
    

    获取照片和获取视频的异同

    无论是拍照还是录制视频都需要创建和启动视频流。上面已经说的很清楚了。不同点只有以下几点:

    • 无论获取照片还是获取视频,都需要预览,所以都需要视频输入设备和相应的视频输入源。通过 [ [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo] firstObject];获取视频输入设备-摄像头
    • 视频需要音频,所以比照片要多一个输入设备。通过[[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject]获得音频输入设备-麦克风。
    • 照片的输出设备同视频不同,照片的输出对象AVCaptureStillImageOutput,视频的输出对象是AVCaptureMovieFileOutput。
    • 照片输出后可以直接将照片对象的指针传给调用方使用,视频只能将视频文件的地址传给调用方。

    CameraDeviceFramework

    这个framework已经将视频流捕获、摄像头设置、视频获取封装,并配有Demo。
    gitlab地址:http://gitlab.alibaba-inc.com/xunfeng.zy/CameraDeviceFramework

    最后介绍一下UIImagePickerController的开发

    UIImagePickerController支持拍照和视频录制,还可以用来选取照片。
    用UIImagePickerController拍照或者录制视频有以下几个步骤:

    1、创建UIImagePickerController对象。

    2、设置源类型sourceType,类型有:

    • UIImagePickerControllerSourceTypePhotoLibrary:照片库
      ,默认值。
    • UIImagePickerControllerSourceTypeCamera:摄像头
    • UIImagePickerControllerSourceTypeSavedPhotosAlbum:相册

    3、设置媒体类型mediaType。类型有:

    • kUTTypeImage:照片
    • kUTTypeVideo:无声视频
    • kUTTypeMovie:有声视频

    4、指定工作模式,类型有:

    • UIImagePickerControllerCameraCaptureModePhoto:拍照模式
    • UIImagePickerControllerCameraCaptureModeVideo:视频模式

    5、设置视频质量videoQuality,如果是拍照则忽略

    • UIImagePickerControllerQualityTypeHigh
    • UIImagePickerControllerQualityTypeMedium
    • UIImagePickerControllerQualityTypeLow
      也可以按照尺寸选择,其实在部分机型下High同1280x720是等价的。
    • UIImagePickerControllerQualityTypeIFrame1280x720
    • UIImagePickerControllerQualityTypeIFrame960x540
    • UIImagePickerControllerQualityType640x480

    6、设置摄像头、闪光灯

    7、展示UIImagePickerController

    8、视频录制完成后在代理方法中处理视频并保存。照片同理

    相关文章

      网友评论

        本文标题:iOS视频流开发(3)— 录制

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