SwiftとSoraを使って映像を装飾する
初めまして、今回初めてアルバイトとしてiTourプロジェクトに関わらせていただいた大越です。
早速ですがタイトル通り、SwiftとSoraを使って映像を装飾していく方法をこちらにまとめていこうと思います。

使用する技術について
WebRTCという技術を使って映像を映し出し、Swiftのあらかじめ用意されている関数を使い、映像を装飾します。
WebRTCとはリアルタイムでコミュニケーションが取れる、いわゆるビデオチャットのことですが、今回はそれが実現可能なSoraというライブラリを使っていきます。

それではセットアップに移ります。

セットアップについて

ざっくり言うとSoraがあらかじめ用意しているサンプルプロジェクトからgit cloneしてあとはCarthageを実行するのみです。
サンプルプロジェクトは複数あると思いますが、Carthageを実行させるのはDecoStreamingSampleとRealTimeStreamingSample内のみで大丈夫です。
1からの詳しいセットアップ方法についてはこちらをご覧ください。

注意

実機のみでしか動かないのであらかじめ実機の用意をしてください。

配信者側

配信側はDecoStreamingSampleプロジェクトのコードを編集していきます。

1.
sora-ios-sdk-samples/DecoStreamingSample/DecoStreamingSample/Classes/PublisherVideoViewController.swiftにグローバル変数として以下のコードを追加してください。

class PublisherVideoViewController: UIViewController{

    /*ここから追加する変数*/
    //uiimageからciimageへ変換するときの準備
    private var ciChange:CIImage?

    //マスクをsub側に送るためにあらかじめ画像を用意
    private static let allMasks:[String: UIImage] = {
        let pikaExMask:UIImage = UIImage(named: "pikaExtend.jpg")!
        let pikaSmiMask:UIImage = UIImage(named: "pikaSmile.jpg")!
        return [
            "伸びてるピカチュウ": pikaExMask,
            "笑顔なピカチュウ": pikaSmiMask
        ]
    }()

    //今選択されている画像
    private var currentMasks: UIImage?


}

画像の名前や変数名は適当なものに変えてください。

2.Assert.xcassets内にドラッグ&ドロップで適当な画像を入れてください。
入れる前に画像の名前@2x.jpgに変えましょう。(iPadの場合)
iPhone6 plusなど
​は@3x、そしてiPad2,iPad mini1などは@1xを名前のあとにつけましょう。(詳しくはこちら)

3.画像を視聴者側に表示させるための処理をビデオキャプチャを行う関数に追加します。

func captureOutput(_ captureOutput: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection:
 AVCaptureConnection) {

       /*ここから追加するコード。フィルタをかける処理の下に記述しましょう*/
       //マスクをサブ側に表示させる
       if let mask = currentMasks {
            guard let maskBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
                return
            }
            //UIImage -> CIImage 変換
            let context = CIContext(options: nil)
            ciChange = CIImage(image: mask)
            context.render(ciChange!, to: maskBuffer)
        
            //サブにマスクを流す
            mediaStream.send(videoFrame: VideoFrame(from: sampleBuffer))
        
        } else {
            // フィルタが選択されていないので、キャプチャした動画をそのまま配信させます。
            mediaStream.send(videoFrame: VideoFrame(from: sampleBuffer))
        }
}

4.画像選択した時の処理を追加します。フィルタ選択した時の処理の下に書くとわかりやすいかもしれないです。

 /**
     フィルタ選択ボタンを押したときの挙動を定義します。
     詳しくはMain.storyboard内の定義をご覧ください。
     */
    @IBAction func onFilterButton(_ sender: UIBarButtonItem) {
       //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    }

    /*ここから追加*/
    //画像を選択できるボタン
    @IBAction func onMaskButton(_ sender: UIBarButtonItem) {
        let alertController = UIAlertController(title: "マスクを選択", message: nil, preferredStyle: .actionSheet)
        for (name, mask) in PublisherVideoViewController.allMasks.sorted(by: { $0.0 < $1.0 }) {
            let action = UIAlertAction(title: name, style: .default) { [weak self] _ in self?.currentMasks = mask }
            alertController.addAction(action)
        }
        alertController.popoverPresentationController?.sourceView = self.view; //ipad用のviewを指定
        let noMaskAction = UIAlertAction(title: "マスクなし", style: .destructive) { [weak self] _ in self?.currentMasks = nil }
        alertController.addAction(noMaskAction)
        present(alertController, animated: true, completion: nil)
    }

 

5.Main.storyboardでUIをカスタマイズしていきます。右下のツールバーから好きなボタンを選び、下の画像のようにドラッグ&ドロップでbutton items内に入れます。そして先ほど記述した処理とsyncさせるために追加したボタンの上で右クリックしてSent ActionsonMaskButton関数を追加しましょう。 
6.SoraSDKManager.swiftに記述されているtargetURLにお手元にあるサーバのURLを入れます。

/** Sora SDKの接続先URLです。 お手元のSoraの接続先を指定してください。 */ private static let targetURL: URL = URL(string: “ここを変更する”)!

これで配信者側の編集は終わりです。

視聴者側

視聴者側はRealTimeStreamingSampleプロジェクトのコードを編集していきます。
と言いつつも、行うのは配信者側で記述した6の項目のみです。ファイル名も同じ名前で存在してるはずです。

ビルド

1.左上の三角ボタンを押してビルドします。(冒頭の方でも記述いたしましたが実機のみでしか動かないです)

2.視聴者側(RealTimeStreamingSample)では他の配信を見るボタンがあるのでタップし、視聴にあたる設定を行います。

3.配信者側(DecoStreamingSample)では配信にあたる設定を行います。視聴も配信者側も同じ設定にして次の画面に移りましょう。

4.配信者側で配信画面内に画像ボタンとして追加したボタンがあると思います。それを押して画像が画面に出てくるか確認しましょう。
視聴者側でも同じ画像が画面に出てきたら成功です。

これで説明は終わります。お疲れ様でした。