一尘不染

从CMSampleBuffer中提取数据以创建深层副本

swift

我正在尝试创建由AVCaptureVideoDataOutputSampleBufferDelegate中的captureOutput返回的CMSampleBuffer的副本。

由于CMSampleBuffers来自(15)个缓冲区的预分配池,因此,如果我对它们附加引用,则无法重新收集它们。这将导致所有剩余的帧被丢弃。

为了保持最佳性能,某些样本缓冲区直接引用了可能需要由设备系统和其他捕获输入重用的内存池。对于未压缩的设备本机捕获,通常是这种情况,在这种情况下,应尽可能少地复制内存块。如果多个样本缓冲区引用此类内存池的时间过长,则输入将不再能够将新样本复制到内存中,并且这些样本将被丢弃。

如果您的应用程序由于将提供的CMSampleBufferRef对象保留太长时间而导致删除样本,但是它需要长时间访问样本数据,请考虑将数据复制到新缓冲区中,然后释放样本缓冲区(如果之前已保留),以便可以重用它引用的内存。

显然,我必须复制CMSampleBuffer,但是CMSampleBufferCreateCopy()只会创建一个浅表副本。因此,我得出结论,我必须使用CMSampleBufferCreate()。我填写了12个!构造函数需要的参数,但是遇到了我的CMSampleBuffers不包含blockBuffer的问题(虽然不能完全确定那是什么,但这似乎很重要)。

一个可能的答案是“我终于想出了如何使用它创建一个深层克隆。所有复制方法都重复使用了保留在堆中的数据,这将锁定AVCaptureSession。因此,我不得不将数据拉出到NSMutableData对象中,然后创建了一个新的样本缓冲区。”归功于罗布。但是,我不知道如何正确地做到这一点。

如果您有兴趣,是的输出print(sampleBuffer)。没有提及blockBuffer,akaCMSampleBufferGetDataBuffer返回nil。有一个imageBuffer,但是使用CMSampleBufferCreateForImageBuffer创建“副本”似乎也不会释放CMSampleBuffer。


编辑:由于已发布此问题,我一直在尝试更多的复制内存的方法。

我做了用户Kametrixom尝试过的相同操作。是我对相同想法的尝试,首先复制CVPixelBuffer,然后使用CMSampleBufferCreateForImageBuffer创建最终的样本缓冲区。但是,这导致两个错误之一:

  • memcpy指令上的EXC_BAD_ACCESS。试图从应用程序的内存之外进行访问也可能导致段错误。
  • 或者,内存将成功复制,但是CMSampleBufferCreateReadyWithImageBuffer()将失败,并显示结果代码-12743,结果代码为-12743,它指示给定媒体的格式与给定格式说明不匹配。例如,与CVImageBuffer配对的格式说明使CMVideoFormatDescriptionMatchesImageBuffer失败。

您可以看到,Kametrixom和我都曾CMSampleBufferGetFormatDescription(sampleBuffer)尝试复制源缓冲区的格式描述。因此,我不确定为什么给定媒体的格式与给定格式说明不匹配。


阅读 507

收藏
2020-07-07

共1个答案

一尘不染

好吧,我想我终于明白了。我创建了一个辅助扩展程序来制作的完整副本CVPixelBuffer

extension CVPixelBuffer {
    func copy() -> CVPixelBuffer {
        precondition(CFGetTypeID(self) == CVPixelBufferGetTypeID(), "copy() cannot be called on a non-CVPixelBuffer")

        var _copy : CVPixelBuffer?
        CVPixelBufferCreate(
            nil,
            CVPixelBufferGetWidth(self),
            CVPixelBufferGetHeight(self),
            CVPixelBufferGetPixelFormatType(self),
            CVBufferGetAttachments(self, kCVAttachmentMode_ShouldPropagate)?.takeUnretainedValue(),
            &_copy)

        guard let copy = _copy else { fatalError() }

        CVPixelBufferLockBaseAddress(self, kCVPixelBufferLock_ReadOnly)
        CVPixelBufferLockBaseAddress(copy, 0)

        for plane in 0..<CVPixelBufferGetPlaneCount(self) {
            let dest = CVPixelBufferGetBaseAddressOfPlane(copy, plane)
            let source = CVPixelBufferGetBaseAddressOfPlane(self, plane)
            let height = CVPixelBufferGetHeightOfPlane(self, plane)
            let bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(self, plane)

            memcpy(dest, source, height * bytesPerRow)
        }

        CVPixelBufferUnlockBaseAddress(copy, 0)
        CVPixelBufferUnlockBaseAddress(self, kCVPixelBufferLock_ReadOnly)

        return copy
    }
}

现在您可以在您的didOutputSampleBuffer方法中使用它:

guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }

let copy = pixelBuffer.copy()

toProcess.append(copy)

但是请注意,一个这样的pixelBuffer会占用大约3MB的内存(1080p),这意味着在100帧中您已经拥有了300MB的内存,这大约是iPhone所说的STAHP(并崩溃)的时间。

请注意,您实际上并不想复制,CMSampleBuffer因为它实际上只包含一个,CVPixelBuffer因为它是一个图像。

2020-07-07