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

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、视频录制完成后在代理方法中处理视频并保存。照片同理

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容