web-dev-qa-db-ja.com

ディープコピーを作成するためにCMSampleBufferからデータをプルする

AVCaptureVideoDataOutputSampleBufferDelegateのcaptureOutputによって返されるCMSampleBufferのコピーを作成しようとしています。

CMSampleBuffersは、事前に割り当てられた(15)バッファーのプールから取得されるため、それらへの参照を添付すると、それらを再収集できません。これにより、残りのすべてのフレームがドロップされます。

最適なパフォーマンスを維持するために、一部のサンプルバッファは、デバイスシステムやその他のキャプチャ入力で再利用する必要があるメモリプールを直接参照します。これは、メモリブロックができるだけコピーされない非圧縮デバイスのネイティブキャプチャの場合によくあります。複数のサンプルバッファがそのようなメモリプールを長時間参照すると、入力は新しいサンプルをメモリにコピーできなくなり、それらのサンプルは削除されます。

アプリケーションが提供されたCMSampleBufferRefオブジェクトを長期間保持することによってサンプルがドロップされる原因であるが、サンプルデータに長期間アクセスする必要がある場合は、データを新しいバッファーにコピーしてからサンプルバッファーを解放することを検討してください(参照するメモリを再利用できるように、以前は保持されていました)。

明らかに、CMSampleBufferをコピーする必要がありますが、CMSampleBufferCreateCopy()は浅いコピーしか作成しません。したがって、CMSampleBufferCreate()を使用する必要があると結論付けます。 12を記入しました!コンストラクターが必要とするパラメーターですが、CMSampleBuffersにblockBufferが含まれていないという問題が発生しました(それが何であるかは完全にはわかりませんが、重要なようです)。

この質問は何度か尋ねられましたが、答えられていません。

CMImageBufferまたはCVImageBufferのディープコピー および Swift 2. でCMSampleBufferのコピーを作成します

考えられる答えの1つは、「これを使用してディープクローンを作成する方法をついに理解しました。保持されたヒープ内のデータを再利用すると、AVCaptureSessionがロックされます。そのため、データをNSMutableDataオブジェクトにプルしてからNSMutableDataオブジェクトにプルする必要がありました。新しいサンプルバッファを作成しました。」 SOのRobへのクレジット 。しかし、私はこれを正しく行う方法がわかりません。

興味があれば、 thisprint(sampleBuffer)の出力です。 blockBufferについては言及されていません。別名、CMSampleBufferGetDataBufferはnilを返します。 imageBufferがありますが、CMSampleBufferCreateForImageBufferを使用して「コピー」を作成しても、CMSampleBufferが解放されないようです。


編集:この質問が投稿されて以来、私はメモリをコピーするさらに多くの方法を試みてきました。

ユーザーが試したのと同じことをしました KametrixomThis は、最初にCVPixelBufferをコピーし、次にCMSampleBufferCreateForImageBufferを使用して最終的なサンプルバッファーを作成するという同じ考えでの私の試みです。ただし、これにより、次の2つのエラーのいずれかが発生します。

  • Memcpy命令のEXC_BAD_ACCESS。別名、アプリケーションのメモリの外部にアクセスしようとすることによるセグメンテーション違反。
  • または、メモリは正常にコピーされますが、CMSampleBufferCreateReadyWithImageBuffer()は失敗し、結果コード-12743が表示されます。「指定されたメディアの形式が指定された形式の説明と一致しないことを示します。たとえば、形式の説明とCMVideoFormatDescriptionMatchesImageBufferに失敗するCVImageBuffer。」

Kametrixomと私はどちらもCMSampleBufferGetFormatDescription(sampleBuffer)を使用してソースバッファのフォーマットの説明をコピーしようとしたことがわかります。したがって、特定のメディアの形式が特定の形式の説明と一致しない理由がわかりません。

19
bennyty

さて、私はついにそれを手に入れたと思います。 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の1つは約3MBのメモリ(1080p)を使用することに注意してください。つまり、100フレームですでに約300MBを取得しています。これは、iPhoneがSTAHPと表示する(そしてクラッシュする)時点です。

CMSampleBufferは画像であるため、実際にはCVPixelBufferしか含まれていないため、実際にはコピーしたくないことに注意してください。

11
Kametrixom

これは、最高評価の回答に対する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
}
}
5
Kent Guerriero

私はこれを機能させるために数時間を費やしました。元のCVPixelBufferからの添付ファイルと、PixelBufferMetalCompatibilityKeyにあるIOSurfaceオプションの両方が必要であることがわかりました。

(参考までに、VideoToolboxのPixelTransferSessionはmacOSのみです。残念ながら。)

これが私が最終的に得たものです。元のCVPixelBufferとコピーされたCVPixelBufferの両方の平均色を比較することで、memcpyを確認できるように、最後に数行残しました。速度が低下するため、copy()が期待どおりに機能していることを確認したら、削除する必要があります。 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
    }
}
1
jakemoves

VideoToolbox.frameworkを使用すると、VTPixelTransferSessionを使用してピクセルバッファをコピーできると思います。実際、このクラスが行うのはそれだけです。

参照: https://developer.Apple.com/documentation/videotoolbox/vtpixeltransfersession-7cg

0
Haotian Yang