libwebrtc on Windowsに挑戦してみた

はじめに

先日、インターンで「libwebrtcをWindows上でビルドし、サンプルプログラムを作成する」ということに挑戦しました。本記事では、作成したプログラムの説明と、実行方法を書いておこうと思います。
sublimer.hatenablog.com

実行環境

Windowsは10 Homeや10 Proでも問題なく開発・実行はできるはずです。

前提

libwebrtcのビルドまでできているものとします。libwebrtcをWindowsでビルドする方法は、公式ドキュメントやQiita記事などがありますので、それを参考にすれば問題なくできるかと思います。また、株式会社時雨堂様が公開している、Windows 向け WebRTC ライブラリ用ビルドツールを使用するという手もあります。(Windows 10 HomeではDocker for Windowsが実行できないため、使用できません)
なお、本記事作成時点ではVisual Studio 2019がリリースされているため、Visual Studio 2017 Communityは無料でダウンロードすることはできなくなっています。少し前に作成された記事などではGYP_MSVS_VERSIONという環境変数2017をセットしている場合がありますが、今回は2019をセットし、問題なくビルドが行われることを確認しました。

実装

今回作成したプログラムは、こちらで公開しています。
github.com
通信の流れはこんな感じで、複雑なことをしているわけではありません。(libwebrtcのHello Worldといったところでしょうか)
f:id:sublimer:20190923170742p:plain
今回実装した際のはまったポイントですが、なぜかICE CandidateにTCPの経路しか出てこないという現象が起きました。原因ですが、WindowsUDP Socketを使用する際は以下のような初期化処理をしてあげる必要があるようです。

#ifdef WEBRTC_WIN
#include <rtc_base/win32_socket_init.h>
#endif

//略
int main(){
    rtc::WinsockInitializer winsock_initializer;

rtc::WinsockInitializerのコンストラクタではWSAStartup()を、デストラクタではWSACleanup()を呼んでいます。このようにコンストラクタで初期化処理、デストラクタで終了処理を行い、インスタンスを生成するだけで必要な処理が行われるようにすることを「RAII」と呼びます。(インターンで教えていただきました)
今回作成したプログラムのシグナリング方法は手動で、Vanilla ICEで行います。
また、動作確認・比較用にこちらのWebアプリケーションを作成しました。
github.com
呼びかける側(caller)と呼びかけられる側(callee)に分かれていて、呼びかけられる側は、受信したMediaStreamをそのまま送り返すようになっています。今回作成したnative-webrtc-loopbackはwebrtc-caller-callee/src/callee.jsをC++で書き直したような感じになっており、処理の流れもほぼ同じになっています。ですので、このWebアプリケーションで疎通確認をしたり、生成されたSDPの比較ができるようになっています。
このWebアプリケーションは単体でも使えるように公開しています。
sublimer.me

また、開発のtipsとして、「Windows上での開発に慣れていない」というときにはVisual Studio CodeのLiveShare拡張機能が便利です。コードだけでなくコンソールも共有できるので、開発、ビルド、動作確認がMacLinuxからもできます。

ビルド・実行

libwebrtcのビルドにはninjaというビルドシステムが使われています。本来であれば、Visual Studioの設定をして、デバッグ・ビルドできるようにすべきなのかもしれませんが、今回は手っ取り早く動かしたかったので、examplesフォルダにファイルを追加し、依存関係を設定ファイルに追記することで、libwebrtcと一緒にビルドできるようにしました。libwebrtcのビルドにはとても時間がかかりますが、差分ビルドの仕組みがあるので、一度ビルドしてしまえば、あまり時間をかけずに自作のプログラムのビルドができます。
設定などは以下の手順で行いました。

  1. src/examples/以下にloopbackという名前のディレクトリを作成
  2. loopback/の中に、main.cppをコピー
  3. src/examples/BUILD.gnに以下のように追記
(49行目)  ":loopback",

(710~724行目)
 rtc_executable("loopback") {
    testonly = true
    sources = [
      "loopback/main.cpp",
    ]
    deps = [
      "../api/audio_codecs:builtin_audio_decoder_factory",
      "../api/audio_codecs:builtin_audio_encoder_factory",
      "../api/video_codecs:builtin_video_decoder_factory",
      "../api/video_codecs:builtin_video_encoder_factory",
      "../rtc_base",
      "../api:create_peerconnection_factory",
      "../api:libjingle_peerconnection_api",
    ]
  }

行番号は、今後の更新で変わる可能性があります。
上記の内容を追記後、ninja -C out/Defaultでビルドが実行され、exeファイルが生成されます。

生成された実行ファイルは標準エラー出力にたくさんメッセージが出されることがありますが、loopback.exe 2>$nullとすることで、標準エラー出力の内容を捨てることができます。
実行後はOffer SDP待ち受け状態となるので、ブラウザで生成されたSDPをコピーし、コンソールに貼り付けてください。
その後、Answer SDPが出力されるので、それをコピーしブラウザ側に入力すればシグナリング完了です。SDPをコピーする際は、最後の改行まで含めてコピーしてください。
シグナリングが完了すると、カメラの映像がループバックしてそのまま返されてきます。わずかにラグがあるので、ループしていることが分かるはずです。
なお、今回作成したプログラムはlocalhost(同一マシン)とLAN内での通信のみ動作確認をしています。NATを挟んだりポート制限があるような状況(TURNサーバーが必要になる状況)での動作は確認していませんので、あくまでも実装のサンプルとして参考にしてください。

おわりに

今回初めて、Windows上で動作するlibwebrtcを使ったプログラムを作成しましたが、examples/以下に作成したソースを配置してビルドするという方法であれば比較的簡単に自作プログラムのビルドができました。とはいえ、Windowsならではのはまりポイントもあり、きちんと動作させるのには少々苦労しました。
今回作成したプログラムはとてもシンプルなものですが、WebRTCのシグナリング時の状態遷移を理解するうえでも、作ってみてよかったなと思います。ここから拡張していけばWebRTC Native Client Momoのようなプロダクトを作ることもできます。(C++力が試されますね!)

(勝手に)宣伝

libwebrtcやWebRTC Native Client Momoの実装の知見がめちゃくちゃ詰まった技術同人誌、「WebRTCをブラウザ外で使ってブラウザでできることを増やしてみませんか?」が発売されました!!
これを読むだけで、libwebrtcについて完全に理解することができます。(当社比)
libwebrtcは「ソースコード==ドキュメント」という感じで、使うためには少なからずコードを読む必要があります。ですので、libwebrtcについて書かれた書籍は大変貴重です。
現在、電子版が1200円で購入できるので、読んでみてはいかがでしょうか?
tnoho.booth.pm