如何在包含框架的两个视频和一个 imageView 中创建一个新视频?
How to create a new video out of two videos and an imageView in an encompassing frame?
我有一个父视图,其中包含两个带有 AVPlayerLayers 的视图和一个 UIImageView。我想将所有这些组合成一个新视频,以捕获父视图的所有内容。
我查看了 ReplayKit,但它没有捕获 AVPlayer 的内容;它不允许我访问视频;它捕获整个屏幕而不是特定视图或框架。
我的一般方法是逐帧遍历视频,捕获帧的图像,将它们设置在覆盖 playerLayer 的 imageView 中,然后使用 [= 捕获父视图的图像10=] -- 然后用所有这些图像制作视频。
我尝试了一些 AVFoundation 选项,但总体而言它们的性能不是很好。以下是我尝试过的一些选项,总是尝试上述模式。
只需使用 videoPlayer.seek(to: frame)
即可设置视频帧 -- 但这种方法非常慢:每 15 秒视频大约需要 42 秒以这种方式遍历每一帧。
使用 AVAssetImageGenerator.generateCGImagesAsynchronously
异步获取所有视频帧,然后迭代上述模式中的那些。这是非常占用内存的,因为我有两个视频的每一帧的图像。我可以分块工作以避免内存崩溃,但总的来说,这种方法仍然相当慢,而且这种批处理的复杂性并不比第一种方法好多少。
使用 AVAssetImageGenerator.copyCGImage(at: frame, actualTime: nil)
同时获取每一帧,但这并不比第一个选项快。
使用 AVAssetReader
并使用 copyNextSampleBuffer
遍历每一帧——与上述任何选项相比都没有真正的改进。
我可能会做一些事情来优化处理,但我认为它们并不能解决上面提到的根本问题。例如,我可能会降低视频质量,或者 trim 因为某些视频在其帧内不可见,或者降低帧速率,但我宁愿尽可能避免这些。
此时我想我可能不得不使用 Metal。有什么建议吗?
这会很困难,因为您要做的是添加一个钩子,通过从视图中实时捕获原始图像缓冲区流来获取已经渲染的 GPU 的合成结果.这在 CPU/GPU 或内存使用方面几乎没有效率。如果您以某种方式访问 GPU 的本机 API 以更直接的方式获取合成的原始缓冲区,您可能会成功,但这很尴尬。
更自然的做法是——在获得所需材料(视频、图像等)后立即进行适当的视频合成,然后向用户展示处理后的结果并对其进行操作随意(将数据转储为文件等)。
总之,尽量google"ios video composition"。 AFAIK,AVFoundation 提供相关功能。您可能还想研究一些库,例如 this 以减少自己编写所有低级代码的麻烦。
我走了一条不同的路线,这似乎可以解决问题。您可以在 repo 中检查工作版本,但大部分代码都在下面。代码不是生产 ready/very 干净的,只是概念的证明——因此使用 !
、长函数、重复等
func overlapVideos() {
let composition = AVMutableComposition()
// make main video instruction
let mainInstruction = AVMutableVideoCompositionInstruction()
guard let pathUrl = Bundle.main.url(forResource: "IMG_7165", withExtension: "MOV") else {
assertionFailure()
return
}
// make first video track and add to composition
let firstAsset = AVAsset(url: pathUrl)
// timeframe will match first video for this example
mainInstruction.timeRange = CMTimeRangeMake(start: .zero, duration: firstAsset.duration)
guard let firstTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) else {
assertionFailure()
return
}
try! firstTrack.insertTimeRange(CMTimeRangeMake(start: .zero, duration: firstAsset.duration), of: firstAsset.tracks(withMediaType: .video)[0], at: .zero)
// add layer instruction for first video
let firstVideoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: firstTrack)
let firstMove = CGAffineTransform(translationX: 500, y: 400)
let firstScale = CGAffineTransform(scaleX: 0.1, y: 0.1)
firstVideoLayerInstruction.setTransform(firstMove.concatenating(firstScale), at: .zero)
mainInstruction.layerInstructions.append(firstVideoLayerInstruction)
// make second video track and add to composition
let secondAsset = AVAsset(url: pathUrl)
guard let secondTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) else {
assertionFailure()
return
}
try! secondTrack.insertTimeRange(CMTimeRangeMake(start: .zero, duration: secondAsset.duration), of: secondAsset.tracks(withMediaType: .video)[0], at: .zero)
// add layer instruction for second video
let secondVideoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: secondTrack)
let secondMove = CGAffineTransform(translationX: -100, y: -100)
let secondScale = CGAffineTransform(scaleX: 0.1, y: 0.1)
secondVideoLayerInstruction.setTransform(secondMove.concatenating(secondScale), at: .zero)
mainInstruction.layerInstructions.append(secondVideoLayerInstruction)
// make third video track and add to composition
let thirdAsset = AVAsset(url: pathUrl)
guard let thirdTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) else {
assertionFailure()
return
}
try! thirdTrack.insertTimeRange(CMTimeRangeMake(start: .zero, duration: thirdAsset.duration), of: thirdAsset.tracks(withMediaType: .video)[0], at: .zero)
// add layer instruction for third video
let thirdVideoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: thirdTrack)
let thirdMove = CGAffineTransform(translationX: 0, y: 1000)
let thirdScale = CGAffineTransform(scaleX: 0.1, y: 0.1)
thirdVideoLayerInstruction.setTransform(thirdMove.concatenating(thirdScale), at: .zero)
mainInstruction.layerInstructions.append(thirdVideoLayerInstruction)
// make video composition
let videoComposition = AVMutableVideoComposition()
videoComposition.instructions = [mainInstruction]
videoComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)
videoComposition.renderSize = CGSize(width: 640, height: 480)
// export
let searchPaths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentDirectory = searchPaths[0]
let filePath = documentDirectory.appending("output.mov")
let outputUrl = URL(fileURLWithPath: filePath)
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath) {
try! fileManager.removeItem(at: outputUrl)
}
guard let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality) else {
assertionFailure()
return
}
exporter.videoComposition = videoComposition
exporter.outputFileType = .mov
exporter.outputURL = outputUrl
exporter.exportAsynchronously {
DispatchQueue.main.async { [weak self] in
// play video, etc.
}
}
}
我有一个父视图,其中包含两个带有 AVPlayerLayers 的视图和一个 UIImageView。我想将所有这些组合成一个新视频,以捕获父视图的所有内容。
我查看了 ReplayKit,但它没有捕获 AVPlayer 的内容;它不允许我访问视频;它捕获整个屏幕而不是特定视图或框架。
我的一般方法是逐帧遍历视频,捕获帧的图像,将它们设置在覆盖 playerLayer 的 imageView 中,然后使用 [= 捕获父视图的图像10=] -- 然后用所有这些图像制作视频。
我尝试了一些 AVFoundation 选项,但总体而言它们的性能不是很好。以下是我尝试过的一些选项,总是尝试上述模式。
只需使用
videoPlayer.seek(to: frame)
即可设置视频帧 -- 但这种方法非常慢:每 15 秒视频大约需要 42 秒以这种方式遍历每一帧。使用
AVAssetImageGenerator.generateCGImagesAsynchronously
异步获取所有视频帧,然后迭代上述模式中的那些。这是非常占用内存的,因为我有两个视频的每一帧的图像。我可以分块工作以避免内存崩溃,但总的来说,这种方法仍然相当慢,而且这种批处理的复杂性并不比第一种方法好多少。使用
AVAssetImageGenerator.copyCGImage(at: frame, actualTime: nil)
同时获取每一帧,但这并不比第一个选项快。使用
AVAssetReader
并使用copyNextSampleBuffer
遍历每一帧——与上述任何选项相比都没有真正的改进。
我可能会做一些事情来优化处理,但我认为它们并不能解决上面提到的根本问题。例如,我可能会降低视频质量,或者 trim 因为某些视频在其帧内不可见,或者降低帧速率,但我宁愿尽可能避免这些。
此时我想我可能不得不使用 Metal。有什么建议吗?
这会很困难,因为您要做的是添加一个钩子,通过从视图中实时捕获原始图像缓冲区流来获取已经渲染的 GPU 的合成结果.这在 CPU/GPU 或内存使用方面几乎没有效率。如果您以某种方式访问 GPU 的本机 API 以更直接的方式获取合成的原始缓冲区,您可能会成功,但这很尴尬。
更自然的做法是——在获得所需材料(视频、图像等)后立即进行适当的视频合成,然后向用户展示处理后的结果并对其进行操作随意(将数据转储为文件等)。
总之,尽量google"ios video composition"。 AFAIK,AVFoundation 提供相关功能。您可能还想研究一些库,例如 this 以减少自己编写所有低级代码的麻烦。
我走了一条不同的路线,这似乎可以解决问题。您可以在 repo 中检查工作版本,但大部分代码都在下面。代码不是生产 ready/very 干净的,只是概念的证明——因此使用 !
、长函数、重复等
func overlapVideos() {
let composition = AVMutableComposition()
// make main video instruction
let mainInstruction = AVMutableVideoCompositionInstruction()
guard let pathUrl = Bundle.main.url(forResource: "IMG_7165", withExtension: "MOV") else {
assertionFailure()
return
}
// make first video track and add to composition
let firstAsset = AVAsset(url: pathUrl)
// timeframe will match first video for this example
mainInstruction.timeRange = CMTimeRangeMake(start: .zero, duration: firstAsset.duration)
guard let firstTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) else {
assertionFailure()
return
}
try! firstTrack.insertTimeRange(CMTimeRangeMake(start: .zero, duration: firstAsset.duration), of: firstAsset.tracks(withMediaType: .video)[0], at: .zero)
// add layer instruction for first video
let firstVideoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: firstTrack)
let firstMove = CGAffineTransform(translationX: 500, y: 400)
let firstScale = CGAffineTransform(scaleX: 0.1, y: 0.1)
firstVideoLayerInstruction.setTransform(firstMove.concatenating(firstScale), at: .zero)
mainInstruction.layerInstructions.append(firstVideoLayerInstruction)
// make second video track and add to composition
let secondAsset = AVAsset(url: pathUrl)
guard let secondTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) else {
assertionFailure()
return
}
try! secondTrack.insertTimeRange(CMTimeRangeMake(start: .zero, duration: secondAsset.duration), of: secondAsset.tracks(withMediaType: .video)[0], at: .zero)
// add layer instruction for second video
let secondVideoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: secondTrack)
let secondMove = CGAffineTransform(translationX: -100, y: -100)
let secondScale = CGAffineTransform(scaleX: 0.1, y: 0.1)
secondVideoLayerInstruction.setTransform(secondMove.concatenating(secondScale), at: .zero)
mainInstruction.layerInstructions.append(secondVideoLayerInstruction)
// make third video track and add to composition
let thirdAsset = AVAsset(url: pathUrl)
guard let thirdTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) else {
assertionFailure()
return
}
try! thirdTrack.insertTimeRange(CMTimeRangeMake(start: .zero, duration: thirdAsset.duration), of: thirdAsset.tracks(withMediaType: .video)[0], at: .zero)
// add layer instruction for third video
let thirdVideoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: thirdTrack)
let thirdMove = CGAffineTransform(translationX: 0, y: 1000)
let thirdScale = CGAffineTransform(scaleX: 0.1, y: 0.1)
thirdVideoLayerInstruction.setTransform(thirdMove.concatenating(thirdScale), at: .zero)
mainInstruction.layerInstructions.append(thirdVideoLayerInstruction)
// make video composition
let videoComposition = AVMutableVideoComposition()
videoComposition.instructions = [mainInstruction]
videoComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)
videoComposition.renderSize = CGSize(width: 640, height: 480)
// export
let searchPaths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentDirectory = searchPaths[0]
let filePath = documentDirectory.appending("output.mov")
let outputUrl = URL(fileURLWithPath: filePath)
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath) {
try! fileManager.removeItem(at: outputUrl)
}
guard let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality) else {
assertionFailure()
return
}
exporter.videoComposition = videoComposition
exporter.outputFileType = .mov
exporter.outputURL = outputUrl
exporter.exportAsynchronously {
DispatchQueue.main.async { [weak self] in
// play video, etc.
}
}
}