Swift 我必须锁定AVCaptureVideoDataOutput生成的CVPixelBuffer吗

Swift 我必须锁定AVCaptureVideoDataOutput生成的CVPixelBuffer吗,swift,avfoundation,core-video,cvpixelbuffer,Swift,Avfoundation,Core Video,Cvpixelbuffer,我有一个AVCaptureVideoDataOutput生成CMSampleBuffer实例传递到我的AVCaptureVideoDataOutputSampleBufferDelegate函数中。我想高效地将像素缓冲区转换为CGImage实例,以便在我的应用程序的其他地方使用 我必须小心,不要保留对这些像素缓冲区的任何引用,否则捕获会话将开始删除帧,原因是OutOfBuffers。此外,如果转换时间过长,则帧将被丢弃,原因是FrameWasLate 之前,我尝试使用CIContext渲染CGI

我有一个
AVCaptureVideoDataOutput
生成
CMSampleBuffer
实例传递到我的
AVCaptureVideoDataOutputSampleBufferDelegate
函数中。我想高效地将像素缓冲区转换为
CGImage
实例,以便在我的应用程序的其他地方使用

我必须小心,不要保留对这些像素缓冲区的任何引用,否则捕获会话将开始删除帧,原因是
OutOfBuffers
。此外,如果转换时间过长,则帧将被丢弃,原因是
FrameWasLate

之前,我尝试使用
CIContext
渲染
CGImage
,但当拍摄速度超过30 FPS时,速度太慢,我希望以60 FPS的速度拍摄。在帧开始下降之前,我进行了测试,速度达到了38帧/秒

现在我尝试使用
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...
}
我很好奇,然后在没有锁定像素缓冲区基址的情况下尝试了这个方法当我注释掉这两行时,我完全停止掉帧,没有任何明显的影响。似乎锁定机制花费了很长时间,以至于帧被掉了下来,移除该机制显著减少了函数的运行时间,并允许处理所有帧

明确说明在调用
CVPixelBufferGetBaseAddress
之前需要调用
CVPixelBufferLockBaseAddress
。但是,由于
avcapturedevideodataoutput
使用预定义的内存池作为其样本缓冲区,因此基址可能不会像通常情况那样发生更改


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

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

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

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

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

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

    总之,我的问题的答案是肯定的。必须锁定基址。


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

    我刚刚发现,这表明锁定像素缓冲区会迫使CPU参与,这可能是帧被删除的原因。通过避免锁,GPU被使用,这当然可以解释加速。现在我想知道为什么文档被更改了,不再说明这一点。出于好奇:你在以后用
    cgmiages
    做什么?为什么需要CPU端的视频帧?通常建议在GPU上进行尽可能多的处理。@FrankSchlegel我们正在通过Core图像过滤器运行它们,并将它们传递给Vision进行CoreML推断。如果我们决定保留图像(取决于视觉观察),我们只需要在最后将它们放在CPU上。你是对的,我们希望尽可能将它们保留在GPU上。感谢你的想法,我将尝试一下,看看它在性能方面如何比较。我唯一担心的是,在步骤1和步骤3之间,我们将保留对原始像素缓冲区的引用,如果处理速度不够快,可能会由于分配的内存池中的缓冲区不足而导致帧丢失。我现在浏览CGImage的原因是在做任何其他事情之前,立即转储像素缓冲区引用。我认为这不应该是一个问题。1和2基本上立即发生,因为直到3才执行渲染。在3。像素缓冲区在用于渲染时立即释放。当你创建像素数据的副本,并且不能像新缓冲区到达时那样快速处理它时,你的内存就会很快用完。奇怪的是,将CIImage渲染到CVPixelBuffer需要很长的时间,比createCGImage要长。在这个速度下,它肯定还在CPU上——我不知道为什么它不使用GPU。createCGImage需要37毫秒,render:toCVPixelBuffer需要46毫秒(带CVPixelBufferPool),而不带CVPixelBufferPool则需要58毫秒。我的OP中的CGContext.makeImage机制在基址锁定时需要21毫秒,如果我不锁定则需要0.6毫秒。您是否要求
    AVCaptureDataOutput
    传递帧