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

Posted

技术标签:

【中文标题】我是不是必须锁定从 AVCaptureVideoDataOutput 生成的 CVPixelBuffer【英文标题】:Do I have to lock a CVPixelBuffer produced from AVCaptureVideoDataOutput我是否必须锁定从 AVCaptureVideoDataOutput 生成的 CVPixelBuffer 【发布时间】:2021-02-02 20:24:59 【问题描述】:

我有一个 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 正在为其样本缓冲区使用预定义的内存池,因此基地址可能不会像通常情况那样发生变化。

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

【问题讨论】:

我刚刚找到***.com/a/30947533/1500134,这表明锁定像素缓冲区会迫使 CPU 参与其中,这可能就是丢帧的原因。通过避免锁定,使用了 GPU,这肯定可以解释加速。现在我想知道为什么文档已更改并且不再说明这一点。 出于好奇:之后你用CGImages 做什么?为什么需要 CPU 端的视频帧?通常建议在 GPU 上进行尽可能多的处理。 @FrankSchlegel 我们正在通过 Core Image 过滤器运行它们并将它们传递给 Vision 以进行 CoreML 推理。如果我们决定保留图像(取决于视觉观察),我们只需要它们在最后的 CPU 上。您是对的,我们宁愿尽可能将它们保留在 GPU 上。 【参考方案1】:

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

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

只有最终将图像数据传输到CPU端。

【讨论】:

感谢您的想法,我会尝试一下,看看它在性能方面的比较。我唯一担心的是,在第 1 步和第 3 步之间,我们将保留对原始像素缓冲区的引用,如果处理速度不够快,可能会因分配的内存池中的缓冲区用完而导致丢帧。我目前正在使用 CGImage 的原因是在执行任何其他操作之前立即转储该像素缓冲区引用。 这不应该是一个问题,我想。 1 和 2 基本上是立即发生的,因为直到 3 才执行渲染。在 3 中,像素缓冲区一用于渲染就被释放。当您创建像素数据的副本并且无法像新缓冲区一样快速处理它时,无论如何您都会很快耗尽内存。 奇怪的是,将 CIImage 渲染到 CVPixelBuffer 的时间比 createCGImage 还要长。在这个速度下,它肯定还在 CPU 上——我不确定它为什么不使用 GPU。 createCGImage 需要 37 毫秒,render:toCVPixelBuffer 需要 46 毫秒,使用 CVPixelBufferPool 需要 58 毫秒。我的 OP 中的 CGContext.makeImage 机制在基地址锁定的情况下需要 21 毫秒,如果我不锁定,则需要 0.6 毫秒。 您是否要求AVCaptureDataOutput 提供kCVPixelFormatType_32BGRA 中的帧?我想Core Image 在渲染到CVPixelBuffer 时也会转换格式,这就是延迟的原因。您的CGContext 代码非常快,因为它基本上只是将内存指针重新转换为CGImageRef,而无需进行任何转换或渲染。 是的,我确实将输出格式设置为 kCVPixelFormatType_32BGRA。我将结束这个问题,因为我在技术上找到了答案 - 但我可能会发布一个后续问题以进行进一步的性能讨论,因为我仍在努力获得我想要的渲染/复制时间。感谢您的帮助,您的建议很有见地!【参考方案2】:

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

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

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

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

【讨论】:

以上是关于我是不是必须锁定从 AVCaptureVideoDataOutput 生成的 CVPixelBuffer的主要内容,如果未能解决你的问题,请参考以下文章

插入和删除查询是不是可以从锁定等待超时中逃脱?

如果我们同时从向量中追加和读取数据,我们是不是需要锁定?(无修改)

PHP检查文件是不是被flock()锁定?

锁定是不是确保从缓存中刷新读取和写入?如果是这样,怎么做?

阅读时是不是需要锁定非线程安全的集合?

在不锁定集合的情况下从通用集合中获取 Count 值是不是安全?