AVFoundation 開發(fā)
當(dāng)接到拍照或者視頻錄制需求的時(shí)候,很多同學(xué)會(huì)選擇UIImagePickerController因?yàn)樗庋b的非常好,幾個(gè)簡單的設(shè)置就可以開始使用。但是同MPMoviePlayerController一樣,由于它的高度封裝性,它的適用性就比較差,比如以下幾個(gè)場景:
- 自定義拍照界面,相信我無論P(yáng)D還是UED他們一定會(huì)要求你改掉系統(tǒng)原生界面的
- 為了使拍出來的照片或者視頻更鮮亮,可以使用Torch硬件。但是用ImagePicker就無法控制了,要看系統(tǒng)娘的心情。
- UIImagePickerController還有一個(gè)比較磨人的問題,就是當(dāng)照相機(jī)控制器被壓入棧的時(shí)候是不會(huì)關(guān)閉的,如果我們有一些后續(xù)操作如圖片編輯、打標(biāo),這個(gè)時(shí)候取景框是一直工作的,對內(nèi)存和電量都是極大的浪費(fèi)。甚至?xí)?dǎo)致memory warning。
- 有時(shí)候我們需要的不是一個(gè)全屏的取景框,需要的可能只是一個(gè)區(qū)域,比如在相冊列表中,加入一個(gè)實(shí)時(shí)取景框區(qū)域表示拍照。
這個(gè)時(shí)候我們就需要使用AVFoundation進(jìn)行視頻流的獲取。
AVFoundation框架簡介

!
AVFoundation是基于CoreAudio CoreMedia CoreAnimation的庫,提供一組Objective-C接口,所以支持ARC,不需要程序員管理引用計(jì)數(shù)。他提供了必要的服務(wù)以便于程序員可以在iOS、OS X上進(jìn)行基于時(shí)間的視頻音頻開發(fā)工作??梢苑奖愕膶uickTime影片和MPEG-4等媒體格式文件進(jìn)行播放、拍攝、編輯和編碼工作。
上文的AVPlayer和AVQueuePlayer也屬于AVFoundation框架。
AVFoundation工作模型

!
AVCaptureSession:
媒體捕獲會(huì)話,負(fù)責(zé)將輸入的音頻、視頻數(shù)據(jù)輸出到輸出設(shè)備中。一個(gè)AVCaptureSession可以有多個(gè)輸入設(shè)備和輸出設(shè)備。實(shí)際上是在輸入源和輸出源之間充當(dāng)中介者。
AVCaptureDevice:
捕獲視頻、音頻信息的設(shè)備,如攝像頭、麥克風(fēng)。通過該對象可以對物理設(shè)備進(jìn)行功能設(shè)置,如對焦、曝光、白平衡、閃光燈、火炬
AVCaptureDeviceInput:
輸入源,這個(gè)對象需要跟AVCaptureDevice對象綁定,并添加到AVCaptureSession工作。
AVCaptureOutput:
輸出源,用于接受輸出的多媒體數(shù)據(jù)或文件。主要子類有:
- AVCaptureVideoDataOutput 獲取視頻數(shù)據(jù)
- AVCaptureAudioDataOutput 獲取音頻數(shù)據(jù)
- AVCaptureStillImageOutput 獲取靜態(tài)圖片數(shù)據(jù)
- AVCaptureFileOutput 獲取多媒體文件
- AVCaptureMovieFileOutput 獲取視頻文件,是AVCaptureFileOutput的子類。
- AVCaptureAudioFileOutput 獲取音頻文件,是AVCaptureFileOutput的子類。
AVCaptureConnection:
當(dāng)把輸入源和輸出源添加到AVCaptureSession之后AVCaptureSession就會(huì)在所有相符的輸入源和輸出源之間建立連接。通過連接可以調(diào)整視頻的方向,視頻錄制時(shí)的穩(wěn)定模式,音量等屬性
AVCaptureVideoPreviewLayer:
呈現(xiàn)捕獲的視頻流的層,是CALayer子類。用于實(shí)時(shí)取景,這個(gè)層上呈現(xiàn)的效果就是最后實(shí)際輸出的效果。PreviewLayer創(chuàng)建的時(shí)候必須綁定一個(gè)AVCaptureSession對象。
視頻流捕獲編程步驟
視頻流捕獲初始化
- 1、獲取照相機(jī)訪問權(quán)限
- 2、創(chuàng)建session
- 3、設(shè)置session
- 4、添加輸入源到session
- 5、添加輸出源到session
- 6、設(shè)置預(yù)覽layer
視頻流捕獲啟動(dòng)
- 1、啟動(dòng)session
- 2、停止session
攝像設(shè)備設(shè)置
- 1、設(shè)置曝光
- 2、設(shè)置對焦
- 3、設(shè)置白平衡
- 4、設(shè)置火炬
多媒體文件輸出
- 1、圖片輸出
- 2、視頻輸出
- 3、音頻輸出
- 4、音頻視頻混合文件movie輸出
下面將詳細(xì)介紹視頻流捕獲的編程工作
視頻流的創(chuàng)建和啟動(dòng)
無論是拍照還是錄制視頻都需要?jiǎng)?chuàng)建和啟動(dòng)視頻流。創(chuàng)建和啟動(dòng)視頻
1、獲取照相機(jī)訪問權(quán)限
- iOS6.x以及之前的iOS版本(包括iOS6),應(yīng)用都可以獲取照相機(jī)不需要用戶授權(quán),可以直接進(jìn)行視頻流的初始化和獲取工作。
- iOS7.0開始,APP第一次使用照相機(jī)的時(shí)候需要用戶授權(quán),所以要先進(jìn)行權(quán)限判斷,如果沒有權(quán)限需要獲取照相機(jī)訪問權(quán)限。
//iOS7之后的版本需要照相機(jī)訪問權(quán)限
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];
}
};
//獲取權(quán)限
[AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:self.handlerBlock];
}
else //iOS7之前的版本直接進(jìn)行初始化工作
{
self.isDeviceAuthorized = YES;
[self initAndRunCameraDeviceWithPosition:self.devicePosition];
}
2、創(chuàng)建session
AVCaptureSession *session = [[AVCaptureSession alloc] init];
3、設(shè)置session
因?yàn)閟ession是可以重用的,所以在設(shè)置之前必須確保是stop的。
//設(shè)置session
if (self.session)
{
[self.session stopRunning];
}
self.session = session;
//設(shè)置session呈現(xiàn)的尺寸
[self setSessionPresent];
//設(shè)置會(huì)話呈現(xiàn)的取景尺寸
- (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
//設(shè)備輸入添加到會(huì)話
- (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(@"獲取視頻輸入錯(cuò)誤:%@", 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(@"獲取音頻輸入錯(cuò)誤:%@", error.localizedDescription);
return NO;
}
if ([self.session canAddInput:audioDeviceInput])
{
[self.session addInput:audioDeviceInput];
}
}
return YES;
}
5、添加輸出源到session
- (void)addDeviceOutputToSession
{
if (self.model == CICameraCaptureModelPhoto) //獲取靜態(tài)圖片
{
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、設(shè)置預(yù)覽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、啟動(dòng)session
啟動(dòng)session之后預(yù)覽界面就會(huì)有畫面呈現(xiàn),因?yàn)閱?dòng)是個(gè)比較耗時(shí)的操作,所以一般會(huì)加入過渡動(dòng)畫
[self.session startRunning];
8、停止session
當(dāng)不需要實(shí)時(shí)取景的時(shí)候可以關(guān)閉session,比如拍照視圖被壓入視圖棧下面,當(dāng)拍照視圖從新到視圖棧頂端的時(shí)候可以再將session start。這樣既不會(huì)對內(nèi)存和電量造成不良影響,也不需要視頻流重新初始化。
[self.session stopRunning];
到此為止一個(gè)視頻流的創(chuàng)建和啟動(dòng)已經(jīng)完成了。當(dāng)然我們最重要捕獲視頻流,所以要進(jìn)行視頻流的捕獲。在此之前,為了獲得更好的效果,我們還要進(jìn)行一些設(shè)置,如設(shè)置對焦、曝光、白平衡和火炬,以便我們能得到更好的拍攝效果。
攝像設(shè)備的設(shè)置
切換攝像頭
- (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];
}
}
設(shè)置對焦、曝光、白平衡、火炬(torch)
對這些設(shè)備狀態(tài)的設(shè)定可以在session啟動(dòng)之后。在設(shè)置之前一定要鎖定,lockForConfiguration:。設(shè)置完后,調(diào)用unlockForConfiguration解除鎖定?;鹁媸茿pple一個(gè)獨(dú)特的功能,它會(huì)為攝像頭提供一個(gè)LED光源,光源的亮度是可以調(diào)節(jié)的,相比閃光燈,torch可以提供持續(xù)的光源,對在黑夜錄制清晰的視頻幫助巨大。
+ (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);
}
}
視頻流輸出文件
獲取靜態(tài)圖片
輸出時(shí)注意connection的方向設(shè)置
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];
}
}];
});
}
獲取視頻
開始獲取視頻流
視頻的方向在這個(gè)時(shí)候設(shè)置,在拍攝期間不能更改。
視頻的保存路徑也在這個(gè)時(shí)機(jī)決定。
- (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;
});
}
}
結(jié)束獲取視頻流
通過AVCaptureMovieFileOutput可以獲得一個(gè)完整的音視頻混合movie文件
- (void)stopRecording
{
WeakSelf
dispatch_async([self sessionQueue], ^{
StrongSelf
if ([strongSelf.movieFileOutput isRecording])
{
[strongSelf.movieFileOutput stopRecording];
strongSelf.isRecording = NO;
}
});
}
視頻錄制delegate
視頻錄制相關(guān)的代理,可以查看AVCaptureFileOutputRecordingDelegate文件。比較常用的是,開始錄制回調(diào)和錄制完成回調(diào)
- (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];
}
}
獲取照片和獲取視頻的異同
無論是拍照還是錄制視頻都需要?jiǎng)?chuàng)建和啟動(dòng)視頻流。上面已經(jīng)說的很清楚了。不同點(diǎn)只有以下幾點(diǎn):
- 無論獲取照片還是獲取視頻,都需要預(yù)覽,所以都需要視頻輸入設(shè)備和相應(yīng)的視頻輸入源。通過 [ [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo] firstObject];獲取視頻輸入設(shè)備-攝像頭
- 視頻需要音頻,所以比照片要多一個(gè)輸入設(shè)備。通過[[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject]獲得音頻輸入設(shè)備-麥克風(fēng)。
- 照片的輸出設(shè)備同視頻不同,照片的輸出對象AVCaptureStillImageOutput,視頻的輸出對象是AVCaptureMovieFileOutput。
- 照片輸出后可以直接將照片對象的指針傳給調(diào)用方使用,視頻只能將視頻文件的地址傳給調(diào)用方。
CameraDeviceFramework
這個(gè)framework已經(jīng)將視頻流捕獲、攝像頭設(shè)置、視頻獲取封裝,并配有Demo。
gitlab地址:http://gitlab.alibaba-inc.com/xunfeng.zy/CameraDeviceFramework
最后介紹一下UIImagePickerController的開發(fā)
UIImagePickerController支持拍照和視頻錄制,還可以用來選取照片。
用UIImagePickerController拍照或者錄制視頻有以下幾個(gè)步驟:
1、創(chuàng)建UIImagePickerController對象。
2、設(shè)置源類型sourceType,類型有:
- UIImagePickerControllerSourceTypePhotoLibrary:照片庫
,默認(rèn)值。 - UIImagePickerControllerSourceTypeCamera:攝像頭
- UIImagePickerControllerSourceTypeSavedPhotosAlbum:相冊
3、設(shè)置媒體類型mediaType。類型有:
- kUTTypeImage:照片
- kUTTypeVideo:無聲視頻
- kUTTypeMovie:有聲視頻
4、指定工作模式,類型有:
- UIImagePickerControllerCameraCaptureModePhoto:拍照模式
- UIImagePickerControllerCameraCaptureModeVideo:視頻模式
5、設(shè)置視頻質(zhì)量videoQuality,如果是拍照則忽略
- UIImagePickerControllerQualityTypeHigh
- UIImagePickerControllerQualityTypeMedium
- UIImagePickerControllerQualityTypeLow
也可以按照尺寸選擇,其實(shí)在部分機(jī)型下High同1280x720是等價(jià)的。 - UIImagePickerControllerQualityTypeIFrame1280x720
- UIImagePickerControllerQualityTypeIFrame960x540
- UIImagePickerControllerQualityType640x480