[Swift] SkyWayを使ったWebRTCの映像をFirebase MLに接続する


# 前書き

とある**ハッカソンで使った技術**で、実装するにあたり壁にぶつかりました。
おそらく前例がないので、備忘録として残します。

# 技術に関して

## 1. SkyWayとは

![スクリーンショット 2020-12-07 3.46.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175242/2755cd21-5e0d-a921-619c-2849f5911364.png)

ほとんど画像に記載がありますが、Webでリアルタイムコミュニケーションを実現する標準技術「**WebRTC（Web Real Time Communication）**」を簡単にアプリに導入できるSDK&APIです。（[公式引用](https://webrtc.ecl.ntt.com/skyway/overview.html)）

## 2. Firebase MLとは

![maxresdefault.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175242/98cdf711-80ef-69a5-53b6-fd2bc04f6b47.jpeg)

ML Kit は、Google の機械学習の機能を Android アプリや iOS アプリとして提供するモバイル SDK です。([公式引用](https://firebase.google.com/docs/ml-kit?hl=ja))

今回は映像部分の顔判定をになっていますが、この部分の中身についての実装は話しません。
あくまでも**SkyWayとFirebaseを繋ぐ部分のみ**になります。
（中身の参考資料「[Firebase MLKitを使って動画の顔認識をさせる](https://qiita.com/hcrane/items/337c94ff506f5a10ede8)」）

## 3. 構成図

アプリ間で**動画のリアルタイム通信**を行いながら、その**映像をFirebase MLで顔認識させる**ということ行います。

![スクリーンショット 2020-12-07 3.44.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175242/da3ddf92-e764-cb94-5d54-98285211ff82.png)

SkyWayが２つ導入されていますが、

```
- 上部はSkyWayサーバー
- 右下アプリ内はSkyWayのSDK
```

になります。

# 実装

## 1.前提

``Firebase ML``側でデータを分析するためには、動画を**CMSampleBuffer**という型で受け取る必要があります。

幸いなことにiOSでは映像から``CMSampleBuffer``型を受け取るために、
「**AVCaptureVideoDataOutputSampleBufferDelegate**」
というものが用意されています。

### Example

```.swift
final class ExampleVideoManager {

    private var videoOutput: AVCaptureVideoDataOutput!
    private let queue = DispatchQueue(label: "videoOutput", attributes: .concurrent)

    init() {
        self.videoOutput = AVCaptureVideoDataOutput()
        self.videoOutput.setSampleBufferDelegate(self, queue: queue) // delegateのセット
    }
}

extension CaptureVideoManager: AVCaptureVideoDataOutputSampleBufferDelegate {

    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        // CMSampleBuffer型の値が受け取れる場所。Firebase MLへ接続。
    }
}
```

## 2.問題点

通常、カメラや動画を実装するためには``AVKit``をimportとして色々と実装していく必要があります。
（実装説明は割愛するので参考として「[Swiftでカメラアプリを作成する](https://qiita.com/t_okkan/items/f2ba9b7009b49fc2e30a)」）

前提のように自前で``AVCaptureVideoDataOutput``を生成して``Firebase ML``接続できれば良いですが、``SkyWay``の場合は指定された実装方法で行う必要があるため、わからないようになっています。

具体的には、``SKWVideo``という指定されたUI型に、勝手にレンダリングされるようになっていることが問題になります。

### Example

```.swift
var localStream: SKWMediaStream?
@IBOutlet weak var streamView: SKWVideo! // レンダリングする画面

func setup() {
     /* ~略~ */

    let constraints = SKWMediaConstraints()
    localStream = SKWNavigator.getUserMedia(constraints)
    localStream?.addVideoRenderer(self.streamView, track: 0) // レンダリングの指定

     /* ~略~ */
}       
```

## 3.解決法

中で隠蔽化されていますが、必ず``AVCaptureVideoDataOutput``は実装されているので、それを``SKWVideo ``の中から自力で探し当てました。

具体的には、レンダリングされる画面のヒエラルキーを**ちまちまとデバッグ**して``AVCaptureVideoDataOutput``を持つレイヤー層を探り当てました。

イメージ図
![0v2Bh.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175242/77cff63f-ea11-2aa0-ffef-0a64f33f1d43.png)

探り当てた部分をコードで取り出す場合は、以下のようになります。

```.swift 
// レンダリング画面
@IBOutlet weak var streamView: SKWVideo!

// レンダリングしている画面の階層を深掘りし、中から`layer`層を取り出す
var videoLayer: AVCaptureVideoPreviewLayer? {
    return streamView.subviews.first?.layer as? AVCaptureVideoPreviewLayer
}

// 上記の`layer`層から変換に必要な`AVCaptureVideoDataOutput`を取り出す
var output: AVCaptureVideoDataOutput? {
    return videoLayer?.session?.outputs.first as? AVCaptureVideoDataOutput
}
```

上記から``AVCaptureVideoDataOutput``にアクセスできるので、``output``に``setSampleBufferDelegate``セットしてあげることで、``CMSampleBuffer``型の値を手に入れることができるようになります。

```.swift
output?.setSampleBufferDelegate(self, queue: /* Queue */)
```

これで``Firebase ML``への接続も可能となりました。

# あとがき

複数のストリームで受け取って加工する、など色々試した結果、この方法が最適なかつ実現可能な方法でした。

少しマニアックなネタだったかもしれませんが、誰かの参考になれば嬉しい所存です。

# 文献
``SkyWay``の実装周りは解説しなかったので、以下を参考にすると良いでしょう。
- [SkyWay - アプリやWebサービスに、ビデオ・音声通話をかんたん導入](https://webrtc.ecl.ntt.com/)
- [iOSのCallKitフレームワークとSkyWay](https://speakerdeck.com/yorifuji/callkit-skyway)
- [SKYWAY SDKを使ったIOS版通話アプリ開発](https://briswell-vn.com/ja/news/videochat-skyway/)

Firebase周りはこちらをご覧ください
- [Firebase MLKitを使って動画の顔認識をさせる](https://qiita.com/hcrane/items/337c94ff506f5a10ede8)