Escrever um aplicativo Remoto de Comunicação Remota Holográfica usando a API do HolographicSpace

Se você não estiver familiarizado com a Comunicação Remota Holográfica, talvez queira ler nossa visão geral.

Importante

Este documento descreve a criação de um aplicativo remoto para HoloLens 2 usando a API do HolographicSpace. Aplicativos remotos para HoloLens (1ª geração) devem usar o pacote NuGet versão 1.x.x. Isso implica que aplicativos remotos gravados para HoloLens 2 não são compatíveis com o HoloLens 1 e vice-versa. A documentação do HoloLens 1 pode ser encontrada aqui.

Aviso de substituição: a linha de lançamento 2.9.x será a última a dar suporte a APIs Holográficas do Windows para desenvolvimento de aplicativos. As próximas versões só darão suporte ao OpenXR para desenvolvimento de aplicativos. Independentemente disso, recomendamos o uso do OpenXR em seu aplicativo para todo o novo desenvolvimento de aplicativos. Os aplicativos existentes que usam 2.9 ou mais antigos continuarão a funcionar não afetados pelas alterações futuras.

Os aplicativos de Comunicação Remota Holográfica podem transmitir conteúdo renderizado remotamente para HoloLens 2 e Windows Mixed Reality headsets imersivos. Você também pode acessar mais recursos do sistema e integrar exibições imersivas remotas ao software de computador desktop existente. Um aplicativo remoto recebe um fluxo de dados de entrada de HoloLens 2, renderiza o conteúdo em uma exibição imersiva virtual e transmite quadros de conteúdo de volta para HoloLens 2. A conexão é feita usando Wi-Fi padrão. A Comunicação Remota Holográfica é adicionada a um aplicativo da área de trabalho ou UWP por meio de um pacote NuGet. É necessário código adicional que manipula a conexão e renderiza em uma exibição imersiva. Uma conexão remota típica terá até 50 ms de latência. O aplicativo player pode relatar a latência em tempo real.

Todos os códigos nesta página e projetos de trabalho podem ser encontrados no repositório github de exemplos de Comunicação Remota Holográfica.

Pré-requisitos

Um bom ponto de partida é um aplicativo UWP ou área de trabalho baseado em DirectX direcionado à API Windows Mixed Reality. Para obter detalhes, confira Visão geral do desenvolvimento do DirectX. O modelo de projeto holográfico do C++ é um bom ponto de partida.

Importante

Qualquer aplicativo que use a Comunicação Remota Holográfica deve ser criado para usar um apartment de vários threads. Há suporte para o uso de um apartamento de thread único , mas levará a um desempenho abaixo do ideal e possivelmente gaguejar durante a reprodução. Ao usar C++/WinRT winrt::init_apartment um apartamento multi-threaded é o padrão.

Obter o pacote NuGet de Comunicação Remota Holográfica

As etapas a seguir são necessárias para adicionar o pacote NuGet a um projeto no Visual Studio.

  1. Abra o projeto no Visual Studio.
  2. Clique com o botão direito do mouse no nó do projeto e selecione Gerenciar Pacotes NuGet...
  3. No painel exibido, selecione Procurar e, em seguida, pesquise "Comunicação Remota Holográfica".
  4. Selecione Microsoft.Holographic.Remoting, escolha a versão mais recente do 2.x.x e selecione Instalar.
  5. Se a caixa de diálogo Visualizar for exibida, selecione OK.
  6. Selecione Aceito quando a caixa de diálogo do contrato de licença for exibida.

Observação

A versão 1.x.x do pacote NuGet ainda está disponível para desenvolvedores que desejam direcionar o HoloLens 1. Para obter detalhes, consulte Adicionar Comunicação Remota Holográfica (HoloLens (1ª geração)).

Criar o contexto remoto

Como primeira etapa, o aplicativo 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

A Comunicação Remota Holográfica funciona substituindo o runtime Windows Mixed Reality que faz parte do Windows por um runtime específico de comunicação remota. Isso é feito durante a criação do contexto remoto. Por esse motivo, qualquer chamada em qualquer API Windows Mixed Reality antes de criar o contexto remoto pode resultar em um comportamento inesperado. A abordagem recomendada é criar o contexto remoto o mais cedo possível antes da interação com qualquer API Realidade Misturada. Nunca misture objetos criados ou recuperados por meio de qualquer API Windows Mixed Reality antes da chamada para CreateRemoteContext com objetos criados ou recuperados posteriormente.

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

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

Conectar-se ao dispositivo

Quando o aplicativo remoto estiver pronto para renderizar conteúdo, uma conexão com o dispositivo player poderá ser estabelecida.

A conexão pode ser feita de duas maneiras.

  1. O aplicativo remoto se conecta ao player em execução no dispositivo.
  2. O player em execução no dispositivo se conecta ao aplicativo remoto.

Para estabelecer uma conexão do aplicativo remoto com o dispositivo player, chame o Connect método no contexto remoto especificando o nome do host e a porta. A porta usada pelo Player de Comunicação Remota Holográfica é 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

Assim como acontece com qualquer API Connect C++/WinRT, pode gerar uma winrt::hresult_error que precisa ser tratada.

Dica

Para evitar o uso da projeção de linguagem C++/WinRT , o arquivo build\native\include\<windows sdk version>\abi\Microsoft.Holographic.AppRemoting.h localizado dentro do pacote NuGet de Comunicação Remota Holográfica pode ser incluído. Ele contém declarações das interfaces COM subjacentes. No entanto, o uso de C++/WinRT é recomendado.

A escuta de conexões de entrada no aplicativo remoto pode ser feita chamando o Listen método . A porta handshake e a porta de transporte podem ser especificadas durante essa chamada. A porta handshake é usada para o handshake inicial. Em seguida, os dados são enviados pela porta de transporte. Por padrão , 8265 e 8266 são usados.

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 dentro do pacote NuGet contém a documentação detalhada da API exposta pela Comunicação Remota Holográfica.

Tratamento de eventos específicos de comunicação remota

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

  1. OnConnected: disparado quando uma conexão com o dispositivo foi estabelecida com êxito.
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: disparado se uma conexão estabelecida for fechada ou uma conexão não puder ser estabelecida.
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: quando a escuta para conexões de entrada é iniciada.
m_onListeningEventRevoker = m_remoteContext.OnListening(winrt::auto_revoke, [this, remoteContextWeakRef]() {
    if (auto remoteContext = remoteContextWeakRef.get())
    {
        // Update UI state
    }
});

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

auto connectionState = m_remoteContext.ConnectionState();

Manipulando eventos de fala

Usando a interface de fala remota, é possível registrar gatilhos de fala com HoloLens 2 e fazer com que eles sejam remotos para o aplicativo remoto.

O seguinte membro extra é necessário para acompanhar o estado da fala remota:

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

Usando um método auxiliar assíncrono, você pode inicializar a fala remota. Isso deve ser feito de forma assíncrona, pois a inicialização pode levar uma quantidade considerável de tempo. Operações simultâneas e assíncronas com C++/WinRT explica como criar 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 maneiras de especificar frases a serem reconhecidas.

  1. Especificação dentro de um arquivo xml de gramática de fala. Confira Como criar uma gramática XML básica para obter detalhes.
  2. Especifique passando-os dentro do vetor de dicionário para ApplyParameters.

Dentro do retorno de chamada OnRecognizedSpeech, os eventos de fala podem 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")
    {
        ...
    }

    ...
}

Visualizar conteúdo transmitido localmente

Para exibir o mesmo conteúdo no aplicativo remoto que é enviado para o dispositivo, o OnSendFrame evento do contexto remoto pode ser usado. O OnSendFrame evento é disparado sempre que a biblioteca de Comunicação Remota Holográfica envia o quadro atual para o dispositivo remoto. Esse é o momento ideal para levar o conteúdo e também acessá-lo na janela da área de trabalho ou da 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.
    });

Reprojeção de profundidade

A partir da versão 2.1.0, a Comunicação Remota Holográfica dá suporte à Reprojeção de Profundidade. Isso requer que o buffer de cores e o buffer de profundidade sejam transmitidos do aplicativo Remoto para o HoloLens 2. Por padrão, o streaming de buffer de profundidade está habilitado e configurado para usar metade da resolução do buffer de cores. Isso pode ser alterado da seguinte maneira:

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

...

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

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

Observe que, se os valores padrão não devem ser usadosConfigureDepthVideoStream, deve ser chamado antes de estabelecer uma conexão com o HoloLens 2. O melhor lugar é logo após você ter criado o contexto remoto. Os valores possíveis para DepthBufferStreamResolution são:

  • Full_Resolution
  • Half_Resolution
  • Quarter_Resolution
  • Desabilitado (adicionado com a versão 2.1.3 e, se usado, nenhum fluxo de vídeo de profundidade adicional é criado)

Tenha em mente que o uso de um buffer de profundidade de resolução completa também afeta os requisitos de largura de banda e precisa ser contabilizado no valor máximo de largura de banda que você fornece ao CreateRemoteContext.

Além de configurar a resolução, você também precisa confirmar um buffer de profundidade por meio 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 reprojeção de profundidade está funcionando corretamente em HoloLens 2, você pode habilitar um visualizador de profundidade por meio do Portal de Dispositivos. Confira Verificar se a profundidade está definida corretamente para obter detalhes.

Opcional: canais de dados personalizados

Canais de dados personalizados podem ser usados para enviar dados do usuário pela conexão remota já estabelecida. Para obter mais informações, consulte Canais de dados personalizados.

Opcional: sincronização do sistema de coordenadas

A partir da versão 2.7.0, a sincronização do sistema de coordenadas pode ser usada para alinhar dados espaciais entre o player e o aplicativo remoto. Para obter mais informações, consulte Visão geral da sincronização do sistema de coordenadas com a comunicação remota holográfica.

Consulte Também