iOS 15下使用AVFoundation时CaptureOutput从未被调用的问题
相机CaptureOutput未触发+绿标快速熄灭问题排查与修复
问题描述
调用CameraOutput的start方法后,相机权限弹窗允许后,相机绿标亮起1秒后熄灭,captureOutput(_:didOutput:from:)从未被调用,日志中"Camera on!"打印两次。
环境信息
- 父类
TOutput继承自NSObject - XCode版本:14.0.1 (14A400)
- macOS版本:12.6(21G115)
- iOS版本:15.0.1
- 设备:搭载M1芯片的设备
原代码
import AVFoundation /// CMSampleBuffer @objcMembers final class CameraOutput: TOutput, AVCaptureVideoDataOutputSampleBufferDelegate { private var videoDataOutput = AVCaptureVideoDataOutput() private var captureSession: AVCaptureSession = AVCaptureSession() override func start() { AVCaptureDevice.requestAccess(for: .video) { granted in if granted { self.runCamera() } } } func runCamera() { self.videoDataOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main) do { captureSession.beginConfiguration() let deviceInput = try AVCaptureDeviceInput(device: AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front)!) captureSession.addInput(deviceInput) captureSession.addOutput(videoDataOutput) captureSession.commitConfiguration() DispatchQueue.global(qos: .userInitiated).async { self.captureSession.startRunning() NSLog("Camera on!") } NSLog("Camera on!") } catch (_){} } public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { // let image: UIImage? = ImageConverter.convert(from: sampleBuffer) NSLog("Capture get") // callAllListeners(image) } }
问题分析
- 未设置视频输出像素格式:
AVCaptureVideoDataOutput未指定像素格式时,部分设备无法生成有效帧数据,直接导致captureOutput不触发。 - Delegate队列错误:使用主线程队列处理帧数据,主线程的UI操作会阻塞帧处理,甚至引发CaptureSession异常终止。
- 错误处理缺失:catch块直接忽略所有错误,无法排查设备输入添加失败、会话配置不兼容等问题。
- 冗余日志与启动逻辑:主线程和后台线程各打印一次"Camera on!",且
startRunning()是同步阻塞方法,启动逻辑不够严谨。
修复方案
1. 指定视频输出像素格式
给videoDataOutput设置设备兼容的像素格式,比如通用的.yuv420格式:
videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_420YpCbCr8BiPlanarFullRange]
2. 使用专用后台队列处理帧
创建独立的串行后台队列,避免占用主线程资源:
private let captureQueue = DispatchQueue(label: "com.yourapp.camera.capture", qos: .userInitiated)
设置delegate时绑定该队列:
self.videoDataOutput.setSampleBufferDelegate(self, queue: captureQueue)
3. 完善错误与兼容性检查
添加输入输出的兼容性判断,同时打印错误信息便于排查:
guard captureSession.canAddInput(deviceInput) else { NSLog("Cannot add camera input to session") return } // 输出同理 guard captureSession.canAddOutput(videoDataOutput) else { NSLog("Cannot add video output to session") return }
4. 优化CaptureSession启动逻辑
移除主线程的冗余日志,仅在后台线程启动完成后打印一次日志:
DispatchQueue.global(qos: .userInitiated).async { self.captureSession.startRunning() NSLog("Camera on!") }
5. 确保实例强引用
确认调用方对CameraOutput实例持有强引用,避免权限回调后实例被提前释放导致会话终止。
修改后的完整代码
import AVFoundation /// CMSampleBuffer @objcMembers final class CameraOutput: TOutput, AVCaptureVideoDataOutputSampleBufferDelegate { private var videoDataOutput = AVCaptureVideoDataOutput() private var captureSession: AVCaptureSession = AVCaptureSession() // 专用捕获队列 private let captureQueue = DispatchQueue(label: "com.yourapp.camera.capture", qos: .userInitiated) override func start() { AVCaptureDevice.requestAccess(for: .video) { granted in if granted { DispatchQueue.main.async { self.runCamera() } } else { NSLog("Camera access denied") } } } func runCamera() { // 设置像素格式 videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_420YpCbCr8BiPlanarFullRange] // 绑定专用队列 self.videoDataOutput.setSampleBufferDelegate(self, queue: captureQueue) do { captureSession.beginConfiguration() guard let cameraDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) else { NSLog("Front camera not available") return } let deviceInput = try AVCaptureDeviceInput(device: cameraDevice) guard captureSession.canAddInput(deviceInput) else { NSLog("Cannot add camera input to session") return } captureSession.addInput(deviceInput) guard captureSession.canAddOutput(videoDataOutput) else { NSLog("Cannot add video output to session") return } captureSession.addOutput(videoDataOutput) // 配置视频连接方向(按需调整) if let connection = videoDataOutput.connection(with: .video), connection.isVideoOrientationSupported { connection.videoOrientation = .portrait } captureSession.commitConfiguration() // 后台线程启动会话 DispatchQueue.global(qos: .userInitiated).async { self.captureSession.startRunning() NSLog("Camera on!") } } catch let error { NSLog("Camera setup error: \(error.localizedDescription)") } } public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { NSLog("Capture get") // 处理帧数据时,若需更新UI需切回主线程 // DispatchQueue.main.async { // // UI更新逻辑 // } } }
额外检查项
- 确认Info.plist中已添加
NSCameraUsageDescription键,填写相机使用用途说明。 - iOS 14+设备需确认隐私权限设置中相机权限已开启。
内容的提问来源于stack exchange,提问作者normidar
相关产品推荐
相关产品推荐

