Как вы можете создать CVPixelBuffer непосредственно из CIImage вместо UIImage в Swift?
Я записываю отфильтрованное видео через камеру iPhone, и при преобразовании CIImage в UIImage в режиме реального времени во время записи значительно увеличивается загрузка ЦП. Моя буферная функция для создания CVPixelBuffer использует UIImage, что до сих пор требует от меня этого преобразования. Вместо этого я хотел бы создать буферную функцию, которая по возможности принимает CIImage, чтобы я мог пропустить преобразование из UIImage в CIImage. Я думаю, что это даст мне огромный прирост производительности при записи видео, так как между процессором и графическим процессором не будет никакой передачи.
Это то, что у меня есть сейчас. В моей функции captureOutput я создаю UIImage из CIImage, который является отфильтрованным изображением. Я создаю CVPixelBuffer из функции буфера, используя UIImage, и добавляю его к pixelBufferInput в assetWriter:
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
}
2 ответа
Создать CIContext
и использовать его для рендеринга CIImage
прямо к вашему CVPixelBuffer
с помощью CIContext.render(_: CIImage, to buffer: CVPixelBuffer)
,
Ответ Роба Мэйоффа подводит итог, но следует помнить о ОЧЕНЬ-ОЧЕНЬ важной вещи:
Core Image откладывает рендеринг до тех пор, пока клиент не запросит доступ к буферу кадра, т.е.
CVPixelBufferLockBaseAddress
.
Я узнал об этом, поговорив с инженером службы технической поддержки Apple, и не смог найти этого ни в одной документации. Я использовал это только с macOS, но представьте, что на iOS все не будет иначе.
Имейте в виду, что если вы заблокируете буфер перед рендерингом, он все равно будет работать, но будет работать на один кадр позже, и первый рендер будет пуст.
Наконец, на SO и даже в этой теме неоднократно упоминалось: избегайте создания новых CVPixelBuffer
для каждого рендера, потому что каждый буфер занимает тонну системных ресурсов. Вот почему у нас CVPixelBufferPool
- Apple использует его в своих фреймворках, поэтому вы можете добиться еще большей производительности! ✌️
Чтобы расширить ответ, который я получил от Роба Мейоффа, я покажу, что я изменил ниже:
В функции 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
}