ハイライト
iOS 26 は Deferred Start API を導入し、カメラ App が写真出力や動画出力などプレビュー以外の出力初期化を、最初のプレビューフレーム描画後まで遅らせられるようにしました。Responsive Capture と組み合わせることで、起動時間を半分にしつつ、ユーザーがシャッターを押す瞬間を逃さずに済みます。
主要内容
カメラの起動が遅いと、ユーザーは何を感じるのか
カメラ App を開いたのに、画面は黒いまま。数秒待ってようやくプレビューが出ても、撮りたかった瞬間はもう過ぎています。登壇者はドミノの例でこの問題を説明しました。赤いドミノが倒れようとしている瞬間にカメラを開く。起動が遅いと、プレビューが出た時にはドミノはもう倒れています。
この痛みの裏には 4 つの段階があります。App の起動読み込み、Session の設定と起動、出力オブジェクトの初期化、プレビューフレームの流れ始めです。このうち最も時間がかかるのが 3 つ目、AVCaptureOutput の初期化です。
Deferred Start: 重い処理をプレビュー後に回す
これまでは、すべての出力が startRunning() のタイミングでまとめて初期化されていました。AVCapturePhotoOutput や AVCaptureMovieFileOutput のような重いオブジェクトは起動全体を遅くしますが、プレビューそのものには寄与しません。
Deferred Start の考え方は単純です。まずプレビュー出力だけを初期化し、他の出力は後回しにします。各 AVCaptureOutput と AVCaptureVideoPreviewLayer に新しい isDeferredStartEnabled プロパティが追加され、true にするとその出力の初期化を遅延できます。
(02:05) で登壇者は実験室での比較を示しています。Deferred Start を有効にしない場合の起動時間は約 1 秒、有効にすると約 0.5 秒まで短縮され、起動速度はほぼ 2 倍になります。Session の構成が複雑なほど効果は大きくなります。
プレビューは出たが、シャッターが反応しない場合
Deferred Start には副作用があります。プレビューは速く表示されても、写真出力の初期化がまだ終わっていないため、ユーザーがシャッターを押しても反応しないことがあります。これはスナップ撮影では致命的です。
Apple の解決策が Responsive Capture です。有効にすると、AVCapturePhotoOutput が内部にバッファを追加し、出力が完全に初期化される前からキャプチャを開始できるようになります。登壇者は再びドミノで説明しました。Responsive Capture を有効にした緑の iPhone は赤いドミノが倒れる瞬間を撮影でき、無効な紫の iPhone は逃しました。
(14:07)
継続性能: カメラを「固まった」と感じさせない
プレビューが流れ始めた後も、安定したフレームレートは同じくらい重要です。デバイスが熱くなると、システムはクロックを下げます。App がそれに適応しなければ、プレビューはフレーム落ちします。
AVCaptureSession には hardwareCost プロパティが追加されました。値は 0 から 1 で、現在の構成がどれくらいハードウェアリソースを使うかを表します。1 を超える場合、その構成はデバイス能力を超えているため、起動前に下位構成へ落とす必要があります。AVCaptureDevice の systemPressureState は、実行中にシステム圧力を監視するために使えます。圧力が上がったら、フレームレートを下げたり GPU/ANE の使用量を減らしたりします。
(20:16)
ProRes 録画の I/O 問題
ProRes のような高ビットレート動画の録画には、非常に高いディスク書き込み速度が必要です。従来のファイルシステム I/O は決定的ではありません。バックグラウンドのインデックス作成、メモリ断片化、ストレージの摩耗が書き込み速度に影響し、フレーム落ちにつながります。
iOS 27 は AVProVideoStorage を導入し、事前割り当てされたストレージ領域によって決定的な書き込み性能を提供します。これはシステムレベルの singleton で、すべての App が共有します。録画前に remainingCapacity と isBusy を確認し、usesProVideoStorage = true を設定します。
(22:17)
詳細
自動 Deferred Start
AVCaptureVideoPreviewLayer でプレビューをレンダリングする App は、iOS 26 SDK で再コンパイルすると自動モードがデフォルトで有効になります。システムはプレビュー表示後すぐに残りの出力を自動初期化します。
import AVFoundation
class DeferredStartDelegate: NSObject, AVCaptureSessionDeferredStartDelegate {
func sessionWillRunDeferredStart(_ session: AVCaptureSession) {
// 遅延初期化が始まる前に呼ばれる。ここでバックグラウンドリソースを作成できる
}
func sessionDidRunDeferredStart(_ session: AVCaptureSession) {
// 遅延初期化が完了した後に呼ばれる。すべての出力が利用可能になる
}
}
let captureSession = AVCaptureSession()
captureSession.beginConfiguration()
captureSession.automaticallyRunsDeferredStart = true
let videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
videoPreviewLayer.isDeferredStartEnabled = false
let photoOutput = AVCapturePhotoOutput()
photoOutput.isDeferredStartEnabled = true
captureSession.addOutput(photoOutput)
captureSession.setDeferredStartDelegate(
deferredStartDelegate,
deferredStartDelegateCallbackQueue: sessionQueue
)
captureSession.commitConfiguration()
captureSession.startRunning()
重要ポイント:
automaticallyRunsDeferredStartは iOS 26 SDK ではデフォルトでtrueです。videoPreviewLayer.isDeferredStartEnabled = falseにより、プレビューを優先的に初期化します。photoOutput.isDeferredStartEnabled = trueで写真出力を遅延させます。setDeferredStartDelegateで callback を登録し、遅延起動の進行状況を把握します。sessionQueueには専用の serial queue を使い、メインスレッドをブロックしないようにします。
(09:14)
手動 Deferred Start
AVCaptureVideoDataOutput でカスタムプレビューをレンダリングする App は、手動モードを使う必要があります。CAMetalLayer の addPresentedHandler で最初のフレームが画面に出た瞬間を捉え、その後に遅延初期化を開始します。
import AVFoundation
import QuartzCore
let captureSession = AVCaptureSession()
captureSession.beginConfiguration()
captureSession.automaticallyRunsDeferredStart = false
let videoOutput = AVCaptureVideoDataOutput()
captureSession.addOutput(videoOutput)
videoOutput.isDeferredStartEnabled = false
let photoOutput = AVCapturePhotoOutput()
photoOutput.isDeferredStartEnabled = true
captureSession.addOutput(photoOutput)
captureSession.setDeferredStartDelegate(
deferredStartDelegate,
deferredStartDelegateCallbackQueue: sessionQueue
)
captureSession.commitConfiguration()
captureSession.startRunning()
// Metal レンダリングループ内
private var firstFramePresented = false
guard let drawable = layer.nextDrawable() else { return }
if !firstFramePresented {
drawable.addPresentedHandler { _ in
captureSession.runDeferredStartWhenNeeded()
}
firstFramePresented = true
}
重要ポイント:
automaticallyRunsDeferredStart = falseで自動モードを無効にします。videoOutput.isDeferredStartEnabled = falseにより、カスタムプレビューを先に利用可能にします。addPresentedHandlerは最初のフレームが提示された後に callback されます。runDeferredStartWhenNeeded()は、遅延初期化を開始してよいことをシステムに伝えます。- 手動モードは、起動フローを精密に制御したい App に向いています。
(11:30)
Responsive Capture を有効にする
Responsive Capture は AVCapturePhotoOutput にバッファ層を追加し、出力が完全に初期化される前からキャプチャ処理を開始できるようにします。
import AVFoundation
func configurePhotoOutput(for session: AVCaptureSession, device: AVCaptureDevice) {
let photoOutput = AVCapturePhotoOutput()
guard session.canAddOutput(photoOutput) else { return }
session.addOutput(photoOutput)
photoOutput.maxPhotoQualityPrioritization = .quality
photoOutput.isResponsiveCaptureEnabled = photoOutput.isResponsiveCaptureSupported
}
重要ポイント:
- まず
isResponsiveCaptureSupportedでデバイスが対応しているかを確認します。 isResponsiveCaptureEnabled = trueでレスポンシブキャプチャを有効にします。- Deferred Start と組み合わせると最も効果的です。
- わずかな処理遅延と引き換えに、シャッター応答速度を得ます。
(14:07)
システム圧力を監視する
import AVFoundation
let captureSession = AVCaptureSession()
let device = activeVideoInput?.device
captureSession.beginConfiguration()
// ... session を設定する ...
captureSession.commitConfiguration()
guard captureSession.hardwareCost <= 1.0 else {
print("hardwareCost \(captureSession.hardwareCost) - session を開始できません。再設定します。")
setupLowCostConfiguration()
}
captureSession.startRunning()
let systemPressureObserver = device?.observe(
\.systemPressureState,
options: [.initial, .new],
changeHandler: { device, change in
// 圧力状態に応じてフレームレートを調整するか、GPU/ANE 使用量を減らす
}
)
重要ポイント:
hardwareCostはcommitConfiguration()の後に確認します。1.0 を超える場合は構成を下げます。systemPressureStateは KVO で監視し、システム圧力の変化にリアルタイムで対応します。- 圧力が上がったら、フレームレートを下げる、binned format を使う、UI の仕事量を減らすといった対応ができます。
(20:16)
AVProVideoStorage を使う
import AVFoundation
func configureProVideoStorage() {
guard AVProVideoStorage.isSupported else { return }
let storage = AVProVideoStorage.shared
guard storage.remainingCapacity != 0 else {
storage.openSettings()
return
}
}
// 録画設定
guard AVProVideoStorage.isSupported else { return }
guard let pvs = AVProVideoStorage.shared else { return }
let movieOutput = AVCaptureMovieFileOutput()
guard movieOutput.isProVideoStorageSupported else { return }
guard !pvs.isBusy else { return }
let movieFileURL = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString)
.appendingPathExtension("mov")
movieOutput.usesProVideoStorage = true
movieOutput.startRecording(to: movieFileURL, recordingDelegate: delegate)
重要ポイント:
AVProVideoStorage.isSupportedでデバイスが対応しているか確認します。remainingCapacityで事前割り当て領域の残量を確認します。0 の場合はユーザーを設定へ案内します。isBusyでストレージが他のタスクに使われていないか確認します。isProVideoStorageSupportedで出力がこの機能に対応しているか確認します。usesProVideoStorage = trueを有効にすると、録画ファイルはいったん事前割り当て領域に書き込まれ、終了後に目的の場所へ移動されます。AVAssetWriterもusesProVideoStorageプロパティをサポートします。
(22:17)
重要な示唆
-
「即起動」するソーシャルカメラ App を作る: Deferred Start で 0.5 秒以内にプレビューを表示し、Responsive Capture と組み合わせて、ユーザーが App を開いた瞬間から撮影できるようにします。実装の考え方: 自動モードではプレビュー層だけを先に残し、写真出力を遅延初期化します。
sessionDidRunDeferredStartcallback 後にシャッターボタンを完全に利用可能にします。 -
ライブ配信 App に熱による降格保護を追加する:
systemPressureStateを監視し、デバイスが過熱したら採集解像度やフレームレートを自動で下げます。これにより配信映像のカクつきや、App がシステムに強制終了される状況を避けられます。実装の考え方:changeHandler内で圧力レベルに応じてactiveFormatを切り替え、frameRateOverrideでフレームレートを精密に制御します。 -
iPhone で ProRes のプロ向け録画を実装する:
AVProVideoStorageを使って高ビットレート録画の I/O ジッターをなくし、動画クリエイターに安定したプロ録画体験を提供します。実装の考え方: 録画前にremainingCapacityとisBusyを確認し、usesProVideoStorage = trueを設定し、録画終了後に一時ディレクトリからファイルを取り出します。 -
VideoDataOutput でリアルタイムフィルタープレビューを作る: カスタム Metal レンダリングパイプラインを使い、
AVCaptureVideoDataOutputのcaptureOutput(_:didOutput:from:)で各フレームにフィルターを適用してから提示します。実装の考え方: 手動 Deferred Start を採用し、最初のフレームが表示された後にrunDeferredStartWhenNeeded()を呼び、同時に写真出力を遅延初期化します。 -
カメラ起動時に UI を段階的に読み込む: 第 1 段階ではプレビューとシャッターボタンだけを表示し、第 2 段階で
sessionDidRunDeferredStart後にアルバムサムネイルやモード選択など非重要 UI をフェードインします。実装の考え方: UI 作成ロジックを 2 つのメソッドに分け、Deferred Start の delegate callback で第 2 段階を起動します。
関連セッション
- 高解像度写真キャプチャを実装する - Responsive Capture と高品質な写真撮影を組み合わせる方法を深掘りする
- よりレスポンシブなカメラ体験を作る - Center Stage とカメラ応答性の最適化
- Metal パフォーマンス最適化 - カスタムプレビューをレンダリングするときの GPU パフォーマンス調整
- Xcode 27 - Instruments を使ってカメラ起動性能を測定する
- Instruments - カメラ App のパフォーマンスボトルネックを特定する
コメント
GitHub Issues · utterances