AVAssetImageGenerator异步生成视频帧重复问题求助
generateCGImagesAsynchronously It sounds like you're hitting a common gotcha with AVAssetImageGenerator's asynchronous method: default time tolerance settings that prioritize performance over precision. Let me break down what's happening and how to fix it.
What's Causing the Duplicate Frames?
By default, AVAssetImageGenerator uses non-zero values for requestedTimeToleranceBefore and requestedTimeToleranceAfter. This lets it return the nearest keyframe instead of the exact timestamp you requested—great for speed, but terrible for precision when you need every individual frame. In your case, the first second's keyframes are being reused for all subsequent timestamps that fall within the tolerance window, hence the repeated frames and mismatched timestamps.
Your synchronous copyCGImage(at:actualTime:) call works because it defaults to strict precision (unless you explicitly set a tolerance), which is why it’s slower but accurate.
The Fix: Enforce Exact Timestamp Matching
To get the precise frames you requested in the asynchronous method, you need to set both tolerance properties to kCMTimeZero. This tells the generator to ignore keyframe optimization and generate the exact frame at your requested time.
Here's how to update your code:
// First, initialize your image generator and set strict tolerance let imageGenerator = AVAssetImageGenerator(asset: yourVideoAsset) imageGenerator.requestedTimeToleranceBefore = kCMTimeZero imageGenerator.requestedTimeToleranceAfter = kCMTimeZero // Your existing NSValue array generation stays valid var values : [NSValue] = [] let timeScale = 30 // Match your video's fps/timescale let duration = 300 // 10s * 30fps let frameDuration = CMTimeMake(1, timeScale) for i in 0..<duration{ let lastFrameTime = CMTimeMake(Int64(i), timeScale) let presentationTime = (i == 0) ? lastFrameTime : CMTimeAdd(lastFrameTime, frameDuration) values.append(NSValue(time: presentationTime)) } // Run the asynchronous generation with the fixed generator imageGenerator.generateCGImagesAsynchronously(forTimes: values) { (requestedTime, image, actualTime, result, error) in guard let image = image, result == .succeeded else { // Handle errors here (e.g., invalid time, asset issues) return } // Process your frame—now it should match the requested timestamp let imageUrl = FileManagerUtil.getTempFileName(parentFolder: FrameExtractor.EXTRACTED_IMAGE, fileNameWithExtension: "\(Constants.FRAME_SUFFIX)\(i)\".jpeg") // Save or use the image as needed }
Additional Notes
- Enforcing exact timestamps will increase processing time compared to the default asynchronous behavior, but it should still be faster than the synchronous method for bulk frame extraction. This tradeoff is necessary if you need frame-perfect results.
- For better reliability, use your video asset's native
duration.timescaleinstead of hardcoding a value like 30—this ensures your timestamp calculations align with the video's internal timing. - You can cross-verify using the
actualTimeparameter in the callback; with zero tolerance enabled, this should match yourrequestedTimeexactly.
内容的提问来源于stack exchange,提问作者Mohammed Atif

