Escrevendo um aplicativo remoto de remoção holográfica usando a API do HolographicSpace

Se você for novo no Holographic Remoting, talvez queira ler nossa visão geral.

Importante

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

Os aplicativos de remotação 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 do 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 o Wi-Fi padrão. A remota holográfica é adicionada a um aplicativo de área de trabalho ou UWP por meio de um NuGet pacote. É necessário um código adicional que trata a conexão e renderiza em uma exibição imersiva. Uma conexão de remoting típica terá até 50 ms de latência. O aplicativo player pode relatar a latência em tempo real.

Todo o código nesta página e projetos de trabalho podem ser encontrados no repositório GitHub de exemplos de Remo holográfica.

Pré-requisitos

Um bom ponto de partida é um aplicativo UWP ou Área de Trabalho baseado em DirectX que tem como destino a API Windows Mixed Reality. Para obter detalhes, consulte Visão geral do desenvolvimento do DirectX. O modelo de projeto holográfico C++ é um bom ponto de partida.

Importante

Qualquer aplicativo que use a Holographic Remoting deve ser autor para usar um apartment multi-threaded. Há suporte para o uso de um apartment de thread único, mas levará ao desempenho abaixo do ideal e possivelmente à gagueja durante a reprodução. Ao usar C++/WinRT winrt::init_apartment um apartment multi-threaded é o padrão.

Obter o pacote de NuGet Holographic Remoting

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 NuGet Pacotes...
  3. No painel exibido, selecione Procurar e, em seguida, pesquise "Holographic Remoting".
  4. Selecione Microsoft.Holographic.Remoting, escolha a versão 2.x.x mais recente e selecione Instalar.
  5. Se a caixa de diálogo Visualização 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 querem direcionar HoloLens 1. Para obter detalhes, consulte Adicionar a HoloLens holográfica (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 remoção holográfica funciona substituindo o runtime Windows Mixed Reality que faz parte do Windows por um runtime específico de remoção. 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 comportamento inesperado. A abordagem recomendada é criar o contexto remoto o mais cedo possível antes da interação com Realidade Misturada API. 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. Não é necessário especificar um CoreWindow. Aplicativos da área de trabalho que não têm um CoreWindow podem passar apenas um nullptr.

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

Conexão para o 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, Connect chame o método no contexto remoto especificando o nome do host e a porta. A porta usada pelo 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());
}

Importante

Assim como com qualquer API Connect do C++/WinRT pode lançar um winrt::hresult_error que precisa ser tratado.

Dica

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

A escuta de conexões de entrada no aplicativo remoto pode ser feita chamando o Listen método . A porta de handshake e a porta de transporte podem ser especificadas durante essa chamada. A porta de 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 documentação detalhada para a API exposta pela Holographic Remoting.

Manipulando eventos específicos de remoting

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 não for possível estabelecer uma conexã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 escutar conexões de entrada é iniciado.
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 remotos para o aplicativo remoto.

O membro extra a seguir é 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 inicializar pode levar um tempo considerável. A concurreência e as operações assíncronas com C++/WinRT explica como a adoção de 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. Consulte 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 ao dispositivo, OnSendFrame o evento do contexto remoto pode ser usado. O OnSendFrame evento é disparado sempre que a biblioteca de remoção holográfica envia o quadro atual para o dispositivo remoto. Esse é o momento ideal para pegar o conteúdo e também blit-lo na área de trabalho ou na 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.
    });

Reprodução de profundidade

A partir da versão 2.1.0, a Holographic Remoting dá suporte à Reprojetação de Profundidade. Isso exige 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 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);

Observe que se os valores padrão não devem ser usados devem ConfigureDepthVideoStream ser chamados 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 contabilado no valor máximo de largura de banda que você fornece ao CreateRemoteContext.

Além de configurar a resolução, você também precisa fazer commit de 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 reprojetação de profundidade está funcionando corretamente no 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

Os canais de dados personalizados podem ser usados para enviar dados do usuário pela conexão de comunicação remo 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 remoção holográfica.

Consulte Também