Estoy grabando un video filtrado a través de la cámara de un iPhone y hay un gran aumento en el uso de la CPU al convertir una imagen CI en una imagen UI en tiempo real durante la grabación. Mi función de búfer para hacer un CVPixelBuffer usa una UIImage, que hasta ahora requiere que haga esta conversión. En su lugar, me gustaría hacer una función de búfer que tome una CIImage si es posible para poder omitir la conversión de UIImage a CIImage. Estoy pensando que esto me dará un gran impulso en el rendimiento al grabar video, ya que no habrá ninguna transferencia entre la CPU y la GPU.
Esto es lo que tengo ahora. Dentro de mi función captureOutput, creo una UIImage a partir de CIImage, que es la imagen filtrada. Creo un CVPixelBuffer a partir de la función de búfer usando UIImage y lo agrego a pixelBufferInput de assetWriter:
let imageUI = UIImage(ciImage: ciImage) let filteredBuffer:CVPixelBuffer? = buffer(from: imageUI) let success = self.assetWriterPixelBufferInput?.append(filteredBuffer!, withPresentationTime: self.currentSampleTime!)Mi función de búfer que usa una 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 }Cree un CIContext y utilícelo para representar CIImage directamente en su CVPixelBuffer usando CIContext.render(_: CIImage, to buffer: CVPixelBuffer) .
La respuesta de Rob Mayoff lo resume, pero hay algo MUY-MUY-MUY importante a tener en cuenta:
Core Image difiere la representación hasta que el cliente solicita el acceso al búfer de fotogramas, es decir,
CVPixelBufferLockBaseAddress.
Aprendí esto al hablar con el ingeniero de soporte técnico de Apple y no pude encontrar esto en ninguno de los documentos. Usé esto solo con macOS, pero imagina que no sería diferente en iOS.
Tenga en cuenta que si bloquea el búfer antes del renderizado, seguirá funcionando, pero se ejecutará un cuadro por detrás y el primer renderizado estará vacío.
Finalmente, se menciona más de una vez en SO e incluso en este hilo: evite crear un nuevo CVPixelBuffer para cada procesamiento porque cada búfer consume una tonelada de recursos del sistema. Es por eso que tenemos CVPixelBufferPool : Apple lo usa en sus marcos, ¡así que puede lograr un rendimiento aún mejor! ✌️
Para ampliar la respuesta que obtuve de Rob Mayoff, mostraré lo que cambié a continuación:
Dentro de la función captureOutput, cambié mi código a:
let filteredBuffer : CVPixelBuffer? = buffer(from: ciImage) filterContext?.render(_:ciImage, to:filteredBuffer!) let success = self.assetWriterPixelBufferInput?.append(filteredBuffer!, withPresentationTime: self.currentSampleTime!)Observe que la función de búfer pasa una ciImage. Formateé mi función de búfer para pasar CIImage y pude deshacerme de mucho de lo que había dentro:
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 }