Swift でタイムラプス動画を撮影をする


タイムラプス撮影を自作アプリでやってみたかったのでやり方を調べてみました。

# タイムラプスとは

「デジタルカメラを用いて連続したスチール画像を撮影し、それを素材として作った動画のこと。インターバル動画ともいう。一定間隔で連続撮影した静止画を、パソコンのソフト上で組み合わせることにより動画を作る。」 - [コトバンク](https://kotobank.jp/word/%E3%82%BF%E3%82%A4%E3%83%A0%E3%83%A9%E3%83%97%E3%82%B9-192015)より

# 方針

つまり一定の間隔で映像を保存していった動画を作ればタイムラプス動画を撮れる、ということのようです。

というわけでここではビデオを毎回 4フレーム ドロップ(捨てて)しつつ映像を保存してみます。

# 1.AVCaptureSession を使って撮影のセットアップ

AVCaptureSession を使って撮影の準備をします。
フレームをドロップしつつ保存するため、撮影する際は Iフレームだけを吐いてもらように sessionPreset を AVCaptureSessionPresetiFrame1280x720 に指定します。

```
let captureSession = AVCaptureSession()
private let videoDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
private let audioDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeAudio)

func startup(){
    videoDevice.activeVideoMinFrameDuration = CMTimeMake(1, 30)

    do
    {
        let videoInput = try AVCaptureDeviceInput(device: videoDevice) as AVCaptureDeviceInput
        captureSession.addInput(videoInput)
    }
    catch let error as NSError {
        Logger.log(error.localizedDescription)
    }

    do
    {
        let audioInput = try AVCaptureDeviceInput(device: audioDevice) as AVCaptureDeviceInput
        captureSession.addInput(audioInput)
    }
    catch let error as NSError {
        Logger.log(error.localizedDescription)
    }
    
    
    // video output
    let videoDataOutput = AVCaptureVideoDataOutput()
    videoDataOutput.setSampleBufferDelegate(self, queue: recordingQueue)
    videoDataOutput.alwaysDiscardsLateVideoFrames = true
    videoDataOutput.videoSettings = [
        kCVPixelBufferPixelFormatTypeKey : Int(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)
    ]
    captureSession.addOutput(videoDataOutput)
    captureSession.sessionPreset = AVCaptureSessionPresetiFrame1280x720
    
    height = videoDataOutput.videoSettings["Height"] as! Int!
    width = videoDataOutput.videoSettings["Width"] as! Int!
    
    // audio output
    let audioDataOutput = AVCaptureAudioDataOutput()
    audioDataOutput.setSampleBufferDelegate(self, queue: recordingQueue)
    captureSession.addOutput(audioDataOutput)
     
    captureSession.startRunning()
}

```


# 2.フレームをドロップしつつファイルに保存する

重要なのは captureOutput(captureOutput: didOutputSampleBuffer fromConnection ) です。
この関数内で sampleBuffer にビデオとオーディオが渡されます。

今回はタイムラプス動画なので、

- 音声は不要なので保存しない
- ビデオが渡される回数をカウントし、5回に1回ビデオを保存する
 - その際はタイムスタンプを修正しておく

```
let skipFrameSize = 5
private var currentSkipCount = 0

func captureOutput(captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, fromConnection connection: AVCaptureConnection!){

     ...      
     
     // オーディオは書き込まない
     if !isVideo {
        return
     }
     
     // 決めた回数分フレームをドロップする
     currentSkipCount += 1
     guard currentSkipCount == skipFrameSize else { return }
     currentSkipCount = 0

     // タイムスタンプを書き換える
     var buffer = sampleBuffer
     if lasttimeStamp.value == 0 {
         lasttimeStamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
     } else {
         lasttimeStamp = CMTimeAdd(lasttimeStamp, CMTimeMake(1, 30))
         buffer = setTimeStamp(sampleBuffer, newTime: lasttimeStamp)
     }
     Logger.log("write")
     
     // ファイルに書き出す
     videoWriter?.write(buffer, isVideo: isVideo)
}

```

## サンプルコード

動作するプロジェクト一式はこちらです。

https://github.com/takecian/video-examples-ios/tree/master