Schreiben einer Holographic Remoting-Remote-App mithilfe der HolographicSpace-API

Wenn Sie noch nicht mit Holographic Remoting sind, sollten Sie unsere Übersicht lesen.

Wichtig

In diesem Dokument wird die Erstellung einer Remoteanwendung für HoloLens 2 mithilfe der HolographicSpace-API beschrieben. Remoteanwendungen für HoloLens (1. Generation) müssen nuGet-Paketversion 1.x.x verwenden. Dies bedeutet, dass Remoteanwendungen, die für HoloLens 2 geschrieben wurden, nicht mit HoloLens 1 kompatibel sind und umgekehrt. Die Dokumentation zu HoloLens 1 finden Sie hier.

Hinweis zur Veraltetkeit: Die Veröffentlichungszeile 2.9.x wird die letzte sein, die Windows Holographic-APIs für die Anwendungsentwicklung unterstützt. Zukünftige Versionen unterstützen OpenXR nur für die Anwendungsentwicklung. Unabhängig davon empfehlen wir die Verwendung von OpenXR in Ihrer Anwendung für alle neuen Anwendungsentwicklung. Vorhandene Anwendungen, die 2.9 oder älter verwenden, funktionieren weiterhin nicht von anstehenden Änderungen.

Holographic Remoting-Apps können remote gerenderte Inhalte an HoloLens 2 und Windows Mixed Reality immersive Headsets streamen. Sie können auch auf weitere Systemressourcen zugreifen und immersive Remoteansichten in vorhandene Desktop-PC-Software integrieren. Eine Remote-App empfängt einen Eingabedatenstrom von HoloLens 2, rendert Inhalte in einer virtuellen immersiven Ansicht und streamt Inhaltsframes zurück an HoloLens 2. Die Verbindung wird über das standardmäßige WLAN hergestellt. Holographic Remoting wird einer Desktop- oder UWP-App über ein NuGet-Paket hinzugefügt. Zusätzlicher Code ist erforderlich, der die Verbindung verarbeitet und in einer immersiven Ansicht rendert. Eine typische Remotingverbindung weist eine Latenz von nur 50 ms auf. Die Player-App kann die Latenz in Echtzeit melden.

Der gesamte Code auf dieser Seite und funktionierende Projekte finden Sie im Github-Repository für Holographic Remoting-Beispiele.

Voraussetzungen

Ein guter Ausgangspunkt ist eine funktionierende DirectX-basierte Desktop- oder UWP-App, die auf die Windows Mixed Reality-API abzielt. Ausführliche Informationen finden Sie unter Übersicht über die DirectX-Entwicklung. Die holografische C++-Projektvorlage ist ein guter Ausgangspunkt.

Wichtig

Jede App, die Holographic Remoting verwendet, sollte für die Verwendung eines Multithread-Apartments erstellt werden. Die Verwendung eines Singlethread-Apartments wird unterstützt, führt aber zu einer suboptimalen Leistung und möglicherweise zu Stottern während der Wiedergabe. Bei Verwendung von C++/WinRT ist winrt::init_apartment ein Multithread-Apartment die Standardeinstellung.

Abrufen des Holographic Remoting NuGet-Pakets

Die folgenden Schritte sind erforderlich, um das NuGet-Paket einem Projekt in Visual Studio hinzuzufügen.

  1. Öffnen Sie das Projekt in Visual Studio.
  2. Klicken Sie mit der rechten Maustaste auf den Projektknoten, und wählen Sie NuGet-Pakete verwalten... aus.
  3. Wählen Sie im daraufhin angezeigten Bereich Durchsuchen aus, und suchen Sie dann nach "Holographic Remoting".
  4. Wählen Sie Microsoft.Holographic.Remoting aus, stellen Sie sicher, dass Sie die neueste Version 2.x.x auswählen, und wählen Sie Installieren aus.
  5. Wenn das Dialogfeld Vorschau angezeigt wird, wählen Sie OK aus.
  6. Wählen Sie Ich stimme zu, wenn das Dialogfeld "Lizenzvereinbarung" angezeigt wird.

Hinweis

Version 1.x.x des NuGet-Pakets ist weiterhin für Entwickler verfügbar, die HoloLens 1 als Ziel verwenden möchten. Weitere Informationen finden Sie unter Hinzufügen von Holographic Remoting (HoloLens (1. Generation)).

Erstellen des Remotekontexts

Im ersten Schritt sollte die Anwendung einen Remotekontext erstellen.

// 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);

Warnung

Holographic Remoting funktioniert, indem die Windows Mixed Reality Runtime, die Teil von Windows ist, durch eine Remoting-spezifische Runtime ersetzt wird. Dies geschieht während der Erstellung des Remotekontexts. Aus diesem Grund kann jeder Aufruf einer Windows Mixed Reality-API vor dem Erstellen des Remotekontexts zu unerwartetem Verhalten führen. Der empfohlene Ansatz besteht darin, den Remotekontext so früh wie möglich vor der Interaktion mit einer Mixed Reality-API zu erstellen. Mischen Sie niemals Objekte, die vor dem Aufruf von CreateRemoteContext über eine Windows Mixed Reality-API erstellt oder abgerufen wurden, mit Objekten, die anschließend erstellt oder abgerufen wurden.

Als Nächstes muss der holografische Raum erstellt werden. Die Angabe eines CoreWindow ist nicht erforderlich. Desktop-Apps ohne CoreWindow können einfach eine nullptrübergeben.

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

Stellen Sie eine Verbindung mit dem Gerät her.

Wenn die Remote-App zum Rendern von Inhalten bereit ist, kann eine Verbindung mit dem Playergerät hergestellt werden.

Die Verbindung kann auf zwei Arten hergestellt werden.

  1. Die Remote-App stellt eine Verbindung mit dem Player her, der auf dem Gerät ausgeführt wird.
  2. Der Auf dem Gerät ausgeführte Player stellt eine Verbindung mit der Remote-App her.

Um eine Verbindung zwischen der Remote-App und dem Playergerät herzustellen, rufen Sie die Connect -Methode im Remotekontext auf, um den Hostnamen und port anzugeben. Der vom Holographic Remoting Player verwendete Port ist 8265.

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

Wichtig

Wie bei jeder C++/WinRT-API Connect kann eine winrt::hresult_error ausgelöst werden, die verarbeitet werden muss.

Tipp

Um die Verwendung der C++/WinRT-Sprachprojektion zu vermeiden, kann die Datei build\native\include\<windows sdk version>\abi\Microsoft.Holographic.AppRemoting.h im Holographic Remoting NuGet-Paket eingeschlossen werden. Sie enthält Deklarationen der zugrunde liegenden COM-Schnittstellen. Die Verwendung von C++/WinRT wird jedoch empfohlen.

Das Lauschen auf eingehende Verbindungen in der Remote-App kann durch Aufrufen der Listen -Methode erfolgen. Während dieses Aufrufs können sowohl der Handshakeport als auch der Transportport angegeben werden. Der Handshakeport wird für den ersten Handshake verwendet. Die Daten werden dann über den Transportport gesendet. Standardmäßig werden 8265 und 8266 verwendet.

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());
}

Wichtig

Das build\native\include\HolographicAppRemoting\Microsoft.Holographic.AppRemoting.idl im NuGet-Paket enthaltene ausführliche Dokumentation für die API, die von Holographic Remoting verfügbar gemacht wird.

Behandeln von Remoting-spezifischen Ereignissen

Der Remotekontext macht drei Ereignisse verfügbar, die wichtig sind, um den Status einer Verbindung zu überwachen.

  1. OnConnected: Wird ausgelöst, wenn eine Verbindung mit dem Gerät erfolgreich hergestellt wurde.
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: Wird ausgelöst, wenn eine verbindung geschlossen oder keine Verbindung hergestellt werden konnte.
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: Beim Lauschen auf eingehende Verbindungen wird gestartet.
m_onListeningEventRevoker = m_remoteContext.OnListening(winrt::auto_revoke, [this, remoteContextWeakRef]() {
    if (auto remoteContext = remoteContextWeakRef.get())
    {
        // Update UI state
    }
});

Darüber hinaus kann der Verbindungsstatus mithilfe der ConnectionState -Eigenschaft im Remotekontext abgefragt werden.

auto connectionState = m_remoteContext.ConnectionState();

Behandeln von Sprachereignissen

Über die Remote-Sprachschnittstelle können Sprachtrigger bei HoloLens 2 registriert und an die Remoteanwendung entfernt werden.

Der folgende zusätzliche Member ist erforderlich, um den Status der Remotesprache nachzuverfolgen:

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

Rufen Sie zunächst die Remote-Sprachschnittstelle ab.

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

Mithilfe einer asynchronen Hilfsmethode können Sie dann die Remotesprache initialisieren. Dies sollte asynchron erfolgen, da die Initialisierung viel Zeit in Anspruch nehmen kann. Parallelität und asynchrone Vorgänge mit C++/WinRT erläutert, wie asynchrone Funktionen mit C++/WinRT erstellt werden.

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);
}

Es gibt zwei Möglichkeiten, zu erkennende Ausdrücke anzugeben.

  1. Spezifikation in einer Xml-Datei für die Sprachgrammatik. Weitere Informationen finden Sie unter Erstellen einer einfachen XML-Grammatik .
  2. Geben Sie diese an, indem Sie sie innerhalb des Wörterbuchvektors an ApplyParametersübergeben.

Innerhalb des OnRecognizedSpeech-Rückrufs können dann die Sprachereignisse verarbeitet werden:

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")
    {
        ...
    }

    ...
}

Lokale Vorschau gestreamter Inhalte

Zum Anzeigen desselben Inhalts in der Remote-App, die an das Gerät gesendet wird, kann das OnSendFrame Ereignis des Remotekontexts verwendet werden. Das OnSendFrame Ereignis wird jedes Mal ausgelöst, wenn die Holographic Remoting-Bibliothek den aktuellen Frame an das Remotegerät sendet. Dies ist der ideale Zeitpunkt, um den Inhalt zu übernehmen und ihn auch in das Desktop- oder UWP-Fenster zu öffnen.

#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.
    });

Tiefenprojektion

Ab Version 2.1.0 unterstützt Holographic Remoting die Tiefenreprojektion. Dazu müssen sowohl der Farbpuffer als auch der Tiefenpuffer von der Remoteanwendung an den HoloLens 2 gestreamt werden. Standardmäßig ist das Tiefenpufferstreaming aktiviert und so konfiguriert, dass die halbe Auflösung des Farbpuffers verwendet wird. Dies kann wie folgt geändert werden:

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

...

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

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

Beachten Sie, dass standardwerte nicht verwendet ConfigureDepthVideoStream werden müssen, bevor eine Verbindung mit dem HoloLens 2 hergestellt wird. Der beste Ort ist direkt, nachdem Sie den Remotekontext erstellt haben. Mögliche Werte für DepthBufferStreamResolution sind:

  • Full_Resolution
  • Half_Resolution
  • Quarter_Resolution
  • Deaktiviert (mit Version 2.1.3 hinzugefügt, und bei Verwendung wird kein zusätzlicher Tiefenvideostream erstellt)

Beachten Sie, dass sich die Verwendung eines Tiefenpuffers mit voller Auflösung auch auf die Bandbreitenanforderungen auswirkt und in dem maximalen Bandbreitenwert berücksichtigt werden muss, den Sie für CreateRemoteContextangeben.

Neben der Konfiguration der Auflösung müssen Sie auch einen Tiefenpuffer über HolographicCameraRenderingParameters.CommitDirect3D11DepthBuffer committen.


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);
                }
            });
        }
    });
}

Um zu überprüfen, ob die Tiefenreprojektion bei HoloLens 2 ordnungsgemäß funktioniert, können Sie eine Tiefenvisualisierer über das Geräteportal aktivieren. Weitere Informationen finden Sie unter Überprüfen, ob die Tiefe richtig festgelegt ist .

Optional: Benutzerdefinierte Datenkanäle

Benutzerdefinierte Datenkanäle können verwendet werden, um Benutzerdaten über die bereits bestehende Remotingverbindung zu senden. Weitere Informationen finden Sie unter Benutzerdefinierte Datenkanäle.

Optional: Koordinatensystemsynchronisierung

Ab Version 2.7.0 kann die Koordinatensystemsynchronisierung verwendet werden, um räumliche Daten zwischen dem Player und der Remoteanwendung auszurichten. Weitere Informationen finden Sie unter Übersicht über die Koordinatensystemsynchronisierung mit holographic Remoting.

Weitere Informationen