iOS 调用相机做人脸采集卡死:3 个原因按序排查

编程狮(w3cschool.cn) 2026-10-23 07:06:10 浏览数 (27)
反馈

先给结论:iOS 上做人脸采集时界面卡死,绝大多数不是设备性能不够,而是三件事之一——相机权限还没拿到就启动了采集会话、采集回调里把耗时活放到了主线程、或者会话配置不当导致内存持续上涨后被系统终止。

iOS 相机做人脸采集卡死怎么排查?

新手遇到相机卡死的第一反应往往是换设备重试,试出来"有的机型卡有的不卡"就更困惑了。其实卡顿集中在三个固定位置:权限检查、回调线程、会话参数。这三处的表现都像"卡死",但定位方式完全不同,按固定顺序排查就能分开。

本文基于 iOS 系统相机框架的通用行为说明,用三段可以放进工程里的最小代码讲清:启动前该检查什么、回调里该怎么分发、以及哪些配置会让内存一路涨上去。每段都给出预期现象和失败分支。

先看结论

  • 先查权限:权限没授权就启动会话,界面会停在等待状态,看起来就是相机卡死。
  • 再查线程:采集回调不在主线程,把图像处理放到主线程必然掉帧。
  • 最后查会话:分辨率和帧率配置过高会让内存持续上涨,最终被系统回收。

相机框架的调用顺序记不清时,翻 iOS 教程 里按功能分组的章节最快。

一、权限没拿到就启动会话

把问题定死:应用一进页面就启动采集,界面停在黑屏,控制台没有报错。

import AVFoundation

func checkCameraPermission() -> Bool {
    switch AVCaptureDevice.authorizationStatus(for: .video) {
    case .authorized:
        return true
    case .notDetermined:
        AVCaptureDevice.requestAccess(for: .video) { granted in
            print("用户选择结果:", granted)
        }
        return false
    default:
        print("权限被拒绝,需要引导用户去设置页开启")
        return false
    }
}

这段做的是在启动任何采集配置之前先查授权状态。三种状态要分开处理:已授权直接继续;未询问要先弹窗,弹窗是异步的,本次调用应当直接返回不启动;已拒绝要引导用户去设置页。

正确顺序是"先检查、再配置、最后启动":

func startCapture() {
    guard checkCameraPermission() else {
        print("权限未就绪,本次不启动会话")
        return
    }
    let session = AVCaptureSession()
    session.beginConfiguration()
    session.sessionPreset = .medium
    session.commitConfiguration()
    session.startRunning()
    print("会话已启动")
}

这段做的是用 guard 拦住权限未就绪的情况。适用边界要讲清:请求授权的回调不在主线程,拿到结果后如果要更新界面,必须切回主线程再改。

二、回调不在主线程,重活压在主线程

第二类相机卡死来自线程错配。采集回调默认不在主线程,如果直接在回调里做图像处理或者刷新界面,表现就是掉帧甚至无响应。

class CaptureDelegate: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate {
    func captureOutput(_ output: AVCaptureOutput,
                       didOutput sampleBuffer: CMSampleBuffer,
                       from connection: AVCaptureConnection) {
        DispatchQueue.main.async {
            // 在这里做逐帧处理,界面会被压住
            self.handle(buffer: sampleBuffer)
        }
    }

    func handle(buffer: CMSampleBuffer) {
        print("处理一帧")
    }
}

这段做的是把每一帧的处理都丢回主线程。预期现象是界面滚动发涩、按钮点击延迟,帧率明显下降。

正确的做法是把耗时部分留在采集队列里,只把最终结果交回主线程:

func captureOutput(_ output: AVCaptureOutput,
                   didOutput sampleBuffer: CMSampleBuffer,
                   from connection: AVCaptureConnection) {
    let result = self.handle(buffer: sampleBuffer)
    DispatchQueue.main.async {
        self.updateUI(with: result)
    }
}

这段做的是先在当前队列完成计算,再只把界面更新切回主线程。判断标准是:主线程只做界面更新,任何逐帧计算都不该出现在它上面。

相机卡死的三步排查顺序

三、会话配置过高导致内存上涨

第三类相机卡死最隐蔽:一切正常,跑几分钟后卡住甚至被系统回收。原因通常是分辨率与帧率配置过高,加上每一帧的缓冲没有被及时释放。

let session = AVCaptureSession()
session.beginConfiguration()
session.sessionPreset = .high

if session.canSetSessionPreset(.high) {
    session.sessionPreset = .high
} else {
    session.sessionPreset = .medium
}
session.commitConfiguration()

这段做的是在设置前先询问设备是否支持该档位。直接写死高档位在老设备上会失败或退化,判断这一步不能省。

性能取舍要讲清:人脸采集并不需要最高档分辨率,中等档位配合较低的帧率通常就够用,还能显著降低内存压力。需要更高画质时再按机型分级开启。

let output = AVCaptureVideoDataOutput()
output.alwaysDiscardsLateVideoFrames = true
output.videoSettings = [
    kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA
]

这段做的是打开丢弃迟到帧的开关,并指定像素格式。这个开关很关键:处理跟不上采集速度时,迟到帧会被丢掉而不是堆积在内存里。关掉它,内存就会随着运行时间一路涨上去。

⚠️ 注意:真机验证之前,模拟器上的相机相关功能不可用,很多"卡死"在模拟器上根本复现不出来。排查必须在真机上进行。

四、常见失败分支与验证清单

4.1 权限弹窗只出现一次

系统授权弹窗每个应用只会主动弹一次,用户拒绝之后再次调用也不会再弹。修复方式是检测到拒绝状态时主动提示用户去设置页手动开启,并在设置页返回后重新检查状态。

4.2 前后台切换后卡住

应用进入后台时会话没有停止,回到前台后状态错乱。修复方式是在进入后台的通知里停止会话,回到前台时重新启动,并处理启动失败的情况。

4.3 界面在真机上卡、模拟器上正常

模拟器的相机链路与真机差异很大,不能用它下结论。修复方式是把全部验证放到真机上,至少覆盖一台较老的机型。

4.4 验证清单

  • 检查:授权状态是否为已授权,弹窗回调是否在主线程之外
  • 运行:在真机上连续采集三分钟,观察内存曲线
  • 预期:内存曲线平稳,帧率维持在设定值附近
  • 失败:内存持续上涨或帧率下滑,说明配置过高或缓冲没释放
  • 修复:降低会话档位,打开丢弃迟到帧的开关
  • 验证:换一台较老机型重复一次,结果一致才算稳定

上面这些步骤做的是按权限、线程、配置三个方向逐一排除。预期结果(未在本机执行)是三处都修正后连续运行不再出现界面无响应;如果仍有卡顿,把内存曲线和帧率数据一起记录下来再定位。

采集回调里耗时计算与界面更新的线程分发

总结

相机卡死集中在三个位置:权限未就绪就启动、回调线程错配、会话配置过高导致内存上涨。按这个顺序排查,比反复换设备试要快得多。

三条要点带走:

  • 启动前必查授权状态,未询问时要先弹窗且本次不启动;
  • 采集回调里只把界面更新切回主线程,逐帧计算留在采集队列;
  • 会话档位按机型分级,并打开丢弃迟到帧的开关。

下一步建议把这三处检查封装成一个启动函数,统一处理失败分支。语法细节可以对照 Swift 教程 里的异步与队列章节再确认一遍。

常见问题

Q:为什么应用一打开相机就卡住不动?

A:先查授权状态。未授权就启动会话会停在等待状态,看起来和卡死一样。把权限检查放在启动之前。

Q:相机卡死只在部分机型出现是什么原因?

A:多数是会话档位过高。老设备不支持高档位,加上缓冲堆积就会卡住。按机型分级设置档位。

Q:模拟器上正常,真机上卡死怎么查?

A:模拟器的相机链路与真机不同,不能用它下结论。排查要全部放到真机上,并至少覆盖一台较老机型。

Q:采集回调里能不能直接更新界面?

A:不能。回调不在主线程,直接更新界面会造成线程错配。应当把计算留在采集队列,只把结果交回主线程。

Q:跑一段时间后才卡住是怎么回事?

A:通常是内存持续上涨。检查是否关闭了丢弃迟到帧的开关,以及每一帧的缓冲是否及时释放。

延伸学习

想把这套排查方法练熟,可以按这个顺序来:

  1. 想补一遍语言基础,Swift 微课 可以跟一遍;
  2. 读一篇相关笔记 Debug 是什么意思,把定位思路理清;
  3. 想随手验证一段代码逻辑,用 在线代码实例 跑一遍最快。

0 人点赞