在我的iOS应用的用户界面中,我显示了CALayer
的复杂层次结构。其中的一层是AVPlayerLayer
,它显示实时(使用CIFilter
应用了AVVideoComposition(asset:, applyingCIFiltersWithHandler:)
的视频。
现在,我想将此图层合成导出到视频文件。 AVFoundation
中有两个似乎有用的工具:
A :AVVideoCompositionCoreAnimationTool
,可在CALayer
层次结构(可能是动画)中渲染视频
B :AVVideoComposition(asset:, applyingCIFiltersWithHandler:)
(我也在用户界面中使用过)将CIFilter
应用于视频资产。
但是,这两个工具不能同时使用:如果我启动结合了这些工具的AVAssetExportSession
,则AVFoundation
会抛出NSInvalidArgumentException
:
期望视频组成仅包含
AVCoreImageFilterVideoCompositionInstruction
我尝试通过以下方法解决此限制:
解决方法1
1)使用AVAssetReader
和AVAssetWriter
2)从资产读取器获取样本缓冲区并应用CIFilter
,将结果保存在CGImage
中。
3)将CGImage
设置为层层次结构中视频层的content
。现在,图层层次结构看起来像最终视频的一帧。
4)使用CVPixelBuffer
从资产编写者那里获取每一帧的CVPixelBufferGetBaseAddress
数据,并使用该数据创建一个CGContext
。
5)使用CALayer.render(in ctx: CGContext)
将图层渲染到该上下文。
此设置有效,但速度非常慢-导出5秒钟的视频有时需要一分钟。看来CoreGraphics
调用是这里的瓶颈(我想是因为使用这种方法,合成发生在CPU上?)
解决方法2
另一种方法可能是分两个步骤执行此操作:首先,将源视频保存为仅将滤镜应用于 B 中的文件,然后使用该视频文件嵌入视频就像 A 一样。但是,由于它使用两次通过,因此我认为这样做效率不高。
摘要
什么是将视频导出到文件的理想方法,最好是一次通过?如何同时使用CIFilter
和AVVideoCompositionCoreAnimationTool
?是否有本机的方法在AVFoundation
中设置结合了这些工具的“管道”?
答案 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
来导出视频