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

iOS Swift开发中如何正确设置或刷新相机采集帧率

问题排查

你的帧率配置不生效是以下几个逻辑错误导致的:

  • 未激活匹配的设备格式:你遍历format时只筛选了最高帧率的格式,但从未将captureDevice.activeFormat设置为该格式,系统默认激活的格式可能不支持你需要的7.6667fps,后续设置帧时长的操作会直接被系统忽略。
  • 配置顺序完全错误:你先启动了session,再设置帧时长、添加output,AVCaptureSession的所有配置变更必须包裹在beginConfiguration()/commitConfiguration()块内完成,启动session后再修改设备参数、添加输出会导致配置被重置。
  • 格式筛选逻辑错误:你需要的是低帧率场景,但当前筛选逻辑是找支持最高帧率的格式,这类高帧率格式通常对应高分辨率、低帧时长下限,不一定支持你需要的长帧间隔。
  • 使用废弃API+冗余逻辑:AVCaptureDevice.devices()是iOS10就废弃的接口,手动创建添加AVCaptureConnection的操作也是多余的,系统在添加input后会自动生成可用连接。
  • Session预设冲突:你设置了sessionPreset = .photo,该预设为静态拍照优化,会强制限制输出参数,覆盖你自定义的帧率配置。
正确实现方案

核心逻辑说明

  • 固定帧率的配置前提:先激活支持目标帧率的设备格式,再将最小/最大帧时长设为相同值,对应单帧间隔 = 60秒/460帧 ≈ 0.1304秒,即CMTime(value: 60, timescale: 460)。
  • 所有session相关配置(添加输入输出、修改设备参数)必须在配置块内完成,全部配置结束后再启动session。
  • 不要使用和目标格式冲突的session预设,手动指定格式时建议将预设设为.inputPriority,让系统优先适配自定义的输入参数。

修复后代码

func configureCamera() {
    // 1. 用非废弃API获取后置广角摄像头
    let discoverySession = AVCaptureDevice.DiscoverySession(
        deviceTypes: [.builtInWideAngleCamera],
        mediaType: .video,
        position: .back
    )
    guard let captureDevice = discoverySession.devices.first else {
        print("未找到后置摄像头")
        return
    }
    
    // 2. 计算目标帧率参数:460帧/分钟 = 7.6667fps
    let targetFPS = 460.0 / 60.0
    let targetFrameDuration = CMTime(value: 60, timescale: 460)
    var matchedFormat: AVCaptureDevice.Format?
    
    // 3. 筛选支持目标帧率的设备格式
    for format in captureDevice.formats {
        for fpsRange in format.videoSupportedFrameRateRanges {
            if fpsRange.minFrameRate <= targetFPS && fpsRange.maxFrameRate >= targetFPS {
                matchedFormat = format
                break
            }
        }
        if matchedFormat != nil { break }
    }
    guard let validFormat = matchedFormat else {
        print("当前设备不支持目标帧率")
        return
    }
    
    let session = AVCaptureSession()
    // 初始化预览层
    videoPreviewLayer = AVCaptureVideoPreviewLayer(session: session)
    videoPreviewLayer?.frame = view.bounds
    videoPreviewLayer?.videoGravity = .resizeAspect
    view.layer.addSublayer(videoPreviewLayer!)
    
    // 4. 开启session配置块,所有配置在此块内完成
    session.beginConfiguration()
    // 设置预设为输入优先,避免覆盖自定义格式
    session.sessionPreset = .inputPriority
    
    do {
        // 添加视频输入
        let videoInput = try AVCaptureDeviceInput(device: captureDevice)
        guard session.canAddInput(videoInput) else {
            session.commitConfiguration()
            return
        }
        session.addInput(videoInput)
        
        // 添加视频数据输出
        let videoOutput = AVCaptureVideoDataOutput()
        // 丢弃延迟帧,避免缓冲堆积
        videoOutput.alwaysDiscardsLateVideoFrames = true
        let outputQueue = DispatchQueue(label: "streamoutput")
        videoOutput.setSampleBufferDelegate(self, queue: outputQueue)
        guard session.canAddOutput(videoOutput) else {
            session.commitConfiguration()
            return
        }
        session.addOutput(videoOutput)
        
        // 配置设备帧率
        try captureDevice.lockForConfiguration()
        // 关键:先激活匹配到的格式
        captureDevice.activeFormat = validFormat
        // 设置固定帧时长
        captureDevice.activeVideoMinFrameDuration = targetFrameDuration
        captureDevice.activeVideoMaxFrameDuration = targetFrameDuration
        // 适配预览层方向
        videoPreviewLayer?.connection?.videoOrientation = .portrait
        captureDevice.unlockForConfiguration()
        
    } catch {
        print("配置失败: \(error.localizedDescription)")
        session.commitConfiguration()
        return
    }
    
    // 5. 提交所有配置
    session.commitConfiguration()
    
    // 6. 后台线程启动session,避免阻塞UI
    DispatchQueue.global(qos: .userInitiated).async {
        session.startRunning()
    }
}

验证方式

在AVCaptureVideoDataOutputSampleBufferDelegate的代理方法中,打印相邻两个CMSampleBuffer的呈现时间戳差值,稳定在0.13秒左右即配置生效:

func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
    let timestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
    // 记录上一帧时间戳,计算差值即可验证帧率
}

内容的提问来源于stack exchange,提问作者ziddi609

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.31 12:21:20