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

Do I have to lock a CVPixelBuffer produced from AVCaptureVideoDataOutput

我有一个 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 明确声明在 CVPixelBufferGetBaseAddress 之前需要调用 CVPixelBufferLockBaseAddress。但是,由于 AVCaptureVideoDataOutput 正在为其示例缓冲区使用预定义的内存池,因此基地址可能不会像通常情况下那样发生变化。

我可以在这里跳过锁定基地址吗?如果我在这种特定情况下不锁定基地址,最糟糕的情况是什么?

根据您的描述,您根本不需要转换为 CGImage。您可以在 Core Image + Vision 管道中进行所有处理:

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

只有最后才会将图像数据传输到CPU端

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

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

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

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