我是否必须锁定从 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 管道中进行所有处理:
- 使用
CIImage(cvPixelBuffer:)
从相机的像素缓冲区创建一个 CIImage
。
- 对
CIImage
应用过滤器。
- 使用
CIContext
将过滤后的图像渲染成新的CVPixelBuffer
。为了获得最佳性能,请使用 CVPixelBufferPool
创建这些目标像素缓冲区。
- 将像素缓冲区传递给 Vision 进行分析。
- 如果 Vision 决定保留图像,请使用相同的
CIContext
将像素缓冲区渲染为您选择的目标格式(像 1 一样再次将其包装到 CIImage
中),例如 context.writeHEIFRepresentation(of:...)
.
只有最后才会将图像数据传输到CPU端
这个问题从一开始就是ill-founded,因为我忽略了测试跳过锁定的实际图像结果。如问题中所述,当我在初始化 CGContext 之前锁定基地址时,makeImage
渲染将花费大约 17 毫秒。如果我跳过锁定并直接进入 CGContext,那么 makeImage
需要 0.3 毫秒。
我错误地将这种速度差异解释为后一种情况下 GPU 正在加速渲染。然而,实际发生的是 CVPixelBufferGetBaseAddress
返回 nil
而 makeImage
没有渲染任何数据——生成一个纯白色的 CGImage。
所以,简而言之,我的问题的答案是肯定的。必须锁定基址。
现在我要弄清楚如何加快速度。我以 60 FPS 的速度捕获,这意味着我希望我的渲染尽可能少于 16 毫秒,以便在下一个到达之前删除 CMSampleBuffer 引用。
我有一个 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 管道中进行所有处理:
- 使用
CIImage(cvPixelBuffer:)
从相机的像素缓冲区创建一个CIImage
。 - 对
CIImage
应用过滤器。 - 使用
CIContext
将过滤后的图像渲染成新的CVPixelBuffer
。为了获得最佳性能,请使用CVPixelBufferPool
创建这些目标像素缓冲区。 - 将像素缓冲区传递给 Vision 进行分析。
- 如果 Vision 决定保留图像,请使用相同的
CIContext
将像素缓冲区渲染为您选择的目标格式(像 1 一样再次将其包装到CIImage
中),例如context.writeHEIFRepresentation(of:...)
.
只有最后才会将图像数据传输到CPU端
这个问题从一开始就是ill-founded,因为我忽略了测试跳过锁定的实际图像结果。如问题中所述,当我在初始化 CGContext 之前锁定基地址时,makeImage
渲染将花费大约 17 毫秒。如果我跳过锁定并直接进入 CGContext,那么 makeImage
需要 0.3 毫秒。
我错误地将这种速度差异解释为后一种情况下 GPU 正在加速渲染。然而,实际发生的是 CVPixelBufferGetBaseAddress
返回 nil
而 makeImage
没有渲染任何数据——生成一个纯白色的 CGImage。
所以,简而言之,我的问题的答案是肯定的。必须锁定基址。
现在我要弄清楚如何加快速度。我以 60 FPS 的速度捕获,这意味着我希望我的渲染尽可能少于 16 毫秒,以便在下一个到达之前删除 CMSampleBuffer 引用。