スマホでNintendo Switchのゲームをする

前々回はNintendo Switch Pro ControllerのWeb対応。 前回はNintendo Switchゲーム画面のWeb対応。 今回はNintendo Switchゲーム音声のWeb対応。

何を作ろうとしてるか、だんだんワクワクしてきましたね?

前回の記事の最後に挙げたまとめです。その成果として表題の通り、スマホでNintendo Switchのゲームをすることができました。 そこまでの道筋です。

釣りタイトルっぽくなっているため、Nintendo Switch本体を持ってない人がスマホでNintendo Switchのゲームができると勘違いして迷い込んだ人はお帰りください

Table of Contents

  1. 振り返り
    1. Pro Controllerの模倣
    2. HDMI入力を扱う
    3. Switchの音声出力を取り込む
  2. 成果物
  3. まとめ

振り返り

これまでの振り返りです。 振り返りつつ、変更点を記します。

Pro Controllerの模倣

header image

スマホでNintendo Switchを操作する 〜 USB GadgetでPro Controllerをシミュレート 〜 - 犬アイコンのみっきー

この回ではNintendo Switch Pro Controllerの挙動を解析し、スマホから操作するところまでやりました。 libusbgxGstreamerとの相性を考え、GoやRustを使わずCで書いていたものの、h2oのWebSocket実装が未熟だっり、glibやlibevent使うの辛かったりしたのもあって、Cで書くのをやめて結局Goで書き直しました。 単純な実装ですが、コントローラーのシミュレーション部はGoモジュールとして分離して公開しました。

mzyy94/nscon: Nintendo Switch Controller simulator written in go - GitHub

HDMI入力を扱う

haeder image

HDMI入力をRaspberry Piで駆使する - 犬アイコンのみっきー

HDMI入力基板を用いてHDMI入力を扱いました。 Raspberry Pi公式のCamera Moduleとして認識してくれるので、何もせずにH.264で入力を扱えて楽でしたが、これはこれで問題を抱えていました。 再接続時に問題があることがこの時点ではわかっていたんですが、もっと使い込んでいくと入力解像度がおかしくなるなど、さらに問題があることがわかりました。

使用したHDMI入力基板はTC358743XBGというチップを使ってHDMI入力をCSI2に変換していると紹介しました。 このTC358743XBGはLinuxにドライバがあり、Camera Moduleのドライバを用いずにVideo4Linux2のデバイスとして認識させることができます。

linux/tc358743.c at v4.19 · torvalds/linux

このドライバを使うことで、入力機器が転送可能な映像の種類を記すEDIDを扱え、入力解像度をはじめとする諸問題も解決できます。

Raspberry Piでこのドライバを用いるには、デバイスツリーにtc358743を認識させるだけです。 /boot/config.txtdtoverlay=tc358743を追記し、再起動することでドライバが読み込まれます。

pi@raspberrypi:~ $ dmesg | grep tc358743
[    5.718785] tc358743 0-000f: tc358743 found @ 0x1e (bcm2835 I2C adapter)
pi@raspberrypi:~ $ lsmod | grep tc358743
tc358743               40960  1
v4l2_dv_timings        36864  2 bcm2835_unicam,tc358743
v4l2_fwnode            20480  2 bcm2835_unicam,tc358743
v4l2_common            16384  3 bcm2835_unicam,bcm2835_v4l2,tc358743
videodev              200704  9 bcm2835_unicam,v4l2_fwnode,bcm2835_codec,v4l2_common,videobuf2_common,bcm2835_v4l2,v4l2_mem2mem,videobuf2_v4l2,tc358743
media                  36864  5 bcm2835_unicam,bcm2835_codec,videodev,v4l2_mem2mem,tc358743

これだけでは入力映像を扱えず、EDIDを機器側に通知して初めて使えるので、ドライバが読み込まれる度にEDIDをセットする必要があります。 このために用意した720P30EDID.txtをダウンロードし、以下のようにEDIDのセットを行うことで、V4L2で1280x720のRAW映像を扱えるようになります。

v4l2-ctl --set-edid=file=720P30EDID.txt
v4l2-ctl --set-dv-bt-timings query

繋がっているHDMI入力の情報はv4l2-ctl --query-dv-timingsで見られます。

結果としてRaspberry PiのCamera Moduleとしては認識しなくなったため、H.264圧縮はされず、YUVのRAW映像としてしか取り込めません。 H.264で遅延の少ない映像を得るにはハードウェアエンコーダーを利用する必要があります。 しかし、このドライバを用いている時は、先の記事で紹介したomxh264encは使えないため、v4l2h264encを使います。

RTPで送信する例は、こんな感じです。

gst-launch-1.0 v4l2src ! video/x-raw,width=1280,height=720,framerate=30/1 ! v4l2h264enc extra-controls="encode,h264_profile=1,h264_level=12;" ! video/x-h264,width=1280,height=720,stream-format=byte-stream,profile=constrained-baseline ! h264parse config-interval=-1 ! rtph264pay pt=96 ! udpsink host=mzyy94.local port=5678

Switchの音声出力を取り込む

header image

UAC GadgetでNintendo Switchの音声出力をRaspberry Piに取り込む - 犬アイコンのみっきー

UAC Gadgetでオーディオデバイスをシミュレートし、ALSA経由で取り込んでWebRTCでブラウザで見られるようにしました。 この時は、GstreamerのWebRTCを用いていましたが、WebRTCのSDPのネゴシエーションに不備があるなど、これも問題を抱えていました。 そのため、iOS SafariやAndroid Chromeなどでは映像と音声が再生されないなどの問題がありました。

Gstreamerはv4l2srcからHDMI入力を取り込み、v4l2h264encでH.264にエンコードするまでを任せ、WebRTCは別のソフトウェアを使うことにしました。 OSSのWebRTCバックエンドは、Goで実装されたPionが有名です。

pion/webrtc: Pure Go implementation of the WebRTC API

以前から使ってみたい興味はあったので、これを用いてWebRTCで転送するようにしました。 exampleにあるコードそのままではコーデック情報が正しく指定できなかったため、SDPをちゃんと読む処理を加えています。 また、GoでGstreamerを扱うため、ある程度必要なものが揃ったバインディングであるnotedit/gstを用いました。

成果物

これまでの成果を全部くっつけたものです。 遅延は0.2秒程度あるものの、どうぶつの森であれば十分にプレイできる程度です。 難点はNintendo Switchを遠隔でスリープ復帰できないので、スリープを解除した状態でDockに入れる必要があることくらいで、他は十分に求めるレベルまで達しています。

mzyy94/ns-remote: Play Nintendo Switch anyware

必要なハードウェアが多いですが、GPLv3ライセンスのもと公開しているので、よかったら遊んでみてください。

まとめ

これで湯船に浸かりながらどうぶつの森が遊べるようになりました。

WebRTCとWebSocketで実装してあるので、デバイスに依存せず、例えばWebXR/WebVRとの組み合わせもできるので、幅広い活用ができそうです。 良い自粛のお供ができました。