如何在包含框架的两个视频和一个 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 选项,但总体而言它们的性能不是很好。以下是我尝试过的一些选项,总是尝试上述模式。

  1. 只需使用 videoPlayer.seek(to: frame) 即可设置视频帧 -- 但这种方法非常慢:每 15 秒视频大约需要 42 秒以这种方式遍历每一帧。

  2. 使用 AVAssetImageGenerator.generateCGImagesAsynchronously 异步获取所有视频帧,然后迭代上述模式中的那些。这是非常占用内存的,因为我有两个视频的每一帧的图像。我可以分块工作以避免内存崩溃,但总的来说,这种方法仍然相当慢,而且这种批处理的复杂性并不比第一种方法好多少。

  3. 使用 AVAssetImageGenerator.copyCGImage(at: frame, actualTime: nil) 同时获取每一帧,但这并不比第一个选项快。

  4. 使用 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.
            }
        }
    }