AVCapturePhotoOutput高级知识

2,758 阅读8分钟

本节内容主要涉及:

  1. AVCapturePhotoOutput基础
  2. 分级捕获
  3. Live Photo
  4. Thumbnail
  5. 场景监控

AVCapturePhotoOutput基础

从iOS 10起,苹果开始使用AVCapturePhotoOutput来替代AVCaptureStillImageOutput,相比于之前的AVCaptureStillImageOutput,我觉得有以下几个优势:

  • AVCapturePhotoOutput直接接管了所有拍照相关特性的封装、管理和启用,如 Live Photo、景深、人像;
  • 使用Delegate的方式回调图片,生命周期上更加清晰和可控,同时避免循环引用的风险;
  • 通过加入AVCapturePhotoSetttings,使得每次拍照设置之间互不影响,更具独立性;
  • 在回调接口能返回photo Setting,提供成像的信息特征,如是否启动了闪光灯等。

b9c65b62-3728-43f1-8d25-08fd42bc6bb7.png 上图展示了自iOS 10之后,苹果新的相机架构。

AVCapturePhotoOutput结构

AVCapturePhotoOutput除了用来捕获静态图片之外,还可以捕获带RAW格式的图片,Live Photo,多图捕获,宽色域捕获(目前貌似只有iPad支持,埋个坑,后续可以继续研究下)。其类结构组织方式大体如下:

通过上面两张图我们可以看出AVCapturePhotoOutput的组织结构十分清晰,大体上分为:

  • 可读属性:其主要是用来反馈当前设备在捕获上支持哪些功能,如 Live Photo;
  • 功能属性:其主要是在判断是否支持某项功能后,用来决定是否开启某项功能,默认都是关闭的,如 Live Photo;
  • 功能方法:主要是一些常用的初始化方法,最关键的就是capturePhoto(with:delegate:)。

AVCapturePhotoCaptureDelegate捕获周期

我们知道AVCapturePhotoOutput是以delegate的方式回调图片,delegate的类型是AVCapturePhotoCaptureDelegate,这有利于开发者管理好图片生成的生命周期,比如外部根据回调的生命周期进行UI上动画控制,那么我们来研究下图片回调的生命周期

截屏2021-01-08 下午5.58.15.png

上图乍一眼看上去,可能大家会很懵逼,让我娓娓道来,当我们按下拍照的那一刻起:

  1. 系统首先会回调photoOutput(_:willBeginCaptureFor:)方法,这里是告诉调用者即将开始拍照了,同时能拿到一个AVCaptureResolvedPhotoSettings类型的实例对象resolvedSettings,resolvedSettings会伴随整个生命周期存在,它存在的目的是为了告诉调用者是否真的使用了某项功能,比如在拍照的时候我们设置了flashMode = auto,那拍的时候flashMode是on还是off,由它来告诉我们。除此之外还有其他信息,这里不展开描述了;
  2. 接着回调photoOutput(_:willCapturePhotoFor:)方法,这一刻即将完成快门的闭合和声音的发出;
  3. 紧接着经过一小段时间的等待,回调photoOutput(_:didFinishProcessingPhoto:error:)方法,表示图片处理完,可以拿到图片数据;
  4. 最后回调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;
  • 除了视频帧外,有音轨。

截屏2021-01-08 下午8.04.20.png 上图可以帮助我们更加容易理解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的参与时,回调又有什么样的变化?

截屏2021-01-08 下午8.11.58.png

通过上图可以发现,在出图后到完成拍照流程的两个回调方法中间,多出了关于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,需要再留意下。文章若有什么描述不准和不当,欢迎指正,共同维护。