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

时间:2016-07-12 17:24:37

标签: ios swift deep-copy pool cmsamplebuffer

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

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

  

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

     

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

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

这个问题已被问过几次但没有回答。

Deep Copy of CMImageBuffer or CVImageBufferCreate a copy of CMSampleBuffer in Swift 2.0

一个可能的答案是"我终于找到了如何使用它来创建深度克隆。所有复制方法都重用堆中的数据,这些数据会保留AVCaptureSession。所以我不得不将数据拉出到NSMutableData对象中,然后创建一个新的样本缓冲区。" credit to Rob on SO。但是,我不知道如何正确地做到这一点。

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

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

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

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

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

4 个答案:

答案 0 :(得分:9)

好吧,我想我终于明白了。我创建了一个帮助扩展来制作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,因为它是一张图片。

答案 1 :(得分:5)

这是获得最高评价答案的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
}
}

答案 2 :(得分:0)

我相信通过VideoToolbox.framework,您可以使用VTPixelTransferSession复制像素缓冲区。实际上,这是此类的唯一操作。

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

答案 3 :(得分:0)

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

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

这就是我最后得到的。最后,我只剩下几行内容,可让您通过比较原始CVPixelBuffers和复制的CVPixelBuffers的平均颜色来验证memcpy。它确实减慢了速度,因此一旦您确信copy()可以按预期工作时应将其删除。 CIImage.averageColour扩展名改编自this code

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
    }
}