Build a responsive camera app that launches quickly
Graphics, Games & Media 进阶 25m

构建灵敏还秒开的相机 App

Build a responsive camera app that launches quickly

2026年6月10日

在 Apple 官方观看视频

一句话判断

这场 Session 最值得关注的一件事是:Apple 终于官方下场解决了 AVCaptureSession 启动慢的顽疾,全新的 Deferred Start(延迟启动)API 让相机首帧预览时间大幅缩短,以前靠 Hack 实现的“秒开”现在成了标准操作。

这场 Session 讲了什么

做相机或扫码 App 的兄弟肯定懂,AVCaptureSession 启动时初始化那些沉重的 AVCaptureOutput(比如 AVCapturePhotoOutput)极其耗时,导致首帧预览慢半拍,用户经常错过精彩瞬间。Apple 这次直接把启动流程拆成了两半:预览层先跑起来,拍照和录像的 Output 延后初始化。

配合全新的 AVProVideoStorage API,高码率视频(像 ProRes)写入时的 I/O 抖动问题也迎来了终结。这套组合拳对重度依赖相机的 App(社交、直播、扫码、专业摄影)影响巨大,不仅能显著降低启动耗时,还能彻底消灭录制高规格视频时的掉帧隐患。

值得深挖的点

Deferred Start:从野路子到正规军

以前为了让相机秒开,开发者没少折腾。常见的 Hack 是先 add 一个 PreviewLayer 启动 Session,等首帧出来了,再 beginConfiguration 把 PhotoOutput 加进去。这种做法不仅代码恶心,还容易引发状态机混乱和画面闪烁。Apple 这次推出的 Deferred Start API 算是把这条野路子招安了。通过把 isDeferredStartEnabled 设为 true,系统会在首帧渲染后自动或手动接管剩余 Output 的初始化。

这个设计的 Trade-off 很明显:首帧确实快了,但如果用户是个急性子,在预览刚出来、PhotoOutput 还没初始化完的零点几秒内狂点快门,就会没反应。Apple 的解法是配合 isResponsiveCaptureEnabled(响应式拍摄),或者在手动模式下,利用 CAMetalLayeraddPresentedHandler 精准捕获首帧上屏时机,再触发 runDeferredStartWhenNeeded()。这比以前的盲猜延迟靠谱太多了。

AVProVideoStorage:给 ProRes 录制吃下定心丸

拍过 ProRes 视频的都知道,这玩意儿码率极高,对磁盘 I/O 要求极其苛刻。如果系统后台刚好在索引文件或者做其他 I/O 密集操作,写入速度一掉,视频就直接断流或掉帧。AVProVideoStorage 就是为了解决这种“不确定性”而生的。它通过预留连续存储空间和独占 I/O 通道,提供确定性的写入带宽。

这个 API 好就好在它把底层文件系统的脏活全包了。你只需要检查 remainingCapacityisBusy,然后把 movieOutput.usesProVideoStorage 设为 true。坑在于它不是万能的,如果设备存储空间本身就碎片化严重,或者容量见底,这个 API 也救不回来,所以一定要做好 fallback 到普通写入的逻辑。

代码片段

1. 自动模式 Deferred Start

场景:绝大多数相机 App 的无脑接入方案,让系统自己决定何时初始化剩余 Output。

let captureSession = AVCaptureSession()
captureSession.beginConfiguration()
// 开启自动延迟启动,iOS 26 编译默认就是 true
captureSession.automaticallyRunsDeferredStart = true

// 预览层不需要延迟,必须马上出来
let videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
videoPreviewLayer.isDeferredStartEnabled = false

// 拍照 Output 比较重,延迟初始化
let photoOutput = AVCapturePhotoOutput()
photoOutput.isDeferredStartEnabled = true
captureSession.addOutput(photoOutput)

captureSession.commitConfiguration()
captureSession.startRunning()

坑:自动模式下,系统可能会在你意想不到的时候触发初始化,导致短暂的 CPU 飙升,如果此时你在做复杂的 UI 动画,可能会卡顿。

2. 手动模式精准触发 Deferred Start

场景:对启动流程有极致控制欲的 App,结合 Metal 首帧回调,确保预览上屏后再初始化 Output。

// 初始化时关闭自动模式
captureSession.automaticallyRunsDeferredStart = false

private var firstFramePresented = false

// 在 Metal 渲染循环中
guard let drawable = layer.nextDrawable() else { return }
if !firstFramePresented {
    drawable.addPresentedHandler { _ in
        // 首帧已经上屏,此时初始化剩余 Output 最安全
        captureSession.runDeferredStartWhenNeeded()
    }
    firstFramePresented = true
}

坑:addPresentedHandler 是在渲染线程回调的,runDeferredStartWhenNeeded() 内部会处理线程切换,但千万别在这个闭包里直接更新 UIKit。

3. 启用专业视频存储 (AVProVideoStorage)

场景:录制 ProRes 等超高码率视频,防止 I/O 抖动导致掉帧。

guard AVProVideoStorage.isSupported, 
      let pvs = AVProVideoStorage.shared else { return }

// 检查剩余容量和是否被占用
guard pvs.remainingCapacity > 0, !pvs.isBusy else {
    // 容量不足或被占用,降级到普通写入或提示用户
    return 
}

let movieOutput = AVCaptureMovieFileOutput()
// 开启专业存储,AVAssetWriter 也支持这个属性
movieOutput.usesProVideoStorage = true 

坑:AVProVideoStorage 是全局单例,同一时间只能有一个录制任务使用它。多机位或者后台有录制任务时,一定要先检查 isBusy

最佳实践

已有项目迁移时,最快的方式是直接升级到 iOS 26 SDK 并重新编译。Apple 把 automaticallyRunsDeferredStart 默认设为了 true,只要你没有写死复杂的 Session 状态机,通常能白嫖到启动速度的提升。

新项目建议直接采用手动模式(Manual Deferred Start)。把 UI 也分两阶段:第一阶段只渲染取景框和快门,第二阶段(首帧上屏后)再淡入相册缩略图、滤镜选择器等重型 UI。

实战中最容易踩的坑是主线程阻塞。AVCaptureSessionstartRunning()commitConfiguration() 依然是同步阻塞调用。千万别在 viewDidLoad 里直接调,老老实实 dispatch 到专门的 sessionQueue 串行队列里执行。

还有什么值得关注

  • hardwareCost 属性:在 startRunning 前检查这个值,如果大于 1.0 说明当前配置超出了硬件能力,可以直接降级配置,避免启动失败。
  • isResponsiveCaptureEnabled:配合 Deferred Start 使用,让 PhotoOutput 在还没完全初始化完时也能强行抓拍,牺牲一点画质换取绝对的快门响应。
  • 系统压力监听:通过 KVO 监听 AVCaptureDevicesystemPressureState,在设备过热或内存紧张时动态降低帧率或分辨率,比直接崩溃强得多。
AVFoundation AVCaptureSession Deferred Start AVProVideoStorage Camera