iOS
カメラ
動画
Swift
swift4

[swift4] swift4で動画を撮影する

はじめに

今回はswift4で動画を撮影するためのコードをご紹介します。

動画はどうやって撮影されているのか?

実装をする前に動画がどうやって撮影されているのかを理解しておくと、より理解をしながら実装ができると思います。理解を深めておくと、動画を撮影するために何が必要なのかがわかり、バグが起きた時などに原因を特定しやすくなります。

動画ファイルの仕組み

動画ファイル(mp4やmov)は映像ファイルと音声ファイルを合体させたものです。なので、動画ファイルを作成するときには、映像を撮影するカメラと、音声を録音するマイクが必要になります。そしてカメラとマイクを使って作成された映像と音声を同時に再生できるように合体させることで動画ファイルになります。

動画を撮影するために必要なもの

動画を撮影するために必要なものは主に以下の4つです。

・カメラとマイクを入れるための箱(セッション)
・映像を撮影するカメラ
・撮影している映像を表示するための画面
・音声を録音するマイク

プログラミングに慣れていない人は、iPhoneのカメラを想像していただくとわかりやすいかと思います。iPhoneというカメラとマイクを管理する箱があり、その中に映像を撮影するカメラ、撮影している映像を表示させるための画面、音声を録音するマイクが入っています。最終的にカメラロールという動画を格納するための箱を作っていくイメージです。

では実際に作って見ましょう

今回はコード量が多いので、コードの完成系のみ紹介します。

開発環境

Xcode :9.3
swift :4.1
iPhone:8以上

今回使うフレームワーク

AVFoundation
Photos

コードの完成系

import UIKit
import AVFoundation
import Photos

class VideoShootingViewController: UIViewController {

    @IBOutlet weak var videoView: UIView!
    @IBOutlet weak var startStopButton: UIButton!

    var isRecoding = false
    var fileOutput: AVCaptureMovieFileOutput?
    var isBackCamera: Bool = true

    // カメラとマイクを入れるための箱(セッションのインスタンス生成)
    var captureSession = AVCaptureSession()
    var filePath: String?
    var shootingVideoFileURL: URL?

    override func viewDidLoad() {
        super.viewDidLoad()
        self.setupCamera(isBack: self.isBackCamera)
    }

    func setupCamera(isBack: Bool) {
        self.isRecoding = false

        self.captureSession = AVCaptureSession()

        // 入力(映像を撮影するカメラ)
        var videoDevice: AVCaptureDevice!
        let deviceDescoverySession = AVCaptureDevice.DiscoverySession.init(deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera],
                                                                           mediaType: AVMediaType.video,
                                                                           position: AVCaptureDevice.Position.unspecified)

        for device in deviceDescoverySession.devices {
            let devicePosition: AVCaptureDevice.Position = isBack ? .back : .front
            if device.position == devicePosition {
                videoDevice = device
            }
        }

        let videoInput = try! AVCaptureDeviceInput(device: videoDevice!)
        self.captureSession.addInput(videoInput)

        // 入力(音声を録音するマイク)
        let audioDevice = AVCaptureDevice.default(for: AVMediaType.audio)
        let audioInput = try! AVCaptureDeviceInput.init(device: audioDevice!)
        self.captureSession.addInput(audioInput)
        // 出力(動画ファイル)
        self.fileOutput = AVCaptureMovieFileOutput()
        self.captureSession.canAddOutput(self.fileOutput!)
        self.captureSession.addOutput(self.fileOutput!)
        self.captureSession.sessionPreset = AVCaptureSession.Preset.hd1280x720

        // プレビュー(撮影している映像を表示するための画面)
        let videoLayer = AVCaptureVideoPreviewLayer(session: captureSession)

        // レイアウト
        videoLayer.frame = self.videoView.bounds
        videoLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
        videoLayer.connection?.videoOrientation = .portrait
        self.videoView.layer.addSublayer(videoLayer)

        // セッションの開始
        DispatchQueue.global(qos: .userInitiated).async {
            self.captureSession.startRunning()
        }
    }

    // 録画の開始・停止ボタン
    @IBAction func tapStartStopButton(_ sender: Any) {

        if self.isRecoding { // 録画終了
            self.fileOutput?.stopRecording()

            DispatchQueue.global(qos: .userInitiated).async {
                self.captureSession.stopRunning()
            }
        }
        else{ // 録画開始

            let fileName = "\(Date().timeIntervalSince1970).mov"
            self.filePath = NSHomeDirectory() + "/tmp/" + fileName
            self.shootingVideoFileURL = NSURL(fileURLWithPath: self.filePath!) as URL
            self.fileOutput?.startRecording(to: self.shootingVideoFileURL!, recordingDelegate: self as AVCaptureFileOutputRecordingDelegate)
        }
    }

}

extension VideoShootingViewController: AVCaptureFileOutputRecordingDelegate {

    func fileOutput(_ output: AVCaptureFileOutput, didStartRecordingTo fileURL: URL, from connections: [AVCaptureConnection]) {
        self.isRecoding = true
    }

    func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
        self.isRecoding = false

        PHPhotoLibrary.shared().performChanges({
            PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputFileURL)
        }) { completed, error in
            if completed {
                print("Video is saved!")
            }
        }
    }

}

まとめ

いかがでしたでしょうか?
動画を撮影するためには、まず撮影するためのカメラを組み立てて、カメラを動かす処理を追加し、最後に動画を保存するための場所と機能が必要です。
ソースコードの参考記事はたくさんあるので、この記事では、カメラを使って動画を撮影するために必要なことや仕組みを理解していただけたら嬉しいです。

参考記事

AVFoundation(AVCaptureMovieFileOutput)を使用したビデオ録画を作ってみた

Swift4で動画を撮影・保存する