Escrevendo uma app remota holográfica de remoting usando a API HolographicSpace

Se é novo no Remoting Holográfico, pode querer ler a nossa visão geral.

Importante

Este documento descreve a criação de uma aplicação remota para HoloLens 2 utilizando a API Holográfica Do Espaço. As aplicações remotas para HoloLens (1ª gen) devem utilizar a versão 1.x.x . Isto implica que as aplicações remotas escritas para HoloLens 2 não são compatíveis com HoloLens 1 e vice-versa. A documentação para HoloLens 1 pode ser consultada aqui.

As aplicações holográficas de remoting podem transmitir conteúdo remotamente renderizado para HoloLens 2 e Windows Mixed Reality auscultadores imersivos. Também pode aceder a mais recursos do sistema e integrar visões imersivas remotas no software pc de desktop existente. Uma aplicação remota recebe um fluxo de dados de entrada de HoloLens 2, torna o conteúdo numa visão imersiva virtual e transmite quadros de conteúdo de volta a HoloLens 2. A ligação é feita com Wi-Fi padrão. O Remoting Holográfico é adicionado a um desktop ou app UWP através de um pacote NuGet. É necessário um código adicional que manuseie a ligação e torna-se numa visão imersiva. Uma ligação típica de remoing terá até 50 ms de latência. A aplicação do jogador pode reportar a latência em tempo real.

Todos os códigos desta página e projetos de trabalho podem ser encontrados no repositório de amostras holográficas de github.

Pré-requisitos

Um bom ponto de partida é uma aplicação de desktop ou UWP baseada em DirectX que tem como alvo a API Windows Mixed Reality. Para mais detalhes, consulte a visão geral do desenvolvimento do DirectX. O modelo de projeto holográfico C++ é um bom ponto de partida.

Importante

Qualquer aplicação que utilize o Remoting Holográfico deve ser da autoria de utilizar um apartamento com vários fios. O uso de um apartamento com fio único é suportado, mas levará a um desempenho sub-ideal e possivelmente gagueja durante a reprodução. Ao utilizar o winrt C++/WinRT::init_apartment um apartamento com vários fios é o padrão.

Obtenha o pacote Holographic Remoting NuGet

São necessários os seguintes passos para adicionar o pacote NuGet a um projeto em Visual Studio.

  1. Abra o projeto no Visual Studio.
  2. Clique com o botão direito no nó do projeto e selecione Gerir pacotes NuGet...
  3. No painel que aparece, selecione Procurar por "Remoing Holográfico".
  4. Selecione Microsoft.Holographic.Remoting, certifique-se de escolher a versão mais recente 2.x.x e selecione Instalar.
  5. Se aparecer o diálogo de pré-visualização, selecione OK.
  6. Selecione Eu Aceito quando o diálogo do contrato de licença aparece.

Nota

A versão 1.x.x do pacote NuGet ainda está disponível para os desenvolvedores que pretendam HoloLens 1. Para mais detalhes, consulte o Add Holographic Remoting (HoloLens (1ª geração)).

Criar o contexto remoto

Como primeiro passo, a aplicação deve criar um contexto remoto.

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

Aviso

O remoing holográfico funciona substituindo o tempo de execução Windows Mixed Reality que faz parte do Windows por um tempo de funcionamento específico de remoagem. Isto é feito durante a criação do contexto remoto. Por essa razão, qualquer chamada em qualquer Windows Mixed Reality API antes de criar o contexto remoto pode resultar em comportamento inesperado. A abordagem recomendada é criar o contexto remoto o mais cedo possível antes da interação com qualquer API de Realidade Mista. Nunca misture objetos criados ou recuperados através de qualquer Windows Mixed Reality API antes da chamada para CreateRemoteContext com objetos criados ou recuperados posteriormente.

Em seguida, o espaço holográfico precisa de ser criado. Especificar um CoreWindow não é necessário. Aplicativos de desktop que não têm um CoreWindow podem simplesmente passar por um nullptr .

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

Ligação ao dispositivo

Quando a aplicação remota estiver pronta para renderizar conteúdo, pode ser estabelecida uma ligação ao dispositivo do jogador.

A ligação pode ser feita de duas maneiras.

  1. A aplicação remota liga-se ao jogador que está a executar no dispositivo.
  2. O leitor em execução no dispositivo liga-se à aplicação remota.

Para estabelecer uma ligação da aplicação remota ao dispositivo do jogador, ligue para o Connect método no contexto remoto especificando o nome de anfitrião e a porta. A porta utilizada pelo Jogador Holográfico de Remoting é 8265.

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

Importante

Tal como qualquer API C++/WinRT Connect pode lançar um winrt::hresult_error que precisa de ser manuseado.

Dica

Para evitar a utilização da projeção linguística C++/WinRT, o ficheiro localizado dentro do pacote Holográfico Remoting NuGet pode ser incluído. Contém declarações das interfaces COM subjacentes. No entanto, recomenda-se a utilização de C++/WinRT.

A escuta de ligações recebidas na aplicação remota pode ser feita ligando para o Listen método. Tanto o porto de aperto de mão como o porto de transporte podem ser especificados durante esta chamada. A porta de aperto de mão é utilizada para o aperto de mão inicial. Os dados são então enviados para o porto de transporte. Por defeito, são utilizados 8265 e 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());
}

Importante

O build\native\include\HolographicAppRemoting\Microsoft.Holographic.AppRemoting.idl pacote NuGet contém documentação detalhada para a API exposta por Holographic Remoting.

Manipulação de eventos específicos de Remoting

O contexto remoto expõe três eventos, que são importantes para monitorizar o estado de uma ligação.

  1. OnConnected: Desencadeado quando uma ligação ao dispositivo foi estabelecida com sucesso.
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. OnDis ligado: Desencadeado se uma ligação estabelecida estiver fechada ou se não for estabelecida uma ligação.
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: Ao ouvir as ligações recebidas começa.
m_onListeningEventRevoker = m_remoteContext.OnListening(winrt::auto_revoke, [this, remoteContextWeakRef]() {
    if (auto remoteContext = remoteContextWeakRef.get())
    {
        // Update UI state
    }
});

Além disso, o estado de ligação pode ser consultado usando a ConnectionState propriedade no contexto remoto.

auto connectionState = m_remoteContext.ConnectionState();

Lidar com eventos de fala

Utilizando a interface de fala remota é possível registar gatilhos de fala com HoloLens 2 e tê-los remotos para a aplicação remota.

Este membro adicional é necessário para rastrear o estado do discurso remoto.

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

Primeiro, recupere a interface de fala remota.

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

Utilizando um método de ajuda assíncronos, pode rubricar a fala remota. Isto deve ser feito de forma assíncronea, uma vez que a inicialização pode demorar um período considerável de tempo. As operações de concurrency e assíncronas com C++/WinRT explicam como autorar funções assíncronas com 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);
}

Há duas formas de especificar frases a serem reconhecidas.

  1. Especificação dentro de um ficheiro xml de gramática da fala. Veja como criar uma Gramática XML básica para mais detalhes.
  2. Especifique passando-os dentro do vetor do dicionário para ApplyParameters .

Dentro da chamada OnRecognizedSpeech, os eventos de fala podem então ser processados:

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

    ...
}

Pré-visualização de conteúdo transmitido localmente

Para exibir o mesmo conteúdo na aplicação remota que é enviada para o OnSendFrame dispositivo, o caso do contexto remoto pode ser utilizado. O OnSendFrame evento é desencadeado sempre que a biblioteca holográfica de remoting envia a moldura atual para o dispositivo remoto. Este é o momento ideal para pegar no conteúdo e também ablit-lo para o ambiente de trabalho ou para a janela 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.
    });

Reprojecção de profundidade

Começando pela versão 2.1.0,o Remoting Holográfico suporta a Reprojecção de Profundidade. Isto requer que tanto o tampão de cor como o tampão de profundidade sejam transmitidos da aplicação Remota para o HoloLens 2. Por padrão, o streaming de tampão de profundidade está ativado e configurado para utilizar metade da resolução do tampão de cor. Isto pode ser alterado da seguinte forma:

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

...

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

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

Note que se não forem utilizados valores predefinidos ConfigureDepthVideoStream deve ser chamado antes de estabelecer uma ligação ao HoloLens 2. O melhor lugar é logo após ter criado o contexto remoto. Os valores possíveis para a DepthBufferStreamResolution são:

  • Full_Resolution
  • Half_Resolution
  • Quarter_Resolution
  • Desativado (adicionado com a versão 2.1.3 e se for utilizado não é criado nenhum fluxo de vídeo de profundidade adicional)

Tenha em mente que a utilização de um tampão de profundidade de resolução completa também afeta os requisitos de largura de banda e precisa de ser contabilizada no valor máximo de largura de banda a que fornece CreateRemoteContext .

Além de configurar a resolução, também tem de comprometer um tampão de profundidade através de 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);
                }
            });
        }
    });
}

Para verificar se a reprojecção de profundidade está a funcionar corretamente no HoloLens 2, pode ativar um visualizador de profundidade através do Portal do Dispositivo. Consulte A profundidade de verificação está definida corretamente para obter detalhes.

Opcional: Canais de dados personalizados

Os canais de dados personalizados podem ser utilizados para enviar dados do utilizador sobre a ligação de remoização já estabelecida. Para mais informações, consulte os Canais de Dados Personalizados.

Opcional: Coordenar a Sincronização do Sistema

A partir da versão 2.7.0,pode ser utilizada sincronização do sistema de coordenadas para alinhar dados espaciais entre o jogador e a aplicação remota. Para obter mais informações, consulte a Sincronização do Sistema de Coordenadas com a Visão Geral de Remoting Holográfica.

Consulte também