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

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

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

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

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

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

Этот вопрос задавали несколько раз, но не отвечали.

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

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

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


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

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

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

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

Ответы

Ответ 1

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

Ответ 2

Это решение 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
}
}