我正在通过iPhone相机录制过滤后的视频,并且在录制时实时将CIImage转换为UIImage时,CPU使用率大幅增加。我制作CVPixelBuffer的缓冲区函数使用UIImage,到目前为止,这需要我进行这种转换。如果可能的话,我想创建一个缓冲区函数,该函数需要 CIImage,这样我就可以跳过从 UIImage 到 CIImage 的转换。我认为这将在录制视频时给我带来巨大的性能提升,因为 CPU 和 GPU 之间不会有任何交接。
这就是我现在所拥有的。在我的captureOutput函数中,我从CIImage创建一个UIImage,这是过滤后的图像。我使用 UIImage 从缓冲区函数创建一个 CVPixelBuffer,并将其附加到 assetWriter 的 pixelBufferInput 中:
let imageUI = UIImage(ciImage: ciImage)
let filteredBuffer:CVPixelBuffer? = buffer(from: imageUI)
let success = self.assetWriterPixelBufferInput?.append(filteredBuffer!, withPresentationTime: self.currentSampleTime!)
我的缓冲区函数使用 UIImage:
func buffer(from image: UIImage) -> CVPixelBuffer? {
let attrs = [kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue, kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue] as CFDictionary
var pixelBuffer : CVPixelBuffer?
let status = CVPixelBufferCreate(kCFAllocatorDefault, Int(image.size.width), Int(image.size.height), kCVPixelFormatType_32ARGB, attrs, &pixelBuffer)
guard (status == kCVReturnSuccess) else {
return nil
}
CVPixelBufferLockBaseAddress(pixelBuffer!, CVPixelBufferLockFlags(rawValue: 0))
let pixelData = CVPixelBufferGetBaseAddress(pixelBuffer!)
let videoRecContext = CGContext(data: pixelData,
width: Int(image.size.width),
height: Int(image.size.height),
bitsPerComponent: 8,
bytesPerRow: videoRecBytesPerRow,
space: (MTLCaptureView?.colorSpace)!, // It's getting the current colorspace from a MTKView
bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue)
videoRecContext?.translateBy(x: 0, y: image.size.height)
videoRecContext?.scaleBy(x: 1.0, y: -1.0)
UIGraphicsPushContext(videoRecContext!)
image.draw(in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height))
UIGraphicsPopContext()
CVPixelBufferUnlockBaseAddress(pixelBuffer!, CVPixelBufferLockFlags(rawValue: 0))
return pixelBuffer
}
创建一个CIContext
,并使用 CIContext.render(_: CIImage, to buffer: CVPixelBuffer)
将CIImage
直接渲染到您的CVPixelBuffer
。
rob mayoff 的回答总结了它,但有一件非常非常重要的事情要记住:
Core Image 延迟渲染,直到客户端请求访问帧缓冲区,即
CVPixelBufferLockBaseAddress
.
我从与Apple的技术支持工程师交谈中了解到这一点,并且在任何文档中都找不到这一点。我只在macOS上使用它,但想象一下它在iOS上不会有什么不同。
请记住,如果在渲染之前锁定缓冲区,它仍然可以工作,但会落后一帧,并且第一个渲染将为空。
最后,在SO上甚至在这个线程中都不止一次提到它:避免为每个渲染创建新的CVPixelBuffer
,因为每个缓冲区都会占用大量的系统资源。这就是为什么我们有CVPixelBufferPool
- Apple在他们的框架中使用它,所以你能实现更好的性能吗!✌️
为了扩展我从rob mayoff那里得到的答案,我将在下面显示我所做的更改:
在captureOutput 函数中,我将代码更改为:
let filteredBuffer : CVPixelBuffer? = buffer(from: ciImage)
filterContext?.render(_:ciImage, to:filteredBuffer!)
let success = self.assetWriterPixelBufferInput?.append(filteredBuffer!, withPresentationTime: self.currentSampleTime!)
请注意,缓冲区函数传递 ciImage。我格式化了我的缓冲区函数以传递 CIImage,并且能够摆脱里面的很多内容:
func buffer(from image: CIImage) -> CVPixelBuffer? {
let attrs = [kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue, kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue] as CFDictionary
var pixelBuffer : CVPixelBuffer?
let status = CVPixelBufferCreate(kCFAllocatorDefault, Int(image.extent.width), Int(image.extent.height), kCVPixelFormatType_32ARGB, attrs, &pixelBuffer)
guard (status == kCVReturnSuccess) else {
return nil
}
return pixelBuffer
}