代码之家  ›  专栏  ›  技术社区  ›  James Woodrow

无法使用AVVideoCompositionCoreAnimationTool在视频中显示动画CALayer

  •  6
  • James Woodrow  · 技术社区  · 8 年前

    更新6: 我已经设法完全解决了我的问题,但我仍然希望有一个更好的解释比我猜测的是,它没有工作的原因,如果我是不正确的

    首先,我的自定义CALayer处理我自己的雪碧床单

    class SpriteLayer: CALayer {
        var frameIndex: Int
    
        override init() {
            // Using 0 as a default state
            self.frameIndex = 0
            super.init()
        }
    
        required init?(coder aDecoder: NSCoder) {
            self.frameIndex = 0
            super.init(coder: aDecoder)
        }
    
        override func display() {
            let currentFrameIndex = self.frameIndex
            if currentFrameIndex == 0 {
                return
            }
            let frameSize = self.contentsRect.size
            self.contentsRect = CGRect(x: 0, y: CGFloat(currentFrameIndex - 1) * frameSize.height, width: frameSize.width, height: frameSize.height)
        }
    
        override func action(forKey event: String) -> CAAction? {
            if event == "contentsRect" {
                return nil
            }
            return super.action(forKey: event)
        }
    
        override class func needsDisplay(forKey key: String) -> Bool {
            return key == "frameIndex"
        }
    }
    

    Gif是一个基本的类,没有什么花哨的东西,工作得很好。 gif.Strip UIImage 表示gif的垂直精灵表。

    现在是应该导出新视频的方法(它是用于导出的更大类的一部分)。

    func convertAndExport(to url :URL , completion: @escaping () -> Void ) {
            // Get Initial info and make sure our destination is available
            self.outputURL = url
            let stripCgImage = self.gif.strip!.cgImage!
            // This is used to time how long the export took
            let start = DispatchTime.now()
            do {
                try FileManager.default.removeItem(at: outputURL)
            } catch {
                print("Remove Error: \(error.localizedDescription)")
                print(error)
            }
            // Find and load "sample.mp4" as a AVAsset
            let videoPath = Bundle.main.path(forResource: "sample", ofType: "mp4")!
            let videoUrl = URL(fileURLWithPath: videoPath)
            let videoAsset = AVAsset(url: videoUrl)
            // Start a new mutable Composition with the same base video track
            let mixComposition = AVMutableComposition()
            let compositionVideoTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)!
            let clipVideoTrack = videoAsset.tracks(withMediaType: .video).first!
            do {
                try compositionVideoTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, videoAsset.duration), of: clipVideoTrack, at: kCMTimeZero)
            } catch {
                print("Insert Error: \(error.localizedDescription)")
                print(error)
                return
            }
            compositionVideoTrack.preferredTransform = clipVideoTrack.preferredTransform
            // Quick access to the video size
            let videoSize = clipVideoTrack.naturalSize
            // Setup CALayer and it's animation
            let aLayer = SpriteLayer()
            aLayer.contents = stripCgImage
            aLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height)
            aLayer.opacity = 1.0
            aLayer.masksToBounds = true
            aLayer.bounds = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height)
            aLayer.contentsRect = CGRect(x: 0, y: 0, width: 1, height: 1.0 / 3.0)
            let spriteAnimation = CABasicAnimation(keyPath: "frameIndex")
            spriteAnimation.fromValue = 1
            spriteAnimation.toValue = 4
            spriteAnimation.duration = 2.25
            spriteAnimation.repeatCount = .infinity
            spriteAnimation.autoreverses = false
            spriteAnimation.beginTime = AVCoreAnimationBeginTimeAtZero
            aLayer.add(spriteAnimation, forKey: nil)
            // Setup Layers for AVVideoCompositionCoreAnimationTool
            let parentLayer = CALayer()
            let videoLayer = CALayer()
            parentLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height)
            videoLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height)
            parentLayer.addSublayer(videoLayer)
            parentLayer.addSublayer(aLayer)
            // Create the mutable video composition
            let videoComp = AVMutableVideoComposition()
            videoComp.renderSize = videoSize
            videoComp.frameDuration = CMTimeMake(1, 30)
            videoComp.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: videoLayer, in: parentLayer)
            // Set the video composition to apply to the composition's video track
            let instruction = AVMutableVideoCompositionInstruction()
            instruction.timeRange = CMTimeRangeMake(kCMTimeZero, mixComposition.duration)
            let videoTrack = mixComposition.tracks(withMediaType: .video).first!
            let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
            instruction.layerInstructions = [layerInstruction]
            videoComp.instructions = [instruction]
            // Initialize export session
            let assetExport = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetPassthrough)!
            assetExport.videoComposition = videoComp
            assetExport.outputFileType = AVFileType.mp4
            assetExport.outputURL = self.outputURL
            assetExport.shouldOptimizeForNetworkUse = true
            // Export
            assetExport.exportAsynchronously {
                let status = assetExport.status
                switch status {
                case .failed:
                    print("Export Failed")
                    print("Export Error: \(assetExport.error!.localizedDescription)")
                    print(assetExport.error!)
                case .unknown:
                    print("Export Unknown")
                case .exporting:
                    print("Export Exporting")
                case .waiting:
                    print("Export Waiting")
                case .cancelled:
                    print("Export Cancelled")
                case .completed:
                    let end = DispatchTime.now()
                    let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds
                    let timeInterval = Double(nanoTime) / 1_000_000_000
                    // Function is now over, we can print how long it took
                    print("Time to generate video: \(timeInterval) seconds")
                    completion()
                }
            }
    }
    

    我的代码基于以下链接

    CABasicAnimation 我的一部分代码和玩我的 CALayer CAKeyframeAnimation 在 contentsRect 在一个Xcode的操场上,它工作得很好,所以我不认为问题出在cabasicanization上,甚至可能不在cabasicanization上 炉匠 它自己。我真的需要一些帮助,因为我不明白为什么我甚至不能得到一个图像显示在我的样本视频的出口。

    更新2: 作为对马特评论的回应,我试着暂时忘记了雪碧床单,把它改成了 CATextLayer

    let aLayer = CATextLayer()
    aLayer.string = "This is a test"
    aLayer.fontSize = videoSize.height / 6
    aLayer.alignmentMode = kCAAlignmentCenter
    aLayer.foregroundColor = UIColor.white.cgColor
    aLayer.bounds = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height / 6)
    

    更新3: 按照马特的要求,我试着换衣服 parentLayer.addSublayer(aLayer) 到 videoLayer.addSublayer(aLayer) documentation 对于 AVVideoCompositionCoreAnimationTool 具体如下

    convenience init(postProcessingAsVideoLayer videoLayer: CALayer, 
                  in animationLayer: CALayer)
    

    parentLayer animationLayer 可能意味着任何动画都应该在这个层中完成。

    我开始发疯了,我现在已经放弃了显示文本或动画图像的想法,我只是想以任何可能的方式影响我的视频,所以我改变了 aLayer 对此:

    let aLayer = CALayer()
    aLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height)
    aLayer.backgroundColor = UIColor.white.cgColor
    

    import PlaygroundSupport
    import UIKit
    import Foundation
    import AVFoundation
    
    func convertAndExport(to url :URL , completion: @escaping () -> Void ) {
        let start = DispatchTime.now()
        do {
            try FileManager.default.removeItem(at: url)
        } catch {
            print("Remove Error: \(error.localizedDescription)")
            print(error)
        }
    
        let videoPath = Bundle.main.path(forResource: "sample", ofType: "mp4")!
        let videoUrl = URL(fileURLWithPath: videoPath)
        let videoAsset = AVURLAsset(url: videoUrl)
        let mixComposition = AVMutableComposition()
        let compositionVideoTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)!
        let clipVideoTrack = videoAsset.tracks(withMediaType: .video).first!
    
        do {
            try compositionVideoTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, videoAsset.duration), of: clipVideoTrack, at: kCMTimeZero)
        } catch {
            print("Insert Error: \(error.localizedDescription)")
            print(error)
            return
        }
        compositionVideoTrack.preferredTransform = clipVideoTrack.preferredTransform
        let videoSize = clipVideoTrack.naturalSize
        print("Video Size Detected: \(videoSize.width) x \(videoSize.height)")
    
        let aLayer = CALayer()
        aLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height)
        aLayer.backgroundColor = UIColor.white.cgColor
    
        let parentLayer = CALayer()
        let videoLayer = CALayer()
        parentLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height)
        videoLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height)
        parentLayer.addSublayer(videoLayer)
        parentLayer.addSublayer(aLayer)
        aLayer.setNeedsDisplay()
        let videoComp = AVMutableVideoComposition()
        videoComp.renderSize = videoSize
        videoComp.frameDuration = CMTimeMake(1, 30)
        videoComp.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: videoLayer, in: parentLayer)
    
        let instruction = AVMutableVideoCompositionInstruction()
        instruction.timeRange = CMTimeRangeMake(kCMTimeZero, mixComposition.duration)
        let videoTrack = mixComposition.tracks(withMediaType: .video).first!
        let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
        instruction.layerInstructions = [layerInstruction]
        videoComp.instructions = [instruction]
    
        let assetExport = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetPassthrough)!
        assetExport.videoComposition = videoComp
        assetExport.outputFileType = AVFileType.mp4
        assetExport.outputURL = url
        assetExport.shouldOptimizeForNetworkUse = true
    
        assetExport.exportAsynchronously {
            let status = assetExport.status
            switch status {
            case .failed:
                print("Export Failed")
                print("Export Error: \(assetExport.error!.localizedDescription)")
                print(assetExport.error!)
            case .unknown:
                print("Export Unknown")
            case .exporting:
                print("Export Exporting")
            case .waiting:
                print("Export Waiting")
            case .cancelled:
                print("Export Cancelled")
            case .completed:
                let end = DispatchTime.now()
                let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds
                let timeInterval = Double(nanoTime) / 1_000_000_000
                print("Time to generate video: \(timeInterval) seconds")
                completion()
            }
        }
    }
    
    let outputUrl = FileManager.default.temporaryDirectory.appendingPathComponent("test.mp4")
    convertAndExport(to: outputUrl) {
        print(outputUrl)
    }
    

    请有人帮我理解我做错了什么。。。

    更新5:

    1 回复  |  直到 8 年前
        1
  •  1
  •   James Woodrow    8 年前

    首先,即使他删除了自己的评论,也要感谢Matt提供了一个工作示例的链接,帮助我把代码中的错误拼凑起来。

    • 首先
    let assetExport = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetPassthrough)!
    

    AVAssetExportPresetHighestQuality 而不是 AVAssetExportPresetPassthrough . 我的猜测是,通过预设意味着你不做任何重新编码,所以设置为最高(不是中等,因为我的导出视频超过400x400)使它,这样我就可以真正重新编码我的视频。我猜这就是阻止导出的视频包含 CALayer

    • 其次(不确定这是否真的影响,但我稍后会尝试)
    parentLayer.addSublayer(aLayer)
    

    我将其替换为:

    videoLayer.addSublayer(aLayer)
    

    不确定这是否真的重要,但我的理解是,这实际上是动画层 AVVideoCompositionCoreAnimationTool 和 parentLayer 只是一个容器,不能容纳更多的东西,但我可能错了。

    • 我做的第三个改变
    let spriteAnimation = CABasicAnimation(keyPath: "frameIndex")
    spriteAnimation.fromValue = 1
    spriteAnimation.toValue = 4
    spriteAnimation.duration = 2.25
    spriteAnimation.repeatCount = .infinity
    spriteAnimation.autoreverses = false
    spriteAnimation.beginTime = AVCoreAnimationBeginTimeAtZero
    aLayer.add(spriteAnimation, forKey: nil)
    

    我改成这样:

    let animation = CAKeyframeAnimation(keyPath: #keyPath(CALayer.contentsRect))
    animation.duration = 2.25
    animation.calculationMode = kCAAnimationDiscrete
    animation.repeatCount = .infinity
    animation.values = [
        CGRect(x: 0, y: 0, width: 1, height: 1/3.0),
        CGRect(x: 0, y: 1/3.0, width: 1, height: 1/3.0),
        CGRect(x: 0, y: 2/3.0, width: 1, height: 1/3.0)
        ] as [CGRect]
    animation.beginTime = AVCoreAnimationBeginTimeAtZero
    animation.fillMode = kCAFillModeBackwards
    animation.isRemovedOnCompletion = false
    aLayer.add(animation, forKey: nil)
    

    这个变化主要是删除了我为sprite工作表定制的动画(因为它总是一样的,我首先想要一个工作示例,然后我将对其进行推广,并可能将其添加到我的私有UI Pod中)。但最重要的是 animation.isRemovedOnCompletion = false CABasicAnimation 导出后不在视频上设置动画,请尝试查看 isRemovedOnCompletion 在动画中设置正确。

    我想这就是我所做的所有改变。

    AVVideoCompositionCoreAnimationTool 和 AVAssetExport 如果有人有兴趣解释的话,我为什么要做我所做的改变来最终让它工作。

    推荐文章