mcpField
A
axiom-camera-capture-diag

axiom-camera-capture-diag

github.com/charleswiltgen开发1.2k181 次安装今天更新
代码仓库

关于 axiom-camera-capture-diag

面向开发者的 AVFoundation 摄像头问题诊断技能,涵盖预览冻结、权限和旋转等故障。它先检查会话状态、线程、权限和中断,再排查拍摄行为。

主要功能

  1. 01摄像头会话状态检查
  2. 02线程与权限诊断
  3. 03中断处理指引
  4. 04预览与照片旋转检查
  5. 05拍摄速度排查

适用场景

  1. 01摄像头预览黑屏或冻结
  2. 02打开摄像头时界面卡顿
  3. 03照片方向异常
  4. 04拍摄缓慢或权限被拒

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-camera-capture-diag

安装后在 Claude Code 中输入/axiom-camera-capture-diag确认已加载。

详细介绍

摄像头采集诊断

针对 AVFoundation 摄像头问题的系统化故障排查:预览冻结、旋转方向错误、采集缓慢、会话中断和权限问题。

概述

核心原则:摄像头无法正常工作时,问题通常出在:

  1. 线程处理(在主线程上执行会话操作)- 35%
  2. 会话生命周期(未启动、已中断或未配置)- 25%
  3. 旋转方向(使用已弃用的 API、缺少协调器)- 20%
  4. 权限(已拒绝或未请求)- 15%
  5. 配置(预设错误、缺少输入/输出)- 5%

调试采集逻辑之前,务必先检查线程处理和会话状态。

危险信号

表明存在摄像头特有问题的症状:

症状可能原因
预览显示黑屏会话未启动、权限被拒绝、没有摄像头输入
打开摄像头时 UI 冻结在主线程上调用 startRunning()
电话来电时摄像头冻结未处理中断
预览旋转方向错误 90°未使用 RotationCoordinator(iOS 17+)
拍摄的照片旋转方向错误未将旋转角度应用于输出连接
前置摄像头拍摄的照片未镜像这是正常的!(预览会镜像,照片不会)
“摄像头正被其他应用使用”其他应用独占了摄像头
采集耗时超过 2 秒photoQualityPrioritization 设为 .quality
会话无法在 iPad 上启动分屏模式下无法使用摄像头
在较旧的 iOS 版本上崩溃使用 iOS 17+ API 时未检查可用性

必须先做的步骤

调查代码之前,先运行以下诊断:

步骤 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 → 未添加摄像头(权限问题?配置问题?)
步骤 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 将会冻结
步骤 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")
}
步骤 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

常见原因:

  1. 从未调用 startRunning()
  2. 调用了 startRunning(),但会话没有输入
  3. 会话已停止,且从未重新启动

诊断:

// 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

常见原因:

  1. 摄像头权限被拒绝
  2. 创建 AVCaptureDeviceInput 失败
  3. canAddInput() 返回 false
  4. 未提交配置

诊断:

// 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,已配置输入,但预览为黑屏

常见原因:

  1. 未设置预览图层的 session
  2. 预览图层不在视图层级中
  3. 预览图层的 frame 为零

诊断:

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:分屏视图中相机不可用

症状:iPad 进入分屏视图时,相机停止工作

根本原因:多个应用处于前台时,相机不可用

诊断:

// Check interruption reason
// InterruptionReason.videoDeviceNotAvailableWithMultipleForegroundApps

修复:显示适当的 UI 消息,并在用户退出分屏视图后恢复:

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:前置相机镜像

症状:设计师表示“前置相机拍的照片和预览不一致”

实际情况:这是正确行为,并非 Bug。

说明:

  • 预览会镜像显示(如同照镜子,符合用户预期)
  • 拍摄的照片不会镜像(分享时文字可正常阅读)
  • 这与系统相机 App 的行为一致

如果业务要求照片镜像(自拍 App):

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 上崩溃检查 @available15

检查清单

升级处理相机问题前:

基础项:

  • ☑ 会话至少有一个输入
  • ☑ 会话至少有一个输出
  • ☑ Session 的 isRunning = true
  • ☑ 预览图层已连接到会话
  • ☑ 预览图层的 frame 非零

线程处理:

  • ☑ 所有会话操作都在 sessionQueue 上执行
  • ☑ startRunning() 在后台线程上执行
  • ☑ UI 更新在主线程上执行

权限:

  • ☑ 已检查授权状态
  • ☑ 状态为 notDetermined 时已请求权限
  • ☑ 为被拒绝状态提供了恰当的 UI

方向:

  • ☑ 使用 device 和 previewLayer 创建了 RotationCoordinator
  • ☑ 已设置对预览角度变化的观察
  • ☑ 拍照时已应用拍摄角度

中断:

  • ☑ 已注册中断观察者
  • ☑ 为中断状态提供了 UI 反馈
  • ☑ 已使用来电进行测试

资源

WWDC:2021-10247、2023-10105

文档:/avfoundation/avcapturesession、/avfoundation/avcapturesessionwasinterruptednotification

技能:axiom-camera-capture、axiom-camera-capture-ref

评论

支持 Markdown