ドローンクラウドサービス開発記録 〜Part 2 リアルタイム映像配信〜
こんにちは、Tomofilesです。
現在開発中のドローンクラウドサービス"Skysign-Cloud"の新機能として、リアルタイム映像配信を実現するため、前回WebRTCの検証を行いました。
今回は、その検証の成果を、Skysign-Cloudに取り込んで、リアルタイム映像配信機能を実装したので、その経過をまとめていきます。
今回は、あまりディープにならないように気をつけるので、どうか最後まで読んでくださいね(いつも長くてすみません)
機能仕様の確認
改めて、Skysign-Cloudに実装する、リアルタイム映像配信機能の仕様を確認します。
前回の記事で目指したのは、できる限りの低遅延で、映像をクラウド経由で、複数人に配信することです。
その仕組みを取り込み、Skysign-Cloud上では以下のような機能を実装することにします。
- 各種アクションコマンドと同じように、映像の配信をボタンからON、OFFできる
- 配信された映像を、マップ上の小窓から視聴できる
- 選択しているドローンから配信されている場合のみ、映像が視聴できるようにする
まぁ、シンプルに、こんなものです。
1、2点目は、前回の検証成果をそのまま使えますが、3点目は新しく考えなければいけないことです。
いわゆるチャットルームとかテレビのチャンネルのような、論理的な境界を区切って、パラレルで存在する集合から、選択的に取り出す仕組みを構築する必要があるということです。
これが意外と厄介でした。
アーキテクチャ
基本的には、前回までに構築したSkysign-Cloudのアーキテクチャを踏襲して、新機能を実装します。
更新した概念図を以下に示します。
大きく変更になったのは、以下のトピックです。
- クラウド側にStreamingコンポーネント(Signalingサーバ)を追加
- クラウド側に新しくGCEを立て、Simpleコンポーネント(STUN/TURNサーバ)を追加
- エッジ側にGStreamerおよびWebカメラの設置
Signalingサーバ、および、STUN/TURNサーバは、前回のWebRTCの検証結果の構成をそのまま採用しています。
改善点はいくつかあるのですが、それをきれいに整理していると、時間がいくらあっても足りないので、優先度を下げます。
そのため、今回お話しするトピックは、以下の1点のみとなります。
- 映像配信のSessionを実現
いつもどおり、以下にソースコードを公開しています。
この記事執筆時点でのバージョンのリポジトリのリンクを貼っておきます。
映像配信のSessionを実現
前回の映像配信の基礎モデルは、WebRTCの仕組みを確認する上では、十分なサンプルでした。
ただ、実際にSkysign-Cloudに取り込んで、映像配信機能としての仕様を詰めているときに、一つだけ検討していないものがあることがわかりました。
それが、冒頭でも少しお話しましたが、論理的な境界を区切って、テレビのチャンネルのような、必要なものを選択して扱う仕組みです。
このSessionは、例をあげると、"ネットで生配信をする配信者と、その視聴者"をひとつのセットとして扱い、他と区別できるようにする、ということです。
配信者はサービスにとって一つではなく、並行で存在するものなので、視聴者は、配信者の集合から、一つ選択して視聴します。
つまり、"今、誰が配信しているか"がわかり、"一覧化された対象のなかから、一つ選択して視聴できる"仕組みが必要ということになります。
前回の映像配信の基礎モデルは、ある意味、物理的な境界で区切られて、このSessionを実現されているようなものです。
サーバに対して、ひとセットの配信者と視聴者しか受け入れられないため、複数のセットを扱いたい場合は、サーバを物理的に増やしていくしかありませんでした。
そのため、今回はこの問題を解決し、一つのサーバで複数のSessionを扱えるように、取り組んでみたいと思います。
アプローチとしては、まぁ簡単な話ですが、配信者と視聴者のそれぞれから、お互いに共通認識の識別情報を受け取り、サーバは識別情報が一致しているPublishとSubscribeを繋ぐことで、論理的なSessionの実現を目指します。
Sessionは永続的なものか
Skysign-Cloudの映像配信における論理的な境界は、ドローンだと考えています。
つまり、ドローンがクラウドに接続されていて、ドローンから映像が配信されている場合、ユーザーが対象のドローンを選択して、映像を視聴できるという仕組みです。
現状のSkysign-Cloudが、ドローンを選択して、各種アクションコマンドを送信する仕組みなので、この考え方にも沿っています。
さて、Sessionの実現方法なのですが、まずSessionというのが永続的なものかどうかを決める必要があります。
永続的とは、つまり、WebRTC通信を始める前に、前提としてSessionを作っておくべきものか、ということです。
非永続的であれば、WebRTC通信を始めたときから、通信が終わるときまで存在していれば良くて、一時的な存在となります。
既存のWebRTCサービスで、永続的なSession形式でサービスを展開しているものに、OpenTokというものがありました。
非永続的なものは探せませんでした。
あまり細かいところまで書いているサービスがなくて、特に日本のWebRTCサービスは全然書いてません。
(ライフサイクルって重要なんですけどね)
これを決めるに当たって重要なのが、既存のSkysign-Cloud側のドローンのライフサイクルです。
クラウドサービスにあらかじめドローンの機体データを登録しておく仕組みであれば、映像配信のSessionも永続的なものでいいと思います。
(機体データの登録・削除の契機と、映像配信のSessionの登録・削除の契機が、一緒に管理できるため)
現状のSkysign-Cloudの機体データは、クラウド側がエッジ側からテレメトリーを受信したタイミングで生成され、終了契機がありません。
つまり、ドローンの機体データに、永続的なリソースとしてのライフサイクルがないのです。
唯一ライフサイクルにできるのは、エッジがクラウドに接続中かどうか、だけとなります。
ということで、今回は非永続的なSession形式、つまり、ドローンの映像配信のPublish開始から終了まで、Sessionが存在するように、機能を実装していきます。
WebRTCのシグナリングプロトコルを拡張
前回の検証で、WebRTCの1対多の配信方式として、SFUを採用しました。
SFUは単に通信を中継するだけでなく、Peer間の間に立つことで、認証・録画・コーデック変換など、他の仕事も受け持つことができる、という役割も持ちます。
今回のSFUの役割としては、あくまで中継を行うことは変えず、Sessionの識別と割り振りの機能だけを追加して、映像配信機能を実現することにします。
シグナリングサーバに対するシグナリングを要求する際に、WebRTCの仕様ではofferとanswerの2つのメッセージを用います。
これは、以下の図のように、SDP(通信経路、メディアコーデック、証明書、などなど)の交換が、メインの目的となります。
ここに、Sessionの識別と割り振りの機能を実装するに当たり、WebRTCのメッセージを拡張して、以下の3つのメッセージを追加します。
- Registerメッセージ
- Acceptメッセージ
- Byeメッセージ
シグナリングのフローは以下のようになります。
詳しく見ていきましょう。
Registerメッセージは、シグナリング用のWebsocket通信を確立してすぐに、サーバに送信します。
サーバでは、Registerメッセージを受信すると、以下のことを行います。
映像配信側(Publish)と映像視聴側(Subscribe)で、それぞれやることが違います。
- Publish:Registerメッセージとともに送られてきた識別情報で、Sessionを作成して保持する
- Subscribe:Registerメッセージとともに送られてきた識別情報で、Sessionが存在するか確認する
Acceptメッセージは、Registerメッセージの結果を通知します。
- Publish:重要な役割は特になし(Publishがすべての軸となるため)
- Subscribe:識別情報に合致するSessionの存在有無を通知する(存在しない場合、ドローン側が映像を配信していない)
Acceptメッセージの結果から、シグナリングを行うかどうかの判断ができます。
- Publish:基本的にシグナリングは必ず行う
- Subscribe:Sessionが存在する場合は、シグナリングを行う。存在しない場合、ドローンが映像配信していない旨をユーザに伝えて、シグナリングを中断する
Byeメッセージは、配信の終了を宣言するために通知します。
- Publish:ドローンがクライアントから、映像配信の中止を指示されたときに、ドローンからByeメッセージを送信し、WebRTC通信を終了させる(WSの意図しない切断時も同様)
- Subscribe:重要な役割は特になし(Subscribeは、任意のタイミングでやめることができる)
ドローンが映像配信中止の指示を受けるルートは、アクションコマンド(飛行の指示の通信経路)で行われるため、そちら経由で受けた指示を契機に、Byeメッセージを送ることになります。
今回、この仕組みを構築するに当たり、以下のサイトを参考にしました。
ほぼ、ここに書かれている内容を、枝葉を削ってそのまま採用しています。
識別情報を何にする?
このシグナリングの仕組みに乗っける識別情報は、具体的に何が良いでしょうか?
現状の仕組みでは、ドローンから通知されるVehicleIDで良いかと思われます。
まぁ前回の記事とかでも説明してなかったので、VehicleIDとはなんぞやと思うかもしれないですが、以下のとおりです。
Skysign-Cloudの全体での、ドローンの識別はフライトコントローラー(PX4)が生成するVehicleIDが、生成元となります。
VehicleIDは、QGCでPX4を初期化したときに、すでに割り当てられています。
このVehicleIDは、Skysign-EdgeがPX4とMavlinkで接続した際に、PX4から取得され、Skysign-EdgeからSkysign-Cloudへのテレメトリーに含まれて、クラウドに通知されます。
Skysign-Cloud自体はデータベースを持たないので、Skysign-Edgeから接続されたWebsocketコネクションをVehicleIDと紐付けて、メモリ上に保持します。
クライアントからのアクションコマンドは、テレメトリーに含まれるVehicleIDをキーに、Skysign-Cloudにコマンドがリクエストされ、クラウド上でVehicleIDからWebsocketコネクションを引き当てて、コマンドをドローンに転送します。
図にすると、以下のとおりです。
Skysign-Cloud上にデータベースを保持すれば、もう少しスタイリッシュになったりしますかね。この辺は要改善かもしれません。
リアルタイム映像配信機能の使い方
それでは、今回実装したリアルタイム映像配信機能の使い方を紹介します。
画面は以下の通り、ボタンがいくつか追加になっています。
ドローンを選択すると、ボタンが活性になります。
STREAM ONボタンを押下すると、ドローン側から映像配信が開始します。
ドローンを選択したまま、VIDEOボタンを押下すると、映像視聴ウィンドウが表示されます。
ドローンが映像を配信していれば、すぐに映像のストリーミングが始まります。
(クライアントの画面をカメラで撮影しているので、少し見づらいですが)
ドローン側が映像を配信していない場合、以下のようにメッセージが表示されます。
STREAM OFFボタンを押下することで、ドローン側からの映像配信を終了することができます。
映像視聴ウィンドウは、今回以下のライブラリを使用しました。
ドラッグ&ドロップで、位置を移動したり、ウィンドウの大きさを変えたりできます。
こんな感じです。
まとめ
リアルタイム映像配信の実装が、これで完了しました。
改善点はたくさんあって、画質の向上はもちろんですが、エラーハンドリングや、ロジックが無理矢理な箇所もあったりして、現状、結構アンバランスな状態ではあります。
どこかで直したいとは思いますが、いつになりますかね。
今回、2回に渡ってWebRTCについて色々調査、検証、実験をしてみました。
結局ここまでやったことは、実際のサービスを開発する際は、商用のWebRTCサービスを使うことになると思うので自分ですべてを賄うことはないかと思いますが、大変勉強になりました。
何より、マルチメディアプログラミングの世界に片足を突っ込めて、自分のなかで全くの未知の技術ではなくなったので、かなり良い経験だったなと思います。
さて、次の展開は色々決めてますが、新機能開発はいったん止めて、基盤、運用のあたりを取り組みますかね。
例えば、コンテナ化、デプロイ自動化、ラズパイのOTAアップデート化、などなど…。
やることは、まだまだあります。
その前に、以下の記事の最後にお話しした、u-bootのシリアルコンソールの無効化の調査と検証もして、結果が出てるので、その件をまとめますかね。
それでは。
◆Prevoius Part
tomofiles.hatenablog.com