ParsecTOP : ParsecをTouchDesignerで使って低遅延60FPSストリーミング送受信

この記事は[TouchDesigner Advent Calendar 2020](https://qiita.com/advent-calendar/2020/touchdesigner)の3日目の記事です。

前日（2日目）の記事は @nariakiiwatani さんの[「[TouchDesigner]gitでのバージョン管理を支援するtox」](https://qiita.com/nariakiiwatani/items/accceeb558163f3df913)です。
翌日（4日目）の記事は @YuskeGoto さんの[TouchDesignerとGrblでやる貧乏人のためのステッピングピングモーター制御入門」](https://qiita.com/YuskeGoto/items/c4de1f868d0d6cce6e03)です。

この記事は過去の記事に内容を追加したVersionです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70810/9db3c948-5be2-5045-2926-e704bb880e81.png)

## ParsecTOP
Parsecはインターネット経由のテクスチャストリーミングによってゲームする用途として提供されています。低遅延でFullHD, 60FPSで映像を伝送することができるので、様々なケースに有用です。同じネットワーク上でなくても、インターネット経由で映像の低遅延ストリーミングを実現しています。詳しくは[こちら](https://parsecgaming.com/game-streaming-technology/)。

> https://parsecgaming.com/
主にビデオストリーミングを通じてゲームをプレイするために使用される独自のフリーウェアデスクトップキャプチャアプリケーションです。

ということで、[Parsec SDK](https://github.com/parsec-cloud/parsec-sdk)を利用して、テクスチャストリーミングとテクスチャに同期したデータをTouchDesignerで**送信と受信**できるようにCPulsPuls OperatorでParsecTOP (`ParsecHostTOP + ParsecClientTOP`)を作ってみました。

- [https://github.com/shks/ParsecTOP](https://github.com/shks/ParsecTOP)
(TouchDesigner 2020.26630 , windows10で動作テスト済み)

映像送出側はHost (`ParsecHostTOP`) と映像受信側は Client(`ParsecClientTOP`)を用います。


## Setting up Parsec

まずはParsecのアカウントが必要です。[https://parsecgaming.com/](https://parsecgaming.com/)からアカウントを作成し、Parsecアプリをダウンロードしてください。
> ホスト（テクスチャを送出できる元のコンピュータ）になれるのはハードウエア制約ありのwindows10（GPU等）のみなので注意してください。

### Parsec API にログインして session_id と peer_id を取得
Parsecのストリーミングセッションを確立するためには、`session_id` と `host_peer_id`が必要です。これらのIDを取得するには、personal_api_parsec-sdk/login.pyを使用します。詳細は [ココ](https://github.com/parsec-cloud/parsec-sdk/tree/master/api/personal) を参照してください。

> 元のsdk scriptでは、API_HOSTのURLが更新されていなかったので、HOSTを'https://kessel-api.parsecgaming.com/に修正しました。

```
cd personal_api_parsec-sdk
python login.py
```

上記を実行しメールアドレスとパスワードを入力してください。(最初の試みでエラーが発生する可能性があり、parsecから受信したメールを経由してアクセスを確認する必要があります。) そうすると `session_id` と `host_peer_id` が取得されます。これらは機密情報ですので、他の人と共有しないでください。

```
{"instance_id":"","user_id":999999999,"session_id":"XXXXXXXXXXXXXXX**872ff21f53a52757399594d5aXXXXXXXXXXXX**","host_peer_id":"1jOuXXXXXXXXXXXXXXXXXXx5je"}
```

なお、利用可能なホストコンピュータのリストを取得するには、`python host.py` を実行すると、以下のようになります。下記の例は、2つのホストコンピュータがある状態です。

```
> python host.py

NAME                 PEER_ID
----                 -------
COMPUTER1            host_peer_idXXXXXXXXXXXXXX
COMPUTER2            host_peer_idYYYYYYYYYYYYYY
```

上記のスクリプトから、ストリーミングの送出と受信に必要な情報：`session_id` と `peer_id`が得られたので、TouchDesignerのParsecTOPで使用します。


# ParsecTOP in TouchDesigner

## ホスト(映像送出）：ParsecHostTOP
使い方は`CPlusPlusTOP` のパラメータで`ParsecHostTOP.dll`を指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70810/3322ed2f-9bcf-7f81-d766-69e70010e328.png)
詳しくは、Githubのサンプルをみてください。

### session IDの指定
まず、ホスト側には前述の`session_id`の設定が必要です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70810/3fa4b284-092e-0dee-5367-a757cace8b29.png)

`ParsecHostTOP_example.toe`を開くと、`ParsecHostTOP` (CPulsPuls オペレータ)が表示されます。パラメータウィンドウを開き、`session_id`を入力して `Reset` ボタンを押してください。

### 送りたいTOPの接続

その上で、送出したい映像のTOPを接続します。現在は1920x1080前提にしていますが、確か4Kも可能であった気がします。60Fpsが可能かどうかは通信環境やマシンスペックに依存する可能性があります。

### 文字列データの送信（ホスト→クライアント）

ParsecSDKでは映像に同期したイベントデータ（キーボード入力等）を映像受信側に送る仕組みが提供されています。その中で、`User Data`　形式として、任意の文字列送る事ができます。映像に同期した情報（文字列情報 / string)を送りたい場合には、`Senduserdata`に入力します。サンプルでは、例として映像内に表示するフレームナンバーの数値をString形式の文字列に入れて、映像と合わせて送出します。

### Parsec情報確認と、Client側からの受信データの取得

`ParsecHostTOP`に`InfoDAT`を接続することで、Parsecのエラーコード、SessionID、接続されてるクライアント数、そして後述するClient側からの受信データ（文字列情報 / string）を取得できます。

## クライアント(映像受信）：ParsecClientTOP

使い方は`CPlusPlusTOP` のパラメータで`ParsecClientTOP.dll`を指定します。詳しくは、Githubのサンプルをみてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70810/43df83ed-d3df-1b5d-46ed-d0f66e5e85e4.png)


### Session ID, Peer IDの指定
クライアント側の設定には、`session_id` と `peer_id`の両方が必要です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70810/cc957fd7-f230-0cf1-a958-1c5f082a0009.png)
`ParsecClientTOP_example.toe`を開くと、`ParsecClientTOP` (CPulsPuls オペレータ)が表示されます。パラメータウィンドウを開き、`session_id`、`peer_id`を入力して `Reset` ボタンを押してください。正しく受信できる場合には、`ParsecClientTOP`に映像が表示されます。なんらかのエラーが発生している場合には、赤い映像が表示されます。


### 文字列データの送信（クライアント→ホスト）

ホスト側でクライアントに対して、User Dataデータの送信できたように、クライアント側からも、ホスト側にUser Data（文字列）データの送信が可能です。`ParsecHostTOP`と同じように（文字列情報 / string)を送りたい場合には、`ParsecClientTOP`のパラメータ`Senduserdata`に入力します。

### 文字列データの受信

ホスト側から送信された`User Data`（文字列情報 / string)は、`InfoDAT`経由で取得できます。サンプル（`ParsecClientTOP_example.toe`）では、実際に映像と文字列情報が同期しているかを確認するために、`InfoDAT`経由で取得したフレームナンバーをTextTOPで文字映像にして、取得した映像と重ねて表示しています。映像でもわかるように、映像と文字情報は同期しています。ホスト側で書かれた文字と、メタデータで取得したうえでレンダリングした数字が一致していることがわかります。つまり、映像とメタデータ（文字列データ）が同期していることが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70810/e0b61b42-7f75-5907-3bb2-674d9ecb52e1.png)

**映像データとメタデータを同期させて送出できると、沢山嬉しいことがあります。**

例えば事前に同期させておいたクロックシグナルデータを入れて時系列をあわせたり、画像認識処理データをあわせて送出したり、映像に同期したfloat 4x4 matrixをメタデータとして送出する等、夢が広がりますね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70810/9ea43e22-0984-41d6-aff4-9cd7068fcb4d.png)
例えば、4x4 table DATを文字列にしてホストからクライアント送ることで、映像に同期した4x4マトリクスを送ることができます。


### アプリケーション版のParsecでの映像受信

なお、アプリケーション版のParsec（デスクトップ画面全体のテクスチャをストリーミング）をホストとして、`ParsecClientTOP`　で受け取る事ができます。対応する`session_id` と `peer_id`を`ParsecClientTOP`のパラメータで指定することで、テクスチャを受け取れます。　

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70810/081ec8df-6470-45ba-991d-250e582a50d5.png)
さらに、マウス位置も送る事ができます。（クリックイベント等は未対応）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70810/cc13f22c-9de1-b8d9-2b19-d37c68a72189.png)

## 既知の問題

ホスト側の`session_id` と `peer_id`等を変えたときなどに、クライアント側の受信が正しくできない、フレームレートが下がるという問題があります。ホスト、クライアント側双方で `Reset` ボタンし、Parsec内部処理を初期化すると解決します。


## 追記
### python経由でsessionID, peerIDを取得する機能を追加 (2021.01.15)
https://github.com/shks/ParsecTOP の`ParsecHostTOP_example.toe`内に追加済み。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70810/f0205616-6469-8563-c1e6-5d862676bc76.png)