AVCaptureSession でマイク入力を得る / AudioBufferList.allocate() 後のメモリリーク

https://github.com/daisuke-t-jp/AVCaptureAudioDataOutputSampleBufferTesting

iOS でマイク入力を取得してみたかったので試してみた。↑が、そのサンプルプロジェクト。

今回、いろいろなプロジェクトを参考にしてやってみたのだが、気になる点がひとつ。

AVCaptureAudioDataOutputSampleBufferDelegatecaptureOutput() を下のような感じで実装するパターンが多いのだが

extension ViewController: AVCaptureAudioDataOutputSampleBufferDelegate {
  func captureOutput(_ output: AVCaptureOutput,
                     didOutput sampleBuffer: CMSampleBuffer,
                     from connection: AVCaptureConnection) {
    
    var blockBuffer: CMBlockBuffer?
    let audioBufferList = AudioBufferList.allocate(maximumBuffers: 1)

    CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(
      sampleBuffer,
      bufferListSizeNeededOut: nil,
      bufferListOut: audioBufferList.unsafeMutablePointer,
      bufferListSize: MemoryLayout<AudioBufferList>.size,
      blockBufferAllocator: nil,
      blockBufferMemoryAllocator: nil,
      flags: UInt32(kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment),
      blockBufferOut: &blockBuffer
    )
  }
}


AudioBufferList.allocate()

https://github.com/apple/swift/blob/6e7051eb1e38e743a514555d09256d12d3fec750/stdlib/public/Darwin/CoreAudio/CoreAudio.swift#L67

f:id:daisuke-t-jp:20191015080703p:plain:w500

ということで free() を要することがコメントされている。
これを Xcode の Instruments で実際に試してみると


f:id:daisuke-t-jp:20191015081216p:plain:w500

やはりこんな感じでメモリリークを確認できた。

なので先ほどの AudioBufferList.allocate の周りのコードはこんな感じに修正するとメモリリークが消える。(defer はこんな風なリソース解放の時に便利だ)

let audioBufferList = AudioBufferList.allocate(maximumBuffers: 1)
    defer {
      free(audioBufferList.unsafeMutablePointer)
    }




・・・というリークが AVCaptureSession のコードで参考にした https://github.com/google/science-journal-ios のプロジェクトでも起きていたので、この修正をプルリクエストしてみたら、レビューが通ってマージされたので、なんか一件落着という感じで落ち着きましたよ。