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

问题描述

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

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

解决方法

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

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

最后,图像数据将被传输到CPU端。

,

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

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

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

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

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...