13
6

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-12-06

前書き

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

技術に関して

1. SkyWayとは

スクリーンショット 2020-12-07 3.46.42.png

ほとんど画像に記載がありますが、Webでリアルタイムコミュニケーションを実現する標準技術「WebRTC(Web Real Time Communication)」を簡単にアプリに導入できるSDK&APIです。(公式引用

2. Firebase MLとは

maxresdefault.jpg

ML Kit は、Google の機械学習の機能を Android アプリや iOS アプリとして提供するモバイル SDK です。(公式引用)

今回は映像部分の顔判定をになっていますが、この部分の中身についての実装は話しません。
あくまでもSkyWayとFirebaseを繋ぐ部分のみになります。
(中身の参考資料「Firebase MLKitを使って動画の顔認識をさせる」)

3. 構成図

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

スクリーンショット 2020-12-07 3.44.07.png

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

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

になります。

実装

1.前提

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

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

Example

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でカメラアプリを作成する」)

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

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

Example

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

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

// レンダリング画面
@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にアクセスできるので、outputsetSampleBufferDelegateセットしてあげることで、CMSampleBuffer型の値を手に入れることができるようになります。

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

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

あとがき

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

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

文献

SkyWayの実装周りは解説しなかったので、以下を参考にすると良いでしょう。
- SkyWay - アプリやWebサービスに、ビデオ・音声通話をかんたん導入
- iOSのCallKitフレームワークとSkyWay
- SKYWAY SDKを使ったIOS版通話アプリ開発

Firebase周りはこちらをご覧ください
- Firebase MLKitを使って動画の顔認識をさせる

13
6
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
13
6