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
相关产品推荐
相关产品推荐

