22

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

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

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

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

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

这个问题被问了好几次,但没有回答。

CMImageBuffer 或 CVImageBuffer 的深层复制在 Swift 2.0 中创建 CMSampleBuffer 的副本

一个可能的答案是“我终于想出了如何使用它来创建深度克隆。所有的复制方法都重用了堆中的数据,这些数据会锁定 AVCaptureSession。所以我必须将数据提取到 NSMutableData 对象中,然后创建了一个新的样本缓冲区。” 归功于 Rob 在 SO 上。但是,我不知道如何正确地做到这一点。

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


编辑:自从发布了这个问题以来,我一直在尝试更多复制内存的方法。

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

  • memcpy 指令上的 EXC_BAD_ACCESS。AKA 尝试访问应用程序内存之外的段错误。
  • 或者,内存将成功复制,但CMSampleBufferCreateReadyWithImageBuffer()将失败,结果代码为 -12743,“指示给定媒体的格式与给定格式描述不匹配。例如,格式描述与 CVImageBuffer 配对失败 CMVideoFormatDescriptionMatchesImageBuffer。”

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

4

4 回答 4

15

好吧,我想我终于明白了。我创建了一个辅助扩展来制作 a 的完整副本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因为它实际上只包含 a CVPixelBuffer,因为它是图像。

于 2016-07-13T22:12:59.043 回答
7

这是评分最高的答案的 Swift 3 解决方案。

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

    var _copy : CVPixelBuffer?
    CVPixelBufferCreate(
        kCFAllocatorDefault,
        CVPixelBufferGetWidth(self),
        CVPixelBufferGetHeight(self),
        CVPixelBufferGetPixelFormatType(self),
        nil,
        &_copy)

    guard let copy = _copy else { fatalError() }

    CVPixelBufferLockBaseAddress(self, CVPixelBufferLockFlags.readOnly)
    CVPixelBufferLockBaseAddress(copy, CVPixelBufferLockFlags(rawValue: 0))


    let copyBaseAddress = CVPixelBufferGetBaseAddress(copy)
    let currBaseAddress = CVPixelBufferGetBaseAddress(self)

    memcpy(copyBaseAddress, currBaseAddress, CVPixelBufferGetDataSize(self))

    CVPixelBufferUnlockBaseAddress(copy, CVPixelBufferLockFlags(rawValue: 0))
    CVPixelBufferUnlockBaseAddress(self, CVPixelBufferLockFlags.readOnly)


    return copy
}
}
于 2017-05-11T20:57:44.463 回答
1

我花了好几个小时试图让它工作。原来 CVPixelBuffer 的附件和 PixelBufferMetalCompatibilityKey 中的 IOSurface 选项都是必需的。

(仅供参考,遗憾的是,VideoToolbox 的 PixelTransferSession 仅适用于 macOS。)

这就是我最终的结果。我在最后留下了几行,允许您通过比较原始和复制的 CVPixelBuffers 的平均颜色来验证 memcpy。它确实会减慢速度,因此一旦您确信您的 copy() 按预期工作,就应该将其删除。CIImage.averageColour 扩展改编自此代码

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

        let ioSurfaceProps = [
            "IOSurfaceOpenGLESFBOCompatibility": true as CFBoolean,
            "IOSurfaceOpenGLESTextureCompatibility": true as CFBoolean,
            "IOSurfaceCoreAnimationCompatibility": true as CFBoolean
        ] as CFDictionary

        let options = [
            String(kCVPixelBufferMetalCompatibilityKey): true as CFBoolean,
            String(kCVPixelBufferIOSurfacePropertiesKey): ioSurfaceProps
        ] as CFDictionary

        var _copy : CVPixelBuffer?
        CVPixelBufferCreate(
            nil,
            CVPixelBufferGetWidth(self),
            CVPixelBufferGetHeight(self),
            CVPixelBufferGetPixelFormatType(self),
            options,
            &_copy)

        guard let copy = _copy else { fatalError() }

        CVBufferPropagateAttachments(self as CVBuffer, copy as CVBuffer)

        CVPixelBufferLockBaseAddress(self, CVPixelBufferLockFlags.readOnly)
        CVPixelBufferLockBaseAddress(copy, CVPixelBufferLockFlags(rawValue: 0))

        let copyBaseAddress = CVPixelBufferGetBaseAddress(copy)
        let currBaseAddress = CVPixelBufferGetBaseAddress(self)

        memcpy(copyBaseAddress, currBaseAddress, CVPixelBufferGetDataSize(self))

        CVPixelBufferUnlockBaseAddress(copy, CVPixelBufferLockFlags(rawValue: 0))
        CVPixelBufferUnlockBaseAddress(self, CVPixelBufferLockFlags.readOnly)

        // let's make sure they have the same average color
//        let originalImage = CIImage(cvPixelBuffer: self)
//        let copiedImage = CIImage(cvPixelBuffer: copy)
//
//        let averageColorOriginal = originalImage.averageColour()
//        let averageColorCopy = copiedImage.averageColour()
//
//        assert(averageColorCopy == averageColorOriginal)
//        debugPrint("average frame color: \(averageColorCopy)")

        return copy
    }
}
于 2018-08-31T00:10:16.127 回答
0

我相信使用 VideoToolbox.framework,您可以VTPixelTransferSession用来复制像素缓冲区。事实上,这是这个类唯一要做的事情。

参考: https ://developer.apple.com/documentation/videotoolbox/vtpixeltransfersession-7cg

于 2018-06-26T06:56:31.167 回答