axiom-camera-capture-diag について
AVFoundation のカメラ問題を調べる開発者向け診断スキルです。プレビューの停止、権限、回転などを扱い、撮影処理の前にセッション状態、スレッド、権限、中断を確認します。
主な機能
- 01カメラセッション状態の確認
- 02スレッドと権限の診断
- 03中断処理のガイダンス
- 04プレビューと写真の回転確認
- 05撮影速度の調査
活用シーン
- 01カメラプレビューが黒画面または停止する場合
- 02カメラ起動時に UI が固まる場合
- 03写真の向きが想定と異なる場合
- 04撮影が遅い、または権限が拒否される場合
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-camera-capture-diagインストール後、Claude Code で/axiom-camera-capture-diagを入力して読み込まれたか確認します。
詳細
カメラキャプチャの診断
AVFoundation のカメラに関する問題(プレビューのフリーズ、回転の誤り、キャプチャの遅延、セッションの中断、権限の問題)を体系的にトラブルシューティングします。
概要
基本原則:カメラが動作しない場合、問題の原因は通常、次のいずれかです。
- スレッド処理(セッションの処理をメインスレッドで実行) - 35%
- セッションのライフサイクル(開始されていない、中断されている、設定されていない) - 25%
- 回転(非推奨 API、コーディネーターの未設定) - 20%
- 権限(拒否されている、リクエストされていない) - 15%
- 設定(プリセットの誤り、入力/出力の不足) - 5%
キャプチャのロジックをデバッグする前に、必ずスレッド処理とセッションの状態を確認してください。
注意すべき兆候
カメラ固有の問題を示す症状:
| 症状 | 考えられる原因 |
|---|---|
| プレビューが黒い画面になる | セッションが開始されていない、権限が拒否されている、カメラ入力がない |
| カメラを開くと UI がフリーズする | startRunning() がメインスレッドで呼び出されている |
| 電話の着信時にカメラがフリーズする | 中断処理がない |
| プレビューが 90°ずれて回転する | RotationCoordinator を使用していない(iOS 17 以降) |
| 撮影した写真の回転が誤っている | 出力接続に回転角度が適用されていない |
| フロントカメラの写真が反転していない | これは正常です!(プレビューは反転しますが、写真は反転しません) |
| 「カメラは別のアプリで使用中」と表示される | 別のアプリが排他的にアクセスしている |
| キャプチャに 2 秒以上かかる | photoQualityPrioritization が .quality に設定されている |
| iPad でセッションが開始しない | Split View ではカメラを使用できない |
| 古い iOS でクラッシュする | 利用可能か確認せずに iOS 17 以降の API を使用している |
必須の初期手順
コードを調査する前に、次の診断を実行してください。
Step 1:セッションの状態を確認する
print("📷 Session state:")
print(" isRunning: \(session.isRunning)")
print(" inputs: \(session.inputs.count)")
print(" outputs: \(session.outputs.count)")
for input in session.inputs {
if let deviceInput = input as? AVCaptureDeviceInput {
print(" Input: \(deviceInput.device.localizedName)")
}
}
for output in session.outputs {
print(" Output: \(type(of: output))")
}
想定される出力:
- ✅ isRunning: true、inputs ≥ 1、outputs ≥ 1 → セッションは動作中
- ⚠️ isRunning: false → セッションが開始されていないか、中断されている
- ❌ inputs: 0 → カメラが追加されていない(権限?設定?)
Step 2:スレッド処理を確認する
print("🧵 Thread check:")
// When setting up session
sessionQueue.async {
print(" Setup thread: \(Thread.isMainThread ? "❌ MAIN" : "✅ Background")")
}
// When starting session
sessionQueue.async {
print(" Start thread: \(Thread.isMainThread ? "❌ MAIN" : "✅ Background")")
}
想定される出力:
- ✅ すべてバックグラウンド → 正常
- ❌ メインスレッドで実行されている処理がある → UI がフリーズします
Step 3:権限を確認する
let status = AVCaptureDevice.authorizationStatus(for: .video)
print("🔐 Camera permission: \(status.rawValue)")
switch status {
case .authorized: print(" ✅ Authorized")
case .notDetermined: print(" ⚠️ Not yet requested")
case .denied: print(" ❌ Denied by user")
case .restricted: print(" ❌ Restricted (parental controls?)")
@unknown default: print(" ❓ Unknown")
}
Step 4:中断がないか確認する
// Add temporary observer to see interruptions
NotificationCenter.default.addObserver(
forName: .AVCaptureSessionWasInterrupted,
object: session,
queue: .main
) { notification in
if let reason = notification.userInfo?[AVCaptureSessionInterruptionReasonKey] as? Int {
print("🚨 Interrupted: reason \(reason)")
}
}
判断ツリー
Camera not working as expected?
│
├─ Black/frozen preview?
│ ├─ Check Step 1 (session state)
│ │ ├─ isRunning = false → See Pattern 1 (session not started)
│ │ ├─ inputs = 0 → See Pattern 2 (no camera input)
│ │ └─ isRunning = true, inputs > 0 → See Pattern 3 (preview layer)
│
├─ UI freezes when opening camera?
│ └─ Check Step 2 (threading)
│ └─ Main thread → See Pattern 4 (move to session queue)
│
├─ Camera freezes during use?
│ ├─ After phone call → See Pattern 5 (interruption handling)
│ ├─ In Split View (iPad) → See Pattern 6 (multitasking)
│ └─ Random freezes → See Pattern 7 (thermal pressure)
│
├─ Preview/photo rotated wrong?
│ ├─ Preview rotated → See Pattern 8 (RotationCoordinator preview)
│ ├─ Captured photo rotated → See Pattern 9 (capture rotation)
│ └─ Front camera "wrong" → See Pattern 10 (mirroring expected)
│
├─ Capture too slow?
│ ├─ 2+ seconds delay → See Pattern 11 (quality prioritization)
│ └─ Slight delay → See Pattern 12 (deferred processing)
│
├─ Permission issues?
│ ├─ Status: notDetermined → See Pattern 13 (request permission)
│ └─ Status: denied → See Pattern 14 (settings prompt)
│
└─ Crash on some devices?
└─ See Pattern 15 (API availability)
診断パターン
パターン 1:セッションが開始されていない
症状:プレビューが黒い、isRunning = false
よくある原因:
startRunning()が一度も呼び出されていないstartRunning()は呼び出されたが、セッションに入力がない- セッションが停止され、その後再開されていない
診断:
// Check if startRunning was called
print("isRunning before start: \(session.isRunning)")
session.startRunning()
print("isRunning after start: \(session.isRunning)")
修正方法:
// Ensure session is started on session queue
func startSession() {
sessionQueue.async { [self] in
guard !session.isRunning else { return }
// Verify we have inputs before starting
guard !session.inputs.isEmpty else {
print("❌ Cannot start - no inputs configured")
return
}
session.startRunning()
}
}
修正時間:10 分
パターン 2:カメラ入力がない
症状:session.inputs.count = 0
よくある原因:
- カメラの権限が拒否されている
AVCaptureDeviceInputの作成に失敗したcanAddInput()が false を返した- 設定が確定されていない
診断:
// Step through input setup
guard let camera = AVCaptureDevice.default(for: .video) else {
print("❌ No camera device found")
return
}
print("✅ Camera: \(camera.localizedName)")
do {
let input = try AVCaptureDeviceInput(device: camera)
print("✅ Input created")
if session.canAddInput(input) {
print("✅ Can add input")
} else {
print("❌ Cannot add input - check session preset compatibility")
}
} catch {
print("❌ Input creation failed: \(error)")
}
修正方法:入力を作成する前に権限が許可されていることを確認し、設定ブロック内で処理してください。
session.beginConfiguration()
// Add input here
session.commitConfiguration()
修正時間:15 分
パターン 3:プレビューレイヤーが接続されていない
症状:isRunning = true で入力も設定されているが、プレビューが黒い
よくある原因:
- プレビューレイヤーのセッションが設定されていない
- プレビューレイヤーがビュー階層にない
- プレビューレイヤーのフレームがゼロ
診断:
print("Preview layer session: \(previewLayer.session != nil)")
print("Preview layer superlayer: \(previewLayer.superlayer != nil)")
print("Preview layer frame: \(previewLayer.frame)")
print("Preview layer connection: \(previewLayer.connection != nil)")
修正方法:
// Ensure preview layer is properly configured
previewLayer.session = session
previewLayer.videoGravity = .resizeAspectFill
// Ensure frame is set (common in SwiftUI)
previewLayer.frame = view.bounds
修正時間:10 分
パターン 4:メインスレッドのブロック
症状:カメラを開くと UI が 1~3 秒間フリーズする
根本原因:startRunning() はメインスレッドで実行されているブロッキング呼び出しです
診断:
// If this prints on main thread, that's the problem
print("startRunning on thread: \(Thread.current)")
session.startRunning()
修正方法:
// Create dedicated serial queue
private let sessionQueue = DispatchQueue(label: "camera.session")
func startSession() {
sessionQueue.async { [self] in
session.startRunning()
}
}
修正時間:15 分
パターン 5:電話の着信による中断
症状:カメラは動作するが、電話の着信時にフリーズする
根本原因:セッションが中断されても、処理や UI へのフィードバックがない
診断:
// Check if session is still running after returning from call
print("Session running: \(session.isRunning)")
// Will be false during active call, true after call ends
修正方法:中断オブザーバーを追加してください(camera-capture スキルのパターン 5 を参照)。
重要な点:中断が終了すると、セッションは自動的に再開します。startRunning() を再度呼び出す必要はありません。UI を更新するだけで十分です。
修正時間:30 分
パターン 6:Split View でカメラを利用できない
症状:iPad が Split View に入るとカメラが動作しなくなります
根本原因:複数のアプリがフォアグラウンドにあるとカメラを利用できません
診断:
// Check interruption reason
// InterruptionReason.videoDeviceNotAvailableWithMultipleForegroundApps
修正:適切な UI メッセージを表示し、ユーザーが Split View を終了したら再開します:
case .videoDeviceNotAvailableWithMultipleForegroundApps:
showMessage("Camera unavailable in Split View. Use full screen.")
修正時間:15 分
パターン 7:熱圧力
症状:カメラがランダムに停止します。特に長時間使用した後に発生します
根本原因:デバイスが熱くなり、システムがリソースを削減しています
診断:
// Check thermal state
print("Thermal state: \(ProcessInfo.processInfo.thermalState.rawValue)")
// 0 = nominal, 1 = fair, 2 = serious, 3 = critical
修正:画質を下げるか、冷却を促すメッセージを表示します:
case .videoDeviceNotAvailableDueToSystemPressure:
// Reduce quality
session.sessionPreset = .medium
showMessage("Camera quality reduced due to device temperature")
修正時間:20 分
パターン 8:プレビューの回転が正しくない
症状:プレビューが想定と比べて 90° 回転しています
根本原因:RotationCoordinator(iOS 17+)を使用していないか、更新を監視していません
診断:
print("Preview connection rotation: \(previewLayer.connection?.videoRotationAngle ?? -1)")
修正:
// Create and observe RotationCoordinator
let coordinator = AVCaptureDevice.RotationCoordinator(device: camera, previewLayer: previewLayer)
// Set initial rotation
previewLayer.connection?.videoRotationAngle = coordinator.videoRotationAngleForHorizonLevelPreview
// Observe changes
observation = coordinator.observe(\.videoRotationAngleForHorizonLevelPreview) { [weak previewLayer] coord, _ in
DispatchQueue.main.async {
previewLayer?.connection?.videoRotationAngle = coord.videoRotationAngleForHorizonLevelPreview
}
}
修正時間:30 分
パターン 9:撮影した写真の回転が正しくない
症状:プレビューは正しく見えますが、撮影した写真が回転しています
根本原因:写真出力接続に回転角度が適用されていません
診断:
if let connection = photoOutput.connection(with: .video) {
print("Photo connection rotation: \(connection.videoRotationAngle)")
}
修正:
func capturePhoto() {
// Apply current rotation to capture
if let connection = photoOutput.connection(with: .video) {
connection.videoRotationAngle = rotationCoordinator.videoRotationAngleForHorizonLevelCapture
}
photoOutput.capturePhoto(with: settings, delegate: self)
}
修正時間:15 分
パターン 10:フロントカメラのミラーリング
症状:デザイナーから「フロントカメラの写真がプレビューと一致しない」と指摘されます
実際のところ:これは正しい動作であり、バグではありません。
説明:
- プレビューはミラーリングされます(鏡を見ているような状態で、ユーザーの期待に沿っています)
- 撮影した写真はミラーリングされません(共有時に文字が正しく読めます)
- これはシステムのカメラアプリと同じ動作です
ビジネス要件で写真のミラーリングが必要な場合(自撮りアプリ):
func mirrorImage(_ image: UIImage) -> UIImage? {
guard let cgImage = image.cgImage else { return nil }
return UIImage(cgImage: cgImage, scale: image.scale, orientation: .upMirrored)
}
修正時間:5 分(説明のみ)、または 15 分(ミラーリングが必要な場合)
パターン 11:撮影が遅い(画質優先)
症状:写真の撮影に 2 秒以上かかります
根本原因:photoQualityPrioritization = .quality(一部のデバイスのデフォルト)
診断:
print("Max quality prioritization: \(photoOutput.maxPhotoQualityPrioritization.rawValue)")
// Check what you're requesting in AVCapturePhotoSettings
修正:
var settings = AVCapturePhotoSettings()
// For fast capture (social/sharing)
settings.photoQualityPrioritization = .speed
// For balanced (general use)
settings.photoQualityPrioritization = .balanced
// Only use .quality when image quality is critical
修正時間:5 分
パターン 12:遅延処理
症状:応答性を最大限に高めたい(シャッターラグをゼロにしたい)
解決策:遅延処理を有効にします(iOS 17+)
photoOutput.isAutoDeferredPhotoDeliveryEnabled = true
// Then handle proxy in delegate:
// - didFinishProcessingPhoto gives proxy for immediate display
// - didFinishCapturingDeferredPhotoProxy gives final image later
修正時間:30 分
パターン 13:権限がリクエストされていない
症状:authorizationStatus = .notDetermined
修正:
// Must request before setting up session
Task {
let granted = await AVCaptureDevice.requestAccess(for: .video)
if granted {
setupSession()
}
}
修正時間:10 分
パターン 14:権限が拒否された
症状:authorizationStatus = .denied
修正:設定を促すメッセージを表示します
func showSettingsPrompt() {
let alert = UIAlertController(
title: "Camera Access Required",
message: "Please enable camera access in Settings to use this feature.",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "Settings", style: .default) { _ in
if let url = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url)
}
})
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
present(alert, animated: true)
}
修正時間:15 分
パターン 15:API の利用可能性チェックが原因のクラッシュ
症状:iOS 16 以前でクラッシュします
根本原因:利用可能性をチェックせずに iOS 17+ の API を使用しています
修正:
if #available(iOS 17.0, *) {
// Use RotationCoordinator
let coordinator = AVCaptureDevice.RotationCoordinator(device: camera, previewLayer: preview)
} else {
// Fallback to deprecated videoOrientation
if let connection = previewLayer.connection {
connection.videoOrientation = .portrait
}
}
修正時間:20 分
クイックリファレンステーブル
| 症状 | 最初に確認すること | 該当する可能性が高いパターン |
|---|---|---|
| プレビューが黒い | ステップ 1(セッションの状態) | 1、2、または 3 |
| UI がフリーズする | ステップ 2(スレッド処理) | 4 |
| 通話中にフリーズする | ステップ 4(割り込み) | 5 |
| 回転が正しくない | 回転角度を出力する | 8 または 9 |
| 撮影が遅い | 画質設定を出力する | 11 |
| アクセスが拒否された | ステップ 3(権限) | 14 |
| 古い iOS でクラッシュする | @available を確認する | 15 |
チェックリスト
カメラの問題をエスカレーションする前に確認してください:
基本事項:
- ☑ セッションに入力が 1 つ以上ある
- ☑ セッションに出力が 1 つ以上ある
- ☑ Session isRunning = true
- ☑ プレビューレイヤーがセッションに接続されている
- ☑ プレビューレイヤーのフレームがゼロ以外である
スレッド処理:
- ☑ セッションに関するすべての処理が sessionQueue 上で実行されている
- ☑ startRunning() がバックグラウンドスレッドで実行されている
- ☑ UI の更新がメインスレッドで実行されている
権限:
- ☑ 認証状態が確認されている
- ☑ notDetermined の場合に権限がリクエストされている
- ☑ 拒否された状態に対して適切な UI が用意されている
回転:
- ☑ RotationCoordinator がデバイス AND プレビューレイヤーを指定して作成されている
- ☑ プレビュー角度の変更を監視する設定がされている
- ☑ 写真撮影時に撮影角度が適用されている
割り込み:
- ☑ 割り込みオブザーバーが登録されている
- ☑ 割り込み状態を知らせる UI フィードバックがある
- ☑ 電話の着信時にテストされている
リソース
WWDC:2021-10247、2023-10105
ドキュメント:/avfoundation/avcapturesession、/avfoundation/avcapturesessionwasinterruptednotification
スキル:axiom-camera-capture、axiom-camera-capture-ref