首页 文章

使用CMTimeMapping寻找AVComposition会导致AVPlayerLayer冻结

提问于
浏览
2

以下是问题的GIF链接:

https://gifyu.com/images/ScreenRecording2017-01-25at02.20PM.gif

我从相机胶卷中取出 PHAsset ,将其添加到可变组合中,添加另一个视频轨道,操纵添加的轨道,然后通过 AVAssetExportSession 导出它 . 结果是一个quicktime文件,其中.mov文件扩展名保存在 NSTemporaryDirectory() 中:

guard let exporter = AVAssetExportSession(asset: mergedComposition, presetName: AVAssetExportPresetHighestQuality) else {
        fatalError()
}

exporter.outputURL = temporaryUrl
exporter.outputFileType = AVFileTypeQuickTimeMovie
exporter.shouldOptimizeForNetworkUse = true
exporter.videoComposition = videoContainer

// Export the new video
delegate?.mergeDidStartExport(session: exporter)
exporter.exportAsynchronously() { [weak self] in
     DispatchQueue.main.async {
        self?.exportDidFinish(session: exporter)
    }
}

然后我将这个导出的文件加载到一个映射器对象中,该对象根据给定的时间映射将“慢动作”应用于剪辑 . 这里的结果是一个AVComposition:

func compose() -> AVComposition {
    let composition = AVMutableComposition(urlAssetInitializationOptions: [AVURLAssetPreferPreciseDurationAndTimingKey: true])

    let emptyTrack = composition.addMutableTrack(withMediaType: AVMediaTypeVideo, preferredTrackID: kCMPersistentTrackID_Invalid)
    let audioTrack = composition.addMutableTrack(withMediaType: AVMediaTypeAudio, preferredTrackID: kCMPersistentTrackID_Invalid)

    let asset = AVAsset(url: url)
    guard let videoAssetTrack = asset.tracks(withMediaType: AVMediaTypeVideo).first else { return composition }

    var segments: [AVCompositionTrackSegment] = []
    for map in timeMappings {

        let segment = AVCompositionTrackSegment(url: url, trackID: kCMPersistentTrackID_Invalid, sourceTimeRange: map.source, targetTimeRange: map.target)
        segments.append(segment)
    }

    emptyTrack.preferredTransform = videoAssetTrack.preferredTransform
    emptyTrack.segments = segments

    if let _ = asset.tracks(withMediaType: AVMediaTypeVideo).first {
        audioTrack.segments = segments
    }

    return composition.copy() as! AVComposition
}

然后我加载这个文件以及原来的文件,它也被映射到slowmo到 AVPlayerItem s,以便在我的应用程序中连接到 AVPlayerLayerAVPlayer 中播放:

let firstItem = AVPlayerItem(asset: originalAsset)
let player1 = AVPlayer(playerItem: firstItem)
firstItem.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmVarispeed
player1.actionAtItemEnd = .none
firstPlayer.player = player1

// set up player 2
let secondItem = AVPlayerItem(asset: renderedVideo)
secondItem.seekingWaitsForVideoCompositionRendering = true //tried false as well
secondItem.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmVarispeed
secondItem.videoComposition = nil // tried AVComposition(propertiesOf: renderedVideo) as well

let player2 = AVPlayer(playerItem: secondItem)
player2.actionAtItemEnd = .none
secondPlayer.player = player2

然后我有一个开始和结束时间来反复循环播放这些视频 . 我不使用 PlayerItemDidReachEnd 因为我对用户输入感兴趣 . 我甚至使用dispatchGroup来确保两个玩家在尝试重播视频之前已经完成搜索:

func playAllPlayersFromStart() {

    let dispatchGroup = DispatchGroup()

    dispatchGroup.enter()

    firstPlayer.player?.currentItem?.seek(to: startTime, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero, completionHandler: { _ in
        dispatchGroup.leave()
    })

    DispatchQueue.global().async { [weak self] in
        guard let startTime = self?.startTime else { return }
        dispatchGroup.wait()

        dispatchGroup.enter()

        self?.secondPlayer.player?.currentItem?.seek(to: startTime, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero, completionHandler: { _ in
            dispatchGroup.leave()
        })


        dispatchGroup.wait()

        DispatchQueue.main.async { [weak self] in
            self?.firstPlayer.player?.play()
            self?.secondPlayer.player?.play()
        }
    }

}

这里奇怪的部分是原始资产,也通过我的compose()函数映射完全正常 . 但是,在其中一个 CMTimeMapping 段中搜索时,已经通过compose()函数运行的renderedVideo有时会冻结 . 冻结的文件与未冻结的文件之间的唯一区别是,已经通过AVAssetExportSession将一个文件导出到NSTemporaryDirectory,以将两个视频轨道合并为一个 . 他们也确定它只是视频层冻结而不是音频,因为如果我将 BoundaryTimeObservers 添加到冻结的播放器,它仍然会击中它们并循环播放 . 音频也正常循环 .

对我来说,最奇怪的部分是视频“恢复”,如果它超过了“冻结”后它暂停开始搜索的位置 . 我已经坚持了好几天,真的很喜欢一些指导 .

其他奇怪的事情需要注意: - 即使原始CMIFM对出口资产的CMTimeMapping是完全相同的持续时间,您会注意到渲染资产的慢动作斜坡比原始资产更加“波动” . - 视频冻结时音频继续播放 . - 视频几乎只会在慢动作部分冻结(由基于片段的CMTimeMapping对象引起 - 渲染的视频似乎必须在开头播放'赶上' . 即使我在两个人完成搜索后都在调用播放,它似乎我认为右侧在开始时起到更快的作用 . 奇怪的是,这些段完全相同,仅引用两个独立的源文件 . 一个位于资产库中,另一个位于NSTemporaryDirectory中 - 在我看来在我调用play之前,AVPlayer和AVPlayerItemStatus是'readyToPlay' . 如果玩家继续PAST它锁定的点,它似乎'解冻' . - 我试图为'AVPlayerItemPlaybackDidStall'添加观察者,但它从未被调用过 .

干杯!

2 回答

  • 0

    问题出在AVAssetExportSession中 . 令我惊讶的是,更改 exporter.canPerformMultiplePassesOverSourceMediaData = true 解决了这个问题 . 尽管文档非常稀少甚至声称'setting this property to true may have no effect',但它似乎确实解决了这个问题 . 非常非常非常奇怪!我认为这是一个错误,并将提交雷达 . 以下是该 properties 的文件:canPerformMultiplePassesOverSourceMediaData

  • 2

    在您的 playAllPlayersFromStart() 方法中, startTime 变量可能在调度的两个任务之间发生了变化(如果该值基于清理更新,则特别有可能) .

    如果你在函数的开头制作 startTime 的本地副本,然后在两个块中使用它,你可能会有更好的运气 .

相关问题