我是否必须锁定从AVCaptureVideoDataOutput生成的CVPixelBuffer



我有一个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。可以在核心映像 + 视觉管道中执行所有处理:

  1. 使用CIImage(cvPixelBuffer:)从摄像机的像素缓冲区创建CIImage
  2. 将过滤器应用于CIImage
  3. 使用CIContext将过滤后的图像渲染到新CVPixelBuffer中。为了获得最佳性能,请使用CVPixelBufferPool来创建这些目标像素缓冲区。
  4. 将像素缓冲区传递给视觉进行分析。
  5. 如果 Vision 决定保留图像,请使用相同的CIContext将像素缓冲区(再次将其包装到CIImage中,如 1 所示)渲染为您选择的目标格式,例如使用context.writeHEIFRepresentation(of:...).

只有最终,图像数据才会传输到CPU端。

这个问题从一开始就没有根据,因为我忽略了测试跳过锁的实际图像结果。如问题中所述,当我在初始化 CGContext 之前锁定基址时,makeImage渲染大约需要 17 毫秒。如果我跳过锁定并直接进入 CGContext,那么makeImage需要 0.3 毫秒。

我错误地将这种速度差异解释为在后一种情况下渲染是由 GPU 加速的。然而,实际发生的事情是CVPixelBufferGetBaseAddress返回nilmakeImage没有渲染任何数据 - 产生一个纯粹的白色CGImage。

所以,简而言之,我的问题的答案是肯定的。必须锁定基址。

现在我要弄清楚如何加快速度。我以 60 FPS 的速度捕获,这意味着如果可能的话,我希望我的渲染时间少于 16 毫秒,以便在下一个引用到达之前删除 CMSampleBuffer 引用。

相关内容

  • 没有找到相关文章

最新更新