【问题标题】:Rendering a video in a CALayer hierarchy using CIFilters使用 CIFilters 在 CALayer 层次结构中渲染视频
【发布时间】:2019-10-24 10:12:36
【问题描述】:

在我的 iOS 应用程序的 UI 中,我显示了 CALayers 的复杂层次结构。其中一个层是AVPlayerLayer,它显示一个实时应用CIFilters 的视频(使用AVVideoComposition(asset:, applyingCIFiltersWithHandler:))。

现在我想将此图层合成导出到视频文件。 AVFoundation 中有两个工具似乎很有帮助:

AAVVideoCompositionCoreAnimationTool 允许在(可能是动画的)CALayer 层次结构中渲染视频

BAVVideoComposition(asset:, applyingCIFiltersWithHandler:),我也在 UI 中使用,将 CIFilters 应用于视频资产。

但是,这两个工具不能同时使用:如果我启动一个结合这些工具的AVAssetExportSessionAVFoundation 会抛出一个NSInvalidArgumentException

期望视频合成仅包含 AVCoreImageFilterVideoCompositionInstruction

我尝试按如下方式解决此限制:

解决方法 1

1) 使用AVAssetReaderAVAssetWriter 设置导出

2) 从资产读取器获取样本缓冲区并应用CIFilter,将结果保存在CGImage

3) 将CGImage设置为图层层次结构中视频图层的content。现在图层层次结构“看起来像”最终视频的一帧。

4) 使用 CVPixelBufferGetBaseAddress 从资产编写器获取每个帧的 CVPixelBuffer 的数据,并使用该数据创建一个 CGContext

5) 使用 CALayer.render(in ctx: CGContext) 将我的图层渲染到该上下文。

此设置有效,但速度极慢 - 导出 5 秒视频有时需要一分钟。看起来CoreGraphics 调用是这里的瓶颈(我猜这是因为使用这种方法,合成发生在 CPU 上?)

解决方法 2

另一种方法是分两步执行此操作:首先,将源视频与应用到文件中的过滤器一起保存,如 B,然后使用该视频文件嵌入视频在 A 中的图层组合中。但是,由于它使用两次传递,我想这可能没有那么有效。

总结

将此视频导出到文件的好方法是什么,最好是一次性完成?如何同时使用CIFilters 和AVVideoCompositionCoreAnimationTool?有没有一种本地方法可以在 AVFoundation 中设置结合这些工具的“管道”?

【问题讨论】:

    标签: ios avfoundation cifilter avasset core-video


    【解决方案1】:

    实现这一点的方法是使用自定义AVVideoCompositing。此对象允许您组合(在本例中应用 CIFilter)每个视频帧。

    这是一个将CIPhotoEffectNoir 效果应用于整个视频的示例实现:

    class VideoFilterCompositor: NSObject, AVVideoCompositing {
    
        var sourcePixelBufferAttributes: [String : Any]? = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]
        var requiredPixelBufferAttributesForRenderContext: [String : Any] = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]
        private var renderContext: AVVideoCompositionRenderContext?
    
        func renderContextChanged(_ newRenderContext: AVVideoCompositionRenderContext) {
            renderContext = newRenderContext
        }
    
        func cancelAllPendingVideoCompositionRequests() {
        }
    
        private let filter = CIFilter(name: "CIPhotoEffectNoir")!
        private let context = CIContext()
        func startRequest(_ asyncVideoCompositionRequest: AVAsynchronousVideoCompositionRequest) {
            guard let track = asyncVideoCompositionRequest.sourceTrackIDs.first?.int32Value, let frame = asyncVideoCompositionRequest.sourceFrame(byTrackID: track) else {
                asyncVideoCompositionRequest.finish(with: NSError(domain: "VideoFilterCompositor", code: 0, userInfo: nil))
                return
            }
            filter.setValue(CIImage(cvPixelBuffer: frame), forKey: kCIInputImageKey)
            if let outputImage = filter.outputImage, let outBuffer = renderContext?.newPixelBuffer() {
                context.render(outputImage, to: outBuffer)
                asyncVideoCompositionRequest.finish(withComposedVideoFrame: outBuffer)
            } else {
                asyncVideoCompositionRequest.finish(with: NSError(domain: "VideoFilterCompositor", code: 0, userInfo: nil))
            }
        }
    
    }
    

    如果你需要在不同的时间有不同的过滤器,你可以使用自定义的AVVideoCompositionInstructionProtocol,你可以从AVAsynchronousVideoCompositionRequest获得

    接下来,您需要将它与您的AVMutableVideoComposition 一起使用,所以:

    let videoComposition = AVMutableVideoComposition()
    videoComposition.customVideoCompositorClass = VideoFilterCompositor.self
    //Add your animator tool as usual
    let animator = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: v, in: p)
    videoComposition.animationTool = animator
    //Finish setting up the composition
    

    有了这个,您应该能够使用常规的AVAssetExportSession 导出视频,设置它的videoComposition

    【讨论】:

    • 很好的解决方案!谢谢!
    猜你喜欢
    • 1970-01-01
    • 2022-01-14
    • 1970-01-01
    • 2019-08-30
    • 2020-10-02
    • 2015-08-08
    • 1970-01-01
    • 2015-01-11
    • 1970-01-01
    相关资源
    最近更新 更多