捕获照片-AVCapturePhotoOutput

在iOS 10之前,自定义相机一般使用AVCaptureStillImageOutput实现。但是AVCaptureStillImageOutput在iOS 10以后已经被弃用了。所以我们使用AVCapturePhotoOutput来实现。AVCapturePhotoOutput的功能自然会更加强大,不仅支持简单的JPEG图片拍摄,还支持Live照片和RAW格式拍摄。AVCapturePhotoOutput是一个功能强大的类,在新系统中也不断有新的功能接入,比如iOS11支持双摄和获取深度数据,iOS 12支持人像模式等。

简单使用

1. 首先初始化,按照需要参数初始化就行了,和AVCaptureStillImageOutput差别不大.

    self.imageOutput = [[AVCapturePhotoOutput alloc] init];
    NSDictionary *setDic = @{AVVideoCodecKey:AVVideoCodecJPEG};
    _outputSettings = [AVCapturePhotoSettings photoSettingsWithFormat:setDic];
    [self.imageOutput setPhotoSettingsForSceneMonitoring:_outputSettings];
    [self.session addOutput:self.imageOutput];
    self.preview = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
    [self.preview setVideoGravity:AVLayerVideoGravityResizeAspectFill];
    [self.preview setFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
    [self.layer addSublayer:self.preview];
    [self.session startRunning];

2. 实现照片获取

AVCaptureStillImageOutput中使用captureStillImageAsynchronouslyFromConnection在block中直接可以获取到图片,AVCapturePhotoOutput需要实现AVCapturePhotoCaptureDelegate协议,在协议中获取。
获取当前屏幕图片输出:

[self.imageOutput capturePhotoWithSettings:_outputSettings delegate:self];

实现AVCapturePhotoCaptureDelegate协议,并在didFinishProcessingPhotoSampleBuffer方法中获取图片:

- (void)captureOutput:(AVCapturePhotoOutput *)captureOutput didFinishProcessingPhotoSampleBuffer:(nullable CMSampleBufferRef)photoSampleBuffer previewPhotoSampleBuffer:(nullable CMSampleBufferRef)previewPhotoSampleBuffer resolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings bracketSettings:(nullable AVCaptureBracketedStillImageSettings *)bracketSettings error:(nullable NSError *)error {
    
    NSData *data = [AVCapturePhotoOutput JPEGPhotoDataRepresentationForJPEGSampleBuffer:photoSampleBuffer previewPhotoSampleBuffer:previewPhotoSampleBuffer];
    UIImage *image = [UIImage imageWithData:data];
    
    UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
}

使用步骤

不管是拍摄静态照片,还是拍摄动态照片,AVCapturePhotoOutput的使用都有一个固定的形式:

  1. 在捕捉会话启动前创建并设置AVCapturePhotoOutput对象
  2. 创建并配置AVCapturePhotoSettings对象以选择特定拍摄的功能和设置
  3. 调用capturePhotoWithSettings:delegate:方法进行操作

capturePhotoWithSettings:delegate:方法需要的两个参数分别是拍照方式设置和拍照过程代理。

注意:
1 由于AVCapturePhotoOutput已经包含AVCaptureStillImageOutput的功能,因此它们不能同时使用。
2 由于动态照片其实是一段小小的视频,因此AVCapturePhotoOutput不支持同时进行动态照片和电影文件输出。如果AVCaptureSession包含AVCaptureMovieFileOutput对象,则livePhotoCaptureSupported属性将变为NO。作为替代方案,可以使用AVCaptureVideoDataOutput类与动态照片拍摄相同的分辨率输出视频数据。

AVCapturePhotoSettings

AVCapturePhotoSettings用于选择在AVCapturePhotoOutput中特定拍摄的功能和设置。
常用属性:

  • format: 类似于AVCaptureStillImageOutput的outputSettings属性,用于使用键值对配置。例如配置视频编码@{AVVideoCodecKey: AVVideoCodecJPEG}
  • flashMode: 闪光灯模式
  • autoStillImageStabilizationEnabled: 自动静态图片稳定(防抖)
  • highResolutionPhotoEnabled: 指定输出摄像头支持的最高质量图像
  • livePhotoMovieFileURL: 动态照片保存路径

苹果规定,一次拍照操作对呀一个AVCapturePhotoSettings实例,因此在AVCapturePhotoSettings中有一个uniqueID属性用于区分是否重用。所以每次拍照之前,我们都要按照需要重新创建一个AVCapturePhotoSettings实例对象

AVCapturePhotoCaptureDelegate

AVCapturePhotoCaptureDelegate流程

在AVCaptureStillImageOutput中我们是直接在闭包中获取静态照片,并没有一个位置供我们告诉用户当前拍照过程。Apple推出AVCapturePhotoCaptureDelegate为开发者提供提高用户体验的位置,使用AVCapturePhotoOutput拍照将会经过以下五个步骤:

  1. 拍照配置的确定: willBeginCapture
  2. 开始曝光: willCapturePhoto
  3. 曝光结束: didCapturePhoto
  4. 拍照结果返回: didFinishProcessingPhoto
  5. 拍照完成: 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) {
    NSData *data = [AVCapturePhotoOutput JPEGPhotoDataRepresentationForJPEGSampleBuffer:photoSampleBuffer previewPhotoSampleBuffer:previewPhotoSampleBuffer];
    UIImage *image = [UIImage imageWithData:data];
}

拍摄动态图片

是否支持 live Photo
//当前是否支持动态照片拍摄。
@property(nonatomic, readonly, getter=isLivePhotoCaptureSupported)
BOOL livePhotoCaptureSupported;

并非所有设备和捕获格式都支持动态照片拍摄。如果当前AVCaptureSession的sessionPreset属性或AVCaptureDevice的activeFormat属性发生更改,则此属性的值可能会更改。如果相机或格式更改导致此属性的值变为NO,则livePhotoCaptureEnabled属性的值也将变为NO。

硬件要求: iPhone 6s及以上机型
像素要求:不支持 AVCaptureSessionPresetHigh

是否拍摄 live Photo

是否拍摄动态照片;在AVCapturePhotoSettings的livePhotoMovieFileURL属性为非nil、启动照片拍摄之前,必须启用此选项。启用此选项后,可以随时发起动态照片和静态照片的拍摄请求。

@property(nonatomic, getter=isLivePhotoCaptureEnabled)
BOOL livePhotoCaptureEnabled;

拍摄动态照片要求AVCaptureSession以不同方式设置其内部渲染管道。如果打算完全拍摄动态照片,在调用AVCaptureSession的 -startRunning方法之前将此属性设置为YES。在AVCaptureSession运行时更改此属性需要对捕获渲染管道进行冗长的重新配置:正在进行的动态照片捕获将立即结束,未实现的照片请求将中止,视频预览将暂时冻结。

在会话启动之前必须设置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];
}
是否暂停 live Photo
//是否暂停但不禁用动态照片拍摄
@property(nonatomic, getter=isLivePhotoCaptureSuspended)
BOOL livePhotoCaptureSuspended;

使用此属性可以缩短当前正在进行的动态照片动画捕捉:例如,如果突然需要执行一些不希望在动态照片电影中显示的内容,例如拍摄快照的静态照片声音。

默认情况下,此属性的值为NO。当将值更改为YES时,正在进行的任何动态照片拍摄将被剪裁到当前时间。同样,当此属性的值从YES更改为NO时,后续的动态照片拍摄将不会包含任何比取消暂停的动态照片拍摄更早的示例。

如果livePhotoCaptureEnabled属性的值为NO,则将此属性设置为YES会引发异常NSInvalidArgumentException。

是否自动修剪 live Photo

是否自动修剪动态照片拍摄以避免过度移动;当livePhotoCaptureSupported为YES时,此属性的值也默认为YES。

@property(nonatomic, getter=isLivePhotoAutoTrimmingEnabled)
BOOL livePhotoAutoTrimmingEnabled;

使用此选项可启用“相机”应用中的相同自动修剪行为。默认情况下,动态照片拍摄持续时间约为三秒:以拍摄请求的时间为中心。 但是,如果用户在拍摄期间升高或降低相机,iOS会实时分析电影捕捉并自动修剪实时照片的持续时间,以避免捕获过多的移动。

启用或禁用此功能要求AVCaptureSession以不同方式设置其内部呈现管道。为获得最佳结果,在调用AVCaptureSession的 -startRunning方法之前更改此属性的值。会话运行时更改此属性需要对捕获渲染管道进行冗长的重新配置:正在进行的动态照片捕获将立即结束,未实现的照片请求将中止,视频预览将暂时冻结。

AVCapturePhotoCaptureDelegate 调用过程

  1. willBeginCapture:设置完毕
  2. willCapturePhoto:开始曝光
  3. didCapturePhoto:结束曝光
  4. didFinishProcessingPhoto:静态照片获取位置
  5. ※ didFinishRecordingLivePhotoMovie:结束动态照片拍摄
  6. ※ didFinishProcessingLivePhotoToMovieFile:动态照片结果处理(获取影片文件进行处理)
  7. didFinishCaptureForResolvedSettings:完成拍摄全过程

动态照片获取

Live Photo 是由一个图片和一个小视频组成的,即两个文件(.jpg+.mov)。因此我们需要在AVCapturePhotoCaptureDelegate中的didFinishProcessingPhoto获取静态照片,在didFinishProcessingLivePhotoToMovieFile中获取小视频。最后,我们将他们保存到相册的时候需要一起写入,系统会帮我们合成动态图片的。

获取格式化输出数据

1. 获取JPEG格式的数据
+ (NSData *)JPEGPhotoDataRepresentationForJPEGSampleBuffer:(CMSampleBufferRef)JPEGSampleBuffer
previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer;

返回与指定样本缓冲区中捕获的照片对应的JPEG格式的数据;
该方法的两个参数:

  • JPEGSampleBuffer:包含JPEG照片捕获结果的样本缓冲区,用于格式化输出。
  • previewPhotoSampleBuffer:包含照片捕获结果的预览分辨率版本的可选附加样本缓冲区,将作为缩略图添加到JPEG输出中。传递nil以跳过将预览图像添加到输出。
  • 返回值:包含所请求的照片捕获结果的JPEG表示的数据对象,如果无法打包样本缓冲区以进行输出,则为nil。
2. 获取DNG格式的数据
+ (NSData *)DNGPhotoDataRepresentationForRawSampleBuffer:(CMSampleBufferRef)rawSampleBuffer
previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer;

返回与指定样本缓冲区中捕获的RAW照片对应的数字负片DNG格式的数据;
该方法的两个参数:

  • rawSampleBuffer:包含RAW照片捕获结果的样本缓冲区,用于格式化输出。
  • previewPhotoSampleBuffer:包含照片捕获结果的预览分辨率版本的可选附加样本缓冲区,将作为缩略图添加到DNG输出中。传递nil以跳过将预览图像添加到输出。
  • 返回值:包含所请求的照片捕获结果的DNG表示的数据对象,如果无法打包样本缓冲区以进行输出,则为nil。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容