使用 AV 播放器访问单个帧

Accesing Individual Frames using AV Player

在最近的项目中,我必须使用 AV Foundation 单独访问视频的所有帧。此外,如果可能的话随机访问它们(如数组)

我试图研究这个问题,但没有得到任何有用的信息。

注意:是否有任何有助于熟悉 AV Foundation 的文档?

您可以使用 AVAssetReader 连续枚举视频的帧数,如下所示:

let asset = AVAsset(URL: inputUrl)
let reader = try! AVAssetReader(asset: asset)

let videoTrack = asset.tracksWithMediaType(AVMediaTypeVideo)[0]

// read video frames as BGRA
let trackReaderOutput = AVAssetReaderTrackOutput(track: videoTrack, outputSettings:[String(kCVPixelBufferPixelFormatTypeKey): NSNumber(unsignedInt: kCVPixelFormatType_32BGRA)])

reader.addOutput(trackReaderOutput)
reader.startReading()

while let sampleBuffer = trackReaderOutput.copyNextSampleBuffer() {
    print("sample at time \(CMSampleBufferGetPresentationTimeStamp(sampleBuffer))")
    if let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) {
        // process each CVPixelBufferRef here
        // see CVPixelBufferGetWidth, CVPixelBufferLockBaseAddress, CVPixelBufferGetBaseAddress, etc
    }
}

随机访问比较复杂。您可以使用 AVPlayer + AVPlayerItemVideoOutput 从任何时间 t 获取帧,使用 copyPixelBufferForItemTime,但微妙之处在于您如何选择 t =14=].

如果你想以统一的间隔对视频进行采样,那很容易,但如果你想在序列 AVAssetReader 代码看到的相同 frames/presentation 时间戳上着陆,那么你将可能必须用 AVAssetReader 预处理文件,以构建帧编号 -> 表示时间戳图。如果您使用 AVAssetReaderTrackOutput.

中的 nil 输出设置跳过解码,这会很快

如果您需要在播放视频时获取当前帧,您可以为播放器添加一个观察者,并可以像这样获取当前帧:

var player: AVPlayer?
var playerController = AVPlayerViewController()
var videoFPS: Int = 0
var currentFrame: Int = 0
var totalFrames: Int?

func configureVideo() {
    guard let videoURL = "" else { return }
    player = AVPlayer(url: videoURL)
    playerController.player = player
    guard player?.currentItem?.asset != nil else {
        return
    }
    let asset = self.player?.currentItem?.asset
    let tracks = asset!.tracks(withMediaType: .video)
    let fps = tracks.first?.nominalFrameRate
    
    self.videoFPS = lround(Double(fps!))
    self.getVideoData()
}
  
func getVideoData() {
    self.player?.addPeriodicTimeObserver(forInterval: CMTimeMake(value: 1,timescale: Int32(self.videoFPS)), queue: DispatchQueue.main) {[weak self] (progressTime) in

        if let duration = self!.player?.currentItem?.duration {

            let durationSeconds = CMTimeGetSeconds(duration)
            let seconds = CMTimeGetSeconds(progressTime)
            if self!.totalFrames == nil {
                self!.totalFrames = lround(Double(self!.videoFPS) * durationSeconds)
            }

            DispatchQueue.main.async {
                if self!.totalFrames != self!.currentFrame {
                    self!.currentFrame = lround(seconds*Double(self!.videoFPS))
                } else {
                    print("Video has ended!!!!!!!!")
                }
                print(self!.currentFrame)
            }
        }
    }
}

获取当前帧数后,您可以使用AVAssetImageGenerator检索特定图像。