HolographicSpace API を使用した Holographic Remoting リモート アプリの作成

Holographic Remoting を初めて使用する場合は、概要を参照してください。

重要

このドキュメントでは、HolographicSpace API を使用して、HoloLens 2 用のリモート アプリケーションを作成する方法について説明します。 HoloLens (第 1 世代) 向けのリモート アプリケーションでは、NuGet パッケージ バージョン 1.x.x を使用する必要があります。このことは、HoloLens 2 用に作成されたリモート アプリケーションは HoloLens 1 と互換性がなく、その逆も同様であることを意味しています。 HoloLens 1 向けのドキュメントについては、こちらを参照してください。

非推奨の通知: 2.9.x リリース行は、アプリケーション開発用の Windows Holographic API をサポートする最後のリリース行になります。 今後のバージョンでは、アプリケーション開発用の OpenXR のみがサポートされます。 それとは無関係に、すべての新しいアプリケーション開発に対してアプリケーションで OpenXR を使用することをお勧めします。 2.9 以前を使用する既存のアプリケーションは、今後の変更の影響を受けずに引き続き機能します。

Holographic Remoting アプリでは、リモートでレンダリングされたコンテンツを HoloLens 2 ヘッドセットや Windows Mixed Reality イマーシブ ヘッドセットにストリーミングできます。 さらに多くのシステム リソースにアクセスして、リモートのイマーシブ ビューを既存のデスクトップ PC ソフトウェアに統合することもできます。 リモート アプリは HoloLens 2 から入力データ ストリームを受信し、仮想イマーシブ ビューでコンテンツをレンダリングし、コンテンツ フレームを HoloLens 2 にストリーミングで戻します。 この接続は、標準の Wi-Fi を使用して行われます。 Holographic Remoting は、NuGet パケットを介してデスクトップ アプリや UWP アプリに追加されます。 接続を処理し、イマーシブ ビューでレンダリングする追加のコードが必要です。 通常のリモート処理接続の待機時間は 50 ミリ秒程度です。 プレーヤー アプリで待機時間をリアルタイムに報告できます。

このページのすべてのコードと作業中のプロジェクトは、Holographic Remoting のサンプル GitHub リポジトリにあります。

必須コンポーネント

正しい開始点は、動作中の DirectX ベースのデスクトップまたは UWP アプリが Windows Mixed Reality API をターゲットにしていることです。 詳細については、DirectX 開発の概要をご覧ください。 C++ ホログラフィック プロジェクト テンプレートは、開始点として最適です。

重要

Holographic Remoting を使用するすべてのアプリは、マルチスレッド アパートメントを使用するように作成する必要があります。 シングルスレッド アパートメントの使用はサポートされていますが、パフォーマンスが低下し、再生中に途切れが生じる可能性があります。 C++/WinRT winrt::init_apartment を使用する場合は、マルチスレッド アパートメントが既定値です。

Holographic Remoting NuGet パッケージを取得する

Visual Studio で NuGet パッケージをプロジェクトに追加するには、次の手順を実行する必要があります。

  1. Visual Studio でプロジェクトを開きます。
  2. プロジェクト ノードを右クリックし、[NuGet パッケージの管理...] を選択します。
  3. 表示されるパネルで、[参照] を選択してから "Holographic Remoting" を検索します。
  4. Microsoft.Holographic.Remoting を選択し、最新の 2.x.x バージョンを選択して [インストール] を選択します。
  5. [プレビュー] ダイアログが表示されたら、[OK] をクリックします。
  6. 使用許諾契約書のダイアログが表示されたら、[同意する] を選択します。

Note

NuGet パッケージ バージョン 1.x.x は、HoloLens 1 をターゲットにしている開発者向けに引き続き提供されています。 詳細については、「Holographic Remoting の追加 (HoloLens (第 1 世代))」を参照してください。

リモート コンテキストを作成する

最初の手順として、アプリケーションでリモート コンテキストを作成する必要があります。

// class declaration
#include <winrt/Microsoft.Holographic.AppRemoting.h>

...

private:
    // RemoteContext used to connect with a Holographic Remoting player and display rendered frames
    winrt::Microsoft::Holographic::AppRemoting::RemoteContext m_remoteContext = nullptr;
// class implementation
#include <HolographicAppRemoting\Streamer.h>

...

CreateRemoteContext(m_remoteContext, 20000, false, PreferredVideoCodec::Default);

警告

Holographic Remoting は、Windows の一部である Windows Mixed Reality ランタイムをリモート処理固有のランタイムに置き換えることによって機能します。 これは、リモート コンテキストの作成時に行なわれます。 そのため、Windows Mixed Reality API を呼び出すのがリモート コンテキストを作成する前だと、予期しない動作が起きる可能性があります。 推奨される方法は、あらゆる Mixed Reality API を操作する前に、可能な限り早くリモート コンテキストを作成することです。 Windows Mixed Reality API を通じて作成または取得したオブジェクトの混在は、その後作成または取得したオブジェクトを使用して CreateRemoteContext を呼び出す前に行なわないでください。

次に、ホログラフィック空間を作成する必要があります。 CoreWindow を指定する必要はありません。 CoreWindow を持たないデスクトップ アプリは、nullptr を渡すだけでできます。

m_holographicSpace = winrt::Windows::Graphics::Holographic::HolographicSpace::CreateForCoreWindow(nullptr);

デバイスに接続する

リモート アプリでコンテンツをレンダリングする準備ができたら、プレーヤー デバイスへの接続を確立できます。

接続は、次の 2 つの方法のいずれかを使用して実行できます。

  1. デバイスで実行されているプレーヤーに、リモート アプリで接続する。
  2. リモート アプリに、デバイスで実行されているプレーヤーで接続する。

リモート アプリからプレーヤー デバイスへの接続を確立するには、ホスト名とポートを指定して、リモート コンテキストで Connect メソッドを呼び出します。 Holographic Remoting Player で使用するポートは、8265 です。

try
{
    m_remoteContext.Connect(m_hostname, m_port);
}
catch(winrt::hresult_error& e)
{
    DebugLog(L"Connect failed with hr = 0x%08X", e.code());
}

重要

C++/WinRT API と同様に、Connect がスローする winrt::hresult_error は、処理する必要があります。

ヒント

C++/WinRT 言語プロジェクションを使用しないようにするには、Holographic Remoting NuGet パッケージ内にあるファイル build\native\include\<windows sdk version>\abi\Microsoft.Holographic.AppRemoting.h を含めることができます。 これには、基になる COM インターフェイスの宣言が含まれています。 それでも C++/WinRT の使用をお勧めします。

着信接続をリモート アプリでリッスンするには、Listen メソッドを呼び出します。 ハンドシェイク ポートとトランスポート ポートの両方を、この呼び出し中に指定できます。 ハンドシェイク ポートは、初期ハンドシェイクに使用されます。 データは、トランスポートポートを介して送信されます。 既定で、8265 および 8266 が使用されます。

try
{
    m_remoteContext.Listen(L"0.0.0.0", m_port, m_port + 1);
}
catch(winrt::hresult_error& e)
{
    DebugLog(L"Listen failed with hr = 0x%08X", e.code());
}

重要

NuGet パッケージ内の build\native\include\HolographicAppRemoting\Microsoft.Holographic.AppRemoting.idl には、Holographic Remoting により公開された API の詳細なドキュメントが含まれています。

リモート処理固有のイベントの処理

リモート コンテキストは、接続の状態を監視するために重要な 3 つのイベントを公開します。

  1. OnConnected: デバイスへの接続が正常に確立されるとトリガーされます。
winrt::weak_ref<winrt::Microsoft::Holographic::AppRemoting::IRemoteContext> remoteContextWeakRef = m_remoteContext;

m_onConnectedEventRevoker = m_remoteContext.OnConnected(winrt::auto_revoke, [this, remoteContextWeakRef]() {
    if (auto remoteContext = remoteContextWeakRef.get())
    {
        // Update UI state
    }
});
  1. OnDisconnected: 確立された接続が閉じられなかった場合、または接続を確立できなかった場合にトリガーされます。
m_onDisconnectedEventRevoker =
    m_remoteContext.OnDisconnected(winrt::auto_revoke, [this, remoteContextWeakRef](ConnectionFailureReason failureReason) {
        if (auto remoteContext = remoteContextWeakRef.get())
        {
            DebugLog(L"Disconnected with reason %d", failureReason);
            // Update UI

            // Reconnect if this is a transient failure.
            if (failureReason == ConnectionFailureReason::HandshakeUnreachable ||
                failureReason == ConnectionFailureReason::TransportUnreachable ||
                failureReason == ConnectionFailureReason::ConnectionLost)
            {
                DebugLog(L"Reconnecting...");

                ConnectOrListen();
            }
            // Failure reason None indicates a normal disconnect.
            else if (failureReason != ConnectionFailureReason::None)
            {
                DebugLog(L"Disconnected with unrecoverable error, not attempting to reconnect.");
            }
        }
    });
  1. OnListening: 着信接続のリッスンが開始するときです。
m_onListeningEventRevoker = m_remoteContext.OnListening(winrt::auto_revoke, [this, remoteContextWeakRef]() {
    if (auto remoteContext = remoteContextWeakRef.get())
    {
        // Update UI state
    }
});

さらに、リモート コンテキストで ConnectionState プロパティを使用して接続状態を照会することができます。

auto connectionState = m_remoteContext.ConnectionState();

音声イベントの処理

リモート音声インターフェイスを使用すると、音声トリガーを HoloLens 2 に登録し、リモート アプリケーションにリモート接続することができます。

リモート音声の状態を追跡するには、次の追加メンバーが必要です。

winrt::Microsoft::Holographic::AppRemoting::IRemoteSpeech::OnRecognizedSpeech_revoker m_onRecognizedSpeechRevoker;

最初に、リモート音声インターフェイスを取得します。

if (auto remoteSpeech = m_remoteContext.GetRemoteSpeech())
{
    InitializeSpeechAsync(remoteSpeech, m_onRecognizedSpeechRevoker, weak_from_this());
}

非同期ヘルパー メソッドを使用すると、リモート音声を初期化できます。 これは、初期化にかなりの時間がかかる可能性があるため、非同期的に行う必要があります。 「C++/WinRT を使用したコンカレンシー操作と非同期操作」では、C++/WinRT を使用して非同期関数を作成する方法について説明しています。

winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::Storage::StorageFile> LoadGrammarFileAsync()
{
    const wchar_t* speechGrammarFile = L"SpeechGrammar.xml";
    auto rootFolder = winrt::Windows::ApplicationModel::Package::Current().InstalledLocation();
    return rootFolder.GetFileAsync(speechGrammarFile);
}

winrt::fire_and_forget InitializeSpeechAsync(
    winrt::Microsoft::Holographic::AppRemoting::IRemoteSpeech remoteSpeech,
    winrt::Microsoft::Holographic::AppRemoting::IRemoteSpeech::OnRecognizedSpeech_revoker& onRecognizedSpeechRevoker,
    std::weak_ptr<SampleRemoteMain> sampleRemoteMainWeak)
{
    onRecognizedSpeechRevoker = remoteSpeech.OnRecognizedSpeech(
        winrt::auto_revoke, [sampleRemoteMainWeak](const winrt::Microsoft::Holographic::AppRemoting::RecognizedSpeech& recognizedSpeech) {
            if (auto sampleRemoteMain = sampleRemoteMainWeak.lock())
            {
                sampleRemoteMain->OnRecognizedSpeech(recognizedSpeech.RecognizedText);
            }
        });

    auto grammarFile = co_await LoadGrammarFileAsync();

    std::vector<winrt::hstring> dictionary;
    dictionary.push_back(L"Red");
    dictionary.push_back(L"Blue");
    dictionary.push_back(L"Green");
    dictionary.push_back(L"Default");
    dictionary.push_back(L"Aquamarine");

    remoteSpeech.ApplyParameters(L"", grammarFile, dictionary);
}

認識される語句を指定する 2 つの方法があります。

  1. 音声文法 xml ファイル内の仕様。 詳細については、「基本的な XML 文法を作成する方法」を参照してください。
  2. ディクショナリ ベクター内で ApplyParameters に渡すことで指定します。

OnRecognizedSpeech コールバック内で、音声イベントを処理できます。

void SampleRemoteMain::OnRecognizedSpeech(const winrt::hstring& recognizedText)
{
    bool changedColor = false;
    DirectX::XMFLOAT4 color = {1, 1, 1, 1};

    if (recognizedText == L"Red")
    {
        color = {1, 0, 0, 1};
        changedColor = true;
    }
    else if (recognizedText == L"Blue")
    {
        color = {0, 0, 1, 1};
        changedColor = true;
    }
    else if (recognizedText == L"Green")
    {
        ...
    }

    ...
}

ストリーミングされたコンテンツをローカルでプレビューする

デバイスに送信されるのと同じコンテンツをリモート アプリに表示するには、リモート コンテキストの OnSendFrame イベントを使用できます。 OnSendFrame イベントは、Holographic Remoting ライブラリが現在のフレームをリモート デバイスに送信するたびトリガーされます。 これは、コンテンツを受け取り、デスクトップまたは UWP ウィンドウに転送するのに最適なタイミングです。

#include <windows.graphics.directx.direct3d11.interop.h>

...

m_onSendFrameEventRevoker = m_remoteContext.OnSendFrame(
    winrt::auto_revoke, [this](const winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DSurface& texture) {
        winrt::com_ptr<ID3D11Texture2D> texturePtr;
        {
            winrt::com_ptr<ID3D11Resource> resource;
            winrt::com_ptr<::IInspectable> inspectable = texture.as<::IInspectable>();
            winrt::com_ptr<Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess> dxgiInterfaceAccess;
            winrt::check_hresult(inspectable->QueryInterface(__uuidof(dxgiInterfaceAccess), dxgiInterfaceAccess.put_void()));
            winrt::check_hresult(dxgiInterfaceAccess->GetInterface(__uuidof(resource), resource.put_void()));
            resource.as(texturePtr);
        }

        // Copy / blit texturePtr into the back buffer here.
    });

深度の再投影

バージョン 2.1.0 以降、Holographic Remoting では深度の再投影がサポートされています。 これには、カラー バッファーと深度バッファーの両方をリモート アプリケーションから HoloLens 2 にストリーミングする必要があります。 既定では、深度バッファー ストリーミングは有効にされ、カラー バッファーの解像度の半分を使用するように構成されています。 これは次のように変更できます。

// class implementation
#include <HolographicAppRemoting\Streamer.h>

...

CreateRemoteContext(m_remoteContext, 20000, false, PreferredVideoCodec::Default);

// Configure for half-resolution depth.
m_remoteContext.ConfigureDepthVideoStream(DepthBufferStreamResolution::Half_Resolution);

既定値を使用しない場合、HoloLens 2 への接続を確立する前に、ConfigureDepthVideoStream を呼び出す必要があることに注意してください。 最適な場所は、リモート コンテキストを作成した直後です。 DepthBufferStreamResolution に指定できる値は次のとおりです。

  • Full_Resolution
  • Half_Resolution
  • Quarter_Resolution
  • 無効 (バージョン 2.1.3 で追加されました。使用する場合、追加の深度ビデオ ストリームは作成されません)

完全な解像度の深度バッファーを使用することは、帯域幅の要件にも影響し、CreateRemoteContext に指定した最大帯域幅の値について考慮する必要があることに注意してください。

解像度を構成した後、HolographicCameraRenderingParameters.CommitDirect3D11DepthBuffer を介して深度バッファーをコミットする必要もあります。


void SampleRemoteMain::Render(HolographicFrame holographicFrame)
{
    ...

    m_deviceResources->UseHolographicCameraResources([this, holographicFrame](auto& cameraResourceMap) {
        
        ...

        for (auto cameraPose : prediction.CameraPoses())
        {
            DXHelper::CameraResources* pCameraResources = cameraResourceMap[cameraPose.HolographicCamera().Id()].get();

            ...

            m_deviceResources->UseD3DDeviceContext([&](ID3D11DeviceContext3* context) {
                
                ...

                // Commit depth buffer if available and enabled.
                if (m_canCommitDirect3D11DepthBuffer && m_commitDirect3D11DepthBuffer)
                {
                    auto interopSurface = pCameraResources->GetDepthStencilTextureInteropObject();
                    HolographicCameraRenderingParameters renderingParameters = holographicFrame.GetRenderingParameters(cameraPose);
                    renderingParameters.CommitDirect3D11DepthBuffer(interopSurface);
                }
            });
        }
    });
}

深度の再プロジェクションが HoloLens 2 で正しく動作しているかどうかを確認するには、デバイス ポータルを使用して深度ビジュアライザーを有効にできます。 詳細については、深度が正しく設定されていることの確認に関するページを参照してください。

省略可能: カスタム データ チャネル

カスタム データ チャネルを使用して、既に確立されているリモート処理接続を介してユーザー データを送信することができます。 詳細については、カスタム データ チャネルに関するページを参照してください。

省略可能: 座標系の同期

バージョン 2.7.0 以降では、座標系同期を使用して、プレーヤーとリモート アプリケーションの間の空間データを揃えることができます。 詳細については、「Holographic Remoting を使用した座標系同期の概要」を参照してください。

参照