简体   繁体   English

从 CMSampleBuffer 中提取数据以创建深拷贝

[英]Pulling data from a CMSampleBuffer in order to create a deep copy

I am trying to create a copy of a CMSampleBuffer as returned by captureOutput in a AVCaptureVideoDataOutputSampleBufferDelegate.我正在尝试创建 CMSampleBuffer 的副本,由 AVCaptureVideoDataOutputSampleBufferDelegate 中的 captureOutput 返回。

Since the CMSampleBuffers come from a preallocated pool of (15) buffers, if I attach a reference to them they cannot be recollected.由于 CMSampleBuffers 来自预先分配的 (15) 个缓冲区池,如果我附加对它们的引用,它们将无法被重新收集。 This causes all remaining frames to be dropped.这会导致所有剩余的帧被丢弃。

To maintain optimal performance, some sample buffers directly reference pools of memory that may need to be reused by the device system and other capture inputs.为了保持最佳性能,一些样本缓冲区直接引用 memory 的池,设备系统和其他捕获输入可能需要重复使用这些池。 This is frequently the case for uncompressed device native capture where memory blocks are copied as little as possible.对于未压缩的设备本机捕获,情况经常如此,其中 memory 块被尽可能少地复制。 If multiple sample buffers reference such pools of memory for too long, inputs will no longer be able to copy new samples into memory and those samples will be dropped.如果多个样本缓冲区引用此类池 memory 的时间过长,输入将无法再将新样本复制到 memory 中,这些样本将被丢弃。

If your application is causing samples to be dropped by retaining the provided CMSampleBufferRef objects for too long, but it needs access to the sample data for a long period of time, consider copying the data into a new buffer and then releasing the sample buffer (if it was previously retained) so that the memory it references can be reused.如果您的应用程序通过将提供的 CMSampleBufferRef 对象保留太久而导致样本被丢弃,但它需要长时间访问样本数据,请考虑将数据复制到新缓冲区,然后释放样本缓冲区(如果它以前被保留了),以便它引用的 memory 可以被重用。

Obviously I must copy the CMSampleBuffer but CMSampleBufferCreateCopy() will only create a shallow copy.显然,我必须复制 CMSampleBuffer,但 CMSampleBufferCreateCopy() 只会创建一个浅表副本。 Thus I conclude that I must use CMSampleBufferCreate().因此我得出结论,我必须使用 CMSampleBufferCreate()。 I filled in the 12. parameters that the constructor needs but ran into the problem that my CMSampleBuffers do not contain a blockBuffer (not entirely sure what that is but it seems important).我填写了构造函数需要的 12. 参数,但遇到了我的 CMSampleBuffers 不包含 blockBuffer 的问题(不完全确定那是什么,但它似乎很重要)。

This question has been asked several times but not answered.这个问题已经被问过好几次了,但没有得到回答。

Deep Copy of CMImageBuffer or CVImageBuffer and Create a copy of CMSampleBuffer in Swift 2.0 深度复制 CMImageBuffer 或 CVImageBuffer在 Swift 2.0 中创建 CMSampleBuffer 的副本

One possible answer is "I finally figured out how to use this to create a deep clone. All the copy methods reused the data in the heap which kept would lock the AVCaptureSession. So I had to pull the data out into a NSMutableData object and then created a new sample buffer."一个可能的答案是“我终于想出了如何使用它来创建深度克隆。所有复制方法都重复使用了堆中的数据,这些数据将锁定 AVCaptureSession。所以我不得不将数据拉出到 NSMutableData object 中,然后创建了一个新的样本缓冲区。” credit to Rob on SO . 归功于 Rob on SO However, I do not know how to do this correcly.但是,我不知道如何正确地执行此操作。

If you are interested, this is the output of print(sampleBuffer) .如果您有兴趣, print(sampleBuffer)的 output。 There is no mention of blockBuffer, aka CMSampleBufferGetDataBuffer returns nil.没有提到 blockBuffer,又名 CMSampleBufferGetDataBuffer 返回 nil。 There is a imageBuffer, but creating a "copy" using CMSampleBufferCreateForImageBuffer does not seem to free the CMSampleBuffer either.有一个 imageBuffer,但是使用 CMSampleBufferCreateForImageBuffer 创建“副本”似乎也没有释放 CMSampleBuffer。


EDIT: Since this question has been posted I have been trying even more ways of copying the memory.编辑:由于发布了这个问题,我一直在尝试更多复制 memory 的方法。

I did the same thing that user Kametrixom tried.我做了与用户Kametrixom尝试过的相同的事情。 This is my attempt at the same idea, to first copy the CVPixelBuffer then use CMSampleBufferCreateForImageBuffer to create the final sample buffer. 是我对相同想法的尝试,首先复制 CVPixelBuffer,然后使用 CMSampleBufferCreateForImageBuffer 创建最终的样本缓冲区。 However this results in one of two error:但是,这会导致以下两个错误之一:

  • A EXC_BAD_ACCESS on the memcpy instruction. memcpy 指令上的 EXC_BAD_ACCESS。 AKA a segfault from trying to access outside of the application's memory.也就是尝试访问应用程序 memory 之外的段错误。
  • Or, the memory will copy successfully but the CMSampleBufferCreateReadyWithImageBuffer() will fail with result code -12743 which "Indicates that the format of the given media does not match the given format description. For example, a format description paired with a CVImageBuffer that fails CMVideoFormatDescriptionMatchesImageBuffer."或者,memory 将成功复制,但CMSampleBufferCreateReadyWithImageBuffer()将失败,结果代码为 -12743,“表示给定媒体的格式与给定的格式描述不匹配。例如,与 CVImageBuffer 配对的格式描述失败 CMVideoFormatDescriptionMatchesImageBuffer ”

You can see that both Kametrixom and I did use CMSampleBufferGetFormatDescription(sampleBuffer) to try to copy the source buffer's format description.您可以看到 Kametrixom 和我都使用CMSampleBufferGetFormatDescription(sampleBuffer)来尝试复制源缓冲区的格式描述。 Thus, I'm not sure why the format of the given media does not match the given format description.因此,我不确定为什么给定媒体的格式与给定的格式描述不匹配。

Alright, I think I finally got it. 好吧,我想我终于明白了。 I created a helper extension to make a full copy of a CVPixelBuffer : 我创建了一个帮助扩展来制作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
    }
}

Now you can use this in your didOutputSampleBuffer method: 现在,您可以在didOutputSampleBuffer方法中使用它:

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

let copy = pixelBuffer.copy()

toProcess.append(copy)

But be aware, one such pixelBuffer takes up about 3MB of memory (1080p), which means that in 100 frames you got already about 300MB, which is about the point at which the iPhone says STAHP (and crashes). 但请注意,一个像pixelBuffer占用大约3MB的内存(1080p),这意味着在100帧中你已经有大约300MB,这大约是iPhone说STAHP(和崩溃)的时间点。

Note that you don't actually want to copy the CMSampleBuffer since it only really contains a CVPixelBuffer because it's an image. 请注意,您实际上并不想复制CMSampleBuffer因为它只包含CVPixelBuffer因为它是一个图像。

This is the Swift 3 solution to the top rated answer. 这是获得最高评级答案的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
}
}

I believe that with VideoToolbox.framework, you can use VTPixelTransferSession to copy pixel buffers. 我相信使用VideoToolbox.framework,您可以使用VTPixelTransferSession来复制像素缓冲区。 In fact it's the only thing that this class do. 事实上,这是这个班级唯一做的事情。

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

I spent a good couple hours trying to get this to work. 我花了几个小时试图让它发挥作用。 It turns out both the attachments from the original CVPixelBuffer and the IOSurface options found in PixelBufferMetalCompatibilityKey are necessary. 事实证明,原始CVPixelBuffer中的附件和PixelBufferMetalCompatibilityKey中的IOSurface选项都是必需的。

(FYI, VideoToolbox's PixelTransferSession is macOS-only, sadly.) (仅供参考,VideoToolbox的PixelTransferSession仅限macOS,令人遗憾。)

Here's what I ended up with. 这就是我最终的结果。 I've left in a few lines at the end that allow you to verify the memcpy by comparing the average colours of both the original and copied CVPixelBuffers. 我在最后留下了几行,通过比较原始和复制的CVPixelBuffers的平均颜色来验证memcpy。 It does slow things down so it should be removed once you're confident your copy() is working as expected. 它确实减慢了速度,所以一旦你确信你的copy()按预期工作就应该删除它。 The CIImage.averageColour extension is adapted from this code . 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
    }
}

Spent some time trying to pull this together.花了一些时间试图把它拉到一起。 I needed a function that should be able to create a CMSampleBuffer deep copy with CVPixelBuffer inside, not just the pixel buffer copy.我需要一个 function,它应该能够创建一个 CMSampleBuffer 深层副本,其中包含 CVPixelBuffer,而不仅仅是像素缓冲区副本。

Here is what I came up with and it works for me in iOS 15, Swift 5:这是我想出的,它适用于 iOS 15、Swift 5:

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),
            nil,
            &_copy)
    guard let copy = _copy else { fatalError() }

    CVBufferPropagateAttachments(self, copy)


    CVPixelBufferLockBaseAddress(self, .readOnly)
    CVPixelBufferLockBaseAddress(copy, CVPixelBufferLockFlags())

    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, CVPixelBufferLockFlags())
    CVPixelBufferUnlockBaseAddress(self, .readOnly)

    return copy
  }
}

Sample buffer extension样本缓冲区扩展

extension CMSampleBuffer {
func copy() -> CMSampleBuffer {
    guard let pixelBuffer = CMSampleBufferGetImageBuffer(self) else { fatalError("CMSampleBuffer copy: get image buffer")  }

    let copiedPixelBuffer = pixelBuffer.copy()
    let format = CMSampleBufferGetFormatDescription(self)!
    var timing = CMSampleTimingInfo()
    CMSampleBufferGetSampleTimingInfo(self, at: 0, timingInfoOut: &timing)

    var copiedSampleBuffer : CMSampleBuffer?

    let status = CMSampleBufferCreateReadyWithImageBuffer(allocator: nil,
            imageBuffer: copiedPixelBuffer,
            formatDescription: format,
            sampleTiming: &timing,
            sampleBufferOut: &copiedSampleBuffer)
    guard let bufOut = copiedSampleBuffer else { fatalError("CMSampleBuffer copy: CreateReady \(status)") }
    return bufOut
  }
}

And a call to copy:并调用复制:

func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
    let bufferCopy = sampleBuffer.copy()
    // free to use bufferCopy as it won't stop video recording
    // don't forget to put it into autoreleasepool { } if used in non-main thread
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM