问题描述
我有一个AVCaptureVideoDataOutput
产生了CMSampleBuffer
个实例,这些实例被传递到我的AVCaptureVideoDataOutputSampleBufferDelegate
函数中。我想将像素缓冲区有效地转换为CGImage
实例,以便在我的应用中的其他地方使用。
我必须注意不要保留对这些像素缓冲区的任何引用,否则捕获会话将由于OutOfBuffers
的原因而开始丢弃帧。另外,如果转换时间太长,则帧会由于FrameWasLate
的原因而被丢弃。
以前,我尝试使用CIContext
来渲染CGImage
,但是在捕获高于30 FPS的速度时,这太慢了,我想以60 FPS的速度捕获。在帧开始下降之前,我进行了测试并达到了38 FPS。
现在,我尝试使用CGContext
,结果会更好。我仍在丢帧,但频率明显下降。
public func captureOutput(_ output: AVCaptureOutput,didOutput sampleBuffer: CMSampleBuffer,from connection: AVCaptureConnection) {
// Capture at 60 FPS but only process at 4 FPS,ignoring all other frames
let timestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
guard timestamp - lastTimestamp >= CMTimeMake(value: 1,timescale: 4) else { return }
// Extract pixel buffer
guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
// Lock pixel buffer before accessing base address
guard kCVReturnSuccess == CVPixelBufferLockBaseAddress(imageBuffer,.readOnly) else { return }
defer { CVPixelBufferUnlockBaseAddress(imageBuffer,.readOnly) }
// Use CGContext to render CGImage from pixel buffer
guard let cgimage = CGContext(data: CVPixelBufferGetBaseAddress(imageBuffer),width: CVPixelBufferGetWidth(imageBuffer),height: CVPixelBufferGetHeight(imageBuffer),bitsPerComponent: 8,bytesPerRow: CVPixelBufferGetBytesPerRow(imageBuffer),space: cgColorSpace,bitmapInfo: cgBitmapInfo).makeImage() else { return }
// Do something with cgimage...
}
我很好奇,接下来尝试了此操作而没有锁定像素缓冲区基址。 当我注释掉这两行时,我会完全停止丢弃帧,而没有任何明显的影响。似乎锁定机制花费了很长时间以至于帧被丢弃,而删除该机制大大减少了该函数的运行时间并允许处理所有帧。
Apple's documentation明确指出在CVPixelBufferLockBaseAddress
之前需要调用CVPixelBufferGetBaseAddress
。但是,由于AVCaptureVideoDataOutput
为其示例缓冲区使用了预定义的内存池,因此基地址可能不会像通常那样发生变化。
我可以在这里跳过锁定基本地址吗?如果在这种特定情况下不锁定基地址 ,会发生什么最糟糕的情况?
解决方法
根据您的描述,您实际上根本不需要转换为CGImage
。您可以在Core Image + Vision管道中进行所有处理:
- 使用
CIImage
从相机的像素缓冲区创建CIImage(cvPixelBuffer:)
。 - 将过滤器应用于
CIImage
。 - 使用
CIContext
将过滤后的图像渲染为新的CVPixelBuffer
。为了获得最佳性能,请使用CVPixelBufferPool
创建这些目标像素缓冲区。 - 将像素缓冲区传递给Vision进行分析。
- 如果Vision决定保留图像,请使用相同的
CIContext
来将像素缓冲区渲染(再次像1一样将其包装为CIImage
)为您选择的目标格式。与context.writeHEIFRepresentation(of:...)
。
最后,图像数据将被传输到CPU端。
,这个问题从一开始就没有根据,因为我忽略了跳过锁来测试实际图像结果。如问题中所述,当我在初始化CGContext之前锁定基地址时,makeImage
渲染将花费大约17毫秒。如果我跳过锁定并直接进入CGContext,则makeImage
花费0.3毫秒。
我错误地解释了这种速度差异,这意味着在后一种情况下GPU正在加速渲染。但是,实际发生的情况是CVPixelBufferGetBaseAddress
返回了nil
,而makeImage
没有呈现任何数据,产生了纯白色的CGImage。
因此,简而言之,我的问题的答案是肯定的。基址必须被锁定。
现在我要弄清楚如何加快速度。我正在以60 FPS进行捕获,这意味着我希望我的渲染花费尽可能少于16毫秒的时间,以便在下一个到达之前删除CMSampleBuffer引用。