AVCaptureVideoDataOutputSampleBufferDelegateのcaptureOutputでフレームを配列で保持するとサンプルを取得できなくなる問題【対処法】

#13フレームで止まる問題
ビデオフレームを取得して処理できるデリゲートメソッド "captureOutput" の **CMSampleBuffer を配列として保持すると、新しいフレームを取得できなくなります。**
１３フレーム取得すると、それより後のフレームは取れなくなります。
**CVPixelBufferやUIImageに変換して配列に保持しても、同様に取得できなくなります。**

```swift
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
           buffers.append(sampleBuffer)
           //このように配列(buffers)に保持してしまうと、新しいフレームが取れなくなる
}
```

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

>提供されたオブジェクトを長時間保持することでアプリケーションがサンプルをドロップする原因となっているが、サンプルデータに長期間アクセスする必要がある場合は、データを新しいバッファーにコピーしてからサンプルバッファーを解放することを検討してください

要するに、**メモリをリサイクルできるように解放しないと、新しいバッファー用のメモリプールの空きがなくなる**、ということだと思います。


#メモリプールは解放しづらい
ではメモリを解放しましょう、となっても、これがなかなかサクッといかない。

・CIImageなどに変換して配列に保持しても、参照が解放されないらしく、同様に13フレームで止まってしまう。

・CMSampleBufferCreateCopy メソッドは浅いコピーを作るので、メモリプールが解放されず、やはり13フレームで止まってしまう。

・ Objective-C で明示的にメモリ解放すればいいのかもしれないが、大変そう（個人的問題ですが）。



#対処法
**CVPixelBufferに変換してディープコピーを作成することで、メモリプールを解放できます。**


CVPixelBufferのディープコピーを作成するエクステンション。

```swift
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, CVAttachmentMode(rawValue: 1)!),
            &_copy)

        guard let copy = _copy else { fatalError() }

        CVPixelBufferLockBaseAddress(self, CVPixelBufferLockFlags(rawValue: 0x00000001))
        CVPixelBufferLockBaseAddress(copy, CVPixelBufferLockFlags(rawValue: 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, CVPixelBufferLockFlags(rawValue: 0))
        CVPixelBufferUnlockBaseAddress(self, CVPixelBufferLockFlags(rawValue: 0))
        _copy = nil
        return copy
    }
}
```

captureOutput メソッド内で使用する。

```swift
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
                return
            }
            let copiedBuffer = pixelBuffer.copy()
            pixelBuffers.append(copiedBuffer)
            //CVPixelBufferの配列で保持
```

**（注）配列で保持したCVPixelBuffer分のメモリはしっかり消費していくので、フレームの管理は注意しておこなってください。**

**＊＊＊**
Core MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。

[Twitter](https://twitter.com/JackdeS11)
[MLBoysチャンネル]
(https://www.youtube.com/channel/UCbHff-wfjTnB3rtXIP6y0xg)
[Medium](https://medium.com/@rockyshikoku)

[相棒](https://twitter.com/oka_yuuji)
[note](https://note.com/oka_yuuji)