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

时间:2019-10-24 10:12:36

标签: ios avfoundation cifilter avasset core-video

在我的iOS应用的用户界面中,我显示了CALayer的复杂层次结构。其中的一层是AVPlayerLayer,它显示实时(使用CIFilter应用了AVVideoComposition(asset:, applyingCIFiltersWithHandler:)的视频。

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

A AVVideoCompositionCoreAnimationTool,可在CALayer层次结构(可能是动画)中渲染视频

B AVVideoComposition(asset:, applyingCIFiltersWithHandler:)(我也在用户界面中使用过)将CIFilter应用于视频资产。

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

  

期望视频组成仅包含AVCoreImageFilterVideoCompositionInstruction

我尝试通过以下方法解决此限制:

解决方法1

1)使用AVAssetReaderAVAssetWriter

设置导出

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

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

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

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

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

解决方法2

另一种方法可能是分两个步骤执行此操作:首先,将源视频保存为仅将滤镜应用于 B 中的文件,然后使用该视频文件嵌入视频就像 A 一样。但是,由于它使用两次通过,因此我认为这样做效率不高。

摘要

什么是将视频导出到文件的理想方法,最好是一次通过?如何同时使用CIFilterAVVideoCompositionCoreAnimationTool?是否有本机的方法在AVFoundation中设置结合了这些工具的“管道”?

1 个答案:

答案 0 :(得分: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来导出视频