如何用AVFoundation提升视频帧率?时间信息为零问题求解
AVFoundation提升视频帧率问题排查与解决方案
一、方案可行性分析
复制帧并缩短单帧时长的方案是可行的,属于帧率提升中的「帧复制」(Frame Duplication)方法,适合对画质要求不高、追求简单实现的场景。但该方案没有帧插值逻辑,运动画面会出现卡顿感,流畅度不如帧插值类方案(如AVVideoComposition硬件插值或第三方算法)。
二、时间信息为零的原因
你通过CMSampleBufferGetSampleTimingInfo获取到大量零值时间信息,核心原因有两点:
- 视频帧存储特性:MP4等容器中,I帧(关键帧)会携带完整时间信息,但P帧/B帧的时间依赖轨道全局时序,不会单独存储在SampleBuffer中,直接读取会返回默认零值。
- AVAssetReader配置问题:当
outputSettings设为nil时,输出的是原始压缩格式SampleBuffer,这类Buffer的时间信息不会直接暴露在CMSampleTimingInfo中,需通过轨道属性计算。
三、正确获取时间信息的方法
放弃直接从SampleBuffer读取时序,改用以下两种可靠方式:
方法1:通过轨道加载帧时间范围
let asset = AVURLAsset(url: url) let videoTrack = asset.tracks(withMediaType: .video)[0] let totalDuration = asset.duration videoTrack.loadTrackTimeRange(CMTimeRangeMake(start: .zero, duration: totalDuration), forMediaType: .video) { timeRanges in for (index, timeRange) in timeRanges.enumerated() { print("\(index):") print("时长:\(timeRange.duration)") print("时间戳:\(timeRange.start)") } }
方法2:修正SampleBuffer读取逻辑
如果坚持使用AVAssetReader,改用更可靠的API读取时间,当返回零值时用轨道标称帧率补全:
if let sampleBuffer { let presentationTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) var duration = CMSampleBufferGetDuration(sampleBuffer) // 若时长为零,用轨道标称帧率计算单帧时长 if CMTimeCompare(duration, .zero) == 0 { let nominalFrameRate = videoTrack.nominalFrameRate duration = CMTimeMake(value: 1, timescale: Int32(nominalFrameRate)) } print("\(nFrames):") print("时长:\(duration)") print("时间戳:\(presentationTime)") nFrames += 1 }
四、完整帧率翻倍实现步骤
- 读取原始帧:用上述方法正确获取每帧的时间戳和时长。
- 复制并调整时序:对每帧复制一份,将原帧与复制帧的时长均设为原时长的1/2;复制帧的时间戳设为原帧时间戳 + 原时长的1/2。
- 写入新视频:用AVAssetWriter输出处理后的帧,设置新的帧率参数(原帧率×2)。
写入环节示例代码:
let outputURL = // 输出文件路径 let writer = try AVAssetWriter(outputURL: outputURL, fileType: .mp4) let videoTrack = asset.tracks(withMediaType: .video)[0] let videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: [ AVVideoCodecKey: AVVideoCodecType.h264, AVVideoWidthKey: videoTrack.naturalSize.width, AVVideoHeightKey: videoTrack.naturalSize.height, AVVideoCompressionPropertiesKey: [ AVVideoAverageBitRateKey: videoTrack.estimatedDataRate * 2 ] ]) writer.add(videoInput) writer.startWriting() writer.startSession(atSourceTime: .zero) // 假设processedSampleBuffers是处理后的帧数组(原帧+复制帧) videoInput.requestMediaDataWhenReady(on: .global()) { while videoInput.isReadyForMoreMediaData { guard let sampleBuffer = processedSampleBuffers.popFirst() else { videoInput.markAsFinished() writer.finishWriting { // 处理完成回调 } return } videoInput.append(sampleBuffer) } }
五、进阶方案与资源
如果追求更流畅的帧率提升,推荐:
- AVVideoComposition硬件插值:iOS 13+支持通过
AVMutableVideoComposition设置frameDuration,系统会自动完成硬件加速的帧插值。 - 第三方库:使用
FFmpegKit(支持多种帧率提升算法)或MetalPetal(基于Metal的实时帧插值)实现更自然的运动画面。
内容的提问来源于stack exchange,提问作者sarunw
相关产品推荐
相关产品推荐

