【问题标题】:Copy a CVPixelBuffer on any iOS device在任何 iOS 设备上复制 CVPixelBuffer
【发布时间】:2019-04-07 12:38:44
【问题描述】:

我很难想出在任何 iOS 设备上可靠地复制 CVPixelBuffer 的代码。在我在 iPad Pro 上尝试之前,我的第一次尝试效果很好:

extension CVPixelBuffer {
    func deepcopy() -> CVPixelBuffer? {
        let width = CVPixelBufferGetWidth(self)
        let height = CVPixelBufferGetHeight(self)
        let format = CVPixelBufferGetPixelFormatType(self)
        var pixelBufferCopyOptional:CVPixelBuffer?
        CVPixelBufferCreate(nil, width, height, format, nil, &pixelBufferCopyOptional)
        if let pixelBufferCopy = pixelBufferCopyOptional {
            CVPixelBufferLockBaseAddress(self, kCVPixelBufferLock_ReadOnly)
            CVPixelBufferLockBaseAddress(pixelBufferCopy, 0)
            let baseAddress = CVPixelBufferGetBaseAddress(self)
            let dataSize = CVPixelBufferGetDataSize(self)
            let target = CVPixelBufferGetBaseAddress(pixelBufferCopy)
            memcpy(target, baseAddress, dataSize)
            CVPixelBufferUnlockBaseAddress(pixelBufferCopy, 0)
            CVPixelBufferUnlockBaseAddress(self, kCVPixelBufferLock_ReadOnly)
        }
        return pixelBufferCopyOptional
    }
}

由于CVPixelBufferGetDataSize(self) 比CVPixelBufferGetDataSize(pixelBufferCopy) 稍大,所以上述情况在 iPad Pro 上崩溃,因此 memcpy 会写入未分配的内存。

所以我放弃了,尝试了这个:

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, .shouldPropagate),
        &_copy)

    guard let copy = _copy else { return nil }

    CVPixelBufferLockBaseAddress(self, .readOnly)
    CVPixelBufferLockBaseAddress(copy, [])
    defer
    {
        CVPixelBufferUnlockBaseAddress(copy, [])
        CVPixelBufferUnlockBaseAddress(self, .readOnly)
    }

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

    return copy
}

这在我的两个测试设备上都有效,但它刚刚接触到实际客户,结果证明它在 iPad 6 上崩溃了(到目前为止只有那个设备)。再次调用memcpy() 时是EXC_BAD_ACCESS。

考虑到让它可靠地工作似乎是多么困难,没有一个简单的 API 调用来解决这个问题似乎很疯狂。还是我让它变得比需要的更难?感谢您的建议!

【问题讨论】:

  • 第二个实现看起来相当可靠。我能想象的唯一问题是新像素缓冲区中的平面分配有不同的步长(每行字节数)。步长基于宽度×(每像素字节数),然后以未指定的方式四舍五入以实现最佳内存访问。所以检查CVPixelBufferGetBytesPerRowOfPlane(self, plane) == CVPixelBufferGetBytesPerRowOfPlane(copy, plane)。如果没有,则逐行复制像素平面。
  • 谢谢,你想把它作为答案发布吗?
  • 它能解决崩溃问题吗?
  • 不幸的是,它在第​​ 6 代 iPad 上崩溃而没有其他问题,直到我在 App Store 上发布后才知道。
  • 有效!没有崩溃了。

标签: ios cvpixelbuffer


【解决方案1】:

这个问答组合是纯金的。让我通过轻微的重构和一些控制流来增加价值,以解决没有平面的CVPixelBuffers。

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

        var _copy: CVPixelBuffer?

        let width = CVPixelBufferGetWidth(self)
        let height = CVPixelBufferGetHeight(self)
        let formatType = CVPixelBufferGetPixelFormatType(self)
        let attachments = CVBufferGetAttachments(self, .shouldPropagate)

        CVPixelBufferCreate(nil, width, height, formatType, attachments, &_copy)

        guard let copy = _copy else {
            throw PixelBufferCopyError.allocationFailed
        }

        CVPixelBufferLockBaseAddress(self, .readOnly)
        CVPixelBufferLockBaseAddress(copy, [])

        defer {
            CVPixelBufferUnlockBaseAddress(copy, [])
            CVPixelBufferUnlockBaseAddress(self, .readOnly)
        }

        let pixelBufferPlaneCount: Int = CVPixelBufferGetPlaneCount(self)


        if pixelBufferPlaneCount == 0 {
            let dest = CVPixelBufferGetBaseAddress(copy)
            let source = CVPixelBufferGetBaseAddress(self)
            let height = CVPixelBufferGetHeight(self)
            let bytesPerRowSrc = CVPixelBufferGetBytesPerRow(self)
            let bytesPerRowDest = CVPixelBufferGetBytesPerRow(copy)
            if bytesPerRowSrc == bytesPerRowDest {
                memcpy(dest, source, height * bytesPerRowSrc)
            }else {
                var startOfRowSrc = source
                var startOfRowDest = dest
                for _ in 0..<height {
                    memcpy(startOfRowDest, startOfRowSrc, min(bytesPerRowSrc, bytesPerRowDest))
                    startOfRowSrc = startOfRowSrc?.advanced(by: bytesPerRowSrc)
                    startOfRowDest = startOfRowDest?.advanced(by: bytesPerRowDest)
                }
            }

        }else {
            for plane in 0 ..< pixelBufferPlaneCount {
                let dest        = CVPixelBufferGetBaseAddressOfPlane(copy, plane)
                let source      = CVPixelBufferGetBaseAddressOfPlane(self, plane)
                let height      = CVPixelBufferGetHeightOfPlane(self, plane)
                let bytesPerRowSrc = CVPixelBufferGetBytesPerRowOfPlane(self, plane)
                let bytesPerRowDest = CVPixelBufferGetBytesPerRowOfPlane(copy, plane)

                if bytesPerRowSrc == bytesPerRowDest {
                    memcpy(dest, source, height * bytesPerRowSrc)
                }else {
                    var startOfRowSrc = source
                    var startOfRowDest = dest
                    for _ in 0..<height {
                        memcpy(startOfRowDest, startOfRowSrc, min(bytesPerRowSrc, bytesPerRowDest))
                        startOfRowSrc = startOfRowSrc?.advanced(by: bytesPerRowSrc)
                        startOfRowDest = startOfRowDest?.advanced(by: bytesPerRowDest)
                    }
                }
            }
        }
        return copy
    }
}

适用于 Swift 5。提供更多背景知识... AVCaptureVideoDataOutput .videoSettings 属性可以采用多种格式。并非所有人都有飞机,尤其是机器学习模型可能需要的飞机。

【讨论】:

    【解决方案2】:

    第二个实现看起来相当可靠。我能想象的唯一问题是新像素缓冲区中的平面分配有不同的步长(每行字节数)。步长基于宽度 ×(每像素字节数),然后以未指定的方式四舍五入以实现最佳内存访问。

    所以检查是否:

    CVPixelBufferGetBytesPerRowOfPlane(self, plane) == CVPixelBufferGetBytesPerRowOfPlane(copy, plane
    

    如果没有,逐行复制像素平面:

    for plane in 0 ..< CVPixelBufferGetPlaneCount(self)
    {
        let dest            = CVPixelBufferGetBaseAddressOfPlane(copy, plane)
        let source          = CVPixelBufferGetBaseAddressOfPlane(self, plane)
        let height          = CVPixelBufferGetHeightOfPlane(self, plane)
        let bytesPerRowSrc  = CVPixelBufferGetBytesPerRowOfPlane(self, plane)
        let bytesPerRowDest = CVPixelBufferGetBytesPerRowOfPlane(copy, plane)
    
        if bytesPerRowSrc == bytesPerRowDest {
            memcpy(dest, source, height * bytesPerRowSrc)
        } else {
            var startOfRowSrc = source
            var startOfRowDest = dest
            for _ in 0..<height {
                memcpy(startOfRowDest, startOfRowSrc, min(bytesPerRowSrc, bytesPerRowDest))
                startOfRowSrc += bytesPerRowSrc
                startOfRowDest += bytesPerRowDest
            }
        }
    }
    

    【讨论】:

    • 适用于 iPad 第 6 代。不过,您似乎在第 7 行打错了字:bytesPerRowDest 应该使用 copy 而不是 self,否则您将始终出现在 if 测试的第一个块中。
    猜你喜欢
    • 2022-01-23
    • 2019-01-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-22
    • 1970-01-01
    • 1970-01-01
    • 2011-05-21
    相关资源
    最近更新 更多