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

特定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
        }
    }
}

排查建议与后续步骤

  1. 检查相机权限状态:
    用户可能在系统设置中关闭了相机权限,或权限请求流程存在遗漏。建议启动相机前主动检查权限状态,添加权限请求回调日志,确认设备是否授予相机访问权限。
  2. 验证视频连接有效性:
    不要直接强制使用captureConnection,添加空值判断和日志输出,确认连接是否成功启用:
    if let captureConnection = videoDataOutput.connection(with: .video) {
        captureConnection.isEnabled = true
        print("Video connection enabled successfully")
    } else {
        print("Failed to get valid video connection")
    }
    
  3. 调整会话预设与像素格式:
    部分设备对特定像素格式或会话预设的支持存在差异。尝试将会话预设改为.high,或调整像素格式为kCVPixelFormatType_32BGRA,测试是否能触发代理方法。
  4. 使用专用代理队列:
    当前使用.global()队列作为代理队列,建议创建专用串行队列,避免队列优先级冲突或被意外释放:
    private let videoProcessingQueue = DispatchQueue(label: "com.yourapp.video-processing", qos: .userInitiated)
    // 替换setSampleBufferDelegate的队列参数
    videoDataOutput.setSampleBufferDelegate(self, queue: videoProcessingQueue)
    
  5. 添加丢帧代理日志:
    实现丢帧代理方法,确认是否因丢帧导致代理不触发:
    func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        print("Frame dropped at \(Date())")
    }
    
  6. 排查硬件与系统异常:
    引导用户在系统相机APP中测试相机是否正常工作,排除硬件故障;同时建议用户重启设备或更新系统到最新可用版本,排查系统层面的异常。
  7. 监听会话运行时错误:
    添加会话错误监听,捕获可能的运行时异常:
    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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 20:59:55