特定iPhone XR设备上captureOutput(_:didOutput:from:)代理未触发问题排查
问题:特定iPhone XR设备无法触发AVCaptureVideoDataOutput代理方法
问题背景
我正在使用captureOutput(_:didOutput:from:)代理方法捕获相机帧(CVPixelBuffers),用于将帧传入CoreML进行图像分类。该功能在目前测试过的所有iOS设备上均正常运行,但某一台特定iPhone XR设备出现异常,无法捕获缓冲帧,代理方法完全不触发。
设备规格
- iPhone XR
- 问题出现在iOS 16.3和iOS 16.7系统版本(未测试其他版本)
已尝试操作与限制
- 在亚马逊购买的翻新XR设备及同事的XR设备上,该功能均可正常运行。
- 无法持续访问出现问题的客户个人XR设备。
- Xcode未抛出任何错误,苹果文档仅定义了两个相关代理方法(
captureOutput和丢帧相关方法)。
相关代码
视图控制器定义
import Foundation import UIKit import AVFoundation import Vision class TestCameraViewController: UIViewController { var bufferSize: CGSize = .zero private var previewView: UIView = UIView() private var previewLayer: AVCaptureVideoPreviewLayer! = nil private let session = AVCaptureSession() private let videoDataOutput = AVCaptureVideoDataOutput() private var currentVideoDevice: AVCaptureDevice? override func viewDidLoad() { super.viewDidLoad() setupView() setupAVCapture() startCaptureSession() } func setupView() { previewView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(previewView) previewView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true previewView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true previewView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true previewView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true } func setupAVCapture() { var deviceInput: AVCaptureDeviceInput! // Select a video device, make an input let videoDevice = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: .back).devices.first if videoDevice == nil { print("Device Does Not Exist, properties: nil, level: .error, skipAnalytics: true") return } do { deviceInput = try AVCaptureDeviceInput(device: videoDevice!) } catch { print("Could not create video device input") return } session.beginConfiguration() session.sessionPreset = .vga640x480 // Model image size is smaller. // Add a video input guard session.canAddInput(deviceInput) else { session.commitConfiguration() print("Could not add video device input") return } session.addInput(deviceInput) if session.canAddOutput(videoDataOutput) { session.addOutput(videoDataOutput) videoDataOutput.alwaysDiscardsLateVideoFrames = true videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)] videoDataOutput.setSampleBufferDelegate(self, queue: .global()) } else { session.commitConfiguration() print("Could not add video data output.") return } let captureConnection = videoDataOutput.connection(with: .video) captureConnection?.isEnabled = true do { try videoDevice!.lockForConfiguration() let dimensions = CMVideoFormatDescriptionGetDimensions((videoDevice?.activeFormat.formatDescription)!) bufferSize.width = CGFloat(dimensions.width) bufferSize.height = CGFloat(dimensions.height) videoDevice!.unlockForConfiguration() } catch { print("Could not lock video device configuration.") return } session.commitConfiguration() previewLayer = AVCaptureVideoPreviewLayer(session: session) previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill self.currentVideoDevice = videoDevice } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() if previewLayer != nil { previewLayer.frame = previewView.bounds previewView.layer.addSublayer(previewLayer) } } func startCaptureSession() { DispatchQueue.global(qos: .background).async { self.session.startRunning() } } // Clean up capture setup func teardownAVCapture() { session.stopRunning() previewLayer.removeFromSuperlayer() previewLayer = nil } }
代理扩展(问题设备上未触发)
extension TestCameraViewController: AVCaptureVideoDataOutputSampleBufferDelegate { func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { //NOT CALLED on one particular iPhone XR physical device guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } } }
排查建议与后续步骤
- 检查相机权限状态:
用户可能在系统设置中关闭了相机权限,或权限请求流程存在遗漏。建议启动相机前主动检查权限状态,添加权限请求回调日志,确认设备是否授予相机访问权限。 - 验证视频连接有效性:
不要直接强制使用captureConnection,添加空值判断和日志输出,确认连接是否成功启用:if let captureConnection = videoDataOutput.connection(with: .video) { captureConnection.isEnabled = true print("Video connection enabled successfully") } else { print("Failed to get valid video connection") } - 调整会话预设与像素格式:
部分设备对特定像素格式或会话预设的支持存在差异。尝试将会话预设改为.high,或调整像素格式为kCVPixelFormatType_32BGRA,测试是否能触发代理方法。 - 使用专用代理队列:
当前使用.global()队列作为代理队列,建议创建专用串行队列,避免队列优先级冲突或被意外释放:private let videoProcessingQueue = DispatchQueue(label: "com.yourapp.video-processing", qos: .userInitiated) // 替换setSampleBufferDelegate的队列参数 videoDataOutput.setSampleBufferDelegate(self, queue: videoProcessingQueue) - 添加丢帧代理日志:
实现丢帧代理方法,确认是否因丢帧导致代理不触发:func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { print("Frame dropped at \(Date())") } - 排查硬件与系统异常:
引导用户在系统相机APP中测试相机是否正常工作,排除硬件故障;同时建议用户重启设备或更新系统到最新可用版本,排查系统层面的异常。 - 监听会话运行时错误:
添加会话错误监听,捕获可能的运行时异常:override func viewDidLoad() { super.viewDidLoad() // 其他初始化代码 NotificationCenter.default.addObserver(self, selector: #selector(handleSessionError), name: .AVCaptureSessionRuntimeError, object: session) } @objc private func handleSessionError(notification: NSNotification) { if let error = notification.userInfo?[AVCaptureSessionErrorKey] as? NSError { print("Capture session error: \(error.localizedDescription)") } } deinit { NotificationCenter.default.removeObserver(self) }
内容的提问来源于stack exchange,提问作者Archid
相关产品推荐
相关产品推荐

