3
3

More than 3 years have passed since last update.

AVFoundationでカメラモジュールを作る

Last updated at Posted at 2020-04-02

概要

iOSのAVFoundationを用いて汎用カメラモジュールを作ります。
Vision.frameworkと組み合わせて画像認識したり,リアルタイムビデオエフェクトアプリを作る際にも利用できるように,汎用のモジュールとして作りましょう。いろいろと使いまわせて便利です。

まずはシングルトンのカメラコントローラクラスを作り,そこにAVCaptureSessionのインスタンスを持たせます。これがカメラのセッションを管理するクラスですね。
このAVCaptureSessionに,インプットとアフトプットを接続し,さらにカメラプレビューとなるAVCaptureVideoPreviewLayerを接続すれば準備完了。
AVCaptureSessionをrunしてやればカメラが起動し,AVCaptureVideoPreviewLayerにカメラの映像が表示されます。

CameraControllerの実装

汎用カメラオブジェクトとして,私はいつもCameraControllerというクラスを作っています。

CameraController.swift

class CameraController: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate, AVCapturePhotoCaptureDelegate {

// MARK: - lifecycle
    static let shared = CameraController()
    private override init() {    

    }
}

Swiftでのシングルトンの実装方法もいくつかあるようですが,シンプルに。このクラスはSampleBufferDelegateとPhotoCaptureDelegateを採用していますが,必要な機能によっては他にも採用するプロトコルはあり得ます。

また,カメラの起動に必要なものは,プロパティとして宣言しておきます。

CameraController.swift
 // MARK: - properties
    let captureSession:AVCaptureSession = AVCaptureSession()
    let videoOutput:AVCaptureVideoDataOutput = AVCaptureVideoDataOutput()
    let photoOutput:AVCapturePhotoOutput = AVCapturePhotoOutput()
    let previewLayer = AVCaptureVideoPreviewLayer()
    var preview:UIView!

最後のpreviewだけ,宣言時にはnilです。
このクラス,例えば他のviewControllerなどから簡単に呼べるように,カメラをスタートするメソッドとストップするメソッドを持たせます。カメラ起動中の映像を表示するために,スタートメソッドにはプレビューを表示するUIViewをパラメータに持たせます。スタートメソッドで受け取ったviewを,プロパティのpreviewにセットするわけですね。

次に,カメラをスタートするメソッド,ストップするメソッドを追加します。スタートメソッド内でcapture sessionやinput, outputを設定します。

CameraController.swift

func startSession(preview:UIView){

    //カメラプレビュー
    self.preview = preview
    self.previewLayer.setSessionWithNoConnection(self.captureSession)
    self.previewLayer.frame = preview.bounds
    self.previewLayer.videoGravity = .resizeAspectFill
    preview.layer.addSublayer(self.previewLayer)

    self.captureSession.beginConfiguration()

    // input
    guard let videoDevice = AVCaptureDevice.default(for: .video) else { return }

    do {
        let deviceInput = try AVCaptureDeviceInput(device: videoDevice)

            if self.captureSession.canAddInput(deviceInput){
                self.captureSession.addInput(deviceInput)
            }
            else{
                print("input error")
            }
        }
        catch {

        }

    // output
    if self.captureSession.canAddOutput(self.photoOutput){        
        self.captureSession.addOutput(self.photoOutput)
        self.photoOutput.isHighResolutionCaptureEnabled = true
        self.photoOutput.isLivePhotoCaptureEnabled = self.photoOutput.isLivePhotoCaptureSupported
    }
    else{
        print("photo output error!")
    }

    if self.captureSession.canAddOutput(self.videoOutput){
        self.videoOutput.setSampleBufferDelegate(self, queue: self.sampleBufferQueue)
        self.captureSession.addOutput(self.videoOutput)
    }
    else{
        print("video output error!")
    }

    self.captureSession.commitConfiguration()
    self.captureSession.startRunning()
}

本来はマルチスレッドを活用して,UIに関わる部分以外はバックグラウンドのqueueで行いましょう。エラーハンドリングも適切に。このコードでは割愛してます。

続いてカメラをストップするメソッドを書きたいところですが,長くなるのでこれも割愛。基本的には,sessionをstopしてinputとoutputを外しておけばOKです。

最後に,必要なデリゲートプロトコルを実装。ここでは使用頻度が高いと思われる2つだけ書いておきます。メソッドの中身は,必要に応じて頑張って書きましょう。

CameraController.swift

// MARK: - capture photo delegate
// 写真撮影後に呼ばれる
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?){

}

// MARK: - sample buffer delegate
// 1フレームごとに呼ばれる
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {

}

これがCameraControllerの実装は終わり(実際にはもっと書かなきゃいけませんよ!)。viewControllerからカメラを起動する場合には,例えば以下のようにします。

ViewController.swift

override func viewDidAppear(_ animated:Bool){

    super.viewDidAppear(animated)

    //ここ
    CameraController.shared.startSession(self.view)
}

以上,汎用カメラクラスの基本設計でした。
カメラなど,特定のデバイスを扱うコードはできる限りシングルトンの専用クラスに書いておいた方が良いと思います。しばしばViewControllerにすべて書いた実装を見ますが,それはあまり良くないです。理由は以下。

・デバイスは1つしかないので,カメラを制御するコードが複数のインスタンスから同時にコールされないようにするため
・コードの再利用が容易(←重要)
・専用のオブジェクトにまとめておけば,どのviewControllerからでも呼びやすい

というところです。

上記コードですが,くれぐれも,これだけではまだ十分ではないのでいろいろと頑張りましょう。改善提案,間違いの指摘は歓迎です。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3