0. はじめに
こんにちは。NTTコノキューの高木です。
今年3月に中途入社し、メインのプロジェクトの傍らMiRZA(ミルザ)向けのアプリケーションやソリューションを模索しています。
MiRZAとは、NTTコノキューデバイスから2024年10月16日に発売された6DoF対応のXRグラスのことです。(MiRZAの詳細についてはこちら)
野元さんの記事ではスマホへの配信でしたが、PCにもMiRZAの視点を映したい、装着者が何を見ているかPCで録画したい、ということで疑似ミラーリングアプリを開発しました。
実際にミラーリングを使用して録画した動画が以下になります。
- 0. はじめに
- 1.開発環境
- 2. 実装方針
- 3. 実装手順
- 3.1 Snapdragon Spaces SDKのセットアップ
- 3.2 Camera Frame Access機能の有効化
- 3.3 Unityシーンの作成
- 3.4 NDIの導入
- 3.5 OBSの導入
- 3.6 OBS-NDI(DistroAV)の導入
- 3.7 KlakNDIの導入
- 3.8 送信用のRenderTextureを作成する
- 3.9 RenderTextureにカメラ映像を反映させる
- 3.10 NDI SenderでRender TextureをNDIで送信する
- 3.11 OBSでNDIが受信出来ていることを確認する
- 3.12 ビルドしてMiRZAからOBSへミラーリング出来ることを確認する
- 4. 応用
- 5. まとめ
1.開発環境
Windows 11 (ハイスペックを推奨)
Unity 2022.3.28f1
Snapdragon Spaces SDK1.0.1
ローカルネットワーク(同一WiFi)で接続されたPC、MiRZA、AQUOS R9 SH-51E
NDI 6.1
OBS-NDI (2024/6~DistroAVへ名称変更)
Klak NDI 2.1.3
2. 実装方針
まず、MiRZAでは2つのレイヤーを考える必要があります。
MiRZAのカメラが捉えている現実のカメラ映像(画像左)
Unityが描画しているUnity上のカメラ映像(画像右)
これら2つを重ね合わせることで、MiRZA装着者が見えている視界を疑似的に再現することができます。(画像下)
あくまで疑似的な再現であり、実際の装着者の視点とは画角などが異なる点に注意が必要です。
巷ではMixedRealityCaptureと呼ばれたりします。
今回はこの2つのレイヤーを別々でNDI(ネットワークデバイスインターフェイス)という規格を使い、ローカルネットワーク上でMiRZAからPCに映像を送信します。
受信側(PC)ではNDIをインストールし、ビューワーとしてOBSを利用します。
OBS上のNDI Sourceとして2つのレイヤーそれぞれを受信することをゴールとします。
また、UnityアプリでNDIを送信するため高橋啓治郎さんのKlakNDIを使用します。
Q. Unity上で2つのレイヤーを合成して1つのレイヤーにしてから送信できませんか?
- 合成用のシェーダーを作成すれば可能です。ただし、合成にはある程度の処理負荷がかかるため、1つのレイヤーにする端末負荷と、2つのレイヤーを映像で送る通信負荷を天秤にかける必要があります。
また、応用でも触れますがレイヤー単位で送信したほうが画像処理する際に応用が効きやすいという利点があります。
一方で、通信によってはレイヤー間でタイミングのズレが発生します。1つのレイヤーの場合はズレが発生しません。
Q. ミラーリングだけでなく録画もしたいです。
- OBS上で録画するか、PCにミラーリングした映像を別のキャプチャアプリで録画すれば可能です。
Q. 音声は録音できますか?
- KlakNDIの制約で現状は出来ません。音声は別の手段を考える必要があります。
3. 実装手順
3.1 Snapdragon Spaces SDKのセットアップ
以下を参考にSnapdragon Spaces SDKをUnityに導入します。
これにより、MiRZAで動くアプリを作成することができます。
https://www.devices.nttqonoq.com/developer/doc/setup/setup-guide/
3.2 Camera Frame Access機能の有効化
以下を参考にCamera Frame Accessの機能を有効化します。
これにより、MiRZAのカメラにアクセスすることができるようになります。
3.3 Unityシーンの作成
今回はCamera Frame Accessのサンプルシーンを改変してミラーリングアプリを作成します。
以下のシーンファイルをコピーして、任意の名前にリネームします。
Assets/Samples/Snapdragon Spaces/1.0.1/Core Samples/Scenes/Camera Frame Access Sample/Camera Frame Access Sample.unity
ここまでで一度APKをこのシーンだけでビルドし、カメラの映像がMiRZAで正しく見れていることを確認することをお勧めします。
見れない場合、カメラの権限がアプリに与えられているか、アプリ上何かエラーが出ていないかご確認ください。
3.4 NDIの導入
公式よりNDI Runtimeをダウンロードします。
https://downloads.ndi.tv/SDK/NDI_SDK/NDI%206%20Runtime.exe
ダウンロードしたものを開き、インストールすれば完了です。
3.5 OBSの導入
OBSとは、各種映像や音声を統合しライブ配信や録画が行える配信向けソフトです。
映像の配信・録画に使用できる様々な機能が利用できるため、OBS上でMiRZAの映像を扱えると録画のみならず様々な応用が可能になります。
OBSのダウンロード、インストール方法は下記を参照してください。
https://haishinsekai.jp/obs-dl
OBSをインストールし、下記のようにOBSが開くことを確認したらOBSは一度閉じてください。
3.6 OBS-NDI(DistroAV)の導入
OBS-NDI(DistroAV)はGitHub上でGPL-2.0 Licenseのもと公開されています。
最新のNDI(6以上)を使用する場合、最新である6.0.0以上を使用してください。
GitHubのReleaseより、OSにあったもの、今回はWindowsなのでdistroav-6.0.0-windows-x64-Installer.exeをダウンロードし、インストールします。
https://github.com/DistroAV/DistroAV/releases
OBSを再起動し、①ソースの追加(「+」ボタン)を押して「NDI Source」が選べるようになっていれば無事インストール出来ています。
3.7 KlakNDIの導入
PC側で受信する準備が整ったため、Unityアプリから送信します。
GitHub上のKlakNDIをUPM(Unity Package Manager)経由でインポートします。
プロジェクトのPackages/manifest.jsonを開き下記のようにします。
https://github.com/keijiro/KlakNDI
{ "scopedRegistries": [ { "name": "Keijiro", "url": "https://registry.npmjs.com", "scopes": [ "jp.keijiro" ] } ], "dependencies": { "jp.keijiro.klak.ndi": "2.1.3", "com.qualcomm.snapdragon.spaces": "file:com.qualcomm.snapdragon.spaces-1.0.1.tgz", --------------中略----------------------- } }
UnityのPackage Managerを開きKlakNDIがインポートされていることを確認します。
3.8 送信用のRenderTextureを作成する
「RenderTexture」フォルダをAssets/に作成し、2つのRenderTextureを作成します。
1. 「RT_RGBCam」(MiRZA本体のカメラ用)
2. 「RT_UnityCam」(Unityのカメラ用)
Create->RenderTextureで作成できます。
2つのRenderTextureを選択し、Sizeを「1920x1080」にします。
これがそのまま送信する画像サイズになるので、解像度よりも速度を優先したい場合は解像度を落としてください。(1280x720など)
3.9 RenderTextureにカメラ映像を反映させる
3.9.1 Unity描画カメラを反映させる
RenderTextureで描画するためのカメラを追加します。
Hierarchy上でCameraを追加し、名前は「UnityRenderCamera」とします。
作成したカメラを以下のように設定します。
TargetTextureに先ほど作成した「RT_UnityCam」を設定するのを忘れないでください。
BackgroundカラーでAlpha=0にするとグリーンバックでなく透過映像としてカメラ映像を扱うことができます。
CullingMaskでUIを除外するとUIをカメラ映像から外したり、必要なレイヤーのみをRenderTextureに含めることが出来ます。
次に、頭の動きにこのカメラを追従させるためXROrigin/Camera Offset/Main Cameraから「TrackedPose Driver(Input System)」コンポーネントをコピーして「Unity Render Camera」にペーストします。
最終的に以下のようになればOKです。
3.9.2 MiRZAのRGBカメラを反映させる
こちらはスクリプトで反映させます。デモ用のスクリプトであるCameraFrameAccessSampleController.csを編集します。
シーン内でSampleAssets/Camera Frame Access Sample Controllerというオブジェクトを参照してください。
スクリプトを開き、2行追加します。
public class CameraFrameAccessSampleController : SampleController { [Header("Camera Feed")] public RawImage CameraRawImage; // 追加 [SerializeField] private RenderTexture CameraTexture; public bool RenderUsingYUVPlanes; ------中略---------- private unsafe void UpdateCameraTexture(XRCpuImage image, bool convertYuvManually) { var format = TextureFormat.RGBA32; -------------中略--------- _cameraTexture.Apply(); CameraRawImage.texture = _cameraTexture; //追加 Graphics.Blit(_cameraTexture, CameraTexture); }
追加したらエディタに戻り、「RT_RGBCam」をCameraTextureにセットします。
エディタでシミュレーターが動作出来る状態であれば、エディタで再生するとそれぞれのRenderTextureが更新されていることが分かります。
シミュレーターを動作させる方法は下記を参考にしてください。
https://www.devices.nttqonoq.com/developer/doc/setup/play-in-editor-setup-guide
このサンプルのUIは若干透過しているためグリーンバックだとあまり見栄えが良くないです。必要に応じてUIの背景マテリアルを変更してください
3.10 NDI SenderでRender TextureをNDIで送信する
用意した2つのRenderTextureをNDIを用いて送信します。
空のGameObjectを作成し、「NDI Sender」コンポーネントを追加します。
NDI Senderコンポーネントは下記のように設定し、「NDISenderRGB」と「NDISenderUnity」2つ作成し、それぞれ適切なRenderTextureを設定します。
Keep Alphaを有効にすることでアルファつきの映像として送信することが可能です。
ここまで完了したらシーンを保存しましょう。
実行してエラーが出ないことを確認し、実行したままにしておきます。
3.11 OBSでNDIが受信出来ていることを確認する
実機で確認する前にUnity Editor -> OBSでNDIが送受信できることを確認します。
OBSを開き「ソース」の「+」ボタンからNDI Sourceを追加します。ソース名は「NDI Source RGBCam」とします。
プロパティの「Source name」で先ほど設定したNDI Nameが表示されていれば送受信に成功しています。
ここに表示されない場合、Unityが実行しているか確認してください。
映像がカクつかないようにするため、以下の設定を行います。
RGBCamを作成したら同様に「NDI Source UnityCam」を作成します。
ソースを2つ作成できたら、2つのレイヤーを並び替えてUnityCamが上になるようにします。
ここまで行うと、以下のように重なった映像になります。
Unity上で右クリック&WASDで移動するとOBSに反映されていることが確認できます。
3.12 ビルドしてMiRZAからOBSへミラーリング出来ることを確認する
最後に実機から送信します。アプリをビルドしてapkをインストールするだけです。
映像がカクつく場合、通信環境か受信側のPCスペック不足が考えられます。
4. 応用
4.1 ミラーリング結果をPCで録画する
OBSの機能を使い録画することが可能です。
デフォルトだと録画ファイルが.mkvで保存されてしまうので.mp4に変更することをお勧めします。
また、エンコーダーはNVIDIAのGPUを使用している場合はNVENCを使用するとCPU負荷が減少します。
4.2 ミラーリング映像を全画面で視聴する
OBSのモニタ画面を右クリックし、「全画面プロジェクター」や「ウィンドウプロジェクター」を使用することでフルスクリーンでミラーリングを視聴可能です。
4.3 カメラ映像の代わりに背景画像を使用する
OBSのソースとしてバーチャル背景を利用することでRGBカメラ映像ではなくバーチャル背景の上にUnityカメラを配置することが出来ます。
MiRZAのキャプチャを撮りたいが、背景映像(現実映像)は不要な場合やイメージ動画の撮影に使用できます。
4.4 LUTを使用して映像の色調を変化させる
映像の世界で使用される「LUT(Look Up Table)」を使用することで映像に独特の雰囲気やルックを与えることができます。
OBSでは各ソースにフィルターとしてLUTを適用させることが出来るため、MiRZAのカメラのみ、もしくは全ての映像をポストエフェクト的に変化させることが出来ます。
UnityでもポストエフェクトでLUTを使用することが可能ですが、処理負荷上厳しい場合や最適なLUTを探したい場合、見栄えの良いキャプチャを作成したい場合にお勧めです。
4.5 カメラ映像で人物検出して顔を隠す
この記事では詳しく説明しませんが、OBS拡張の中には機械学習を使用したARフィルターもあり、下記の動画のように人物にフィルタをかけて録画することも可能です。
下記デモではstreamfogを使用しています。
(攻殻機〇隊の笑い男を思い出しますね。笑い男事件は作中2024年に起きる事件でした。)
AR録画や配信で課題となるプライバシー配慮などにもOBSへのミラーリングは役立つかもしれません。
5. まとめ
Camera Frame AccessとKlakNDIを用いて映像をPCへミラーリングすることで録画や画像処理など、モバイル環境で行うには負荷が高い処理をPCへ委譲することが出来ます。
MiRZAのアプリを映像として残したい場合にはPCへのミラーリングがとても有効で、かつOBSへ送信することで様々な応用が効きます。
この仕組みを活用して、SF作品のような未来を描いていきましょう!


















