Escrever uma aplicação Holographic Remoting Remote com a API holographicSpace

Se não estiver familiarizado com a Comunicação Remota Holográfica, poderá querer ler a nossa descrição geral.

Importante

Este documento descreve a criação de uma aplicação remota para HoloLens 2 com a API holographicSpace. As aplicações remotas para o HoloLens (1.ª geração) têm de utilizar a versão do pacote NuGet 1.x.x. Isto implica que as aplicações remotas escritas 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 preterição: a linha de versão 2.9.x será a última a suportar APIs do Windows Holographic para desenvolvimento de aplicações. As versões futuras só suportarão o OpenXR para desenvolvimento de aplicações. Independentemente disso, recomendamos a utilização do OpenXR na sua aplicação para todo o desenvolvimento de novas aplicações. As aplicações existentes com a versão 2.9 ou mais antiga continuarão a funcionar sem serem afetadas pelas alterações futuras.

As aplicações Holographic Remoting podem transmitir conteúdos compostos remotamente para HoloLens 2 e Windows Mixed Reality headsets envolventes. Também pode aceder a mais recursos do sistema e integrar vistas envolventes remotas em software de PC de secretária existente. Uma aplicação remota recebe um fluxo de dados de entrada de HoloLens 2, compõe conteúdos numa vista envolvente virtual e transmite os pacotes de conteúdo de volta para HoloLens 2. A ligação é efetuada com Wi-Fi padrão. A Comunicação Remota Holográfica é adicionada a um ambiente de trabalho ou a uma aplicação UWP através de um pacote NuGet. É necessário código adicional que processa a ligação e é composto numa vista envolvente. Uma ligação remota típica terá até 50 ms de latência. A aplicação player pode comunicar 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 é uma aplicação UWP ou de Ambiente de Trabalho baseada em DirectX que se destina à API de Windows Mixed Reality. Para obter detalhes, veja DirectX development overview (Descrição geral do desenvolvimento do DirectX). O modelo de projeto holográfico C++ é um bom ponto de partida.

Importante

Qualquer aplicação que utilize a Comunicação Remota Holográfica deve ser criada para utilizar um apartamento multi-threads. A utilização de um apartamento de thread único é suportada, mas levará a um desempenho sub-ideal e possivelmente gaguez durante a reprodução. Ao utilizar C++/WinRT winrt::init_apartment um apartamento com vários threads é a predefinição.

Obter o pacote Holographic Remoting NuGet

Os passos seguintes são necessários 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 rato no nó do projeto e selecione Gerir Pacotes NuGet...
  3. No painel apresentado, selecione Procurar e, em seguida, procure "Comunicação Remota Holográfica".
  4. Selecione Microsoft.Holographic.Remoting, certifique-se de que escolhe a versão 2.x.x mais recente e selecione Instalar.
  5. Se a caixa de diálogo Pré-visualizar for apresentada, selecione OK.
  6. Selecione Aceito quando a caixa de diálogo do contrato de licença é apresentada.

Nota

A versão 1.x.x do pacote NuGet ainda está disponível para programadores que pretendam ter como destino o HoloLens 1. Para obter detalhes, consulte Adicionar Comunicação Remota Holográfica (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

A Comunicação Remota Holográfica funciona ao substituir o runtime Windows Mixed Reality que faz parte do Windows por um runtime específico de comunicação remota. Isto é 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 num comportamento inesperado. A abordagem recomendada é criar o contexto remoto o mais cedo possível antes da interação com qualquer API Mixed Reality. Nunca misture objetos criados ou obtidos através de qualquer API Windows Mixed Reality antes da chamada para CreateRemoteContext com objetos criados ou obtidos posteriormente.

Em seguida, é necessário criar o espaço holográfico. Não é necessário especificar um CoreWindow. As aplicações de ambiente de trabalho que não tenham um CoreWindow podem simplesmente passar um nullptr.

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

Ligar ao dispositivo

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

A ligação pode ser feita de uma de duas formas.

  1. A aplicação remota liga-se ao leitor em execução 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 leitor, chame o Connect método no contexto remoto especificando o nome do anfitrião e a porta. A porta utilizada 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

Tal como acontece com qualquer API Connect C++/WinRT, pode gerar um winrt::hresult_error que tem de ser processado.

Dica

Para evitar utilizar a projeção de idioma C++/WinRT , o ficheiro build\native\include\<windows sdk version>\abi\Microsoft.Holographic.AppRemoting.h localizado dentro do pacote NuGet Remoting Holographic 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 ao chamar o Listen método . A porta de handshake e a porta de transporte podem ser especificadas durante esta chamada. A porta de handshake é utilizada para o handshake inicial. Em seguida, os dados são enviados através da porta de transporte. Por predefinição, 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 no interior contém documentação detalhada para a API exposta pela Comunicação Remota Holográfica.

Handling Remoting specific events (Processar eventos específicos)

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

  1. OnConnected: acionado quando uma ligação ao 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: acionado se uma ligação estabelecida estiver fechada ou se não for possível estabelecer 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 escutar ligações recebidas, é iniciado.
m_onListeningEventRevoker = m_remoteContext.OnListening(winrt::auto_revoke, [this, remoteContextWeakRef]() {
    if (auto remoteContext = remoteContextWeakRef.get())
    {
        // Update UI state
    }
});

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

auto connectionState = m_remoteContext.ConnectionState();

Processar eventos de voz

Ao utilizar a interface de voz remota, é possível registar acionadores de voz com HoloLens 2 e fazer com que sejam remotos para a aplicação remota.

É necessário o seguinte membro adicional para controlar o estado da voz remota:

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

Primeiro, obtenha a interface de voz remota.

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

Ao utilizar um método auxiliar assíncrono, pode inicializar a voz remota. Isto deve ser feito de forma assíncrona, uma vez que a inicialização pode demorar um período de tempo considerável. A simultaneidade e as operações 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);
}

Existem duas formas de especificar expressões a serem reconhecidas.

  1. Especificação dentro de um ficheiro xml de gramática de voz. Veja Como criar uma Gramática XML básica para obter detalhes.
  2. Especifique ao transmiti-los dentro do vetor do dicionário para ApplyParameters.

Na chamada de retorno OnRecognizedSpeech, os eventos de voz 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")
    {
        ...
    }

    ...
}

Pré-visualizar conteúdo transmitido em fluxo localmente

Para apresentar o mesmo conteúdo na aplicação remota que é enviada para o dispositivo, o OnSendFrame evento do contexto remoto pode ser utilizado. O OnSendFrame evento é acionado sempre que a biblioteca Holographic Remoting envia a moldura atual para o dispositivo remoto. Este é o momento ideal para levar o conteúdo e utilizá-lo na janela de ambiente de trabalho ou 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.
    });

Proteção de Profundidade

A partir da versão 2.1.0, a Comunicação Remota Holográfica suporta a Proteção de Profundidade. Isto requer que a memória intermédia de cores e a memória intermédia de profundidade sejam transmitidas da aplicação Remota para a HoloLens 2. Por predefinição, a transmissão em fluxo da memória intermédia de profundidade está ativada e configurada para utilizar metade da resolução da memória intermédia de cores. 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);

Tenha em atenção que, se os valores predefinidos não devem ser utilizadosConfigureDepthVideoStream, tem de ser chamado antes de estabelecer uma ligação ao HoloLens 2. O melhor local é logo após ter criado o contexto remoto. Os valores possíveis para DepthBufferStreamResolution são:

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

Tenha em atenção que a utilização de uma memória intermédia de profundidade de resolução completa também afeta os requisitos de largura de banda e tem de ser contabilizada no valor máximo de largura de banda que fornecer a CreateRemoteContext.

Além de configurar a resolução, também tem de consolidar uma memória intermédia 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 reprojeção de profundidade está a funcionar corretamente no HoloLens 2, pode ativar um visualizador de profundidade através do Portal do Dispositivo. Consulte Verificar se a Profundidade está Definida Corretamente para obter detalhes.

Opcional: Canais de dados personalizados

Os canais de dados personalizados podem ser utilizados para enviar dados de utilizador através da ligação remota já estabelecida. Para obter mais informações, veja Canais de Dados Personalizados.

Opcional: Coordenar a Sincronização do Sistema

A partir da versão 2.7.0, a sincronização do sistema coordenada pode ser utilizada para alinhar dados espaciais entre o leitor e a aplicação remota. Para obter mais informações, veja Coordenar a Sincronização do Sistema com a Descrição Geral da Remoting Holográfica.

Consulte também