Вытягивание данных из CMSampleBuffer для создания глубокой копии

Я пытаюсь создать копию CMSampleBuffer, возвращенную captureOutput в AVCaptureVideoDataOutputSampleBufferdelegate.

Так как CMSampleBuffers происходят из предварительно выделенного пула (15) буферов, если я приложу к ним ссылку, их нельзя будет вспомнить. Это приводит к удалению всех оставшихся кадров.

для поддержания оптимальной производительности некоторые образцы буферов непосредственно ссылаются на пулы памяти, которые могут потребоваться повторно использовать устройство система и другие входные сигналы захвата. Это часто имеет место для несжатого собственного захвата устройства, где блоки памяти копируются как можно меньше. Если несколько буферов выборки ссылаются на такие пулы памяти слишком долго, входные данные больше не смогут копировать новые образцы в память, и эти образцы будут удалены.

Если ваше приложение вызывает выборку образцов, сохраняя предоставленные объекты CMSampleBufferRef слишком долго, но ему нужен доступ к образцу данные в течение длительного периода времени рассмотрите возможность копирования данных в новый буфер, а затем освобождения буфера выборки (если он был ранее сохранен), чтобы память, на которую он ссылается, можно было использовать повторно.

очевидно, я должен скопировать CMSampleBuffer, но CMSampleBufferCreateCopy () создаст только мелкую копию. Таким образом, я заключаю, что я должен использовать CMSampleBufferCreate(). Я заполнил 12! параметры, которые нужны конструктору, но столкнулись с проблемой, которую мои CMSampleBuffers не делают содержать blockBuffer (не совсем уверена, что это, но это кажется важным).

этот вопрос был задан несколько раз, но не ответил.

глубокая копия CMImageBuffer или CVImageBuffer и создайте копию CMSampleBuffer в Swift 2.0

один из возможных ответов: "я, наконец, понял, как использовать это, чтобы создать глубокий клон. Все методы копирования повторно использовали данные в куче, которая хранила бы блокировку AVCaptureSession. Поэтому мне пришлось вытащить данные в объект NSMutableData, а затем создать новый буфер образцов."кредит грабить на SO. Однако я не знаю, как это сделать правильно.

Если вы заинтересованы, этой выход print(sampleBuffer). Нет упоминания о blockBuffer, он же CMSampleBufferGetDataBuffer возвращает nil. Существует imageBuffer, но создание "копии" с помощью CMSampleBufferCreateForImageBuffer, похоже, не освобождает CMSampleBuffer любой.


EDIT: поскольку этот вопрос был опубликован, я пытался еще больше способов копирования памяти.

Я сделал то же самое, что Kametrixom попробовал. этой это моя попытка той же идеи, сначала скопировать CVPixelBuffer, а затем использовать CMSampleBufferCreateForImageBuffer для создания буфера окончательного образца. Однако это приводит к одной из двух ошибок:

  • A EXC_BAD_ACCESS в инструкции memcpy. Он же segfault от попытки доступа за пределами памяти приложения.
  • или память будет успешно копировать, но CMSampleBufferCreateReadyWithImageBuffer() завершится ошибкой с кодом результата -12743 ,который " указывает, что формат данного носителя не соответствует описанию данного формата. Например, описание формата в паре с CVImageBuffer, который терпит неудачу CMVideoFormatDescriptionMatchesImagebuffer."

вы можете видеть, что и Kametrixom и я использовали CMSampleBufferGetFormatDescription(sampleBuffer) чтобы попытаться скопировать описание формата исходного буфера. Таким образом, я не уверен, почему формат данного носителя не соответствует описанию данного формата.

4 ответов


хорошо, думаю, я наконец-то понял. Я создал вспомогательное расширение, чтобы сделать полную копию 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 занимает около 3 МБ памяти (1080p), что означает, что в 100 кадрах вы получили уже около 300 МБ, что примерно в тот момент, когда iPhone говорит STAHP (и падает).

обратите внимание, что вы на самом деле не хочу, чтобы скопировать CMSampleBuffer так как это только действительно содержит CVPixelBuffer потому что это изображение.


Это Свифт 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
}
}

Я считаю, что с VideoToolbox.рамки, вы можете использовать VTPixelTransferSession для копирования буферов пикселей. На самом деле это единственное, что делает этот класс.

ссылка: https://developer.apple.com/documentation/videotoolbox/vtpixeltransfersession-7cg


Я потратил пару часов, пытаясь получить эту работу. Получается как вложения с оригинальной CVPixelBuffer и варианты IOSurface нашли в PixelBufferMetalCompatibilityKey необходимы.

(к вашему сведению, PixelTransferSession VideoToolbox-только для macOS, к сожалению.)

вот что я закончил. Я оставил в конце несколько строк, которые позволяют вам проверить memcpy, сравнивая средние цвета как оригинальных, так и скопированных CVPixelBuffers. Он замедляет работу, поэтому его следует удалить, как только вы уверены, что ваша копия() работает так, как ожидалось. В 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
    }
}