静态图片和视频捕捉

通过输入 (inputs) 和输出 (outputs) 对象来对采集设备 (比如摄像头或麦克风) 进行管理. 使用 AVCaptureSessionarrow-up-right 对象协调 inputs 和 outputs 之间的数据.

使用 CALayer 的子类 AVCaptureVideoPreviewLayerarrow-up-right, 可以展示摄像头采集的画面预览.

对于一个 session, 可以配置多个 inputs 和 outputs, 如图所示:

对于大部分的应用而言, 这已经足够了. 但是有些情况下, 会涉及到如何表示一个 inputs 的多个端口 (ports), 以及这些 ports 如何连接到 outputs.

Capture session 中使用 AVCaptureConnectionarrow-up-right 表示 inputs 和 outputs 之间的连接. 一个 Inputs 包含一个或多个 input ports(AVCaptureInputPortarrow-up-right). Outputs 可以从一个或多个来源接收数据, 比如 AVCaptureMovieFileOutputarrow-up-right 可以同时接收视频和音频数据.

如下图所示, 当在 session 中添加一个 input 或 output 时, session 会为所有可匹配的 inputs 和 outputs 之前生成 connections(AVCaptureConnectionarrow-up-right).

可以使用一个 connection 来开启或关闭一个 input 或 output 数据流. 也可以使用 connection 监控一个 audio 频道的码率平均值和峰值.

注意: 媒体捕捉不支持模拟器, 也不能同时使用 iOS 设备上的前置摄像头和后置摄像头进行捕捉

使用Capture Session协调数据流

数据采集管理的核心是 AVCaptureSessionarrow-up-right. 在 session 中添加采集设备并对 output 进行配置之后, 可以向 session 发送 startRunningarrow-up-right 消息开始采集, 发送 stopRunningarrow-up-right 消息停止采集.

配置 Capture Session

使用 session 的sessionPreset属性指定图片质量和分辨率:

  • AVCaptureSessionPresetHigh: 高分辨率, 最终效果根据设备不同有所差异

  • AVCaptureSessionPresetMedium: 中等分辨率, 适合 Wi-Fi 分享. 最终效果根据设备不同有所差异

  • AVCaptureSessionPresetLow: 低分辨率, 适合 3G 分享, 最终效果根据设备不同有所差异

  • AVCaptureSessionPreset640x480: 640x480, VGA

  • AVCaptureSessionPreset1280x720: 1280x720, 720p HD

  • AVCaptureSessionPresetPhoto: 全屏照片, 不能用来作为输出视频

在设置一个 preset 之前, 需要判断设备是否支持该 preset 值:

如果需要设置一个更高分辨率的 preset, 或者在 session 运行时修改一些配置, 需要在 beginConfigurationarrow-up-rightcommitConfigurationarrow-up-right 之间完成修改. beginConfigurationarrow-up-rightcommitConfigurationarrow-up-right 方法确保所有的修改被整体应用, 减少对预览状态的影响. 在调用 beginConfigurationarrow-up-right 之后, 可以添加或移除一个 output, 修改sessionPreset属性, 或者单独配置 input 和 output 的属性. 只有调用了 commitConfigurationarrow-up-right 方法, 改变才会生效.

监测 Capture Session 的状态

可以使用通知 (NSNotification) 监测 session 的状态, 并且所有的通知都在主线程中发送. 注册监听 AVCaptureSessionRuntimeErrorarrow-up-right 通知可以捕捉运行时发生的错误. 也可以使用 session 的running属性判断当前的运行状态, interrupted属性则可以判断当前是否中断. 此外, runninginterrupted属性都可以通过 KVO 进行监听.

使用AVCaptureDevice表示输入设备

AVCaptureDevicearrow-up-right 是对实际的物理捕捉设备的抽象, 物体捕捉设备向AVCaptureSession提供数据. 每个AVCaptureDevice对象代表一个实际的输入设备, 例如前摄像头或后摄像头, 或麦克风.

使用AVCaptureDevice类的 devicesarrow-up-rightdevicesWithMediaType:arrow-up-right 方法可以获取当前可用的捕捉设备. 而且可以获取捕捉设备的设备特性 (参见 Device Capture Settingsarrow-up-right). 当前的可用设备的状态可能会发生改变, 当前使用的输入设备可能会变为不可用状态 (如果设备被另外一个应用使用), 也可能会有新的设备变为可用状态 (被其他应用释放). 注册接收AVCaptureDeviceWasConnectedNotificationAVCaptureDeviceWasDisconnectedNotification通知可以得知可用设备列表的变化.

设备特性

可以获取一个设备的设备特性. 也可以通过 hasMediaType:arrow-up-right 方法判断设备是否支持特定媒体类型的捕捉, 通过 supportsAVCaptureSessionPreset:arrow-up-right 判断设备是否支持特定的分辨率. 当要提供一个可用的捕捉设备列表给用户进行选择时, 获取展示出设备的位置以及名称 (比如前摄像头或后摄像头) 拥有更好的用户体验.

下图展示了前摄像头 (AVCaptureDevicePositionFront) 和后摄像头 (AVCaptureDevicePositionBack):

下面的代码遍历了所有的可用设备并打印其名称, 如果是视频设备, 则打印其位置:

此外, 还可以获取设备的 model ID 以及 unique ID.

捕捉设置

不同的设备之间存在性能差异, 比如一些设备支持特殊的对焦或闪光灯模式, 某些设备还支持兴趣点对焦.

下面的代码示例了如何找出一个支持手电筒模式和特定 preset 的设备:

如果找到了多个符合要求的设备, 你可能需要让用户选择其中的某一个设备, 这时可以使用 localizedNamearrow-up-right 属性获取设备的描述信息.

可以用类似的方式实现各种不同的捕捉设置. 框架预定义了一些常量用来代表特定的捕捉模式, 你可以使用这些常量以便于判断设备是否支持特定的模式. 在大部分情况下, 可以通过属性监听获取设备特性的变化状态. 任何情况下, 在改变设备的捕捉设置之前, 都应该先锁定设备, 详见下节设备的配置.

兴趣点对焦模式和兴趣点曝光模式是互斥的, 正如对焦模式和曝光模式也是互斥的一样

对焦模式

有三种对焦模式:

  • AVCaptureFocusModeLocked: 固定焦点

  • AVCaptureFocusModeAutoFocus: 自动对焦然后锁定焦点

  • AVCaptureFocusModeContinuousAutoFocus: 连续自动对焦

使用 isFocusModeSupported:arrow-up-right 方法判断设备是否支持给定的对焦模式, 然后设置属性 focusModearrow-up-right 改变对焦模式.

此外, 一些设备还支持兴趣点对焦模式. 通过方法 focusPointOfInterestSupportedarrow-up-right 判断是否支持该模式, 然后使用属性 focusPointOfInterestarrow-up-right 设置焦点. 无论设备是横屏 (Home 键靠右) 或竖屏模式, CGPoint{0,0}代表设备左上角, CGPoint{1,1}代表设备右下角.

属性 adjustingFocusarrow-up-right 可以用来判断当前设备是否正在对焦中. 可以使用 KVO 监听该属性获取对焦开始与结束的通知.

设置对焦模式的示例代码如下:

曝光模式

有两种曝光模式:

  • AVCaptureExposureModeContinuousAutoExposure: 自动调整曝光等级

  • AVCaptureExposureModeLocked: 固定曝光等级

使用 isExposureModeSupported:arrow-up-right 方法判断设备是否支持给定的曝光模式, 然后设置属性 exposureModearrow-up-right 改变曝光模式. 此外, 一些设备还支持兴趣点曝光模式. 通过方法 exposurePointOfInterestSupportedarrow-up-right 判断是否支持该模式, 然后使用属性 exposurePointOfInterestarrow-up-right 设置曝光点. 无论设备是横屏 (Home 键靠右) 或竖屏模式, CGPoint{0,0}代表设备左上角, CGPoint{1,1}代表设备右下角.

属性 adjustingExposurearrow-up-right 可以用来判断当前设备是否正在改变曝光设置中. 可以使用 KVO 监听该属性获取开始设置曝光模式与结束设置曝光模式的通知.

设置曝光模式的示例代码如下:

闪光模式

有三种闪光模式:

  • AVCaptureFlashModeOff: 关闭

  • AVCaptureFlashModeOn: 打开

  • AVCaptureFlashModeAuto: 根据环境亮度自动开启或关闭

使用方法 hasFlasharrow-up-right 判断一个设备是否有闪光灯. 使用方法 isFlashModeSupported:arrow-up-right 判断是否支持某个闪光模式, 使用属性 flashModearrow-up-right 设置闪光灯模式.

手电筒模式

手电筒模式下, 闪光灯会一直处于开启状态, 用于视频捕捉. 有三种手电筒模式:

  • AVCaptureTorchModeOff: 关闭

  • AVCaptureTorchModeOn: 打开

  • AVCaptureTorchModeAuto: 根据需要自动开启或关闭

使用方法 hasTorcharrow-up-right 判断一个设备是否有闪光灯. 使用方法 isTorchModeSupported:arrow-up-right 判断是否支持某个手电筒模式, 使用属性 torchModearrow-up-right 设置手电筒模式.

对于一个有手电筒的设备, 手电筒只有在设备与一个运行中的 capture session 进行了关联后才可以设置为开启.

视频稳定性

依赖于某些特殊的硬件设备, 视频会有更好设置达到电影级别的稳定性. 但并不支持所有的视频格式和分辨率.

开启电影级别的视频稳定性特性在捕捉视频时可能会增加延迟. 使用属性 videoStabilizationEnabledarrow-up-right 可以判断当前是否使用了视频稳定性特性. 属性 enablesVideoStabilizationWhenAvailablearrow-up-right 可以在设备支持的情况下自动开启视频稳定性特性, 该属性默认为关闭状态.

白平衡

有两种白平衡模式:

  • AVCaptureWhiteBalanceModeLocked: 固定参数的白平衡

  • AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance: 由相机自动调整白平衡参数

使用方法 isWhiteBalanceModeSupported:arrow-up-right 判断设备是否支持给定的白平衡模式, 然后通过属性 whiteBalanceModearrow-up-right 设置白平衡模式.

使用属性 adjustingWhiteBalancearrow-up-right 判断当前是否正在修改白平衡模式. 可以使用 KVO 监听该属性获取开始设置白平衡模式与结束设置白平衡模式的通知.

设置设备方向

可以在AVCaptureConnection上指定期望的设备方向, 用来设置输出时AVCaptureOutput(AVCaptureMovieFileOutput, AVCaptureStillImageOutputAVCaptureVideoDataOutput) 的设备方向.

使用属性AVCaptureConnectionsupportsVideoOrientation判断设备是否支持修改视频方向, 使用属性videoOrientation指定一个方向. 下面的代码将AVCaptureConnection的方向设置为AVCaptureVideoOrientationLandscapeLeft:

设备配置

要修改设备的捕捉属性, 首先需要使用方法 lockForConfiguration:arrow-up-right 锁定设备, 这样可以避免与其他应用的设置产生冲突.

切换设备

某些场景下可能需要允许用户切换输入设备, 比如前后摄像头. 为了避免卡顿, 可以重新配置正在运行的 session, 但是嵌套使用 beginConfigurationarrow-up-rightcommitConfigurationarrow-up-right 方法.

当最后的commitConfiguration方法被调用时, 所有的设置变化会一起执行, 确保了切换的流畅性.

使用AVCaptureInput添加输入设备

要把一个 capture device 添加到 capture session 中, 需要使用 AVCaptureDeviceInputarrow-up-right(抽象类AVCaptureInput的子类). Capture device input 管理设备的端口.

使用 addInput:arrow-up-right 添加输入. 使用 canAddInput:arrow-up-right 判断该设备是否可以被添加到 session 中.

一个AVCaptureInput对象包含一个或多个数据流. 例如, 输入设备可能同时提供音频和视频数据. 每个 AVCaptureInputPortarrow-up-right 对象代表一个媒体数据流. Capture session 使用一个AVCaptureConnection对象定义一组AVCaptureInputPort和一个AVCaptureOutput之间的映射关系.

使用AVCaptureOutput输出数据

要从 capture session 中输出数据, 可以向其添加一个或多个 outputs(AVCaptureOutputarrow-up-right 的子类), 你可以使用:

使用方法 addOutput:arrow-up-right 在 capture session 中添加 outputs. 使用方法 canAddOutput:arrow-up-right 判断是否可以添加一个给定的 output. 可以根据需要在 session 运行过程中添加或移除一个 output.

输出为视频文件

使用 AVCaptureMovieFileOutputarrow-up-right 将视频数据保存为一个本地文件. 可以对 movie file output 的参数进行配置, 比如最大的录制时长, 最大的录制文件大小, 如果设备磁盘空间不足的话, 还可以阻止用户进行视频录制.

输出的分辨率和码率依赖于 capture session 的sessionPreset 属性, 常用的视频编码格式是 H.264, 音频编码格式是 AAC. 实际的编码格式可能由于设备不同有所差异.

开始录制

使用方法 startRecordingToOutputFileURL:recordingDelegate:arrow-up-right 开始录制一段 QuickTime 视频, 方法中需要传入一个本地文件的 URL 和一个录制的 delegate. 传入的本地 URL 不能是已经存在的文件, 因为 movie file output 不会对已存在的文件进行重写, 而且对传入的文件路径, 程序必须有写入权限. 传入的 delegate 必须遵循 AVCaptureFileOutputRecordingDelegatearrow-up-right 协议, 且必须实现 captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:arrow-up-right 方法. 在这个代理方法中, delegate 可能会向相册写入数据, 需要对错误进行检测.

确保文件写入成功

要判断文件是否写入成功, 在 captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:arrow-up-right 方法中不仅需要检测 error, 还需要对 error 中的 user info 字典中的 AVErrorRecordingSuccessfullyFinishedKeyarrow-up-right 进行判断.

需要对AVErrorRecordingSuccessfullyFinishedKey进行判断是因为即使写入过程中抛出了一个 error, 文件也可能被成功写入了. 抛出的 error 可能是因为达到了一些设置的限制约束条件, 比如 AVErrorMaximumDurationReachedarrow-up-right, 以及 AVErrorMaximumFileSizeReachedarrow-up-right. 其他可能导致录制中断的情况如下:

在文件中添加元数据

可在任何时刻对文件的元数据 (metadata) 进行设置, 哪怕是在录制过程中. 一个 file output 的 metadata 由一个 AVMetadataItemarrow-up-right 对象的数组来表示. 可以使用其可变子类 AVMutableMetadataItemarrow-up-right 创建自定义的 metadata.

处理视频帧

AVCaptureVideoDataOutputarrow-up-right 使用代理模式来对视频帧进行处理. 使用方法 setSampleBufferDelegate:queue:arrow-up-right 设置代理, 此外还需要传入代理方法被调用的队列. 必须使用同步队列确保视频帧按照录制顺序被传递到代理方法中. 可以使用队列修改视频帧传递处理的优先级, 参见示例 SquareCamarrow-up-right.

在代理方法 captureOutput:didOutputSampleBuffer:fromConnection:arrow-up-right 中, 视频帧由 CMSampleBufferRefarrow-up-right 类型表示. 默认情况下, buffers 被设置为当前设备相机效率最高的格式. 也可以使用属性 videoSettingsarrow-up-right 自定义输出格式. videoSettings属性是一个字典类型, 目前只支持kCVPixelBufferPixelFormatTypeKey. 系统建议的视频格式可以通过属性 availableVideoCVPixelFormatTypesarrow-up-right 获取, 属性 availableVideoCodecTypesarrow-up-right 返回支持的编码格式. Core Graphics 和 OpenGL 都很好的兼容了BGRA格式.

视频处理时的性能考虑

导出视频应当尽可能的使用低分辨率, 高分辨率会消耗额外的 CPU 和电量.

确保在代理方法captureOutput:didOutputSampleBuffer:fromConnection:中处理 sample buffer 时不要使用耗时操作, 如果处理占用时间过长, AV Foundation 会停止向代理方法中传递视频帧, 而且会停止其他的输出, 比如 preview layer 上的预览.

可以设置 capture video data 的属性 minFrameDurationarrow-up-right 通过降低帧率来确保有足够的时间对视频帧进行处理. 将属性 alwaysDiscardsLateVideoFramesarrow-up-right 设置为YES(默认值) 的话, 后面的视频帧将会被丢弃, 而不是排队等待处理. 如果你并不介意延迟, 而且需要处理所有的视频帧, 也可以将alwaysDiscardsLateVideoFrames设置为NO(即使如此, 也可能会出现掉帧的情况).

捕捉静态图像

使用 AVCaptureStillImageOutputarrow-up-right 捕捉带元数据的静态图像. 图片的分辨率依赖于 session 的 preset 设置和具体的硬件设备.

像素和编码格式

不同的设备支持不同的图片格式. 可以使用 availableImageDataCVPixelFormatTypesarrow-up-right 获取设备支持的所有像素格式, 使用 availableImageDataCodecTypesarrow-up-right 可以获取设备支持的图像编码类型. 设置属性字典 outputSettingsarrow-up-right 可以指定需要的图片格式:()

如果需要的是 JPEG 图片, 则不要指定压缩格式. 相反, 应该让 still image output 进行压缩 (硬件加速). 可以使用 jpegStillImageNSDataRepresentation:arrow-up-right 将图片转换为无压缩的NSData对象.

捕捉图片

使用方法 captureStillImageAsynchronouslyFromConnection:completionHandler:arrow-up-right 捕捉图片. 第一个参数是需要捕捉的 connection, 需要判断当前的 connection 中哪个 input 正在采集视频.

方法的第二个参数是一个有两个参数的block: 一个包含图像数据的CMSampleBuffer类型, 另一个是 NSError 对象. Sample buffer 自身包含了元数据, 比如 EXIF 信息字典, 可以对这些元数据进行修改.

录制预览

可以提供给用户一个 preview, 用来展示正在通过摄像头录制的内容 (使用 preview layer), 或者正在通过麦克风记录的音频内容 (通过监听 audio channel).

视频预览

使用 AVCaptureVideoPreviewLayerarrow-up-right 可以进行视频预览. AVCaptureVideoPreviewLayerCALayer的子类. 进行视频预览不需要设置任何的 output 对象.

使用 AVCaptureVideoDataOutputarrow-up-right 类可以在视频展示给用户预览之前对视频进行处理.

与 capture output 不同, 一个 video preview layer 会强引用与其相关联的 session. 这是为了确保在进行视频预览时 session 不会被销毁.

大体上, video preview layer 的性质与CALayer类似. 你可以对图像进行缩放, 向操作其他任何 layer 一样进行 transformations, rotations 等操作. 一个不同点在于你可能需要设置 layer 的orientation属性指定如何对摄像头捕捉的图像方向进行旋转. 此外, 通过属性 supportsVideoMirroringarrow-up-right 可以判断设备是否支持预览镜像. 属性 automaticallyAdjustsVideoMirroringarrow-up-right 的默认值为YES, 但是仍然可以根据需要设置属性 videoMirroredarrow-up-right 进行修改.

视频重力模式

Preview layer 支持三种重力模式, 可以使用属性 videoGravityarrow-up-right 进行设置:

  • AVLayerVideoGravityResizeAspect: 保持视频款高比, 当视频内容不能铺满屏幕时, 不足的部分使用黑色背景进行填充.

  • AVLayerVideoGravityResizeAspectFill: 保持视频款高比, 但是会铺满整个屏幕, 必要时会对视频内容进行裁剪.

  • AVLayerVideoGravityResize: 拉伸视频内容铺满屏幕, 可能导致图像变形.

预览时使用点击聚焦功能

在 preview layer 上实现点击聚焦功能时, 需要注意视频方向, 视频重力模式以及可能设置了视频镜像. 参见代码示例 AVCam-iOS: Using AVFoundation to Capture Images and Moviesarrow-up-right.

展示声音等级

要在 capture connection 中检测声音的均值和峰值, 可以使用 AVCaptureAudioChannelarrow-up-right 对象. 声音等级不能使用 KVO 的方式获取, 所以需要根据界面更新的需求定时进行轮询 (比如每秒 10 次).

示例: 捕捉视频帧为UIImage对象

接下来的代码简单示例了如何捕捉视频, 并将捕捉到的视频帧转换为 UIImage 对象:

  • 创建AVCaptureSession对象

  • 找到合适类型的AVCaptureDevice对象进行输入

  • 为设备创建AVCaptureDeviceInput对象

  • 创建AVCaptureVideoDataOutput对象获取视频帧

  • 实现AVCaptureVideoDataOutput的代理

  • 实现一个方法将接收到的CMSampleBuffer转换为UIImage

提示: 为了展示核心代码, 这份示例省略了某些内容, 比如内存管理和通知的移除等. 使用 AV Foundation 之前, 你最好已经拥有 Cocoa 框架的使用经验.

创建和配置 Capture Session

AVCaptureSession用来协调 input 和 output 之间的数据流.

创建和配置 Device 和 Device Input

AVCaptureDevice表示捕捉设备, AVCaptureInput用来配置捕捉设备的端口

创建和配置 Video Data Output

使用AVCaptureVideoDataOutput处理未压缩的视频帧.

实现 Sample Buffer 代理方法

将转换为 UIImage 的操作代码参见 Converting CMSampleBuffer to a UIImage Objectarrow-up-right.

开始和停止录制

配置 capture session 之后, 需要确保应用有访问相机的权限.

当获取到相应的访问权限之后, 可以使用startRunning方法开始录制. startRunning会阻塞线程, 所以需要异步调用, 以免阻塞主线程.

类似的调用stopRunning可以停止录制.

Last updated