相机捕获的新特性
What's new in camera capture
2021年6月10日
一句话判断
AVFoundation 的相机管线在 iOS 15 拿到了几个专业级的能力——ProRes 视频录制、Cinematic 模式的景深数据、以及 AsyncSequence 封装的帧回调,做相机类 app 的开发者会非常开心。
这场 Session 讲了什么
AVFoundation 的相机捕获能力在 iOS 15 得到了全面升级。Session 覆盖了三个主要方向:视频格式扩展、景深数据增强和 API 现代化。
视频格式方面,iPhone 13 Pro 系列支持了 ProRes 视频录制。ProRes 是专业后期制作的标准格式,之前需要外接录机才能实现。现在通过 AVFoundation 可以直接录制 ProRes 422 和 ProRes 422 HQ 到 iPhone 存储中。
景深数据方面,iOS 15 引入了 Disparity Output(视差输出),这是 Cinematic 模式的核心数据。你可以获取每一帧的深度图,用于自定义的景深效果或者背景虚化。AVDepthData 现在支持更高精度的视差数据。
API 现代化方面,AVCaptureVideoDataOutput 的帧回调现在可以用 AsyncSequence 的方式消费,不再需要 delegate 回调。这让视频处理管线可以用 async/await 写成线性的代码流。
值得深挖的点
ProRes 录制的性能限制
ProRes 的数据量非常大——1080p 30fps 的 ProRes 422 大约需要 60MB/s 的写入速度。iPhone 13 Pro 的 NVMe 存储可以应付,但只有 256GB 及以上容量的机型才支持 ProRes 录制(128GB 机型限制在 1080p 30fps,4K 仅限 256GB 以上)。在 app 中需要检测设备是否支持 ProRes,不支持时回退到 H.264/H.265。
AsyncSequence 帧回调的背压控制
用 AsyncSequence 消费视频帧时,如果你的处理逻辑跟不上帧率(比如每帧要做 50ms 的图像识别),AsyncSequence 会在内部缓冲区满后丢弃旧帧。这个行为比 delegate 回调更优雅——你不会因为处理慢而积累大量未处理帧导致内存暴涨。
代码片段
// 配置 ProRes 视频录制
import AVFoundation
class ProResRecorder {
let session = AVCaptureSession()
func setupProResCapture() throws {
session.beginConfiguration()
session.sessionPreset = .inputPriority
guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else {
throw CameraError.deviceNotFound
}
let input = try AVCaptureDeviceInput(device: camera)
session.addInput(input)
let movieOutput = AVCaptureMovieFileOutput()
// 配置 ProRes 编码
let connection = movieOutput.connection(with: .video)
if let availableCodec = AVCaptureVideoCodecType.availableCodecs.first(where: { $0 == .proRes422 }) {
movieOutput.setOutputSettings(
[AVVideoCodecKey: availableCodec],
for: connection!
)
}
session.addOutput(movieOutput)
session.commitConfiguration()
}
}
enum CameraError: Error {
case deviceNotFound
}
// 使用 AsyncSequence 消费视频帧
func processVideoFrames() async {
let output = AVCaptureVideoDataOutput()
// 配置 output...
// 将 delegate 回调转换为 AsyncSequence
let frameStream = output.delegateStream()
for await sampleBuffer in frameStream {
guard let pixelBuffer = sampleBuffer.imageBuffer else { continue }
// 处理每一帧
let processedImage = processFrame(pixelBuffer)
// 如果处理太慢,下一帧会自动跳过
// 不会累积未处理帧
}
}
// AVCaptureVideoDataOutput 的 AsyncSequence 扩展
extension AVCaptureVideoDataOutput {
func delegateStream() -> AsyncStream<CMSampleBuffer> {
AsyncStream { continuation in
let delegate = FrameDelegate { buffer in
continuation.yield(buffer)
}
self.setSampleBufferDelegate(delegate, queue: DispatchQueue(label: "video"))
continuation.onTermination = { _ in
self.setSampleBufferDelegate(nil, queue: nil)
}
}
}
}
class FrameDelegate: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate {
let handler: (CMSampleBuffer) -> Void
init(handler: @escaping (CMSampleBuffer) -> Void) {
self.handler = handler
}
func captureOutput(_ output: AVCaptureVideoDataOutput,
didOutput sampleBuffer: CMSampleBuffer,
from connection: AVCaptureConnection) {
handler(sampleBuffer)
}
}
// 获取 Cinematic 模式的深度数据
func captureDepthData() {
let output = AVCaptureDepthDataOutput()
output.isFilteringEnabled = false
// 设置深度数据格式为视差图(Disparity)
// 提供更高精度的深度信息
let session = AVCaptureSession()
// 配置 session...
}
最佳实践
ProRes 文件体积巨大,录制前检查可用存储空间。一个 5 分钟的 4K ProRes 片段可能超过 10GB。建议在录制开始前用 URL.resourceValues(forKeys:) 检查剩余空间,不足时提示用户。
使用 AsyncSequence 处理帧时,在 for await 循环中加入 Task.isCancelled 检查。当用户停止录制时,外部 Task 被取消,循环应该及时退出而不是处理完缓冲区中的所有帧。
还有什么值得关注
AVCaptureDevice新增了isMultiCamSupported属性,可以检查设备是否支持多摄像头同时录制。- iOS 15 的照片捕获管线支持「零快门延迟」(Zero Shutter Lag),通过预缓冲机制实现按下即拍。
AVCapturePhoto现在可以同时输出 JPEG 和 HEIF 格式。