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