しめ鯖日記

swift, iPhoneアプリ開発, ruby on rails等のTipsや入門記事書いてます

【iOS】CMSampleBufferGetImageBufferがnilを返す時の対処法

AVFoundationを使うと端末のカメラで撮っている映像をUIImageとして取得する事ができます。
その際にCMSampleBufferGetImageBufferというメソッドを使うんですが、これがnilを返した時の対処法です。

func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
    let buffer = CMSampleBufferGetImageBuffer(sampleBuffer) // → これがnilになる
}

原因ですがメソッド名間違いでした。
didDropのメソッドではなくdidOutputを使う事で無事にUIImageを取得する事ができました。

// 間違い
func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
}

// 正解
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
}

didDropとdidOutputの違い

簡単に言うとdidDropはフレームをドロップした時に呼ばれるメソッドで、CMSampleBufferには映像データが入っていません。
そのため映像データが欲しい場合はdidOutputを使う必要があります。

フレームをドロップするとはどういうことか

フレームのドロップとは映像の時間の帳尻を合わせる為のものです。

動画は想定していた1分間辺りフレーム数と実際のフレーム数がずれる事があります。
フレーム数がずれてしまうと10分の映像だったつもりが実際には少し長くなったりしてしまったり、困った事態が起こります。

その対策として、帳尻を合わせるためにフレームをスキップしたりします。
これがフレームをドロップするということです。

詳しい挙動を調べるため、下のようにdidDropとdidOutputの呼び出しタイミングを調べてみました。

func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
    print(#function)
}

func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
    print(#function)
}        

調べたところ、didDropとdidOutputは下のような呼ばれ方がしている事が分かりました。

f:id:llcc:20180603190013p:plain

まとめ

最初はCMSampleBufferGetImageBufferがnilを返す原因について探っていたんですが、動画のドロップフレームなど新しい事を学べて面白かったです。
この辺りはいざ不具合にぶつかった時にハマりやすいので今後も地道に勉強していこうと思います。