本周,我有一段悲惨的时间试图从Quicktime电影中获取视频帧并将其显示在Metal纹理中。它比我预想的要困难得多。我只想显示第0帧,然后使用箭头键在视频帧中前后移动。在使用AVPlayererItemVideoOutput.hasNewPixelBuffer(forItemTime:time)函数失败后,我现在尝试使用seek()函数。这几乎可行。视频暂停后,我会在我想要的时间内调用搜索功能,并说它成功了。
但是,每隔几帧,hasNewPixelBuffer()函数返回'false',而copyPixelBuffer()函数只返回与当前显示的帧相同的数据。起初,认为它可能与视频(h264)具有帧重新排序的事实有关。但是当我以正确的顺序使用所有帧的视频时,我得到相同的结果。我已经尝试将搜索容差设置为0,然后尝试各种值,如半帧或更少,以防万一我的计算时间有一些浮点舍入问题。似乎没什么用。
任何人都知道如何做到这一点?它应该是如此简单,但我已经尝试了我能想到的一切。我也尝试了AVAssetImageGenerator,它做了同样的事情。 AVAssetReader将按顺序报告所有帧,但如果您只是想随机访问这些帧,则无法提供帮助。
本周之后我讨厌AVFoundation - 为什么这么简单的视频任务必须如此困难......
任何建议表示赞赏。这是我现在代码的基础知识。
func gotoFrame(_ frame:Int) {
frameNanoseconds = Int64( (Double(frame)/Double(BigRender.framerate)) * Double(1_000_000_000))
let cmTime = CMTimeMake(frameNanoseconds ,1_000_000_000)
playerItem.seek(to: cmTime, toleranceBefore:tolerance, toleranceAfter:tolerance) {
(result: Bool) in
if result {
//print("seek got back: \(result)")
self.needFrame = true
self.videoTime = cmTime
}
else {
print("bad seek!")
}
}
}
当搜索回调完成时,我的渲染器将尝试抓取视频帧。寻求总是会成功。它抓住了似乎失败的新视频帧......每隔几帧,'hasNewPixelBuffer'返回false。我每按一次键只想尝试一次新帧,所以这不是无法跟上播放速度的问题。它也是可重复的 - 它跳过的帧总是和我来回走动一样。
我感谢任何人的建议。
if playerItemVideoOutput.hasNewPixelBuffer(forItemTime: time), let pixelBuffer = playerItemVideoOutput.copyPixelBuffer(forItemTime: time, itemTimeForDisplay: nil) {
// Get width and height for the pixel buffer
let width = CVPixelBufferGetWidth(pixelBuffer)
let height = CVPixelBufferGetHeight(pixelBuffer)
// Converts the pixel buffer in a Metal texture.
if CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, self.textureCache!, pixelBuffer, nil, .bgra8Unorm, width, height, 0, &cvTextureOut) != kCVReturnSuccess {
print ("texture create failed!")
}
guard let cvTexture = cvTextureOut, let inputTexture = CVMetalTextureGetTexture(cvTexture) else {
print("Failed to create metal texture")
return
}
texture = inputTexture
}