捕捉会话 AVCaptureSession
AVFoundation捕捉栈的核心类是AVCaptureSession。一个捕捉会话相当于一个虚拟的“插线板”,用于连接输入和输出的资源。
捕捉会话管理从屋里设备得到的数据流,比如摄像头和麦克风设备,输出到一个或多个目的地。可以动态配置输入和输出的线路,可以再会话进行中按需配置捕捉环境。
捕捉会话还可以额外配置一个会话预设值(session preset),用来控制捕捉数据的格式和质量。会话预设值默认为AVCaptureSessionPresetHigh,适用于大多数情况。还有很多预设值,可以根据需求设置。
捕捉设备 AVCaptureDevice
AVCaptureDevice为摄像头或麦克风等物理设备定义了一个接口。对硬件设备定义了大量的控制方法,如对焦、曝光、白平衡和闪光灯等。
AVCaptureDevice定义大量类方法用用访问系统的捕捉设备,最常用的是defaultDeviceWithMediaType:,根据给定的媒体类型返回一个系统指定的默认设备
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];请求的是一个默认的视频设备,在包含前置和后置摄像头的iOS系统,返回后置摄像头。
捕捉设备的输入 AVCaptureInput
AVCaptureInput是一个抽象类,提供一个连接接口将捕获到的输入源连接到AVCaptureSession。
抽象类无法直接使用,只能通过其子类满足需求:AVCaptureDeviceInput-使用该对象从AVCaptureDevice获取设备数据(摄像头、麦克风等)、AVCaptureScreenInput-通过屏幕获取数据(如录屏)、AVCaptureMetaDataInput-获取元数据
以 AVCaptureDeviceInput 为例
使用捕捉设备进行处理前,需要将它添加为捕捉会话的输入。通过将设备(AVCaptureDevice)封装到AVCaptureDeviceInput实例中,实现将设备插入到AVCaptureSession中。
AVCaptureDeviceInput在设备输出数据和捕捉会话间,扮演接线板的作用。
//AVMediaTypeVideo指视频,也可以设置音频AVMediaTypeAudio
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error;
AVCaptureDeviceInput *input = [[AVCaptureDeviceInput alloc] initWithDevice:device error:&error];
坐标空间转换
对于iphone 5手机 屏幕左上角为(0,0),垂直模式下右下角坐标为(320,568)。
水平模式下右下角为(568,320)。捕捉设备坐标系有着不同的定义,他们通常是基于摄像头传感器的本地设置,左上角为(0,0),右下角为(1,1),AVCaptureVideoPreviewLayer提供两个方法,让两者之间相互转换
captureDevicePointOfInterestForPoint :获取屏幕坐标系的CGPoint数据,返回转换得到设备坐标系CGPoint数据
pointForCaptureDevicePointOfInterest:获取摄像头坐标系的CGPoint数据,返回转换得到屏幕坐标系CGPoint数据。
设置输出
// Setup the still image output//拍照
self.imageOutput = [[AVCaptureStillImageOutput alloc] init]; // 8
self.imageOutput.outputSettings = @{AVVideoCodecKey : AVVideoCodecJPEG};
if ([self.captureSession canAddOutput:self.imageOutput]) {
[self.captureSession addOutput:self.imageOutput];
}
// Setup movie file output //视频记录
self.movieOutput = [[AVCaptureMovieFileOutput alloc] init]; // 9
if ([self.captureSession canAddOutput:self.movieOutput]) {
[self.captureSession addOutput:self.movieOutput];
}
//记录视频方法
- (void)startRecording {
if (![self isRecording]) {
AVCaptureConnection *videoConnection = // 2
[self.movieOutput connectionWithMediaType:AVMediaTypeVideo];
if ([videoConnection isVideoOrientationSupported]) { // 3
videoConnection.videoOrientation = self.currentVideoOrientation;
}
if ([videoConnection isVideoStabilizationSupported]) { // 4
videoConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
// Deprecated approach below
// videoConnection.enablesVideoStabilizationWhenAvailable = YES;
}
AVCaptureDevice *device = [self activeCamera];
if (device.isSmoothAutoFocusSupported) { // 5
NSError *error;
if ([device lockForConfiguration:&error]) {
device.smoothAutoFocusEnabled = NO;
[device unlockForConfiguration];
} else {
[self.delegate deviceConfigurationFailedWithError:error];
}
}
self.outputURL = [self uniqueURL]; // 6
[self.movieOutput startRecordingToOutputFileURL:self.outputURL // 8
recordingDelegate:self];
}
}
//拍照
- (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];
[self writeImageToAssetsLibrary:image]; // 1
} else {
NSLog(@"NULL sampleBuffer: %@", [error localizedDescription]);
}
};
// Capture still image
[self.imageOutput captureStillImageAsynchronouslyFromConnection:connection
completionHandler:handler];
}
切换摄像头
- (BOOL)switchCameras {
if (![self canSwitchCameras]) { // 1
return NO;
}
NSError *error;
AVCaptureDevice *videoDevice = [self inactiveCamera]; // 2
AVCaptureDeviceInput *videoInput =
[AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];
if (videoInput) {
[self.captureSession beginConfiguration]; // 3
[self.captureSession removeInput:self.activeVideoInput]; // 4
if ([self.captureSession canAddInput:videoInput]) { // 5
[self.captureSession addInput:videoInput];
self.activeVideoInput = videoInput;
} else {
[self.captureSession addInput:self.activeVideoInput];
}
[self.captureSession commitConfiguration]; // 6
} else {
[self.delegate deviceConfigurationFailedWithError:error]; // 7
return NO;
}
return YES;
}
- (AVCaptureDevice *)inactiveCamera { // 4
AVCaptureDevice *device = nil;
if (self.cameraCount > 1) {
if ([self activeCamera].position == AVCaptureDevicePositionBack) { // 5
device = [self cameraWithPosition:AVCaptureDevicePositionFront];
} else {
device = [self cameraWithPosition:AVCaptureDevicePositionBack];
}
}
return device;
}
- (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position { // 1
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
for (AVCaptureDevice *device in devices) { // 2
if (device.position == position) {
return device;
}
}
return nil;
}
配置设备对焦模式
- (BOOL)cameraSupportsTapToFocus { // 1
return [[self activeCamera] isFocusPointOfInterestSupported];
}
- (void)focusAtPoint:(CGPoint)point { // 2
AVCaptureDevice *device = [self activeCamera];
if (device.isFocusPointOfInterestSupported && // 3
[device isFocusModeSupported:AVCaptureFocusModeAutoFocus]) {
NSError *error;
if ([device lockForConfiguration:&error]) { // 4
device.focusPointOfInterest = point;
device.focusMode = AVCaptureFocusModeAutoFocus;
[device unlockForConfiguration];
} else {
[self.delegate deviceConfigurationFailedWithError:error];
}
}
}
重新设置对焦和曝光
- (void)resetFocusAndExposureModes {
AVCaptureDevice *device = [self activeCamera];
AVCaptureExposureMode exposureMode =
AVCaptureExposureModeContinuousAutoExposure;
AVCaptureFocusMode focusMode = AVCaptureFocusModeContinuousAutoFocus;
BOOL canResetFocus = [device isFocusPointOfInterestSupported] && // 1
[device isFocusModeSupported:focusMode];
BOOL canResetExposure = [device isExposurePointOfInterestSupported] && // 2
[device isExposureModeSupported:exposureMode];
CGPoint centerPoint = CGPointMake(0.5f, 0.5f); // 3
NSError *error;
if ([device lockForConfiguration:&error]) {
//对焦
if (canResetFocus) { // 4
device.focusMode = focusMode;
device.focusPointOfInterest = centerPoint;
}
//曝光
if (canResetExposure) { // 5
device.exposureMode = exposureMode;
device.exposurePointOfInterest = centerPoint;
}
[device unlockForConfiguration];
} else {
[self.delegate deviceConfigurationFailedWithError:error];
}
}
调整闪光灯和手电筒
typedef NS_ENUM(NSInteger, AVCaptureFlashMode) {
AVCaptureFlashModeOff = 0, //闪光关闭
AVCaptureFlashModeOn = 1,//闪光开启
AVCaptureFlashModeAuto = 2,//闪光自适应
}
typedef NS_ENUM(NSInteger, AVCaptureTorchMode) {
AVCaptureTorchModeOff = 0,//手电筒关闭
AVCaptureTorchModeOn = 1,//手电筒开启
AVCaptureTorchModeAuto = 2,//自动
}
- (BOOL)cameraHasFlash {
return [[self activeCamera] hasFlash];
}
- (AVCaptureFlashMode)flashMode {
return [[self activeCamera] flashMode];
}
- (void)setFlashMode:(AVCaptureFlashMode)flashMode {
AVCaptureDevice *device = [self activeCamera];
if (device.flashMode != flashMode &&
[device isFlashModeSupported:flashMode]) {
NSError *error;
if ([device lockForConfiguration:&error]) {
device.flashMode = flashMode;
[device unlockForConfiguration];
} else {
[self.delegate deviceConfigurationFailedWithError:error];
}
}
}
- (BOOL)cameraHasTorch {
return [[self activeCamera] hasTorch];
}
- (AVCaptureTorchMode)torchMode {
return [[self activeCamera] torchMode];
}
- (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];
}
}
}
捕捉静态图片
- (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];
[self writeImageToAssetsLibrary:image]; // 1
} else {
NSLog(@"NULL sampleBuffer: %@", [error localizedDescription]);
}
};
// Capture still image
[self.imageOutput captureStillImageAsynchronouslyFromConnection:connection
completionHandler:handler];
}
保存相册
- (void)writeImageToAssetsLibrary:(UIImage *)image {
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
[PHAssetChangeRequest creationRequestForAssetFromImage:image];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
if (success) {
NSLog(@"成功");
} else {
NSLog(@"失败");
}
}];
}
视频捕捉
- (BOOL)isRecording { // 1
return self.movieOutput.isRecording;
}
- (void)startRecording {
if (![self isRecording]) {
AVCaptureConnection *videoConnection = // 2
[self.movieOutput connectionWithMediaType:AVMediaTypeVideo];
if ([videoConnection isVideoOrientationSupported]) { // 3
videoConnection.videoOrientation = self.currentVideoOrientation;
}
if ([videoConnection isVideoStabilizationSupported]) { // 4
videoConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
// Deprecated approach below
// videoConnection.enablesVideoStabilizationWhenAvailable = YES;
}
AVCaptureDevice *device = [self activeCamera];
if (device.isSmoothAutoFocusSupported) { // 5
NSError *error;
if ([device lockForConfiguration:&error]) {
device.smoothAutoFocusEnabled = NO;
[device unlockForConfiguration];
} else {
[self.delegate deviceConfigurationFailedWithError:error];
}
}
self.outputURL = [self uniqueURL]; // 6
[self.movieOutput startRecordingToOutputFileURL:self.outputURL // 8
recordingDelegate:self];
}
}
- (CMTime)recordedDuration {
return self.movieOutput.recordedDuration;
}
- (NSURL *)uniqueURL { // 7
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 { // 9
if ([self isRecording]) {
[self.movieOutput stopRecording];
}
}
录制视频写入相册
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput
didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
fromConnections:(NSArray *)connections
error:(NSError *)error {
if (error) { // 1
[self.delegate mediaCaptureFailedWithError:error];
} else {
[self writeVideoToAssetsLibrary:[self.outputURL copy]];
}
self.outputURL = nil;
}
- (void)writeVideoToAssetsLibrary:(NSURL *)videoURL {
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
[PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:videoURL];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
if (success) {
NSLog(@"成功");
} else {
NSLog(@"失败");
}
}];
}