AVFoundation
-
捕捉会话:AVCaptureSession
-
捕捉设备:AVCaptureDevice
-
捕捉设备输入:AVCaptureDeviceInput: AVCaptureSession和AVCaptureDevice 通过AVCaptureDeviceInput进行连接
-
捕捉设备的输出:AVCaptureOutput . 这是一个抽象类
- AVCaptureStillImageOutput: 静态图片输出
- AVCaptureMovieFileOutput: 视频文件输出
- AVCaptureAudioDataOutput: 音频数据输出
- AVCaptureVideoDataOutput: 视频数据输出
-
捕捉链接:AVCaptureConnection
-
捕捉预览: AVCaptureVideoPreviewLayer :做视频捕捉的时候需要记得屏幕坐标系和捕获设备用的坐标系不是一个坐标系,使用的时候通过以下方法彼此之间进行转换
//AVCaptureVideoPreviewLayer的两个关键方法
captureDevicePointOfInterestForPoint: 根据屏幕坐标获取捕获设备对应的坐标
pointForCaptureDevicePointOfInterest: 根据捕获设备的坐标获取对应的屏幕坐标
-
捕捉视频/照片时session 的设置
- 初始化
- 设置分辨率
- 配置输入设备(注意转换为AVCaptureDeviceInput)
- 配置输出设备
- 在为session 添加输入输出设备时需要判断是否能添加,因为相机并不属于单个应用,所以在操作在操作前先判断是否有其他应用再使用,是否能添加
-(BOOL)setupSession:(NSError **)error{
//创建捕捉会话。AVCaptureSession 是捕捉场景的中心枢纽
self.captureSession = [[AVCaptureSession alloc] init];
//设置图像分辨率
self.captureSession.sessionPreset = AVCaptureSessionPresetHigh;
//拿到默认视频捕捉设备
AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];
if(videoInput){
if([self.captureSession canAddInput:videoInput]){
[self.captureSession addInput:videoInput];
self.activeVideoInput = videoInput;//记录当前的活跃输入设备
}
}else{
return NO;
}
AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
AVCaptureInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioInput error:error];
if(audioInput){
if([self.captureSession canAddInput:audioInput]){
[self.captureSession addInput:audioInput];
}
}else{
return NO;
}
//设置输出:(照片/视频)
self.imageOutput = [[AVCaptureStillImageOutput alloc]init];
//配置字典:希望捕捉到JPEG格式的图片
self.imageOutput.outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG};
if([self.captureSession canAddOutput:self.photoOutput]){
[self.captureSession addOutput:self.photoOutput];
}
self.movieOutput = [[AVCaptureMovieFileOutput alloc]init];
if([self.captureSession canAddOutput:self.movieOutput]){
[self.captureSession addOutput:self.movieOutput];
}
//视频队列
self.videoQueue = dispatch_queue_create("AB.videoQueue", NULL);
return YES;
}
//开始捕捉
-(void)startSession{
if(![self.captureSession isRunning]){
dispatch_async(self.videoQueue, ^{
[self.captureSession startRunning];
});
}
}
//停止捕捉
-(void)stopSession{
if([self.captureSession isRunning]){
dispatch_async(self.videoQueue, ^{
[self.captureSession stopRunning];
});
}
}
- 切换前后摄像机
//寻找指定位置的摄像头设别
-(AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position{
AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified];
NSArray<AVCaptureDevice *> *devices = discoverySession.devices;
for (AVCaptureDevice *device in devices) {
if(device.position == position){
return device;
}
}
return nil;
}
//获取当前活跃的设备
-(AVCaptureDevice *)activeCamera{
return self.activeVideoInput.device;
}
//获取当前不活跃的摄像头设别
-(AVCaptureDevice *)inActiveCamera{
AVCaptureDevice *device = nil;
//先判断当前设备有几个摄像头设备
if(self.cameraCount > 1){
if ([self activeCamera].position == AVCaptureDevicePositionBack) {
device = [self cameraWithPosition:AVCaptureDevicePositionFront];
}else{
device = [self cameraWithPosition:AVCaptureDevicePositionBack];
};
}
return device;
}
-(BOOL)canSwitchCamera{
return self.cameraCount > 1;
}
//切换摄像头
-(NSUInteger)cameraCount{
AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified];
return discoverySession.devices.count;
}
//切换相机
-(BOOL)switchCamera{
if(![self canSwitchCamera]){
return NO;
}
AVCaptureDevice *device = [self inActiveCamera];
NSError *error;
AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if(videoInput){
//输入设备变化之前需先告知系统sesstion配置要变
[self.captureSession beginConfiguration];
[self.captureSession removeInput:self.activeVideoInput];
if([self.captureSession canAddInput:videoInput]){
[self.captureSession addInput:videoInput];
self.activeVideoInput = videoInput;
}else{
//如果新的设备为能成功添加进去,需要把更换前的设备加进去,以免出现空白
if([self.captureSession canAddInput:self.activeVideoInput]){
[self.captureSession addInput:self.activeVideoInput];
}
}
//修改完session 后需要提交
[self.captureSession commitConfiguration];
}else{
//如果发生错误
if([self.delegate respondsToSelector:@selector(deviceConfigurationFaildWithError:)]){
[self.delegate deviceConfigurationFaildWithError:error];
return NO;
}
}
return YES;
}
捕捉设备(聚焦/曝光)
- 聚焦
-(void)focusAtPoint:(CGPoint)point{
//判断该设别是否支持兴趣点对焦/是否支持自动对焦
AVCaptureDevice *device = [self activeCamera];
if(device.isFocusPointOfInterestSupported && [device isFocusModeSupported:AVCaptureFocusModeAutoFocus]){
NSError *error;
//因为配置时不能让多个对象对它进行修改,所以要对该过程上锁
if([device lockForConfiguration:&error]){
//聚焦的位置
device.focusPointOfInterest = point;
//聚焦的模式,自动聚焦
device.focusMode = AVCaptureFocusModeAutoFocus;
//修改完毕,释放该锁
[device unlockForConfiguration];
}else{
if([self.delegate respondsToSelector:@selector(deviceConfigurationFaildWithError:)]){
[self.delegate deviceConfigurationFaildWithError:error];
}
}
}
}
- 曝光
static const NSString *THCameraAdjustingExposureContext;
-(void)exposeAtPoint:(CGPoint)point{
AVCaptureDevice *device = [self activeCamera];
AVCaptureExposureMode exposureMode = AVCaptureExposureModeContinuousAutoExposure;
if(device.isExposurePointOfInterestSupported && [device isExposureModeSupported:exposureMode]){
[device isExposureModeSupported:exposureMode];
NSError *error;
if([device lockForConfiguration:&error]){
device.exposurePointOfInterest = point;
device.exposureMode = exposureMode;
if([device isExposureModeSupported:AVCaptureExposureModeLocked]){
//支持,则使用kvo确定设备的adjustingExposure属性的状态。
/**
AVCaptureDevice 的 adjustingExposure 是一个只读 BOOL 属性
@property(nonatomic, readonly, getter=isAdjustingExposure) BOOL adjustingExposure;
YES:设备正在自动调整曝光。
NO:设备已经调整完毕,曝光值稳定
当手动设置 exposurePointOfInterest 后,设备会开始调整曝光,adjustingExposure 变为 YES。调整完成后,它会变回 NO,此时可以锁定曝光,防止自动曝光系统继续调整
*/
[device addObserver:self forKeyPath:@"adjustingExposure" options:NSKeyValueObservingOptionNew context:&THCameraAdjustingExposureContext];
}
[device unlockForConfiguration];
}else{
if([self.delegate respondsToSelector:@selector(deviceConfigurationFaildWithError:)]){
[self.delegate deviceConfigurationFaildWithError:error];
}
}
}
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if(context == &THCameraAdjustingExposureContext){
//获取device
AVCaptureDevice *device = (AVCaptureDevice *)object;
if(!device.isExposurePointOfInterestSupported && [device isExposureModeSupported:AVCaptureExposureModeLocked]){
[object removeObserver:self forKeyPath:@"adjustingExposure" context:&THCameraAdjustingExposureContext];
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error;
if([device lockForConfiguration:&error]){
device.exposureMode = AVCaptureExposureModeLocked;
[device unlockForConfiguration];
}else{
if([self.delegate respondsToSelector:@selector(deviceConfigurationFaildWithError:)]){
[self.delegate deviceConfigurationFaildWithError:error];
}
}
});
}else{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
}
曝光的实现与聚焦相比稍微复杂一些,其中AVCaptureDevice 的 adjustingExposure属性需要我们关注,adjustingExposure为AVCaptureDevice的只读属性,当设备正在调整曝光时该值为YES, 设备已经调整完毕,则该值为NO。我们对该值进行监听,当该值为NO时,我们对已经调整完成的曝光进行锁定,以免自动曝光系统继续调整。
- 重制曝光与聚焦
//重新设置对焦&曝光
-(void)resetFocusAndExposeMode{
AVCaptureDevice *device = [self activeCamera];
AVCaptureFocusMode focusMode = AVCaptureFocusModeContinuousAutoFocus;
//获取对焦兴趣点 和 连续自动对焦模式 是否被支持
BOOL canResetFocus = [device isFocusPointOfInterestSupported]&& [device isFocusModeSupported:focusMode];
AVCaptureExposureMode exposureMode = AVCaptureExposureModeContinuousAutoExposure;
//确认曝光度可以被重设
BOOL canResetExposure = [device isFocusPointOfInterestSupported] && [device isExposureModeSupported:exposureMode];
//回顾一下,捕捉设备空间左上角(0,0),右下角(1,1) 中心点则(0.5,0.5)
CGPoint centPoint = CGPointMake(0.5f, 0.5f);
NSError *error;
//锁定设备,准备配置
if ([device lockForConfiguration:&error]) {
//焦点可设,则修改
if (canResetFocus) {
device.focusMode = focusMode;
device.focusPointOfInterest = centPoint;
}
//曝光度可设,则设置为期望的曝光模式
if (canResetExposure) {
device.exposureMode = exposureMode;
device.exposurePointOfInterest = centPoint;
}
//释放锁定
[device unlockForConfiguration];
}else
{
[self.delegate deviceConfigurationFaildWithError:error];
}
}
- 手电筒 与 闪光灯设置
(BOOL)cameraHasFlash{
return [[self activeCamera] hasFlash];
}
-(AVCaptureFlashMode)flashMode{
return [[self activeCamera] flashMode];
}
-(void)setFlashMode:(AVCaptureFlashMode)flashMode{
AVCaptureDevice *device = [self activeCamera];
if([device isFlashModeSupported:flashMode]){
NSError *error;
if([device lockForConfiguration:&error]){
device.flashMode = flashMode;
[device unlockForConfiguration];
}else{
[self.delegate deviceConfigurationFaildWithError:error];
}
}
}
-(BOOL)cameraHasTorch{
return [[self activeCamera] torchMode];
}
-(void)setTorchMode:(AVCaptureTorchMode)torchMode{
AVCaptureDevice *device = [self activeCamera];
if([device isTorchModeSupported:torchMode]){
NSError *error;
if([device lockForConfiguration:&error]){
device.torchMode = torchMode;
[device unlockForConfiguration];
}else{
[self.delegate deviceConfigurationFaildWithError:error];
}
}
}
*静态图片的拍摄
#pragma mark image capture method
-(void)cantureStillImage{
//获取链接
AVCaptureConnection *connection = [self.imageOutput connectionWithMediaType:AVMediaTypeVideo];
//程序只支持纵向,但是如果用户横向拍照时,需要调整结果照片的方向
//判断是否支持设置视频方向
if(connection.isVideoOrientationSupported){
//获取方向值
connection.videoOrientation = [self currentVideoOrientaition];
}
//定义一个handler 块,会返回1个图片的NSData数据
id handle = ^(CMSampleBufferRef sampleBuffer, NSError *error){
if(sampleBuffer != NULL){
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:sampleBuffer];
UIImage *image = [UIImage imageWithData:imageData];
[self writeImageToAssetsLibrary:image];
}else{
NSLog(@"NULL sampleBuffer:%@",[error localizedDescription]);
}
};
[self.imageOutput captureStillImageAsynchronouslyFromConnection:connection completionHandler:handle];
}
//调整图片的拍摄方向
-(AVCaptureVideoOrientation)currentVideoOrientaition{
AVCaptureVideoOrientation orientation;
//获取UIDevice 的 orientation
switch ([UIDevice currentDevice].orientation) {
case UIDeviceOrientationPortrait:
orientation = AVCaptureVideoOrientationPortrait;
break;
case UIDeviceOrientationLandscapeRight:
orientation = AVCaptureVideoOrientationLandscapeLeft;
break;
case UIDeviceOrientationPortraitUpsideDown:
orientation = AVCaptureVideoOrientationPortraitUpsideDown;
break;
default:
orientation = AVCaptureVideoOrientationLandscapeRight;
break;
}
return orientation;
}
- (void)writeImageToAssetsLibrary:(UIImage *)image{
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
//参数1:图片(参数为CGImageRef 所以image.CGImage)
//参数2:方向参数 转为NSUInteger
//参数3:写入成功、失败处理
[library writeImageToSavedPhotosAlbum:image.CGImage orientation:(NSInteger)image.imageOrientation completionBlock:^(NSURL *assetURL, NSError *error) {
if(!error){
[self postThumbnailNotifification:image];
}else{
//失败打印错误信息
id message = [error localizedDescription];
NSLog(@"%@",message);
}
}];
}
//发送缩略图通知,显示拍摄图片的缩略图
- (void)postThumbnailNotifification:(UIImage *)image {
dispatch_async(dispatch_get_main_queue(), ^{
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName:THThumbnailCreatedNotification object:image];
});
}











网友评论