以下是问题的GIF链接:
https://gifyu.com/images/ScreenRecording2017-01-25at02.20PM.gif
我从相机胶卷中取出PHAsset
,将其添加到可变组合中,添加另一个视频曲目,操纵添加的曲目,然后通过AVAssetExportSession
导出。结果是在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
中以在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'添加观察者。但它从未被召唤过。
干杯!
答案 0 :(得分:2)
问题出在AVAssetExportSession中。令我惊讶的是,更改exporter.canPerformMultiplePassesOverSourceMediaData = true
修复了问题。尽管文档很稀疏,甚至声称“将此属性设置为true可能没有效果”,但它似乎确实解决了这个问题。非常非常非常奇怪!我认为这是一个错误,并将提交雷达。以下是该属性的文档:canPerformMultiplePassesOverSourceMediaData
答案 1 :(得分:0)
在您的playAllPlayersFromStart()
方法中,startTime
变量可能在调度的两个任务之间发生了变化(如果该值基于清理更新,则特别有可能)。
如果您在函数开头制作了startTime
的本地副本,然后在两个块中使用它,那么您可能会有更好的运气。