Tomofiles Note

ドローンとインターネット、そして人との関係を考えるソフトウェアエンジニアのアウトプットブログ

ドローンの映像配信にWebRTCの利用を考えてみる

こんにちは。Tomofilesです。

前回、開発中のSkysign-Cloudというドローンクラウドサービスの紹介をしました。

tomofiles.hatenablog.com

その中で、次に実装したい機能として、映像配信機能を挙げました。
映像配信機能を実装するにあたり、現在、色々と調査・検証を進めているところですが、いったんの見通しが立ったので、ここでまとめておくことにします。

ドローンの飛行中の映像がリアルタイムでクラウド上で見られるようになれば、
遠隔地を飛ぶドローンによるリアルタイム監視業務をメインに、点検、農業、物流等の業務への付加価値としてや、一般パイロットのミッションフライトの補助機能としてなど、
大きな価値を生むテクノロジーとなる可能性があります。

現時点でも、映像配信機能を提供している既存サービスも存在していますが、私もそれらに同調して実装してみようと思います。

SENSYN DC|株式会社センシンロボティクス
Drone Video Streaming & Fleet Management Solution - FlytNow
Unleash Live - Emergency services

なお、リアルタイム性というのが要件次第でどこまで求められるかが変わってくると思いますが、
今回は、ある要件を満たすために、なるべく低遅延で映像をクラウド経由で複数人で視聴できるようにしたい、というニーズが発生したことを想定して、色々考えていきたいと思います。
(まぁ、つまり、理由は特にないということですね 笑)

それでは、いきましょう。
ちなみに、今回も1万字を越えています。

映像配信方式について考える

映像配信といっても、実現方式は色々選択肢があります。
少し調べてみると、映像配信(というか、動画配信)といっても3種類のパターンがあるそうです。

  • 静的動画コンテンツ配信
  • ライブ動画配信
  • リアルタイム動画配信

静的動画コンテンツ配信はそのままですね。
ライブ動画配信は生放送系サービスです。ニコ生とかSHOWROOMとか、1対多で、基本的に1→多の一方通行の配信方式のことを言います。
リアルタイム動画配信は、テレビ会議システムやビデオチャットなどのサービスですね。Skypeとかがそうなんでしょうか。

この辺の詳しい話は、以下のサイトがわかりやすいです。

リアルタイム動画配信コトハジメ · GitHub

さて、ドローンの映像配信を考えたときにも、これらの3パターンは以下のように利用シーンが考えられます。

静的動画コンテンツ配信

イメージとしては、ミッションフライト中の撮影動作と、フライト後の映像配信が厳密に分かれている場合ですね。
今すぐに映像データが必要ない場合などは、無理にリアルタイム配信を目指す必要はありません。ドローンが帰還してから、映像データをクラウドにアップロードすれば済みます。

ライブ動画配信

ライブ動画配信は、ある程度の遅延が許容される場合に採用されます。
例えば、ネット動画の生放送配信は、ある程度のリアルタイム性さえ実現できていれば、30秒遅れていても視聴者は気になりません。
(あまりに遅いとSNSに先を越されて、視聴者が違和感を感じてしまうかもしれないですが)
遠隔ドローン監視の場合、正直このレベルで用が足りる可能性はあります。
農業におけるほ場の観測データなどは、ある程度リアルタイムで視聴できればいいので、ライブ動画配信方式で十分でしょう。
(逆にこれは、静的動画コンテンツ配信方式だと、やり直しの場合最初からとなってしまうので、経過を見ながらフライトさせたいですね)

リアルタイム動画配信

リアルタイム動画配信方式は、ドローンとコミュニケーションを取りたい場合などは有効です。
例えば、配信映像を見ながらフライトの指示を出したい(ホーム帰還コマンド、撮影コマンドなど)場合などは、あまりに遅延があると難しいですよね。
それ以外にも、例えば災害時に巡回飛行しながら、逃げ遅れた市民にドローン搭載のスピーカーから、遠隔で声を掛けたい場合などは、映像と時間的にリンクしていないと実現できません。
(現在、世界中で新型肺炎が流行していますが、中心地の中国では、ドローンで市民にマスク着用を警告してまわってる、なんて噂も出てきてますね)

この記事の導入でざっくりと要件を示したのは、映像配信方式として、これらの3パターンが挙げられるため、どれを選択すべきかという根拠が必要なためでした。
なので、今回は、なるべくリアルタイムに近づけることで、ドローンとコミュニケーションをするシーンを想定して、実現を目指してみたいと思います。

マルチメディアの通信プロトコル

さて、ウェブを前提として、現在どのような方式で、映像データ(マルチメディアデータ)が配信されているでしょうか。
ストリーミング配信に絞って少し調べた限りでは、以下の選択肢があるようです。

そして、配信という枠とは少し違う位置付けで、

  • WebRTC

となるようです。

ウェブを前提とするなら、HTTPベースのHLS、MPEG-DASHが選択肢として挙がるでしょう。
動画をHTTPで扱えるコンテンツとして配信できるため、CDNと連携することで広域に対して大規模なストリーミング配信が実現できます。
ただ、HTTPなのでPULL型の仕組みとなり、遅延はある程度発生するようです。
1〜2秒までは抑えることができるそうですが、それなりにチューニングを施さないといけないのかもしれません。

前述の3つのパターンのうち、静的動画コンテンツ配信と、ライブ動画配信であれば、このプロトコルが採用となるでしょう。

RTMPAdobe Flashで使用されていた通信プロトコルで、HTTPではなくTCPの上に構築されています。
PUSH型なので、仕組み的には遅延が発生しにくいプロトコルのようですが、ウェブベースではないのでブラウザで直接扱うことはできず、Flashを導入するか、クライアントアプリケーションが必要となります。
Adobe Flashが今年(2020年)に終了宣言を出されているので、このプロトコルも徐々に勢いは減っていくのでしょうか?
以前、インターネットラジオ"Radiko"でRTMPの音声データをストリーミングしたことありますが、ffmpegなどのツールと組み合わせても、とても扱いやすくて良かった印象があります。

同じく前述の3つのパターンでは、RTMPはリアルタイム動画配信の場合に、相性がいいでしょう。

今回、私は、これらの通信プロトコルの採用も検討しましたが、結局WebRTCを使用してみることにしました。
Internet of Drones(IoD)の私の中のイメージは、ウェブベースでドローンとコミュニケーションして双方向にアプローチできる必要がある、というものです。
できるところまでドローンとリアルタイムで通信する方法を目指しつつ、必要に応じて不要な枝葉を落としていけばいいかなと考えています。

WebRTCとは

WebRTCは、厳密には通信プロトコルではありません。
まったく新しい技術というより、既存の標準技術を一つにまとめて、ブラウザから手軽にリアルタイム通信ができるように整備された、いわばセットものです。
難しい話も多く、全体像を理解しにくい技術ですが、以下のサイトのシリーズ記事が分かりやすかったので、貼っておきます。
2016年の記事ですが、現在の仕様と大きくズレるところはないと思います。

html5experts.jp

あとは、以下のサイトとかも、全体感がまとまってて、わかりやすいですね。

WebRTC コトハジメ · GitHub

WebRTCはPeer to Peer(P2P)を実現する技術なのですが、P2Pを実現するには多くの障害があり、
それを様々な方法で、どうにか解決しようというのが、この技術のキモです。
障害とは、例えば、

  • 接続したいPeerが、別ホスト内にいる場合、宛先(IPアドレス)が認識できない
  • 接続したいPeerが、どのようなメディアコーデックに対応しているのか、わからない

さらに、

  • 接続先のホスト内がFirewall的に働くネットワーク構成にしていると、外から見てホスト内が隠蔽されているため、直接接続することができない

という感じです。

宛先(IPアドレス)が分かれば、接続先Peerとの通信経路を探すことができます。
通信経路を確立するために必要なIPアドレスは、

となります。

これらの情報を集めて通信経路を探すために、「STUNサーバ」と「TURNサーバ」という2つのサーバに、アクセスして情報を集めます。
STUNサーバは、自分のグローバルIPアドレスを取得できます。相手も同じく、自分のグローバルIPアドレスをSTUNサーバに問い合わせます。
ここで集めた接続情報から、実際に通信経路を探せなかった場合に、TURNサーバ経由でアクセスすることで、通信経路の候補を探します。例のFirewall的なネットワーク構成とかですね。
TURNサーバは、こういうときの、最終手段の通信経路を提供するサーバということです。
このSTUN、TURNサーバで行っていることをICE(Interactive Connectivity Establishment)といい、集めた通信経路候補を、ICE Candidateといいます。

そして、この情報を「シグナリングサーバ」というサーバで、P2P接続をする前に、相手と交換して、接続の準備を行います。
この、P2P通信を行うために交換する情報を、SDP(Session Description Protocol)といい、

  • 相手との通信経路の合意
  • メディアコーデックの合意

などを含みます。このSDPの交換をシグナリングといいます。
これ以外にも証明書情報の交換なども含むそうです。

この一連の手続きを行うことで、やっとP2P通信を開始することができるのです。

超ザックリとした解説となりましたが、図付きのわかりやすい説明をしているQiitaの記事があったので、貼っておきます。

qiita.com

あとは、以下も手続きがわかりやすいです。

https://blog.wnotes.net/posts/webrtc-beginning/

ドローンの映像配信に適用する

今回、WebRTCをドローンの映像配信に適用するにあたり、以下のことが実現できるか検証しました。

  • 単一ドローンから、複数クライアントに対する映像配信
  • ドローンのコンパニオンコンピュータ上で動く、ブラウザレスなWebRTCクライアントの構築
  • ドローンとクライアントが別のホストに属する場合の、NATを越えたPeer間の映像配信

これらのトピックから、Skysign-Cloudへのリアルタイム映像配信機能の実装に向けて、以下の概念図のような基礎モデルを構築し、実現性の確認を行いました。

f:id:Tomofiles:20200208215649j:plain

ソースコードは例のごとく、Githubに公開しています。

github.com

今回の基礎モデル構築に向けて、以下のGo言語のOSSのWebRTCライブラリを使用しました。
この基礎モデルは、以下のOSSサンプルソースやサンプルアプリケーションを組み合わせて作成しています。

github.com

早速、動かしながら、検証結果を見ていきましょう。

クラウド側の構築

まずは、クラウド側の構築です。
クラウド側で実現したいことは、以下のトピックです。

  • STUN/TURNサーバの構築
  • シグナリングサーバの構築
  • 1対多の映像配信(SFU)の実現
STUN/TURNサーバの構築

STUN/TURNサーバは、今回pion/turnのサンプルに含まれている、turn-server-simpleを使用します。

github.com

pion/turn自体は、APIライブラリなのですが、それを簡単に試すことができるサンプルとして、simple版を提供しています。
これを使えば、認証情報などは固定となりますが(起動時に引数で渡す)、挙動の確認などが行えます。
turnとしか表記されていないですが、STUNの機能も持っていますので、これ一つで大丈夫です。

このturn-server-simpleを、クラウド上で実行してみましょう。
クラウドサービスは、いつもどおり、Google Cloud Platform(GCP)を使用します。
GCEインスタンスを一つ作成して、諸々の初期設定を行ってください。
ここで、作成したGCEのパブリックIPアドレスを確認しておきます。

次に、turn-server-simpleのバイナリを取得します。
以下から、「turn_2.0.1_Linux_x86_64.tar.gz」をダウンロードして解凍します。この記事執筆時は、最新バージョンが2.0.1でした。

Releases · pion/turn · GitHub

あとは、実行するだけです。

$ ./turn-server-simple -public-ip <GCEのパブリックIPアドレス> -users <任意のユーザ名>=<任意のパスワード>
# ユーザ名とパスワードを=で繋ぎます

これだけです。
ここで設定したユーザ名とパスワード、および、GCEのパブリックIPアドレスは、あとで使うので、控えておいてください。

シグナリングサーバの構築

シグナリングサーバは、私が作成したプロジェクトのものを使用します。
Go言語で書かれているので、Goのコンパイラを用意してください。

シグナリングサーバ用に、もう一台GCEのインスタンスを新しく作成します。
そして、以下の手順で、ビルド・実行します。

# プロジェクトを取得
$ git clone git@github.com:Tomofiles/webrtc_base_model.git

# ビルド
$ cd webrtc_base_model/webrtc-cloud-sfu
$ go build

# 実行(引数に指定するのは、すべてSTUN/TURNサーバのもの)
$ ./webrtc-cloud-sfu -s <パブリックIPアドレス>:3478 -u <ユーザ名>=<パスワード>

なお、シグナリングサーバは、Websocketでクライアントから接続されますが、セキュア通信でないといけないので、証明書の登録をしないといけません。
さきほどシグナリングの説明のときに証明書の話が少し出てきましたが、セキュア通信であることがWebRTCの仕様に含まれているのです。

GCPでセキュア通信をするには、以下の作業が必要です。

  1. ドメインの取得
  2. 証明書の取得
  3. L7ロードバランサの設定
  4. DNSに登録

あまり細かくは書かないですが、私は以下のように、これらを用意しました。

1. ドメインの取得
ドメインGoogle Domainsで購入しました。
今回用ではなく、そもそも私個人のドメインを取得しておきたいと思ったので、過去に買ったやつを今回も利用します。

2. 証明書の取得
証明書はGCPのロードバランサを設定する際に、フロントエンドの設定の項目で作成できます。
特に独自の証明書等の指定がなければ、Google管理の証明書を数クリックで作成できます。

3. L7ロードバランサの設定
GCPの負荷分散で、HTTP(L7)ロードバランサを作成します。
バックエンドをシグナリングサーバのインスタンスに指定して、フロントエンドをHTTPSで設定して、証明書を作成し、登録します。

4. DNSに登録
GCPのCloud DNSに、取得したドメインでゾーンを作成します。
適宜サブドメインを切るなどして、レコードセットを追加してください。このとき、上で作成したロードバランサのIPアドレスを指定します。
最後に、ゾーンのネームサーバを、DNSに設定します。
GCPのCloud DNSから「レジストラの設定」で確認できるので、これをGoogle Domainsのネームサーバの欄に入力して保存します。
DNSに反映されるまで、数時間程度を見ておきましょう。

URLにアクセスすると、以下のような画面が表示されます。
このシグナリングサーバは、pionの「sfu-ws」というサンプルアプリケーションがベースになっているので、このサンプルと同じ機能が試せます。
試しに、複数端末で、PublishとSubscribeをして、WebRTCの映像配信を試してみてください。

f:id:Tomofiles:20200208235755p:plain

1対多の映像配信(SFU)の実現

1機のドローンのカメラから、複数のクライアントに映像を配信するためには、シンプルなWebRTCだと問題があります。
WebRTCはP2Pでメディアデータをやりとりするので、Peerが複数になってくると、配信側に負荷がかかるようになります。
(受信側は、配信されてきたものを受け取るだけだが、配信側は、接続しているすべてのPeerに配信しないといけないため)

そこで、WebRTCの構成パターンとして、SFU(Selective Forwarding Unit)というモデルを利用することで、この問題を解決できます。
SFUは厳密にはP2Pではなく、サーバ/クライアント構成で、擬似的なP2Pを実現します。

イメージとしては、通常のP2Pがクライアント⇔クライアントだとしたら、SFUは間に中継サーバを挟んで、クライアント⇔SFUサーバ⇔クライアントという感じに構成されます。
今回の基礎モデルのイメージ図の中の、緑色の線がP2P接続(ICE)を表すのですが、SFUサーバからも伸びていますね。これは、このSFUを意味しています。

SFUに関しては、以下のサイトで詳しく説明されています。
(ネット上のWebRTC関連に記事は、ほとんど時雨堂さんの書かれたものですね)

WebRTC SFU コトハジメ · GitHub

前述したとおり、今回のサンプルはpionの「sfu-ws」というサンプルアプリケーションがベースになっています。
もともと荒削りなサンプルで、一回SFUの接続を行ったら、プロセスを再起動しないといけない仕様ですが、今回は理論のお試しなので、そのまま使用しています。

エッジ側の構築

続いて、エッジ側の構築を行っていきます。

クラウド側で構築したWebRTC SFUのサンプルアプリケーションは、ブラウザ同士のP2P接続であれば、これだけで動作を試すことができます。
エッジ側に構築するのは、ブラウザの代わりに、ドローン上のコンパニオンコンピュータからP2P接続を行うモジュールです。
配信側のPeerに、ドローンがなりすますイメージですね。

エッジ側のトピックは、以下のとおりです。

  • カメラデバイスにアクセス
  • WebRTCクライアントの構築
カメラデバイスにアクセス

WebRTCの標準仕様には、カメラデバイスのアクセスに関する決まりごとも含まれているのですが、あくまでブラウザから利用する場合を想定したものです。
ブラウザレスでP2P接続を行う場合、何らかの方法でカメラデバイスにアクセスし、メディアデータのストリームを取得して、WebRTCのメディアチャネルに流し込む必要があります。

pionのサンプルソースの中には、GStreamerでカメラデバイスに接続して、メディアストリームを取得するサンプルが提供されています。
今回は、その仕組みを応用して、エッジ側のアプリケーションを作成しました。

github.com

ただ、今回私はエッジ側では、以下のことをこだわっていたのですが、このサンプルをそのまま使うには、少し都合が悪いことがいくつかありました。

  • ピュアGoで実装する
  • ビルド環境でクロスコンパイルして、RPiに持ち込む

サンプルソースでは、GStreamerをcgoライブラリでインポートして利用していました。
これが非常に厄介で、クロスコンパイルでARM7向けにビルドしようとすると、ビルドエラーとなってうまくいかないのです。
色々調べて、結局解決は諦めたのですが、

  • cgoのクロスコンパイラをダウンロードして、参照させる
  • ビルド環境にRPiのSDカードをマウントして、pkg-configを参照させる

などがネット検索すると出てきて、少し敷居が高い+環境依存し始めていて、途中でストップしました。

私は、Go言語の良いところが、環境に依存せず、クロスコンパイルすれば、どこでも動くものが作れることだと思っています。
成果物がシングルバイナリなので、ランタイムも不要で、環境を汚しません。この特徴を考えたとき、無理に一つの実行体に固める必要はないと判断しました。

ということで、諸々を考慮して、GStreamerは直接実行して、カメラデバイスから取得したメディアストリームをRTPで、RPi内のポートに公開することにしました。
以下のコマンドをRPiで実行し、GStreamerを起動しましょう。
(今回、コマンドの細かい意味とかまで調べられていないので、もしかしたら間違ったエレメントとかプロパティが含まれているかもしれませんが、ご了承ください)

# audioストリームの配信
$ gst-launch-1.0 audiotestsrc ! opusenc ! rtpopuspay pt=111 ! udpsink host=127.0.0.1 port=8889 &

# videoストリームの配信
$ gst-launch-1.0 autovideosrc ! video/x-raw, width=320, height=240 ! videoconvert ! queue ! vp8enc error-resilient=partitions keyframe-max-dist=10 auto-alt-ref=true cpu-used=5 deadline=1 ! rtpvp8pay ! udpsink host=127.0.0.1 port=8888 &

上の概念図の数字が書いた青い箱はポート番号を示していますが、videoストリームは8888ポート、audioストリームは8889ポートに公開するようにします。
なお、今回のドローンの映像配信では、audioはなくても良いのですが、WebRTCとしては揃っていないといけないので(videoだけ、とかできるのでしょうか?)、audiotestsrcを用いて空のストリームを作成しています。

カメラデバイスとRPi4は、以下の写真の様にUSBで接続しています。

f:id:Tomofiles:20200209131945j:plain

WebRTCクライアントの構築

最後です。
エッジ上に載せるWebRTCクライアントアプリケーションを、コンパニオンコンピュータ(RPi4)上に構築しましょう。
先程ダウンロードした私のサンプル「webrtc_base_model」から、webrtc-edge-webcamを使用します。

# ビルド(クロスコンパイル)
$ cd webrtc_base_model/webrtc-edge-webcam
$ env GOOS=linux GOARCH=arm GOARM=7 go build

# ここでビルドされた成果物「webrtc-edge-webcam」をRPi4に転送します

# 実行(引数の-sと-uに指定するのは、すべてSTUN/TURNサーバのもの)
./webrtc-edge-webcam -w <シグナリングサーバのURL> -s <パブリックIPアドレス>:3478 -u <ユーザ名>=<パスワード>

このwebrtc-edge-webcamを実行するタイミングは、webrtc-cloud-sfuを起動してから一番最初でないといけません。
webrtc-cloud-sfuがPublisherから受け入れる仕様となっているためです。
なので、以下の順番で実行していくと、映像配信が試せるかと思います。

  1. turn-server-simpleを起動
  2. webrtc-cloud-sfuを起動
  3. webrtc-edge-webcamを起動
  4. クライアントがブラウザからアクセスしてSubscribeボタン押下

これで、配信されてきた映像が視聴できます。
以降、複数のクライアントがSubscribeしても、配信されている映像を、複数同時に見ることができます。

まとめ

今回も、とても長文となってしまいましたが、ドローンの映像配信の基礎モデルについて、考察してきました。
ここまで、ドローンの映像配信にWebRTCを利用できないか検証してきましたが、実際に飛行させてないので、有効性の判断などはできていません。
ただ、RPi上にWebRTCの映像配信モジュールの構築ができて、SFUによる複数クライアントの視聴が確認できたので、実現性はあるのではと考えています。

あとは、Skysign-CloudのEdgeに適用したときの、リソース使用量とかネットワーク帯域の確認とか、RPi4でどこまで実用に耐えられるのか、確認してみたいですね。

一応、映像の遅延は、体感的に1秒未満という感じです。
ただし、GStreamerにより映像のサイズが320×240に縮小されているので、この辺りを調整していくと、もっと遅延したりする可能性はあります。
チューニングでどこまでできるのか、これから色々試してみたいですね。

まずは、Skysign-Cloudへの映像配信機能の実装を、目指してみましょうか。
ここまで読んでくださって、ありがとうございます。