我有一个AVCaptureVideoDataOutput
,用于生成传递给我的AVCaptureVideoDataOutputSampleBufferDelegate
函数的CMSampleBuffer
实例。我想有效地将像素缓冲区转换为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 的文档明确指出,在CVPixelBufferGetBaseAddress
之前需要调用CVPixelBufferLockBaseAddress
。但是,由于AVCaptureVideoDataOutput
为其示例缓冲区使用预定义的内存池,因此基址可能不会像通常那样发生变化。
我可以跳过在此处锁定基址吗?在此特定方案中,如果我不锁定基址,可能发生的最坏情况是什么?
根据您的描述,您根本不需要转换为CGImage
。可以在核心映像 + 视觉管道中执行所有处理:
- 使用
CIImage(cvPixelBuffer:)
从摄像机的像素缓冲区创建CIImage
。 - 将过滤器应用于
CIImage
。 - 使用
CIContext
将过滤后的图像渲染到新CVPixelBuffer
中。为了获得最佳性能,请使用CVPixelBufferPool
来创建这些目标像素缓冲区。 - 将像素缓冲区传递给视觉进行分析。
- 如果 Vision 决定保留图像,请使用相同的
CIContext
将像素缓冲区(再次将其包装到CIImage
中,如 1 所示)渲染为您选择的目标格式,例如使用context.writeHEIFRepresentation(of:...)
.
只有最终,图像数据才会传输到CPU端。
这个问题从一开始就没有根据,因为我忽略了测试跳过锁的实际图像结果。如问题中所述,当我在初始化 CGContext 之前锁定基址时,makeImage
渲染大约需要 17 毫秒。如果我跳过锁定并直接进入 CGContext,那么makeImage
需要 0.3 毫秒。
我错误地将这种速度差异解释为在后一种情况下渲染是由 GPU 加速的。然而,实际发生的事情是CVPixelBufferGetBaseAddress
返回nil
,makeImage
没有渲染任何数据 - 产生一个纯粹的白色CGImage。
所以,简而言之,我的问题的答案是肯定的。必须锁定基址。
现在我要弄清楚如何加快速度。我以 60 FPS 的速度捕获,这意味着如果可能的话,我希望我的渲染时间少于 16 毫秒,以便在下一个引用到达之前删除 CMSampleBuffer 引用。