👶

WebRTC(libdatachannel)に取り組み始めて一ヶ月が経った

2023/11/04に公開

OBS Studioでlibdatachannelが使われた、という情報を目にしてずばっと飛びついてWebRTCで遊び始めて一ヶ月が経ちました。

わたしとWebRTCとの関わりというと、WebRTC自体はずっと前から興味があったけれどあんまり普段の業務と関連がなく放置してたらWebRTCが出て10年近く経っていた、というのでほとんど初見です。

ちょうど今は色々あって空きがあるタイミングだったのでlibdatachannelをUnityで使えるようにしてみつつ、WebRTCの勉強をすることにしてみました。

筆者の前提

  • WebRTCははじめて
  • サーバー/クライアント(web frontendじゃなくてゲーム)どっちもいける
    • ある程度やると両方かけるよねーっていう
  • 映像、音声ちょっと分かる
    • 映像は制作側の話。音声はもともと音楽畑なのでだいたい分かる。
  • Unityスキル的にはほどほど
    • おおまかに必要なものは全部調達して最後まで着地させられるというところ。別にすごい優秀というわけでもない。
  • C++力はそんなにない
    • 書くタイミングがあまり無いので忘れている。書けないことはないけどあんまり書かないなーっていう。

一ヶ月でできたこと

WebRTCを勉強して、とありますが実際に扱っているのがlibdatachannelなのでどちらかといえば下側のレイヤです。一ヶ月やって何ができたか、というと

  • Unity用Binding作成: 現状非公開
    • web向けのWebRTC APIチックにしようと思ったら死ぬほど書くものが多くて一ヶ月では無理だった。メディア周りに手を出してしまったので現状書きなぐりのコードの山。たぶん使い物になるまであと2〜3ヶ月はかかるんじゃなかろうか。
    • 現実的にlibdatachannelを使うのであればDataChannelに限定して組んだほうが圧倒的に楽だった。
      • DataChannelはさくっと動いちゃったので物足りなく、うっかりメディアまわりをやってしまったのが敗因
  • go binding作成: https://github.com/chobie/datachannel-go
    • 動作確認のテスト用で実用するつもりはあまりない。c++よりgoのほうがいろいろいじるときにライブラリ選定が慣れているので楽というだけ。
  • その他: vp8/vp9 packetizationとかOpus bindingとか
    • (libdatachannelを扱うという観点で)WebRTCだけやればいいかと思ったら関連技術も押さえておかないと実装確認ができなくて全く話が進まなかった。実装検証目的だとgstreamer使えばいいんですがUnityで動かしたいとなるとlibvpxやlibaomなどを使った実装ぶちこまないといけないんすよ。
    • libdatachannelのどうでもいいPRをちらちらだしはじめた、ぐらい。
  • TURNサーバーはとりあえず検証用にcoturnおいてる

webでの用途のWebRTCに注力していればもうちょっと色々使い方的なのは覚えられたであろう、と思います。勉強してどこまでできたか、というサンプルとしてはちょっと特殊になってしまいました。

学習方法

まずゴールとして決めたのが「Unityで使える自分用の(趣味の)libdatachannelのライブラリを作る!」というゴールです。ゴールが曖昧だと学習の進捗もあやふやになってしまいます。
とりま、Unityでlibdatachannel使えるようにWebRTC勉強していくぜ!とトライしたわけです。

基本的には

  • libdatachannelのCAPIをC#で使えるようにする
  • 実際にUnityで組んで動くようにかいてみる
  • 未実装部分はRFCを参考に実装し、動かない部分は調査して知らない知識を勉強して実装を突っ込む

というサイクルを繰り返す学習スタイルです。

勉強のお供はChatGPTとgoogle検索です。ChatGPTはどうでもいいボイラープレート的なコードを生成する際はとても便利でした。地味に面倒くさいP/Invokeの生成とかにはとても役立ちました。どうでもいい部分でChatGPT頼りにするとすげぇ楽です。

一方で、少しだけ期待していたChatGPTがいい感じにコード書いてくれる!という部分では期待したようなコード生成は全くできませんでした。ライブラリに使うようなコードレベルでなにか実現させたい、という用途だとちょっと難しいという認識です。数十行の生成ならよいのですが履歴込みで数百行を超えていくとだいぶあやしいコードになっていきます。
ChatGPTをしばいて使えるコードを生成されるよりも自分で勉強したほうが確実で早いですね。

そもそも、ChatGPTに自分の分からないコードを生成されても妥当性の検証が出来ませんし、自分が分かる範囲でも(モノによるけれど)結構な頻度で動作しないコードを出してきます。
また、RFCについて聞いてもふんわりは説明してくれるけれども、実装に必要な情報を詳細に説明してくれるわけではないので、結局は人力です。
とはいえ、プログラミング言語の仕様周りはものすごく詳しいみたいなのでそういうピンポイントな部分ではとても参考になる情報をだしてくれます。

    [StructLayout(LayoutKind.Sequential)]
    public struct rtcWsConfiguration
    {
         // UnmanagedType.I1つかうとP/Invokeでbool使えるとかしらんかったわー
        [MarshalAs(UnmanagedType.I1)]
        public bool disableTlsVerification; // if true, don't verify the TLS certificate
        [MarshalAs(UnmanagedType.LPStr)]
        public string proxyServer; // only non-authenticated http supported for now
        public IntPtr protocols;
        public int protocolsCount;
        public int connectionTimeoutMs; // in milliseconds, 0 means default, < 0 means disabled
        public int pingIntervalMs; // in milliseconds, 0 means default, < 0 means disabled
        public int maxOutstandingPings; // 0 means default, < 0 means disabled
    }

自分の理解だとChatGPTには自分のレベルの範囲でできることをちょっと便利にしてくれるツールという認識です。もちろんないよりはあったほうが断然楽。

とはいえ、煮詰まった時に他人の時間を消費せずにChatGPT相手に壁打ちできるのはとても良かったです。今までは動かなかったら数日〜数週間一人で試行錯誤し続けるというのが普通だったのでその点はとてもよい相棒でした。良い相棒といいつつちょくちょくしれっと嘘をついてくるのでその辺は注意が必要です。

こういうスタイルで勉強をしていたのですが、WebRTCスタックを作って動かすというのは一部のエクストリームな人たちだけがやっているものなので先達者の実装を(ライセンスに気をつけながら)参考にしつつ、RFCとにらめっこして実装して実装したものとリファレンスとなるブラウザの動作で動くかというぐらいしかないんじゃないでしょうか。

参考にした情報

時雨堂の情報が一番参考になりました。ありがとうございます。

と、いうので世の大半の人はWebRTCをブラウザで使いたい人なのでそこらへんの方々の情報はあまり参考に出来ませんでした。私としてはWebRTCスタックを作成している人からの説明や情報がほしいのですが、そういった人は少数なので仕方がありません。
世にあるWebRTC情報の大半はブラウザでWebRTC使う話なのでしゃあないですね。

作って動かすならRFCとコード読んでいろいろ試行錯誤するのが一番てっとり早いという結論です。

一ヶ月やってきたWebRTC関連の理解度

  • おおまかにWebRTCの動きの流れはわかるが細かい詳細まではわからない
  • RFCは自分に必要な部分は大まかに読んだが、全部は網羅できていない。それぞれの技術でさわりは分かるが詳細は知らん。
    *https://datatracker.ietf.org/doc/html/draft-ietf-payload-vp9-16 など実装に必要な部分は特に読んだ

機能確認でlibdatachannelで実装があまりされてなかったメディア関連に注力してしまったのでRTPまわりはちょっとだけ分かる、というのが現状です。まだまだ勉強が必要ですねー。

WebRTCのデバッグ

chromeの chrome://webrtc-internals を見て推測してどうにかするというのが現状です。

chromiumであればopen -a /Applications/Chromium.app --args --disable-webrtc-encryptionでDTLSが無効にできるのでやったはかどる!と思いましたが、
libdatachannel側でDTLSを無効にするにはパッチを当てるしかなく、かつパッチが現状outdatedなので使えません。
https://github.com/paullouisageneau/libdatachannel/issues/399
patchを参考にopenssl周りを迂回すれば動作するか?と思ってトライしましたが疎通確認が取れなくて断念しました。

webrtc-internalsだよりでがんばるか、libdatachannel側でpacketを受信したタイミングでtext2pcap用のフォーマットに書き出すコードを使ってwiresharkで確認しています。

    public static string ConvertToText2PcapFormat(byte[] data, DateTime timestamp)
    {
        if (data == null || data.Length == 0) return string.Empty;

        StringBuilder sb = new StringBuilder();
        int offset = 0;

        // Add timestamp for the first line
        sb.AppendFormat("{0}.{1:ffffff} \n", timestamp, timestamp.Millisecond);

        for (int i = 0; i < data.Length; i++)
        {
            if (i % 16 == 0)
            {
                if (i != 0) sb.AppendLine();
                sb.AppendFormat("{0:x4}  ", offset);
                offset += 16;
            }

            sb.AppendFormat("{0:x2} ", data[i]);
        }

        sb.AppendLine();

        return sb.ToString();
    }

で出来上がったテキストをtext2pcapに流してWiresharkであれこれ眺めて実装調整というのが現状うまくワークしている手法です。

その他はひたすらブラウザと接続して動作するかしないかをチェックするというスタイル。とても効率が悪いです。

一ヶ月を振り返ってみて

振り返ってみると自分の環境でなんか動かん、というので数日ハマった大半がICE周りのコネクション問題(IPV6を選択されるとうちの環境だとつながらん問題)だったのでいつかはICEまわりを勉強しようと思います。

https://github.com/paullouisageneau/libdatachannel/issues/1006#issuecomment-1773534964
ローカル開発用途であればビルド時にENABLE_LOCAL_ADDRESS_TRANSLATIONをONにしてあげると繋がらん問題がちょっと改善されるらしい(が、しばしば繋がらない問題に出くわしたので後日IPV6関連のCandidateを潰す方を選択しています)

WebRTCはメディアとかDataChannelやるよりもSDP NegotiationやらICEのフローをきっちり理解しておけばもっと早くこれが問題だな!という判断ができたのに無駄に時間を使っちゃったな、と思います

が、これはこれで勉強しようとおもうと結構な時間がかかりますし、SDP周りのNegotiationはなんかテキストのSDPをゴニョっと書き換えるぜ!みたいなノリっぽいのでこれはこれでなかなか学習大変そうだな、と思っています。

現実問題としては自分の環境だとIPV6関連のCandidateを潰せば問題に遭遇しないのでいまはまだいいや、とICEまわりは放置しています。

今後の予定

さっさとUnity向けライブラリの着地をしたいところですが、現状はまだまだ実装が足りていないのでそこを埋める必要があります。

メディア送受信周りに手を出してしまったので正直実用できる部分に至るまで足りない機能が山ほどあってですね・・・、

WebRTCライブラリとして実用レベルを目指すと、libdatachannel本体で色々試行錯誤するのは時間がかかるので、実験的にバインディング側で各種packetのserializer/deserializer等の実装をしつつlibdatachannel側にフィードバックしていくのが良さそうです。
今更DataChannelだけに絞るというのもアレですし、当分は水面下で余暇に実装をひたすら続けるつもりです。

libdatachannelはメディア周りはまだ実装されていない機能も多いのでWebRTCの実装や設計を勉強するのにはちょうどいい、、、、のでしょうか?実装されていないのは明らかな課題なのでなにもわからんところから興味を広げるという観点では良いと思っています。

まぁ、趣味だからおもしろければいっかー、というところで。

Discussion