Swift 4中AVCaptureSession未调用captureOutput代理方法求助
解决AVCaptureVideoDataOutput代理方法captureOutput未触发的问题
嘿,我之前在Swift 4+Xcode 9环境下也踩过这个captureOutput不触发的坑!咱们从最常见的几个排查点入手,一步步解决:
代理对象的生命周期问题
这是最容易忽略的点!如果你的FrameExtractor实例只是某个方法里的局部变量,方法执行完后它就会被ARC自动释放,代理自然不会触发。一定要把它作为强引用属性持有,比如在ViewController里声明:private var frameExtractor: FrameExtractor!然后在
viewDidLoad里初始化,确保它的生命周期和VC一致。协议与代理方法签名是否正确
首先你的FrameExtractor必须遵守AVCaptureVideoDataOutputSampleBufferDelegate协议,而且Swift 4对协议方法的参数名匹配要求非常严格,不能写错任何一个参数名!正确的代理方法签名应该是:func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { // 你的帧处理逻辑 }另外,设置代理时必须指定一个有效的串行队列(不是不能用主队列,但串行队列更适合处理视频帧这种高频率任务):
let videoQueue = DispatchQueue(label: "com.yourapp.videoCaptureQueue") videoDataOutput.setSampleBufferDelegate(self, queue: videoQueue)AVCaptureSession的配置顺序错误
配置session必须遵循正确的流程,顺序错了就会导致output无法正常工作:- 创建
AVCaptureSession实例 - 添加相机输入设备
- 创建并配置
AVCaptureVideoDataOutput(包括设置代理和队列) - 将output添加到session
- 启动session(
captureSession.startRunning())
如果先启动session再添加output,代理肯定不会触发。
- 创建
检查AVCaptureConnection是否被禁用
有时候系统会因为设备方向、摄像头切换等原因自动禁用连接,你可以在添加output后手动激活:if let connection = videoDataOutput.connection(with: .video) { if !connection.isActive { connection.isActive = true } }权限与视频格式问题
- 确认已经请求了相机权限,并且用户允许了。可以用
AVCaptureDevice.requestAccess(for: .video)检查权限状态,没有权限的话session根本无法启动。 - 确保
videoDataOutput的videoSettings设置了设备支持的格式,推荐用这个兼容性最好的配置:
videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]- 确认已经请求了相机权限,并且用户允许了。可以用
最后给你补全一个能正常工作的FrameExtractor类示例,你可以对比自己的代码找差异:
import UIKit import AVFoundation protocol FrameExtractorDelegate: class { func captured(image: UIImage) } class FrameExtractor: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate { weak var delegate: FrameExtractorDelegate? private let captureSession = AVCaptureSession() private let videoQueue = DispatchQueue(label: "com.yourapp.videoCaptureQueue") override init() { super.init() setupCaptureSession() } private func setupCaptureSession() { // 获取后置相机输入 guard let captureDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else { print("无法获取后置相机") return } guard let input = try? AVCaptureDeviceInput(device: captureDevice) else { print("无法创建相机输入") return } // 配置session预设 captureSession.sessionPreset = .high // 添加输入 if captureSession.canAddInput(input) { captureSession.addInput(input) } // 创建并配置视频输出 let videoDataOutput = AVCaptureVideoDataOutput() videoDataOutput.setSampleBufferDelegate(self, queue: videoQueue) videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA] videoDataOutput.alwaysDiscardsLateVideoFrames = true // 添加输出到session if captureSession.canAddOutput(videoDataOutput) { captureSession.addOutput(videoDataOutput) } // 激活视频连接 if let connection = videoDataOutput.connection(with: .video) { connection.isActive = true } // 启动session captureSession.startRunning() } // 正确的代理方法实现 func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } let ciImage = CIImage(cvImageBuffer: imageBuffer) let context = CIContext(options: [.useSoftwareRenderer: false]) guard let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else { return } let uiImage = UIImage(cgImage: cgImage) // 回到主队列通知代理(避免UI操作在后台队列执行) DispatchQueue.main.async { self.delegate?.captured(image: uiImage) } } }
在ViewController里使用时,记得强引用并设置代理:
class ViewController: UIViewController, FrameExtractorDelegate { private var frameExtractor: FrameExtractor! override func viewDidLoad() { super.viewDidLoad() frameExtractor = FrameExtractor() frameExtractor.delegate = self } func captured(image: UIImage) { // 在这里处理捕获到的视频帧 print("成功捕获到帧!") } }
内容的提问来源于stack exchange,提问作者Zach Butcher
相关产品推荐
相关产品推荐

