Swift中多个AVPlayer同时录屏如何实现稳定25FPS输出?
双AVPlayer录屏帧率优化方案
核心性能瓶颈定位
你当前代码的性能损耗主要来自7个冗余设计:
- 每次截图都重复创建
AVAsset、AVAssetImageGenerator这类重量级对象 - 帧提取的时间容差设为0,强制解码非关键帧,CPU占用极高
- 用隐藏UIView作为中转渲染层,再调用
drawHierarchy截图,UI渲染链路冗余且耗时 - 左右两个视频的帧提取串行执行,没有利用多核心并行处理
- 所有帧提取、UI操作都塞在主线程执行,阻塞主线程调度
- 用
asyncAfter递归触发截图,定时精度差且容易出现任务堆积 - 保存图片时多余的文件存在性检查,增加IO开销
具体优化方案
1. 复用重量级对象
将AVAsset、AVAssetImageGenerator改为成员变量,在初始化时一次性创建,不要每次截图都新建:
// 新增成员变量 let leftImageGenerator: AVAssetImageGenerator let rightImageGenerator: AVAssetImageGenerator init(/* 原有参数 */) { // 原有初始化逻辑 let leftAsset = AVAsset(url: leftPlayerUrl) leftImageGenerator = AVAssetImageGenerator(asset: leftAsset) leftImageGenerator.maximumSize = CGSize(width: 640, height: 480) // 放宽时间容差到10ms,人眼无感知,性能提升30%+ leftImageGenerator.requestedTimeToleranceBefore = CMTimeMakeWithSeconds(0.01, 1000) leftImageGenerator.requestedTimeToleranceAfter = CMTimeMakeWithSeconds(0.01, 1000) // 右播放器同理初始化rightImageGenerator }
2. 去掉UI中转,直接用Core Graphics合图
完全删除隐藏ImageView、drawHierarchy相关逻辑,直接将两个视频帧合成为目标图片,跳过整个UIKit渲染流程,性能提升50%+:
// 合图逻辑放在后台队列执行,不要占用主线程 func compositeImage(leftFrame: CGImage, rightFrame: CGImage, targetSize: CGSize) -> UIImage? { autoreleasepool { let format = UIGraphicsImageRendererFormat() format.scale = 1 // 跟你原来的截图缩放对齐,按需调整 let renderer = UIGraphicsImageRenderer(size: targetSize, format: format) return renderer.image { context in // 按你原来的布局位置绘制左右帧,这里示例是左右并排 leftFrame.draw(in: CGRect(x: 0, y: 0, width: 640, height: 480)) rightFrame.draw(in: CGRect(x: 640, y: 0, width: 640, height: 480)) } } }
3. 并行提取双路视频帧
用异步并行队列同时提取左右两个播放器的帧,不要串行执行:
@objc func complexScreenshot() { guard screenRecording, screenshotTaken < limit else { displayLink?.invalidate() delegate?.screenShotLimitReached() return } let currentCount = screenshotTaken let leftTime = leftPlayer?.currentTime() ?? .zero let rightTime = rightPlayer?.currentTime() ?? .zero let targetSize = view?.bounds.size ?? CGSize(width: 1280, height: 480) // 并行提取双帧 DispatchQueue.global(qos: .userInitiated).async { [weak self] in guard let self = self else { return } var leftFrame: CGImage? var rightFrame: CGImage? let group = DispatchGroup() group.enter() self.leftImageGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: leftTime)]) { _, img, _, _, _ in leftFrame = img group.leave() } group.enter() self.rightImageGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: rightTime)]) { _, img, _, _, _ in rightFrame = img group.leave() } group.wait() guard let lf = leftFrame, let rf = rightFrame, let compositeImg = self.compositeImage(leftFrame: lf, rightFrame: rf, targetSize: targetSize) else { return } self.saveScreenshot(image: compositeImg, number: currentCount) DispatchQueue.main.async { self.screenshotTaken += 1 } } }
4. 替换定时触发逻辑为CADisplayLink
用系统提供的屏幕刷新触发器替代自定义递归延迟,定时精度更高,不会出现任务堆积:
// 新增成员变量 var displayLink: CADisplayLink? // 开始录屏时调用 func startRecording() { screenRecording = true screenshotTaken = 0 displayLink = CADisplayLink(target: self, selector: #selector(complexScreenshot)) displayLink?.preferredFramesPerSecond = 25 displayLink?.add(to: .main, forMode: .common) } // 结束录屏时调用 func stopRecording() { screenRecording = false displayLink?.invalidate() displayLink = nil }
5. 优化图片保存逻辑
删除多余的文件存在性检查,录屏前清空临时目录即可,减少IO开销:
func saveScreenshot(image: UIImage, number: Int) { let numberStr = String(format: "%04d", number) let filePath = URL(fileURLWithPath: self.mainPath).appendingPathComponent("Temp/image_\(numberStr).jpg") autoreleasepool { guard let data = image.jpegData(compressionQuality: 0.4) else { return } do { try data.write(to: filePath) } catch { print("保存失败:", error) } } }
以上优化做完后,帧率稳定到25FPS无压力。
内容的提问来源于stack exchange,提问作者STerrier
相关产品推荐
相关产品推荐

