使用 CIFilters 在 CALayer 层次结构中渲染视频

The*_*heo 1 avfoundation core-video ios avasset cifilter

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

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

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

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

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

期望视频合成仅包含 AVCoreImageFilterVideoCompositionInstruction

我尝试解决此限制,如下所示:

解决方法 1

1) 使用AVAssetReader和设置导出AVAssetWriter

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

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

4)CVPixelBuffer使用资产编写器获取每个帧的数据,CVPixelBufferGetBaseAddressCGContext使用该数据创建一个。

5) 使用CALayer.render(in ctx: CGContext).

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

解决方法 2

另一种方法可能是分两步完成此操作:首先,将源视频仅保存应用到文件的过滤器,如B,然后使用该视频文件将视频嵌入到图层合成中,如A。但是,由于它使用两次传递,我想这并不像它本来的那样有效。

概括

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

Fab*_*ato 5

实现这一点的方法是使用自定义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))
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

如果您需要在不同时间使用不同的过滤器,您可以使用自定义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
Run Code Online (Sandbox Code Playgroud)

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