[UE4] NDIを受信する

# はじめに

NDIの映像をUnreal Engine 4で受信する方法を記載。Windowsのみ検証した。

![Annotation 2020-09-05 021446.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76966/199b21da-dc26-9caa-c46a-33173dbd60cd.png)

NDIについては、[この記事](https://panda-times.com/ndi-2/)などを参照すると良い。HDMIやSDIのケーブル伝送に比べてしまうと（たとえローカル内であっても）遅延が発生するものの、それが許容される場合であれば、映像のとりまわしが容易なぶん非常に役立ち様々なストレスから開放される。NDIはハードウェアさえ不要で、設定も比較的容易でまさにメシア。

~~自分自身はBlackmagic DeckLinkなどのキャプチャボードからのUE4へストリームメディアとしての取り込みを何度も試みたが全てくじかれた。Duo2、Quad Recoreder 4K、BlackMagic以外ではAverMedia製品などを試したが、これらすべてUE4（とくに4.25）で直接受信させることができなかった。~~

Blackmagic Decklinkについては、Blackmagic designから提供されるドライバー類を同梱した、[Desktop Video SDK](https://www.blackmagicdesign.com/support/family/capture-and-playback)が、[バージョン11.6において不具合があり、UE4では受信できないことが報告されている](https://answers.unrealengine.com/questions/977506/no-input-from-decklink-card.html)。しかし、11.5.1や11.7においてはこの問題がなくSDIでの映像受信ができる。(事後加筆)


以下インストール方法。

## 1. NDI SDK、Pluginのインストール
以下2つをダウンロードし、解凍後、インストーラを用いてインストールする。

* [NDI Tools](https://www.ndi.tv/tools/) : 
    * Runtimeや各種ユーティリティツールなどがインストールできる
    * 同梱されている Test Patterns や Studio Monitor など便利なツールがたくさんありそれだけでも価値がある
* [NDI SDK for Unreal Engine](https://www.ndi.tv/sdk/) : 
    * ドキュメントも同梱されており、この記事と同じような説明が記載されている

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76966/c646c7cb-391f-18fb-0ad2-bba1fa215611.png)
上記はTest Patternsからの配信をStudio Monitorで受信している。なお、NDIの配信固有の識別名は `マシン名 (アプリ名)`のようになる。自分のマシン（DESKTOP-O6G5712）内でTest Patternsを配信した場合、`DESKTOP-O6G5712 (Test Pattern)`という命名になった。

## 2. プロジェクトにPluginを配置して有効化

上記の NDI SDK for Unreal Engine がインストールされた場所の、 NDI SDK for Unreal Engine > NDIExamples > Plugins にある、`NDIIOPlugin` がPluginとなるファイル群の実体。デフォルトのパスは、 `C:\Program Files\NewTek\NDI SDK for Unreal Engine\NDIExamples\Plugins` となる。

この `NDIIOPlugin` をそのまま、UE4の適用したいプロジェクトディレクトリ直下の `Plugins` ディレクトリ（なければ作成）に、コピーして配置。その後、プロジェクトを立ち上げると、Pluginが有効化され、NDIに関するオブジェクトなどが利用できるようなる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76966/c0c71373-c87c-1ac7-52f1-e2c574d94a2d.png)


## 3. NDI用のMedia SourceとTextureを用意

`NDI Media Receiver` と `NDI Media Texture2D` をそれぞれコンテンツブラウザ内で作成、名前をつける。
`NDI Media Receiver`の設定項目にある、`Video Texture`に先程作成した`NDI Media Texture2D`を設定する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76966/c72fadb9-63f2-1b69-2e9c-93546d396f60.png)

## 4. NDI Receiverコンポーネントを持つアクターもしくはプレーヤーを配置する

* アクター（`Actor Blueprint`）もしくはプレーヤー（`Player Controller Blueprint`）を作成
* `NDI Receiver`コンポーネントを追加する
* `NDI Receiver`コンポーネントの`NDI Media Source`に先程作成した、`NDI Media Receiver`を設定

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76966/46806e5a-838f-7980-5977-7e2babe31adc.png)

`NDI Receiver`を持つには、アクターかプレーヤーのどちらでも良いが見通しが良くなる方が吉。

なお余談だが、`NDI Receiver`は`Non-Scene Component`のC++ファイルであるが、上述の`NDI Media Source`がPrivate変数となっており、BlueprintのDetailsパネル上からしかアクセスできないのが非常に不便。Blueprintノード上でセットしたり、Levelエディタから設定することができず、このブループリントをNDIソース毎に使い回すことができないからだ。これを解消するにはこのコンポーネントのヘッダファイル（`NDIReceiverComponent.h`）を書き換えて再Buildする。このステップは任意。

```C++
private:
	/** The NDI Media Receiver representing the configuration of the network source to receive audio, video, and
	 * metadata from */
	UPROPERTY(EditDefaultsOnly, Category = "Properties",
			  META = (DisplayName = "NDI Media Source", AllowPrivateAccess = true))
	UNDIMediaReceiver* NDIMediaSource = nullptr;

```
↓

```C++
public:
	/** The NDI Media Receiver representing the configuration of the network source to receive audio, video, and
	 * metadata from */
	UPROPERTY(EditAnywhere, Category = "Properties",
			  META = (DisplayName = "NDI Media Source", AllowPrivateAccess = true))
	UNDIMediaReceiver* NDIMediaSource = nullptr;
```



## 5. 任意のBlueprintから、NetworkSourceを監視し、変更された際に受信を開始する

`NDI Receiver`を参照できる任意のBlueprintにノードを配置していく

* `NDI Finder`コンポーネントを用意する

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76966/1ba9caa6-2aea-97dc-a7c2-121ba1e3d42d.png)
    
* Network Sourceを監視し、ネットワークが接続されたりするタイミングでNDIを受信するようにする（ `NDI Finder` > `Bind Event to On Network Source Changed`）
* Callbackとなるイベントに、NDI Finderを引数にとれるCustomEventを追加して用いる。ここでは事前に`OnNetworkChanged`というCustomEventをつくっておいている。

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76966/4874efaf-c59c-fe67-a2bc-5d372dd03a9f.png)

* このCallbackイベントにて、ネットワーク内の配信の中から識別名（`DESKTOP-O6G5712 (Test Pattern)`のような）を使って、ほしい配信を検索し（`NDI Finder` > `Find Network Source By Name`）
* 見つかったら、`NDI Receiver`というコンポーネントの`Start Receiver`をコールする
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76966/e91f45bb-a9cc-6194-ab3b-9040c6ec620a.png)

NetworkSourceを監視しないで、いきなり`Find Network Source By Name`して`Start Receiver`を走らせてもPIEでは動くかもしれない。しかし、自分の環境ではコンパイルした実行ファイルにおいては、上記のようにNetworkSouceを監視して、変更の度にNDIを受信をする関数を呼ばないと、NDIを正常に受信することができなかった。（解決にすごく時間を溶かした…）
また、ここでは省略するが、ストリームが不要になった際に、`NDI Receiver`の`End Receiver`を呼んで、明示的に受信を終わらせたほうが良い。

## 6. NDI Media Texture2Dで受信を確認し、利用する

作成したNDI Media Texture2Dを確認すると、ただしく受信できていれば動画が確認できる。このTextureはMaterialなどで参照して自由に使える。Chromakeyをしてリアルタイムで切り抜きもできた。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76966/6abced9c-5960-1f43-71db-53641cf12cb4.png)
[元動画](https://www.youtube.com/watch?v=CILDxkAB3rU)

# 参照

* [How to use the NewTek NDI SDK & Unreal Engine (UE4) to composite real-time green screen footage (4K)](https://www.youtube.com/watch?v=ZLngVM5XRSI)
* https://www.ndi.tv/