【iOS】自定义相机(八)拍照进阶
在前面【iOS】自定义相机(六)拍照录像中,我们介绍了如何使用AVCaptureStillImageOutput
进行简单的拍照操作。但是从 iOS 10 开始,Apple 就弃用这个类并提供AVCapturePhotoOutput
用于进行更多的拍照操作,比如拍摄动态照片(Live Photo)。AVCapturePhotoOutput
是一个功能强大的类,在新系统中也不断有新的功能接入,比如iOS11支持双摄和获取深度数据、iOS12支持人像模式。不过,在本篇文章中主要介绍的还是他在 iOS10 中最基本的两项功能拍摄静态照片和动态图片。
该部分的代码主要在SCCamera中的SCPhotoManager中
不管是拍摄静态照片,还是拍摄动态照片,AVCapturePhotoOutput
的使用都有一个固定的形式:
- 在捕捉会话启动前创建并设置
AVCapturePhotoOutput
对象 - 创建并配置
AVCapturePhotoSettings
对象以选择特定拍摄的功能和设置 - 调用
capturePhotoWithSettings:delegate:
方法进行操作
capturePhotoWithSettings:delegate:
方法需要的两个参数分别是拍照方式设置和拍照过程代理。拍照的配置将在下面的AVCapturePhotoSettings
中介绍,至于拍照过程的代理调用顺序就穿插在后面的两种拍摄方式的处理过程中。
注意:
- 由于
AVCapturePhotoOutput
已经包含AVCaptureStillImageOutput
的功能,因此它们不可同时使用。- 由于动态照片其实是一段小小的视频,因此
AVCapturePhotoOutput
的动态照片启用的时候,AVCaptureMovieFileOutput
是不可用的。
AVCapturePhotoSettings
AVCapturePhotoSettings
用于选择在AVCapturePhotoOutput
中特定拍摄的功能和设置。
常用属性:
-
format
:类似于AVCaptureStillImageOutput
的outputSettings
属性,用于使用键值对配置。- 例如配置视频编码
@{AVVideoCodecKey: AVVideoCodecJPEG}
。
- 例如配置视频编码
-
flashMode
:闪光灯模式 -
autoStillImageStabilizationEnabled
:自动静态图片稳定(防抖) -
highResolutionPhotoEnabled
: 指定输出摄像头支持的最高质量图像 -
livePhotoMovieFileURL
:动态照片保存路径
PS: 苹果规定,一次拍照操作对应一个
AVCapturePhotoSettings
实例,因此在AVCapturePhotoSettings
中有一个uniqueID
属性用于区分是否重用。所以,每次拍照之前,我们都要按照需要重新创建一个AVCapturePhotoSettings
实例对象。
AVCapturePhotoCaptureDelegate
AVCapturePhotoCaptureDelegate基本在AVCaptureStillImageOutput
中我们是直接在闭包中获取静态照片,并没有一个位置供我们告诉用户当前拍照过程。Apple 推出AVCapturePhotoCaptureDelegate
为开发者提供提高用户体验的位置。使用AVCapturePhotoOutput
拍照将会经过以下五个步骤:
-
拍照配置的确定
willBeginCapture
-
开始曝光
willCapturePhoto
-
曝光结束
didCapturePhoto
-
拍照结果返回
didFinishProcessingPhoto
-
拍照完成
didFinishCapture
拍摄静态图片
主要操作是AVCapturePhotoSettings
的创建与设置,预先设定好图片编码方式
- (void)takeStillPhoto:(AVCaptureVideoPreviewLayer*)previewLayer {
// 设置照片方向
AVCaptureConnection *connection = [self.photoOutput connectionWithMediaType:AVMediaTypeVideo];
if (connection.supportsVideoOrientation) {
connection.videoOrientation = previewLayer.connection.videoOrientation;
}
// 创建 AVCapturePhotoSettings
AVCapturePhotoSettings *photoSettings = [AVCapturePhotoSettings photoSettings];
if ([self.photoOutput.availablePhotoCodecTypes containsObject:AVVideoCodecJPEG]) {
NSDictionary *format = @{AVVideoCodecKey: AVVideoCodecJPEG};
photoSettings = [AVCapturePhotoSettings photoSettingsWithFormat:format];
}
// 防抖
photoSettings.autoStillImageStabilizationEnabled = YES;
// 拍照
[self.photoOutput capturePhotoWithSettings:photoSettings delegate:self];
}
静态图片获取
在AVCapturePhotoCaptureDelegate
中的didFinishProcessingPhotoSampleBuffer:
中获取,具体裁剪方式和之前相似:
- (void)captureOutput:(AVCapturePhotoOutput *)output didFinishProcessingPhotoSampleBuffer:(nullable CMSampleBufferRef)photoSampleBuffer previewPhotoSampleBuffer:(nullable CMSampleBufferRef)previewPhotoSampleBuffer resolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings bracketSettings:(nullable AVCaptureBracketedStillImageSettings *)bracketSettings error:(nullable NSError *)error) {
// 1. 获取 originImage
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:photoSampleBuffer];
UIImage *originImage = [[UIImage alloc] initWithData:imageData];
originImage = [originImage fixOrientation];
// 2. 获取 scaledImage
CGFloat width = self.currentPreviewFrame.size.width;
CGFloat height = self.currentPreviewFrame.size.height;
CGFloat scale = [[UIScreen mainScreen] scale];
CGSize size = CGSizeMake(width*scale, height*scale);
UIImage *scaledImage = [originImage resizedImageWithContentMode:UIViewContentModeScaleAspectFill size:size interpolationQuality:kCGInterpolationHigh];
// 3. 获取 croppedImage
CGRect cropFrame = CGRectMake((scaledImage.size.width - size.width) * 0.5, (scaledImage.size.height - size.height) * 0.5, size.width, size.height);
UIImage *croppedImage = [scaledImage croppedImage:cropFrame];
// 4. 后续处理
// ...
}
拍摄动态图片
硬件要求:iPhone 6s及以上机型
像素要求:不支持 AVCaptureSessionPresetHigh
在会话启动之前必须设置AVCapturePhotoOutput
的属性livePhotoCaptureEnabled
为YES
。然后在拍摄之前,AVCapturePhotoSettings
的重点配置属性是livePhotoMovieFileURL
。
- (void)takeLivePhoto:(AVCaptureVideoPreviewLayer*)previewLayer {
self.currentPreviewFrame = previewLayer.frame;
self.livePhotoCompletion = completion;
// 照片方向
AVCaptureConnection *connection = [self.photoOutput connectionWithMediaType:AVMediaTypeVideo];
if (connection.supportsVideoOrientation) {
connection.videoOrientation = previewLayer.connection.videoOrientation;
}
// 创建 AVCapturePhotoSettings
AVCapturePhotoSettings *photoSettings = [AVCapturePhotoSettings photoSettings];
// 设置动态图片保存路径
NSString *livePhotoMovieFileName = [[NSUUID UUID] UUIDString];
NSString *livePhotoMovieFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[livePhotoMovieFileName stringByAppendingPathExtension:@"mov"]];
photoSettings.livePhotoMovieFileURL = [NSURL fileURLWithPath:livePhotoMovieFilePath];
// 拍照
[self.photoOutput capturePhotoWithSettings:photoSettings delegate:self];
}
AVCapturePhotoCaptureDelegate 调用过程
-
willBeginCapture
:设置完毕 -
willCapturePhoto
:开始曝光 -
didCapturePhoto
:结束曝光 -
didFinishProcessingPhoto
:静态照片获取位置 - ※
didFinishRecordingLivePhotoMovie
:结束动态照片拍摄 - ※
didFinishProcessingLivePhotoToMovieFile
:动态照片结果处理(获取影片文件进行处理) -
didFinishCaptureForResolvedSettings
:完成拍摄全过程
动态照片获取
Live Photo 是由一个图片和一个小视频组成的,即两个文件(.jpg
+.mov
)。因此我们需要在AVCapturePhotoCaptureDelegate
中的didFinishProcessingPhoto
获取静态照片,在didFinishProcessingLivePhotoToMovieFile
中获取小视频。最后,我们将他们保存到相册的时候需要一起写入,系统会帮我们合成动态图片的。