前書き
とあるハッカソンで使った技術で、実装するにあたり壁にぶつかりました。
おそらく前例がないので、備忘録として残します。
技術に関して
1. SkyWayとは
ほとんど画像に記載がありますが、Webでリアルタイムコミュニケーションを実現する標準技術「WebRTC(Web Real Time Communication)」を簡単にアプリに導入できるSDK&APIです。(公式引用)
2. Firebase MLとは
ML Kit は、Google の機械学習の機能を Android アプリや iOS アプリとして提供するモバイル SDK です。(公式引用)
今回は映像部分の顔判定をになっていますが、この部分の中身についての実装は話しません。
あくまでもSkyWayとFirebaseを繋ぐ部分のみになります。
(中身の参考資料「Firebase MLKitを使って動画の顔認識をさせる」)
3. 構成図
アプリ間で動画のリアルタイム通信を行いながら、その映像をFirebase MLで顔認識させるということ行います。
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
を持つレイヤー層を探り当てました。
探り当てた部分をコードで取り出す場合は、以下のようになります。
// レンダリング画面
@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
型の値を手に入れることができるようになります。
output?.setSampleBufferDelegate(self, queue: /* Queue */)
これでFirebase ML
への接続も可能となりました。
あとがき
複数のストリームで受け取って加工する、など色々試した結果、この方法が最適なかつ実現可能な方法でした。
少しマニアックなネタだったかもしれませんが、誰かの参考になれば嬉しい所存です。
文献
SkyWay
の実装周りは解説しなかったので、以下を参考にすると良いでしょう。
Firebase周りはこちらをご覧ください