Swift で動画を撮影・保存するサンプル その１ - シンプル編


やることは Objective-c でやる内容を Swift で書くだけではあります。100 行くらいでできましたのでメモ。最後にコードを一式貼り付けています。

# やること

1. 必要な framework の追加
1. Session, Device の準備 
1. プレビュー画面の生成
1. 録画ボタンで録画開始
1. 停止ボタンで録画停止、ライブラリに保存

# 必要な framework の追加

AVFoundation と AssetsLibrary を追加します。

# Session, Device の準備

iOS では録画時のメディアは AVCaptureSession というクラスが管理します。AVCaptureSession に入力、出力情報を渡します。

参考：AVCaptureSession
https://developer.apple.com/library/ios/documentation/avfoundation/reference/AVCaptureSession_Class/index.html

映像はデフォルトのカメラ（iPhone6 だと背面カメラでした）、音声もデフォルトのマイクを使用します。
ファイルとして保存するので AVCaptureMovieFileOutput を使います。

ここでは default device を使いましたが前面カメラを使う、カメラの切り替えを行いたい場合は Device の列挙などから選択する必要があります。

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

生成した Device から AVCaptureDeviceInput を生成して AVCaptureSession に追加します。
出力先となる AVCaptureMovieFileOutput も追加します。

```
    let videoInput = AVCaptureDeviceInput.deviceInputWithDevice(self.videoDevice, error: nil) as AVCaptureDeviceInput
    self.captureSession.addInput(videoInput)
    let audioInput = AVCaptureDeviceInput.deviceInputWithDevice(self.audioDevice, error: nil)  as AVCaptureInput
    self.captureSession.addInput(audioInput);

    self.captureSession.addOutput(self.fileOutput)
```

#. プレビュー画面の生成

カメラからの映像を ViewController に表示するのに AVCaptureVideoPreviewLayer を使います。
ここでは画面いっぱいに表示しています。

```
    let videoLayer : AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer.layerWithSession(self.captureSession) as AVCaptureVideoPreviewLayer
    videoLayer.frame = self.view.bounds
    videoLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
    self.view.layer.addSublayer(videoLayer)
```

#. 録画ボタンで録画開始

ViewController 上に配置した録画ボタンを押されたら録画開始処理を行います(ボタンを設置するコードなどは最後に載せています)。
録画データを保存するパスを決めて、 startRecordingToOutputFileURL を呼び出します。

```
    let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
    let documentsDirectory = paths[0] as String
    let filePath : String? = "\(documentsDirectory)/temp.mp4"
    let fileURL : NSURL = NSURL(fileURLWithPath: filePath!)!
    fileOutput.startRecordingToOutputFileURL(fileURL, recordingDelegate: self)
```

#. 停止ボタンで録画停止、ライブラリに保存

録画を停止する時は stopRecording を呼び出します。

```
    fileOutput.stopRecording()
```

録画が停止した時は AVCaptureFileOutputRecordingDelegate の下記メソッドが呼ばれるのでその中でライブラリへ保存する処理を行います。

```
   func captureOutput(captureOutput: AVCaptureFileOutput!, didFinishRecordingToOutputFileAtURL 
               outputFileURL: NSURL!, fromConnections connections: [AnyObject]!, error: NSError!) {
        let assetsLib = ALAssetsLibrary()
        assetsLib.writeVideoAtPathToSavedPhotosAlbum(outputFileURL, completionBlock: nil)
    }
```


# ソースコード

プロジェクトを作って、ViewController のクラスに下記を貼り付けるとそのまま動くと思います。

環境：XCode 6.1, iOS 8.1 で確認

```CaputureViewController.swift
import UIKit
import AVFoundation
import AssetsLibrary

class CaputureViewController: UIViewController, AVCaptureFileOutputRecordingDelegate {
    var index = 2
    
    let captureSession = AVCaptureSession()
    let videoDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
    let audioDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeAudio)
    let fileOutput = AVCaptureMovieFileOutput()
    
    var startButton, stopButton : UIButton!
    var isRecording = false

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let videoInput = AVCaptureDeviceInput.deviceInputWithDevice(self.videoDevice, error: nil) as AVCaptureDeviceInput
        self.captureSession.addInput(videoInput)
        let audioInput = AVCaptureDeviceInput.deviceInputWithDevice(self.audioDevice, error: nil)  as AVCaptureInput
        self.captureSession.addInput(audioInput);
        
        self.captureSession.addOutput(self.fileOutput)
        
        let videoLayer : AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer.layerWithSession(self.captureSession) as AVCaptureVideoPreviewLayer
        videoLayer.frame = self.view.bounds
        videoLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
        self.view.layer.addSublayer(videoLayer)
        
        self.setupButton()
        self.captureSession.startRunning()
    }
    
    func setupButton(){
        self.startButton = UIButton(frame: CGRectMake(0,0,120,50))
        self.startButton.backgroundColor = UIColor.redColor();
        self.startButton.layer.masksToBounds = true
        self.startButton.setTitle("start", forState: .Normal)
        self.startButton.layer.cornerRadius = 20.0
        self.startButton.layer.position = CGPoint(x: self.view.bounds.width/2 - 70, y:self.view.bounds.height-50)
        self.startButton.addTarget(self, action: "onClickStartButton:", forControlEvents: .TouchUpInside)
        
        self.stopButton = UIButton(frame: CGRectMake(0,0,120,50))
        self.stopButton.backgroundColor = UIColor.grayColor();
        self.stopButton.layer.masksToBounds = true
        self.stopButton.setTitle("stop", forState: .Normal)
        self.stopButton.layer.cornerRadius = 20.0
        
        self.stopButton.layer.position = CGPoint(x: self.view.bounds.width/2 + 70, y:self.view.bounds.height-50)
        self.stopButton.addTarget(self, action: "onClickStopButton:", forControlEvents: .TouchUpInside)
        
        self.view.addSubview(self.startButton);
        self.view.addSubview(self.stopButton);
    }
    
    func onClickStartButton(sender: UIButton){
        if !self.isRecording {
            // start recording
            let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
            let documentsDirectory = paths[0] as String
            let filePath : String? = "\(documentsDirectory)/temp.mp4"
            let fileURL : NSURL = NSURL(fileURLWithPath: filePath!)!
            fileOutput.startRecordingToOutputFileURL(fileURL, recordingDelegate: self)

            self.isRecording = true
            self.changeButtonColor(self.startButton, color: UIColor.grayColor())
            self.changeButtonColor(self.stopButton, color: UIColor.redColor())
        }
    }
    
    func onClickStopButton(sender: UIButton){
       if self.isRecording {
            fileOutput.stopRecording()
            
            self.isRecording = false
            self.changeButtonColor(self.startButton, color: UIColor.redColor())
            self.changeButtonColor(self.stopButton, color: UIColor.grayColor())
        }
    }
    
    func changeButtonColor(target: UIButton, color: UIColor){
        target.backgroundColor = color
    }
    
    func captureOutput(captureOutput: AVCaptureFileOutput!, didStartRecordingToOutputFileAtURL fileURL: NSURL!, fromConnections connections: [AnyObject]!) {
    }

    func captureOutput(captureOutput: AVCaptureFileOutput!, didFinishRecordingToOutputFileAtURL outputFileURL: NSURL!, fromConnections connections: [AnyObject]!, error: NSError!) {
        let assetsLib = ALAssetsLibrary()
        assetsLib.writeVideoAtPathToSavedPhotosAlbum(outputFileURL, completionBlock: nil)
    }
}
```