What's new in camera capture
Photos & Camera 进阶 26m

相机捕获的新特性

What's new in camera capture

2021年6月10日

在 Apple 官方观看视频

一句话判断

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 格式。
WWDC 2021