
41553175912_.pic.jpg
AV Foundation捕捉會(huì)話的核心是AVCaptureSession。一個(gè)捕捉會(huì)話相當(dāng)于一個(gè)虛擬的“插線板”,用于連接輸入和輸出的資源。捕捉會(huì)話管理從設(shè)備得到的數(shù)據(jù)流,比如攝像頭和麥克風(fēng)設(shè)備,輸出一個(gè)或多個(gè)目的地。
捕捉設(shè)備
AVCaptureDevice是諸如攝像頭和麥克風(fēng)等物理設(shè)備定義了一個(gè)接口。AVCaptureDevice針對(duì)物理硬件設(shè)備定義了大量的控制方法,比如攝像頭的對(duì)焦、曝光、白平衡和閃光燈等。
捕捉設(shè)備的輸入
AVCaptureDevice需要包裝成AVCaptureDeviceInput實(shí)例來與AVCaptureSession進(jìn)行對(duì)接
捕捉設(shè)備的輸出
上圖AVCaptureSession的右邊即設(shè)備的輸出。
捕捉預(yù)覽
與視頻播放類似預(yù)覽層也是一個(gè)有Core Animation的CALayer的子類滿足的----AVCaptureVideoPreviewLayer.
代碼部分
創(chuàng)建捕捉控制器
THCameraController.h
//引入AVFoundation頭文件
#import <AVFoundation/AVFoundation.h>
extern NSString *const THThumbnailCreatedNotification;
@protocol THCameraControllerDelegate <NSObject> // 1 定義了當(dāng)有錯(cuò)誤發(fā)生時(shí),委托代理的方法
- (void)deviceConfigurationFailedWithError:(NSError *)error;
- (void)mediaCaptureFailedWithError:(NSError *)error;
- (void)assetLibraryWriteFailedWithError:(NSError *)error;
@end
@interface THCameraController : NSObject
@property (weak, nonatomic) id<THCameraControllerDelegate> delegate;
@property (nonatomic, strong, readonly) AVCaptureSession *captureSession;
// 配置會(huì)話 // 2 配置會(huì)話 傳入一個(gè)error對(duì)象的指針的指針,一旦出錯(cuò)外界可以直接獲取錯(cuò)誤信息
- (BOOL)setupSession:(NSError **)error;
- (void)startSession;
- (void)stopSession;
// 攝像頭相關(guān) // 3
- (BOOL)switchCameras; //切換攝像頭
- (BOOL)canSwitchCameras; // 能否切換攝像頭
@property (nonatomic, readonly) NSUInteger cameraCount; //能用的攝像頭的個(gè)數(shù)
@property (nonatomic, readonly) BOOL cameraHasTorch; //是否支持手電筒模式
@property (nonatomic, readonly) BOOL cameraHasFlash; //是否支持閃光燈模式
@property (nonatomic, readonly) BOOL cameraSupportsTapToFocus;//是否支持聚焦
@property (nonatomic, readonly) BOOL cameraSupportsTapToExpose; // 是否支持曝光
@property (nonatomic) AVCaptureTorchMode torchMode; //手電筒模式
@property (nonatomic) AVCaptureFlashMode flashMode; //閃光燈模式
/** Media Capture Methods **/ // 5 捕捉圖片和視頻
- (void)captureStillImage;
// Video Recording
- (void)startRecording;
- (void)stopRecording;
- (BOOL)isRecording;
- (CMTime)recordedDuration;
@end
@interface THCameraController () <AVCaptureFileOutputRecordingDelegate>
@property (strong, nonatomic) AVCaptureSession *captureSession;
@property (weak, nonatomic) AVCaptureDeviceInput *activeVideoInput;
@property (strong, nonatomic) AVCaptureStillImageOutput *imageOutput;
@property (strong, nonatomic) AVCaptureMovieFileOutput *movieOutput;
@property (strong, nonatomic) NSURL *outputURL;
@end
@implementation THCameraController
- (BOOL)setupSession:(NSError **)error{
self.captureSession = [[AVCaptureSession alloc] init]; // 1創(chuàng)建會(huì)話
//設(shè)置預(yù)設(shè)值
self.captureSession.sessionPreset = AVCaptureSessionPresetHigh;
// Set up default camera device
AVCaptureDevice *videoDevice = // 2得到一個(gè)默認(rèn)視頻捕捉設(shè)備的的實(shí)例
[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
AVCaptureDeviceInput *videoInput = // 3 把AVCaptureDevice包裝成AVCaptureDeviceInput對(duì)象添加到會(huì)話中
[AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];
if (videoInput) {
if ([self.captureSession canAddInput:videoInput]) { // 4是否可以被添加到會(huì)話中,如果可以則添加
[self.captureSession addInput:videoInput];
self.activeVideoInput = videoInput;
}
} else {
return NO;
}
AVCaptureDevice *audioDevice = // 5 獲取音頻設(shè)備的指針
[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
AVCaptureDeviceInput *audioInput = // 6 包裝audioDevice的輸入
[AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:error];
if (audioInput) {
if ([self.captureSession canAddInput:audioInput]) { // 7是否可以被添加到會(huì)話中,如果可以則添加
[self.captureSession addInput:audioInput];
}
} else {
return NO;
}
self.imageOutput = [[AVCaptureStillImageOutput alloc] init]; // 8 創(chuàng)建一個(gè)AVCaptureStillImageOutput實(shí)例,用于從攝像頭中捕捉靜態(tài)圖片
self.imageOutput.outputSettings = @{AVVideoCodecKey : AVVideoCodecJPEG};
if ([self.captureSession canAddOutput:self.imageOutput]) {
[self.captureSession addOutput:self.imageOutput];
}
self.movieOutput = [[AVCaptureMovieFileOutput alloc] init]; // 9 創(chuàng)建一個(gè)AVCaptureMovieFileOutput實(shí)例用于輸出視頻文件。
if ([self.captureSession canAddOutput:self.movieOutput]) { //同樣方式添加輸出
[self.captureSession addOutput:self.movieOutput];
}
return YES;
}
- (void)startSession {
if (![self.captureSession isRunning]) { // 10 開始會(huì)話 異步調(diào)用不會(huì)阻塞主線程
dispatch_async([self globalQueue], ^{
[self.captureSession startRunning];
});
}
}
- (void)stopSession {
if ([self.captureSession isRunning]) { // 11 停止會(huì)話
dispatch_async([self globalQueue], ^{
[self.captureSession stopRunning];
});
}
}
//返回一個(gè)隊(duì)列
- (dispatch_queue_t)globalQueue {
return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
}
// 攝像頭
- (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position { // 12返回指定位置的攝像頭
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
for (AVCaptureDevice *device in devices) { // 遍歷可用的視頻設(shè)備并返回對(duì)對(duì)應(yīng)的設(shè)備
if (device.position == position) {
return device;
}
}
return nil;
}
- (AVCaptureDevice *)activeCamera { // 13 返回當(dāng)前激活的視頻設(shè)備
return self.activeVideoInput.device;
}
- (AVCaptureDevice *)inactiveCamera { // 14 返回當(dāng)前未激活的設(shè)備
AVCaptureDevice *device = nil;
if (self.cameraCount > 1) {
if ([self activeCamera].position == AVCaptureDevicePositionBack) { //
device = [self cameraWithPosition:AVCaptureDevicePositionFront];
} else {
device = [self cameraWithPosition:AVCaptureDevicePositionBack];
}
}
return device;
}
- (BOOL)canSwitchCameras { // 15 返回可用視頻捕捉設(shè)備
return self.cameraCount > 1;
}
- (NSUInteger)cameraCount { //
return [[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo] count];
}
//切換攝像頭
- (BOOL)switchCameras {
if (![self canSwitchCameras]) { // 16 是否可以切換攝像頭(攝像頭至少多余1個(gè))
return NO;
}
NSError *error;
AVCaptureDevice *videoDevice = [self inactiveCamera]; // 17 獲取為激活攝像頭指針,并包裝一個(gè)AVCaptureDeviceInput輸入
AVCaptureDeviceInput *videoInput =
[AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];
if (videoInput) {
[self.captureSession beginConfiguration]; // 18 開始設(shè)置會(huì)話
[self.captureSession removeInput:self.activeVideoInput]; // 19 移除正在使用的激活攝像頭
if ([self.captureSession canAddInput:videoInput]) { // 20 添加17不創(chuàng)建的新的AVCaptureDeviceInput輸入
[self.captureSession addInput:videoInput];
self.activeVideoInput = videoInput;
} else {
[self.captureSession addInput:self.activeVideoInput];
}
[self.captureSession commitConfiguration]; // 21 提交設(shè)置 18-21步固定步驟, 先鎖住 然后切換 自后提交配置
} else {
[self.delegate deviceConfigurationFailedWithError:error]; // 22 錯(cuò)誤回調(diào)
return NO;
}
return YES;
}
// 閃光 和 手電筒模式
- (BOOL)cameraHasFlash {
return [[self activeCamera] hasFlash];
}
- (AVCaptureFlashMode)flashMode {
return [[self activeCamera] flashMode];
}
// 設(shè)置閃光燈模式
- (void)setFlashMode:(AVCaptureFlashMode)flashMode {
AVCaptureDevice *device = [self activeCamera];
if (device.flashMode != flashMode &&
[device isFlashModeSupported:flashMode]) {
NSError *error;
if ([device lockForConfiguration:&error]) { // 鎖定設(shè)置
device.flashMode = flashMode; // 解鎖設(shè)置
[device unlockForConfiguration]; // 解鎖設(shè)置
} else {
[self.delegate deviceConfigurationFailedWithError:error];
}
}
}
- (BOOL)cameraHasTorch {
return [[self activeCamera] hasTorch];
}
- (AVCaptureTorchMode)torchMode {
return [[self activeCamera] torchMode];
}
// 設(shè)置手電筒模式
- (void)setTorchMode:(AVCaptureTorchMode)torchMode {
AVCaptureDevice *device = [self activeCamera];
if (device.torchMode != torchMode &&
[device isTorchModeSupported:torchMode]) {
NSError *error;
if ([device lockForConfiguration:&error]) {
device.torchMode = torchMode;
[device unlockForConfiguration];
} else {
[self.delegate deviceConfigurationFailedWithError:error];
}
}
}
// 獲取靜態(tài)圖片
- (void)captureStillImage {
AVCaptureConnection *connection =
[self.imageOutput connectionWithMediaType:AVMediaTypeVideo];
if (connection.isVideoOrientationSupported) {
connection.videoOrientation = [self currentVideoOrientation];
}
id handler = ^(CMSampleBufferRef sampleBuffer, NSError *error) {
if (sampleBuffer != NULL) {
NSData *imageData =
[AVCaptureStillImageOutput
jpegStillImageNSDataRepresentation:sampleBuffer];
UIImage *image = [[UIImage alloc] initWithData:imageData];
// 1
} else {
NSLog(@"NULL sampleBuffer: %@", [error localizedDescription]);
}
};
// Capture still image 一個(gè)異步方法
[self.imageOutput captureStillImageAsynchronouslyFromConnection:connection
completionHandler:handler];
}
// 錄制視頻部分
- (BOOL)isRecording { //
return self.movieOutput.isRecording;
}
- (void)startRecording {
if (![self isRecording]) {
AVCaptureConnection *videoConnection = // 獲取AVCaptureConnection指針
[self.movieOutput connectionWithMediaType:AVMediaTypeVideo];
if ([videoConnection isVideoOrientationSupported]) { //
videoConnection.videoOrientation = self.currentVideoOrientation;
}
if ([videoConnection isVideoStabilizationSupported]) { //
if ([[[UIDevice currentDevice] systemVersion] floatValue] < 8.0) {
videoConnection.enablesVideoStabilizationWhenAvailable = YES; // 是否支持視頻穩(wěn)定
} else {
videoConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
}
}
AVCaptureDevice *device = [self activeCamera];
if (device.isSmoothAutoFocusSupported) { // 攝像頭平滑對(duì)焦操作
NSError *error;
if ([device lockForConfiguration:&error]) {
device.smoothAutoFocusEnabled = NO;
[device unlockForConfiguration];
} else {
[self.delegate deviceConfigurationFailedWithError:error];
}
}
self.outputURL = [self uniqueURL]; //
[self.movieOutput startRecordingToOutputFileURL:self.outputURL // 設(shè)置代理和視頻存放路徑 開始錄制
recordingDelegate:self];
}
}
- (CMTime)recordedDuration {
return self.movieOutput.recordedDuration;
}
- (NSURL *)uniqueURL { //
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *dirPath =
[fileManager temporaryDirectoryWithTemplateString:@"kamera.XXXXXX"];
if (dirPath) {
NSString *filePath =
[dirPath stringByAppendingPathComponent:@"kamera_movie.mov"];
return [NSURL fileURLWithPath:filePath];
}
return nil;
}
- (void)stopRecording { // 停止錄制
if ([self isRecording]) {
[self.movieOutput stopRecording];
}
}
#pragma mark - AVCaptureFileOutputRecordingDelegate
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput
didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
fromConnections:(NSArray *)connections
error:(NSError *)error {
if (error) {
[self.delegate mediaCaptureFailedWithError:error];
} else {
[self writeVideoToAssetsLibrary:[self.outputURL copy]];
}
self.outputURL = nil;
}
- (void)writeVideoToAssetsLibrary:(NSURL *)videoURL {
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; //
if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:videoURL]) { //
ALAssetsLibraryWriteVideoCompletionBlock completionBlock;
completionBlock = ^(NSURL *assetURL, NSError *error){ //
if (error) {
[self.delegate assetLibraryWriteFailedWithError:error];
}
};
[library writeVideoAtPathToSavedPhotosAlbum:videoURL // 寫入資源庫中
completionBlock:completionBlock];
}
}
@end
預(yù)覽層
AVCaptureVideoPreviewLayer需要相關(guān)的session(AVCaptureSession)才能激活。
//空間坐標(biāo)轉(zhuǎn)換的方法。
- (CGPoint)captureDevicePointForPoint:(CGPoint)point {
AVCaptureVideoPreviewLayer *layer =
(AVCaptureVideoPreviewLayer *)self.layer;
return [layer captureDevicePointOfInterestForPoint:point];
}
// 重寫layerClass方法 返回預(yù)覽層
+ (Class)layerClass {
return [AVCaptureVideoPreviewLayer class];
}
- (AVCaptureSession*)session {
return [(AVCaptureVideoPreviewLayer*)self.layer session];
}
// 設(shè)置session會(huì)話,關(guān)鍵layer和session。激活相應(yīng)的layer
- (void)setSession:(AVCaptureSession *)session {
[(AVCaptureVideoPreviewLayer*)self.layer setSession:session];
}
AVCaptureVideoPreviewLayer的兩個(gè)坐標(biāo)轉(zhuǎn)換的方法。
獲取屏幕坐標(biāo)系數(shù)據(jù) 返回得到額設(shè)備坐標(biāo)系數(shù)據(jù)
- (CGPoint)captureDevicePointOfInterestForPoint:(CGPoint)pointInLayer;
獲取攝像頭坐標(biāo)系的數(shù)據(jù),返回轉(zhuǎn)換得到的屏幕坐標(biāo)系數(shù)據(jù)
- (CGPoint)pointForCaptureDevicePointOfInterest:(CGPoint)captureDevicePointOfInterest;