はじめに
こんにちは、nukkyです。
今回はメインのビューの右上とかに小窓の様なプレビューにカメラの映像を出して、動画撮影するという機能を作成したのですが、思ったより苦戦したので備忘録がてら記事にしたいと思います。
前提条件
Xcode 9.1
iOS 11 Simulator
Swift 4.0
実装
まずは以下の二つをインポートします。
「AVFoundation」は動画撮影に、「Photos」動画を保存する際に必要になってきます。
1 2 | import AVFoundation import Photos |
動画保存の為に「AVCaptureFileOutputRecordingDelegate」を設定します。
1 | class RecordViewController: UIViewController, AVCaptureFileOutputRecordingDelegate { |
以下のグローバル変数を宣言しておきます。
1 2 3 4 5 6 7 8 | // カメラやマイクへのセッション管理用 var captureSession:AVCaptureSession! // 動画ファイルの出力管理用 var fileOutput:AVCaptureMovieFileOutput! // View上にカメラの映像を表示用 var videoLayer:AVCaptureVideoPreviewLayer! // 動画撮影状態管理用フラグ var isRecoding = false |
AVCaptureSessionとAVCaptureMovieFileOutputの初期化を行います。
今回はフロントカメラを使用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | // セッションのインスタンス生成 captureSession = AVCaptureSession() // 入力(前面カメラ) var videoDevice: AVCaptureDevice? // OSバージョンでカメラの指定が異なる if #available(iOS 10.0, *) { let discoverySession = AVCaptureDeviceDiscoverySession(deviceTypes: [.builtInDuoCamera], mediaType: AVMediaTypeVideo, position: .front) for device in (discoverySession?.devices)! { videoDevice = device } } else { let videoDevices = AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo) for device in videoDevices!{ let device = device as! AVCaptureDevice if device.position == AVCaptureDevicePosition.front { videoDevice = device break } } } let videoInput = try! AVCaptureDeviceInput.init(device: videoDevice) captureSession.addInput(videoInput) // 入力(マイク)動画の音声いらない?もしくは動画保存中に録音もするから無いほうがいい? let audioDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeAudio) let audioInput = try! AVCaptureDeviceInput.init(device: audioDevice) captureSession.addInput(audioInput); // 出力(動画ファイル) fileOutput = AVCaptureMovieFileOutput() captureSession.addOutput(fileOutput) |
ここが今回の肝ですが、カメラの映像を小窓の様にして表示させます。
今回はViewの左上に240の正方形で表示させます。
1 2 3 4 5 6 7 8 9 | // プレビュー表示 func previewInitialization(view:UIView, frame: CGRect = CGRect(x: 0, y: 60, width: 240, height: 240)) { if let videoLayer = AVCaptureVideoPreviewLayer.init(session: captureSession) { videoLayer.frame = frame videoLayer.videoGravity = AVLayerVideoGravityResizeAspectFill self.videoLayer = videoLayer view.layer.addSublayer(self.videoLayer) } } |
録画の開始と終了は以下になります。
1 2 3 4 5 6 7 8 9 10 | // 録画開始 func startRecord(fileURL:NSURL) { // toOutputFileURLにファイルのパスをファイル名と拡張子込みで渡す fileOutput?.startRecording(toOutputFileURL: fileURL as URL!, recordingDelegate: self) } // 録画終了 func stopRecord() { fileOutput.stopRecording() } |
stopRecordingメソッドを呼ぶと以下のDelegateが呼ばれるので、ここでファイルを保存します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // 録画終了時に動画を保存 public func capture(_ captureOutput: AVCaptureFileOutput!, didFinishRecordingToOutputFileAt outputFileURL: URL!, fromConnections connections: [Any]!, error: Error!) { if error != nil { // エラー処理 return } // ライブラリへの保存 PHPhotoLibrary.shared().performChanges({ PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputFileURL) }) { completed, error in if completed { print("Video is saved!") } } } |
さいごに
駆け足になってしまいましたが、これで自由にカメラの映像をViewに重ねて動画の保存ができると思います。
個人的に、カメラの映像のframe自由に設定して画面表示する箇所がソースにすると大した事ないのですが調査も含めて苦戦したので、これが誰かのお役に立てれば報われます。