You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)
    }
}

问题分析

  1. 未设置视频输出像素格式:AVCaptureVideoDataOutput未指定像素格式时,部分设备无法生成有效帧数据,直接导致captureOutput不触发。
  2. Delegate队列错误:使用主线程队列处理帧数据,主线程的UI操作会阻塞帧处理,甚至引发CaptureSession异常终止。
  3. 错误处理缺失:catch块直接忽略所有错误,无法排查设备输入添加失败、会话配置不兼容等问题。
  4. 冗余日志与启动逻辑:主线程和后台线程各打印一次"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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 06:50:28