01 使用 AVFoundation 构建相机

  • 2019 年 11 月 27 日
  • 笔记

01 前言

本文是 iOS/Android 音视频开发专题 第十篇,该专题中项目代码将在 Github 进行托管,你可在微信公众号(GeekDev)后台回复 资料 获取项目地址。

在上篇文章 AVFoundation 框架介绍 一文中,我们简单介绍了 AVFoundation 的整体架构。在本篇文章中,我们将从一个简单的相机实例入手,从零开发一个 AVCam 相机App。

该相机应用支持捕获照片和录制一段视频。根据设备情况还支持深度数据,哑光人像(Portrait effects matte) 和实时照片捕获(Live Photos)。

运行 AVCam, 需要在 iOS13 或 更高版本的 iOS 设备,由于 XCode 无法访问设备的摄像头,因此该示例无法在 Simulator 中使用。

本期内容:

  • AVCaptureSession 创建与配置
  • 拍摄一张 Photo
  • 拍摄一张 Live Photos
  • 捕获图像深度及肖像数据
  • 录制视频文件
  • 结束语

02 AVCaptureSession 创建与配置

流程:

  • 初始化 AVCaptureSession
  • 配置 AVCamPreviewView 及 AVCaptureVideoPreviewLayer
  • 配置 AVCatpureDevice, AVCatpureDeviceInput 及 AVCatpureDeviceInput
  • 配置相机相关权限

AVCaptureSession 是负责协调沟通 AVCatpureDevice 及 AVCatpueOutput 的中间对象。 AVCaptureSession 从摄像头和麦克风 AVCatpureDevice 设备中接收采集到的输入数据,并将数据发送至 AVCatpueOutput ,最终生成一张照片或者视频文件。

在 AVCam 示例 AVCamCameraViewController 中 viewDidLoad 方法中,我们首先创建了一个 AVCaptureSession。

/ Create the capture session.self.session = [[AVCaptureSession alloc] init];

如果将采集到的画面显示屏幕上,我们需要可以通过两种方式。

一种为 AVCaptureVideoPreviewLayer 设置一个 AVCaptureSession ,iOS 系统自动会将采集到的画面输出到 View 中。

另外一种方式是通过为 AVCaptureSession 添加 AVCaptureVideoDataOutput , AVCaptureVideoDataOutput 可以将采集到数据 CMSampleBufferRef 输出给客户端,我们可以通过 OpenGL ES 将画面渲染到视图上。

AVCaptureVideoDataOutput 的控制粒度更精细,我们可以在渲染到屏幕之前,对 CMSampleBufferRef 进行处理。后边我们介绍 GPUImage 时会介绍该部分内容。

AVCaptureVideoPreviewLayer 是 CALayer 的子类,可用于显示 AVCaptureSession采集到的视频画面。

为 AVCaptureVideoPreviewLayer 的 session 属性设置 AVCaptureSession 实例,就建立了两者的关系。

在 AVCam 项目中我们使用了另外一种创建 AVCaptureVideoPreviewLayer 的方式 ,就是为我们自定义的 AVCamPreviewView 视图指定 layerClass 。这种方式方便我们进行页面布局。

03 AVCaptureSession 添加视频采集设备

为 configureSession 方法中, [self.session begconfiguration] 用于将多个配置转为原子更新,当你为 AVCaptureSession 添加或删除 input , output 或配置其他参数时,并不会立即生效,直到客户端调用 [session commitConfiguration] 时才会提交到 AVCaptureSession 中。如果 begconfiguration 和 commitConfiguration 存在嵌套,仅当最外层调用时才会被应用。begconfiguration , commitConfiguration 必须成对出现。

   self.session.sessionPreset = AVCaptureSessionPresetPhoto;

通过 sessionPresent 可以设置相机采集分辨率,该配置可以在相机运行时动态设置。在 AVCam 实例中我们取值为 AVCapturePresetPhoto,系统会分配一个最佳的分辨率。

sessionPresent 是一个枚举值,除了 AVCapturePresetPhoto 外,我们还可以指定以下选项:

  • AVCaptureSessionPresetLow 低质量.
  • AVCaptureSessionPresetMedium 中等质量.
  • AVCaptureSessionPresetHigh 高质量
  • AVCaptureSessionPresetPhoto
  • AVCaptureSessionPresetInputPriority
  • AVCaptureSessionPreset960x540
  • AVCaptureSessionPreset1280x720
  • AVCaptureSessionPreset1920x1080
  • AVCaptureSessionPreset3840x2160
  • AVCaptureSessionPreset320*480
  • AVCaptureSessionPreset640x480
  • AVCaptureSessionPreset652x288

AVCaptureSessionPresetInputPriority 是不是一脸懵逼? 从 iOS 7 开始,在特定的设备硬件中,iOS 支持高帧率视频采集(被称为 Slomo 视频)。

在之前采集的视频帧率一般最高在 30fps, 高帧率视频采集支持 50fps 60fps 120fps… 。在低帧率采集模式下,通过 session.sessionPresent 和 AVCaptureDevice 的 setActiveVideoMinFrameDuration: / setActivityVideoMaxFrameDuration: 可以别设置采集分辨率及帧率。高帧率下 Apple 弃用了这种方式,要求我们为 AVCaptureDevice 指定合适的 activeFormat 格式。如果你通过 activeFormat 的方式设置采集配置,之前的 sessionPresent 将会被重置为 AVCaptureSessionPresetInputPriority。

高帧率视频的具体用法 ,在 AVCam 项目中我们会涉及到。目前 AVCam 中 sessionPresent 被设置为 AVCaptureSessionPresetHigh。

现在 AVCaptureSession 已经被创建完成,紧接着我们的任务是为它添加具体的采集输入设备 (AVCaptureDevice)。

在之前的文章中我们已经知道,AVCaptureDevice 是一个抽象类,每个具体的示例都会对应一个设备,例如摄像机或麦克风。

AVCaptureDevice 的创建有两种方式,第一种是通过 AVCaptureDevice 提供的类方法,另外一种是通过 AVCaptureDeviceDiscoverySession 提供的类方法。

在这里我们暂且使用 AVCaptureDevice 创建,完整的函数签名如下:

[AVCaptureDevic deviceWithDeviceType:AVCaptureDeviceType mediaType:AVMediaType position:AVCaptureDevicePosition];

AVCaptureDeviceType: 采集设备类型

  • AVCaptureDeviceTypeBuiltInMichrophone 麦克风设备
  • AVCaptureDeviceTypeBuiltInWideAngleCamera 内置广角相机设备
  • AVCaptureDeviceTypeBuiltInTelephotoCamera 内置的相机设备 焦距比广角相机长。注意: 此类设备只能使用 AVCaptureDeviceDiscoverySession 发现
  • AVCaptureDeviceTypeBuiltInUltraWideCamera 比广角相机焦距短的内置相机设备。注意: 此类设备只能使用 AVCaptureDeviceDiscoverySession 发现
  • AVCaptureDeviceTypeBuiltInDualCamera 一种由两个固定的焦距照相机组成的设备。一个是广角镜头(Wide),一个是远摄镜头 (Telephoto)。
  • AVCaptureDeviceTypeBuiltInDualWideCamera 一种由两个固定焦距照相机组成的设备。一个是超宽镜头(Ultra Wide),一个是广角镜头(Wide Angle)。
  • AVCaptureDeviceTypeBuiltInTripleCamera 一种有三个固定焦距照相机组成的设备。一个超宽镜头(Ultra Wide),一个广角镜头(Wide Angle)和一个远摄镜头(Telephoto)组成。
  • AVCaptureDeviceTypeBuiltInTrueDepthCamera 一种由两台摄像机组成的设备。一台 YUV 和一台红外线。红外线摄像头可提供高质的深度信息,该信息可与 YUV 摄像头产生的帧同步并进行透视纠正。两台摄像头的分辨率可能不通透,但他们的相同的纵横比。

AVMediaType: 支持的媒体类型

  • AVMediaTypeVideo 视频
  • AVMediaTypeAudio 音频
  • AVMediaTypeText 文本
  • AVMediaTypeSubtitle 字幕
  • AVMediaTypeMetadata 元数据

AVCaptureDevicePosition: 支持摄像头位置

  • AVCaptureDevicePositionUnspecified 未指定
  • AVCaptureDevicePositionBack 后置
  • AVCaptureDevicePositionFront 前置

AVCaptureDevice 创建完成后,根据图示我们还缺一个 AVCatpureDeviceInput。

AVCatpureDeviceInput 同样为我们提供提供了类方法。

[AVCatpureDeviceInput deviceInputWithDevice:AVCaptureDevice error:NSError];

接下来就是将 AVCaptureDeviceInput 添加到 AVCaptureSession 中,AVCaptureDeviceInput 可以从指定的 AVCatpureDevice 采集媒体数据并交由 AVCaptureSession。

AVCaptureSession 关联采集设备

在添加设备之前,我们有必要看看 AVCatpureSession 的详细接口。

  • canSetSessionPreset:(AVCaptureSessionPreset)preset 是否支持该 preset
  • canAddInput:(AVCaptureInput *)input 是否可以添加指定的采集输入设备
  • addInput:(AVCaptureInput *)input 添加采集输入设备
  • removeInput:(AVCaptureInput *)input 移除指定的采集输入设备
  • canAddOutput:(AVCaptureOutput *)output 是否可以添加指定的输出接口
  • addOutput:(AVCaptureOutput *)output 添加采集输出接口
  • removeOutput:(AVCaptureOutput *)output 移除指定的输出接口
  • startRuning 启动采集会话,开始采集并输出
  • stopRuning 停止采集会话

为 AVCatpureSession 添加采集设备,需使用 addInput:(AVCaptureInput *)input 函数。

添加完成后, 使用 startRuning 启动采集会话,就可以看到相机捕获的画面。

可是这里我们没有指定 output 啊?? 不知道你记得 AVCaptureVideoPreviewLayer, previewLayer 内部维护的就是一个 Ouput,获取到相机数据后,渲染到视图上。后边我们录制视频时,会涉及到 Ouput。

04 配置相机权限

配置权限千万不要忘记,需要我们在 plist 中配置相关说明。

还需要在启动相机之前,让用户授权。

当用户授权完成后,使用 [session startRuning] 启动相机采集。

完整代码可以参考 AVCam 项目。

05 结束语

关注 GeekDev 公众号你将在第一时间获取最新内容。