使用CIFilters在CALayer层级中渲染视频

4
在我的iOS应用程序的用户界面中,我展示了一个复杂的CALayer层次结构。其中之一是一个AVPlayerLayer,它在实时应用CIFilter (使用 AVVideoComposition(asset:,applyingCIFiltersWithHandler:)),显示一个视频。
现在我想将这个图层组合导出到一个视频文件中。在AVFoundation中有两个工具似乎很有用: A:AVVideoCompositionCoreAnimationTool,可以在(可能是动画的)CALayer层次结构内呈现视频 B:我在UI中也使用它,将CIFilter应用于视频资源的AVVideoComposition(asset:,applyingCIFiltersWithHandler:)
然而,这两个工具不能同时使用:如果我启动一个合并这些工具的AVAssetExportSessionAVFoundation会抛出NSInvalidArgumentException

Expecting video composition to contain only 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一样使用该视频文件将视频嵌入层组合中。但是,由于要进行两次处理,所以我认为这不太有效率。 总结

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

1个回答

8

实现此功能的方法是使用自定义 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属性。


太棒了!谢谢! - Alexander Vasenin
有没有人有一个示例项目可以看到如何应用这个完整的代码?也就是说,AVMutableVideoComposition是否可以在AVCaptureMovieFileOutput中应用,或者必须在captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection)中应用? - Peder Wessel
@PederWessel,这里有一个类似的例子。https://github.com/claygarrett/CustomVideoCompositor 我采用了类似的方法来将CIFilter作为指令传递。 - Zahid Usman Cheema
如果你需要应用变换,你可以在filter.setValue行之后这样做,通过添加let outputImage = filter.outputImage?.transformed(by: transformforCompositor)。然后,如果let outputImage = filter.outputImage变成了if let outputImage = outputImage。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接