一、AVFoundation简介
AVFoundation 是苹果在8.0之后推出的一个音视频框架.
AVFoundation 最强大的功能是对 照片&视频 的捕捉功能. 例如一些APP中的小视频、直播等, 可以通过AVFoundation来进行实现捕捉.
二、AVFoundation常用类
2.1、捕捉会话
捕捉会话主要是用到AVCaptureSession类, 它类似于一个排插, 各种设备都需要与捕捉会话关联起来。
2.2、捕捉设备
通过AVCaptureDevice可以获取到手机的各种硬件设备, 例如: 麦克风、前后摄像头、闪光灯等。
2.3、捕捉设备输入
通过AVCaptureDeviceInput可以捕捉到设备的输入。
在AVFoundation中, 捕捉设备输入是无法直接添加到Session中的, 所以需要将捕捉设备输入转化为捕捉设备添加进会话中。
2.4、捕捉设备输出
有输入就有输出。 在iOS10.0之后, 可以通过AVCapturePhotoOutput来进行获取图片的输出, 通过AVCaptureMovieFileOutput来进行视频文件的输出。 还有AVCaptureAudioDataOutput、还有AVCaptureVideoDataOutput等。
2.5、捕捉连接
AVCaptureConnection 可以根据捕捉的媒体的类型来建立一个连接
2.6、捕捉预览
AVCaptureVideoPreviewLayer主要是一个图层,主要是用来显示摄像头实时捕捉的内容。
三、AVFoundation的简单使用
这里涉及到摄像头、麦克风、相册, 需要配置用户隐私需求。
3.1、配置会话
- 创建session会话
- 设置分辨率
- 创建捕捉设备
- 将捕捉设备转化为捕捉设备输入
- 将捕捉设备输入添加到会话中(添加过程需要注意是否能够添加进会话中)
- 配置捕捉设备的输出
- 将捕捉设备输出添加到会话中(添加过程需要注意是否能够添加进会话中)
#pragma mark - session设置
/// 配置session
/// @param error 错误回调
- (BOOL)setupSession:(NSError **)error {
/**
* 添加视频的输入类别
*/
//初始化
self.captureSession = [[AVCaptureSession alloc] init];
//设置分辨率
self.captureSession.sessionPreset = AVCaptureSessionPresetHigh;
//拿到默认视频捕捉设备: iOS默认后置摄像头为默认视频捕捉色别
AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
//一定要将捕捉设备转化AVCaptureDeviceInput
//注意: 为session添加捕捉设备, 必须将此封装成AVCaptureDeviceInput对象
AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];
if (videoInput) {
//摄像头不隶属于任何一个app, 是公共设备, 需要判断是否能添加
if ([self.captureSession canAddInput:videoInput]) {
[self.captureSession addInput:videoInput];
self.activeVideoInput = videoInput;//摄像头分前置后置, 需要保存做切换操作
}
} else {
return NO;
}
/**
* 添加音频的输入设备
*/
//添加音频输入设备: 麦克风
AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:error];
if (audioInput) {
if ([self.captureSession canAddInput:audioInput]) {
[self.captureSession addInput:audioInput];
//音频输入只有麦克风, 无需保存
}
} else {
return NO;
}
/**
* 设置输出 (照片/视频文件)
*/
//图片
self.imageOutput = [[AVCapturePhotoOutput alloc] init];
if ([self.captureSession canAddOutput:self.imageOutput]) {
[self.captureSession addOutput:self.imageOutput];
}
//视频AVCaptureMovieFileOutput实例, QuickTime
self.movieOutput = [[AVCaptureMovieFileOutput alloc] init];
if ([self.captureSession canAddOutput:self.movieOutput]) {
[self.captureSession addOutput:self.movieOutput];
}
//视频队列
self.videoQueue = dispatch_queue_create("glen.videoQueue", NULL);
return YES;;
}
配置完捕捉会话之后,就需要通过外界的按钮点击等操作来告诉AVFoundation来开启或停止捕捉会话。
/// 启动捕捉
- (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];
});
}
}
3.2、摄像头的切换
获取当前设备上可用的摄像头设备,并根据需求来获得指定的摄像头设备
/// 寻找指定摄像头
/// @param positon 指定摄像头设备
- (AVCaptureDevice *)cameraWithPositon:(AVCaptureDevicePosition)positon {
AVCaptureDeviceDiscoverySession *captureDeviceDiscoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera]
mediaType:AVMediaTypeVideo
position:AVCaptureDevicePositionUnspecified];
//获取到所有设备
NSArray *captureDevices = [captureDeviceDiscoverySession devices];
//遍历设备
for (AVCaptureDevice *device in captureDevices) {
if (device.position == positon) {
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 cameraWithPositon:AVCaptureDevicePositionFront];
} else if ([self activeCamera].position == AVCaptureDevicePositionFront) {
//前置变后置
device = [self cameraWithPositon:AVCaptureDevicePositionBack];
}
}
return device;;
}
在进行切换之前,必须要知道其他的摄像头是否是一个可进行使用的状态
/// 是否能切换摄像头
- (BOOL)canSwitchCameras {
return self.cameraCount > 1;
}
接下来就是对摄像头进行切换
/// 切换摄像头
- (BOOL)switchCameras {
//判断是否能切换
if (![self canSwitchCameras]) {
return NO;
}
//获取当前设备的反向设备(不活跃的摄像头)
AVCaptureDevice *device = [self inactiveCamera];
//将device添加进AVCaptureDeviceInput
NSError *error;
AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
//添加进会话中
if (videoInput) {
//标注原始配置要发生改变
[self.captureSession beginConfiguration];
//将原来的输入设备移除
[self.captureSession removeInput:self.activeVideoInput];
//判断能否加入
if ([self.captureSession canAddInput:videoInput]) {
[self.captureSession addInput:videoInput];
//活跃设备更新
self.activeVideoInput = videoInput;
} else {
//如果新设备无法加入, 则将原来的视频输入设备添加进去
[self.captureSession addInput:self.activeVideoInput];
}
//提交修改配置
[self.captureSession commitConfiguration];
} else {
//如果错误! 设备添加错误
return NO;
}
return YES;
}
3.3、聚焦
- 拿到当前设备
- 需要判断设备是否支持对焦等。
- 配置时不能让多个对象对他进行更改,所以锁定该设备
- 设置对焦点、对焦模式等。
- 解锁设备
/// 询问当前活跃的摄像头是否支持兴趣点对焦
- (BOOL)cameraSupportsTapToFocus {
return [[self activeCamera] isFocusPointOfInterestSupported];
}
/// 设置对焦
- (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 {
//设备错误
}
}
}
3.4、曝光
- 拿到当前活跃设备
- 创建曝光mode
- 判断设备是否支持指定的模式
- 锁定该设备
- 设置曝光点、曝光模式
- 判断是否支持锁定曝光
- 支持则使用KVO去设置状态
- 监听回调,获取设备
- 判断是否支持曝光
- 移除观察者,修改设备的曝光模式
- 解锁设备
- 对外声称一个重设对焦曝光的接口
static const NSString *CameraAdjustingExposureContext;
/// 当前活跃摄像头是否支持曝光
- (BOOL)cameraSupportsTapToExpose {
return [[self activeCamera] isExposurePointOfInterestSupported];
}
- (void)exposeAtPoint:(CGPoint)point {
//获取活跃摄像头
AVCaptureDevice *device = [self activeCamera];
//设置根据场景曝光
AVCaptureExposureMode exposureMode = AVCaptureExposureModeContinuousAutoExposure;
//活跃摄像头是否支持曝光 并且支持’根据场景曝光‘这个模式
if (device.isExposurePointOfInterestSupported && [device isExposureModeSupported:exposureMode]) {
//过程锁定
NSError *error;
if ([device lockForConfiguration:&error]) {
//设备曝光点
device.exposurePointOfInterest = point;
//设置曝光模式
device.exposureMode = exposureMode;
//是否支持锁定曝光
if ([device isExposureModeSupported:AVCaptureExposureModeLocked]) {
//使用kvo确定设备的adjustingExposure属性状态
[device addObserver:self forKeyPath:@"adjustingExposure" options:NSKeyValueObservingOptionNew context:&CameraAdjustingExposureContext];
}
//解锁
[device unlockForConfiguration];
}
}
}
/// 观察者回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if (context == &CameraAdjustingExposureContext) {
//获取设备
AVCaptureDevice *device = (AVCaptureDevice *)object;
//判断设备是否不再调整曝光等级,确认设备的exposureMode是否可以设置为AVCaptureExposureModeLocked
if (!device.isExposurePointOfInterestSupported && [device isExposureModeSupported:AVCaptureExposureModeLocked]) {
//移除作为adjustingExposure 的self,就不会得到后续变更的通知
[object removeObserver:self forKeyPath:@"adjustingExposure" context:&CameraAdjustingExposureContext];
//
dispatch_async(dispatch_get_main_queue(), ^{
if ([device lockForConfiguration:nil]) {
device.exposureMode = AVCaptureExposureModeLocked;
[device unlockForConfiguration];
} else {
//设备错误回调
}
});
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
}
//重新设置对焦&曝光
- (void)resetFocusAndExposureModes {
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
{
//设备错误回调
}
}
3.5、拍照
- 对外暴露拍照接口 captureStillImage
- 对图片输出设置setting和代理
- 通过代理获取到图片的data数据
- 将图片的data数据转化为UIImage
- 将UIImage通过PHPhotoLibrary保存进手机相册,并通知外部一个UIImage用作显示略缩图
#pragma mark - 拍照
- (void)captureStillImage {
//捕捉到图片存储格式jpg
NSDictionary *setDic = @{AVVideoCodecKey:AVVideoCodecTypeJPEG};
AVCapturePhotoSettings *outputSettings = [AVCapturePhotoSettings photoSettingsWithFormat:setDic];
[self.imageOutput capturePhotoWithSettings:outputSettings delegate:self];
}
//代理方法
- (void)captureOutput:(AVCapturePhotoOutput *)output didFinishProcessingPhoto:(AVCapturePhoto *)photo error:(nullable NSError *)error {
//图片数据
NSData *imageData = photo.fileDataRepresentation;
UIImage *image = [[UIImage alloc] initWithData:imageData];
//将图片写入到Library
[self writeImageToAssetsLibrary:image];
}
/// 写入到相册
/// @param image 图片
- (void)writeImageToAssetsLibrary:(UIImage *)image {
__block PHObjectPlaceholder *assetPlaceholder = nil;
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetChangeRequest *changeRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:image];
assetPlaceholder = changeRequest.placeholderForCreatedAsset;
} completionHandler:^(BOOL success, NSError * _Nullable error) {
NSLog(@"OK");
dispatch_async(dispatch_get_main_queue(), ^{
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName:ThumbnailCreatedNotification object:image];
});
}];
}