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

时间:2017-01-25 22:35:05

标签: ios swift xcode avfoundation avasset

以下是问题的GIF链接:

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

我从相机胶卷中取出PHAsset,将其添加到可变组合中,添加另一个视频曲目,操纵添加的曲目,然后通过AVAssetExportSession导出。结果是在NSTemporaryDirectory()

中保存了.mov文件扩展名的quicktime文件
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中以在AVPlayer s中播放,该AVPlayerLayer s连接到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 s我的应用:

PlayerItemDidReachEnd

然后我有一个开始和结束时间来反复循环播放这些视频。我没有使用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() } } } ,因为我对最终没有兴趣,我对用户输入时间感兴趣。在尝试重播视频之前,我甚至将dispatchGroup用于 ENSURE 两位玩家都已完成搜索:

CMTimeMapping

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

对我而言,最奇怪的部分是视频“恢复”'如果它超过了暂停的地点,在“冻结”之后开始搜寻。我已经坚持了几天,并且非常喜欢一些指导。

其他奇怪的事情要注意: - 即使原始资产与出口资产的CMTimeMapping完全相同,但您会注意到渲染资产的慢动作斜坡更加“不稳定”。比原来的。 - 视频冻结时音频继续播放。 - 视频几乎只在慢动作部分冻结(由基于段的CMTimeMapping对象引起) - 呈现的视频似乎必须发挥并赶上'在开始。尽管在两人完成搜索之后我都在打电话,但在我看来,右侧在开始时起到了更快的作用。奇怪的是,段是完全相同的,只是引用两个单独的源文件。一个位于资产库中,另一个位于NSTemporaryDirectory中 - 在我看来,AVPlayer和AVPlayerItemStatus是' readyToPlay'在我打电话之前。 - 似乎解冻了#39;如果玩家继续PAST它锁定的点。 - 我试图为' AVPlayerItemPlaybackDidStall'添加观察者。但它从未被召唤过。

干杯!

2 个答案:

答案 0 :(得分:2)

问题出在AVAssetExportSession中。令我惊讶的是,更改exporter.canPerformMultiplePassesOverSourceMediaData = true修复了问题。尽管文档很稀疏,甚至声称“将此属性设置为true可能没有效果”,但它似乎确实解决了这个问题。非常非常非常奇怪!我认为这是一个错误,并将提交雷达。以下是该属性的文档:canPerformMultiplePassesOverSourceMediaData

答案 1 :(得分:0)

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

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