使用AVAssetImageGenerator从AVPlayer提取帧出现较大时间偏移如何解决?
问题根因
你遇到的时间偏差是AVAssetImageGenerator的默认行为导致的:为了提升截图效率,它默认会优先查找距离指定时间最近的关键帧返回,不会严格匹配你传入的时间点,这就是出现0.2-0.4s偏差的核心原因。
修复方案
方案1:调整AVAssetImageGenerator配置(最小改动适配现有代码)
给AVAssetImageGenerator设置时间容差为0,强制它精准匹配你传入的时间点,不要跳关键帧:
- 新增两行配置
assetIG.requestedTimeToleranceBefore = .zero和assetIG.requestedTimeToleranceAfter = .zero - 优先复用当前播放器持有的
AVAsset实例,不要每次都重新创建AVURLAsset,避免重复加载资源带来的额外误差 - 不要硬编码时间刻度为30,可优先使用视频轨道自身的时间刻度,减少时间转换误差
修改后的工具方法代码如下:
public func imageFromVideo(asset: AVAsset, at time: TimeInterval, completion: @escaping (UIImage?) -> Void) { DispatchQueue.global(qos: .background).async { let assetIG = AVAssetImageGenerator(asset: asset) assetIG.appliesPreferredTrackTransform = true assetIG.apertureMode = .encodedPixels // 核心配置:设置时间容差为0,强制精准匹配指定时间 assetIG.requestedTimeToleranceBefore = .zero assetIG.requestedTimeToleranceAfter = .zero // 可选:使用视频轨道自身的时间刻度,避免硬编码30带来的误差 let timescale = asset.tracks(withMediaType: .video).first?.naturalTimeScale ?? 600 let cmTime = CMTime(seconds: time, preferredTimescale: timescale) var actualTime = CMTime.zero let thumbnailImageRef: CGImage do { thumbnailImageRef = try assetIG.copyCGImage(at: cmTime, actualTime: &actualTime) } catch let error { print("Error: \(error)") return completion(nil) } print("指定时间: %f", cmTime.seconds) print("实际截图时间: %f", actualTime.seconds) DispatchQueue.main.async { completion(UIImage(cgImage: thumbnailImageRef)) } } }
调用的时候直接从播放器拿asset即可,不用重新加载文件:
pausePlayer() let time = player!.currentTime() // 直接拿player当前的asset,不要重新创建AVURLAsset guard let asset = player.currentItem?.asset else { return } imageFromVideo(asset: asset, at: time.seconds) { image in guard let image = image else { return } let result = self.detectLines(image: image) if let result = result { DispatchQueue.main.async { self.drawNewResults(result: result) } } }
方案2:直接从AVPlayer取帧(更适合当前播放场景,精度更高)
如果你是要截取当前播放器暂停时的画面,完全不需要用AVAssetImageGenerator,直接从播放队列取帧速度更快、精度100%匹配当前播放画面:
// 先给playerItem添加输出 let playerItemOutput = AVPlayerItemVideoOutput(pixelBufferAttributes: [ kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA ]) player.currentItem?.add(playerItemOutput) // 截图的时候直接调用 pausePlayer() let currentTime = player.currentTime() guard let pixelBuffer = playerItemOutput.copyPixelBuffer(forItemTime: currentTime, itemTimeForDisplay: nil) else { return } let ciImage = CIImage(cvPixelBuffer: pixelBuffer) let context = CIContext(options: [.useSoftwareRenderer: false]) guard let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else { return } let currentFrame = UIImage(cgImage: cgImage) // 直接用currentFrame处理即可 let result = self.detectLines(image: currentFrame) if let result = result { DispatchQueue.main.async { self.drawNewResults(result: result) } }
内容的提问来源于stack exchange,提问作者El_Loco
相关产品推荐
相关产品推荐

