本节内容主要涉及:
- AVCapturePhotoOutput基础
- 分级捕获
- Live Photo
- Thumbnail
- 场景监控
AVCapturePhotoOutput基础
从iOS 10起,苹果开始使用AVCapturePhotoOutput来替代AVCaptureStillImageOutput,相比于之前的AVCaptureStillImageOutput,我觉得有以下几个优势:
- AVCapturePhotoOutput直接接管了所有拍照相关特性的封装、管理和启用,如 Live Photo、景深、人像;
- 使用Delegate的方式回调图片,生命周期上更加清晰和可控,同时避免循环引用的风险;
- 通过加入AVCapturePhotoSetttings,使得每次拍照设置之间互不影响,更具独立性;
- 在回调接口能返回photo Setting,提供成像的信息特征,如是否启动了闪光灯等。
上图展示了自iOS 10之后,苹果新的相机架构。
AVCapturePhotoOutput结构
AVCapturePhotoOutput除了用来捕获静态图片之外,还可以捕获带RAW格式的图片,Live Photo,多图捕获,宽色域捕获(目前貌似只有iPad支持,埋个坑,后续可以继续研究下)。其类结构组织方式大体如下:
通过上面两张图我们可以看出AVCapturePhotoOutput的组织结构十分清晰,大体上分为:
- 可读属性:其主要是用来反馈当前设备在捕获上支持哪些功能,如 Live Photo;
- 功能属性:其主要是在判断是否支持某项功能后,用来决定是否开启某项功能,默认都是关闭的,如 Live Photo;
- 功能方法:主要是一些常用的初始化方法,最关键的就是capturePhoto(with:delegate:)。
AVCapturePhotoCaptureDelegate捕获周期
我们知道AVCapturePhotoOutput是以delegate的方式回调图片,delegate的类型是AVCapturePhotoCaptureDelegate,这有利于开发者管理好图片生成的生命周期,比如外部根据回调的生命周期进行UI上动画控制,那么我们来研究下图片回调的生命周期
上图乍一眼看上去,可能大家会很懵逼,让我娓娓道来,当我们按下拍照的那一刻起:
- 系统首先会回调photoOutput(_:willBeginCaptureFor:)方法,这里是告诉调用者即将开始拍照了,同时能拿到一个AVCaptureResolvedPhotoSettings类型的实例对象resolvedSettings,resolvedSettings会伴随整个生命周期存在,它存在的目的是为了告诉调用者是否真的使用了某项功能,比如在拍照的时候我们设置了flashMode = auto,那拍的时候flashMode是on还是off,由它来告诉我们。除此之外还有其他信息,这里不展开描述了;
- 接着回调photoOutput(_:willCapturePhotoFor:)方法,这一刻即将完成快门的闭合和声音的发出;
- 紧接着经过一小段时间的等待,回调photoOutput(_:didFinishProcessingPhoto:error:)方法,表示图片处理完,可以拿到图片数据;
- 最后回调photoOutput(_:didFinishCaptureFor:error:),代表整个拍照流程结束,可以将捕获到的数据存入相册。
这里有几点细节需要我们注意:
- AVCapturePhotoCaptureDelegate提供了完善的生命周期状态供开发者使用,开发者可以将其反馈到UI上,使用户体验更加良好;
- 回调的方法都是可选的;
- 回调的具体方法,同调用capturePhoto(with:delegate:)传入的AVCapturePhotoSettings息息相关,比如使用Live Photo等;
- 所有回调方法都带了AVCaptureResolvedPhotoSettings,记录了当前拍照的属性和唯一id,在多摄上可以用来区别不同摄像头的捕获。
分级捕获
分级捕获,英文全称叫Bracketed Capture,是iOS 8就有的API,所以在AVCapturePhotoOutput还没出世的时候就已经诞生了。分级捕获,其实际上就是在拍照的时候设置多组参数(目前支持曝光和ISO),系统会拍出多张图片,AVCapturePhotoCaptureDelegate的photoOutput(_:didFinishProcessingPhoto:error:)方法会跟着成倍回调,调用者可以使用某种算法(如HDR)对图片进行融合,得到一张令人满意的图片。
补充一下,目前在拍照的时候貌似无法启动HDR模式,这里埋个坑,后续继续深究下
分级捕获的基类是AVCaptureBracketedStillImageSettings,无法实例化,但提供了两个子类:
- AVCaptureAutoExposureBracketedStillImageSettings:用来设置不同的曝光值
- AVCaptureManualExposureBracketedStillImageSettings:用来设置不同的ISO值
接着我们来看下使用方法,主要以多组不同曝光值为例:
let bracketedStillImageSettings = [AVCaptureAutoExposureBracketedStillImageSettings.autoExposureSettings(exposureTargetBias: -1), AVCaptureAutoExposureBracketedStillImageSettings.autoExposureSettings(exposureTargetBias: 0), AVCaptureAutoExposureBracketedStillImageSettings.autoExposureSettings(exposureTargetBias: 1)]
photoSettings = AVCapturePhotoBracketSettings.init(rawPixelFormatType: 0, processedFormat: nil, bracketedSettings: bracketedStillImageSettings)
self.photoOutput.capturePhoto(with: photoSettings, delegate: photoCaptureProcessor)
调用完拍照后,AVCapturePhotoCaptureDelegate的photoOutput(_:didFinishProcessingPhoto:error:)会回调三次,我们来看下产图,从左到右曝光度分别为-1,0,1:
Live Photo
Live Photo是iOS 10推出来的功能,已经不算新了。Live Photo的本质,其实就是JPEG+MOV,其特点是:
- 视频总长是3s,即图片的前1.5s和图片的后1.5s;
- 分辨率有1440x1080和1290x960;
- 除了视频帧外,有音轨。
上图可以帮助我们更加容易理解Live Photo。
Live Photo的捕获
那么我们如何捕获Live Photo呢? 获取Live Photo是有一定的先决条件,具体条件如下:
- 需要使用AVCapturePhotoOutput的isLivePhotoCaptureSupported判断设备是否支持Live Photo;
- Live Photo只能是在AVCaptureSessionPresetPhoto模式下工作;
- 需要将AVCapturePhotoOutput的isLivePhotoCaptureEnable设置为true;
- 若存在AVCaptureMovieFileOutput,系统会自动禁用Live Photo。
使用代码组织如下:
self.photoOutput.isLivePhotoCaptureEnabled = self.photoOutput.isLivePhotoCaptureSupported
if self.photoOutput.isLivePhotoCaptureSupported {
let livePhotoMovieFileName = NSUUID().uuidString
let livePhotoMovieFilePath = (NSTemporaryDirectory() as NSString).appendingPathComponent((livePhotoMovieFileName as NSString).appendingPathExtension("mov")!)
photoSettings.livePhotoMovieFileURL = URL(fileURLWithPath: livePhotoMovieFilePath)
}
Live Photo捕获回调
前面我们说了AVCapturePhotoCaptureDelegate的方法回调会受到AVCapturePhotoSettings的影响,当有Live Photo的参与时,回调又有什么样的变化?
通过上图可以发现,在出图后到完成拍照流程的两个回调方法中间,多出了关于Live Photo的两个回调方法,它们分别是:
- photoOutput(_:didFinishRecordingLivePhotoMovieForEventualFileAt:resolvedSettings:):表示完成整段视频的记录,但此时还没写入到沙盒里;
- photoOutput(_:didFinishProcessingLivePhotoToMovieFileAt:duration:photoDisplayTime:resolvedSettings:error:):视频已经写入到沙盒内,可以读取视频。
Live Photo在使用和保存上十分简单,这得益于苹果良好的API设计规范,但存储Live Photo的时候,有两个点我们需要注意:
- 通常我们使用PhotoKit来存储图片到系统相册,需要构建一个PHAssetCreationRequest对象,Live Photo必须同图片使用同一个PHAssetCreationRequest对象,才能关联起来,否则存储不成功;
- 系统将Live Photo存储到沙盒里,但不负责删除,使用完应该按需决定是否删除。
Thumbnail
iOS 10加入的AVCapturePhotoSettings支持拍照的时候由系统生成缩略图,而不需要开发者将大图通过各种方式进行裁剪缩放,在性能上对原有的流程影响不大,我们来看下具体的使用方式代码:
if !photoSettings.__availablePreviewPhotoPixelFormatTypes.isEmpty {
photoSettings.previewPhotoFormat = [kCVPixelBufferPixelFormatTypeKey as String: photoSettings.__availablePreviewPhotoPixelFormatTypes.first!, kCVPixelBufferWidthKey as String: 160, kCVPixelBufferHeightKey as String: 160]
}
代码上使用也是十分简单,也可以设置大小,当完成拍照流程时,预览图会可以在回调的AVCapturePhoto的属性previewPixelBuffer中访问(这个需要看具体的回调版本,但本质上是和原图一起返回的)。
场景监控
场景监控相信大家对这个词一定很陌生,但顾名思义,其实就是对Camera捕获的视频流进行分析监控,目前发现苹果提供了两个监控,一个闪光灯,一个是静态图像稳定技术,后者在iOS 13已经废弃,这里也埋一个坑,后续得找下具体原因,所以我们这次以闪光灯为例。
当我们使用系统相机,将闪光灯改为auto时(off会使场景监控失效,on会一定会启动闪光灯,没有实际意义),若用手指去挡住摄像头,发现UI上会立马提示已打开闪光灯的提示,这就是苹果提供的关于闪光灯的场景监控了,当系统感知此时成像会过暗,就会暗示开发者要打开闪光灯,过亮就会关闭闪光灯。
看到这里,可能有读者说我直接将flashMode设置成auto不就可以了么?没错,可以这么做,但和场景监控的唯一区别是,用户不知道此刻拍下去是否会触发闪光灯,以及用户可能就是想不开闪光灯拍比较暗的环境等。不过这些都是比较上层的业务逻辑了,我们来看下具体怎么使用这个特性:
@objc dynamic private let photoOutput = AVCapturePhotoOutput()
if self.session.canAddOutput(self.photoOutput) {
let photoSettings = AVCapturePhotoSettings()
photoSettings.flashMode = .on
self.photoOutput.photoSettingsForSceneMonitoring = photoSettings
let flashKeyValueObservation = self.photoOutput.observe(\.isFlashScene, options: .new) { (_, change) in
guard let open = change.newValue else { return }
debugPrint("闪光灯的推荐状态:\(open ? "打开" : "关闭")")
}
}
这里有个注意点:如前面举的例子一样,flashMode需要为auto或者on才会返回监控信息,off是不会推荐闪光灯合理状态。
结语
对于AVCapturePhotoOutput的高级知识,目前先介绍到这里,其实还有几个点,这些点涉及的有点大,比如景深模式、人像模式,RAW和广色域捕获等。景深和人像后面会陆续补上,RAW最近在iOS 14的动作很大,后面是也是一个值得追踪的点,而广色域这个依赖硬件,目前有些iPad上是可以支持,至于iPhone,需要再留意下。文章若有什么描述不准和不当,欢迎指正,共同维护。