使用 OpenXR API 编写全息远程处理远程应用

如果你是全息远程处理新手,建议阅读概述

重要

本文档介绍如何使用 OpenXR API 创建适用于 HoloLens 2 和 Windows Mixed Reality 头戴显示设备的远程应用程序。 适用于 “HoloLens(第一代)”的远程应用程序必须使用 NuGet 包版本 “1.xx”。这意味着,为 HoloLens 2 编写的远程应用程序与 HoloLens 1 不兼容,反之亦然。 可在此处找到关于 HoloLens 1 的文档。

全息远程处理应用可将远程呈现的内容流式传输到 HoloLens 2 和 Windows Mixed Reality 沉浸式头戴显示设备。 你还可以访问更多系统资源,并将远程沉浸式视图集成到现有的台式电脑软件中。 远程应用从 HoloLens 2 接收输入数据流,在虚拟沉浸式视图中呈现内容,并将内容帧流式传输回 HoloLens 2。 使用标准 Wi-Fi 建立连接。 全息远程处理通过 NuGet 包添加到桌面或 UWP 应用。 需要使用额外的代码来处理连接,并在沉浸式视图中进行呈现。 典型的远程连接会有低至 50 毫秒的延迟。 播放器应用可以实时报告延迟。

本页上的所有代码和工作项目可以在全息远程处理示例 github 存储库中找到。

先决条件

我们可以从一个正在运行的基于 OpenXR 的桌面或 UWP 应用开始。 有关详细信息,请参阅 OpenXR 入门

重要

应将任何使用全息远程处理的应用编写成使用多线程单元。 支持使用单线程单元,但这会导致性能欠佳,并可能在播放过程中出现卡顿。 使用 C++/WinRT winrt::init_apartment 时,多线程单元是默认设置。

获取全息远程处理 NuGet 包

若要在 Visual Studio 中将 NuGet 包添加到某个项目,需要执行以下步骤。

  1. 在 Visual Studio 中打开项目。
  2. 右键单击项目节点,然后选择“管理 NuGet 包...”
  3. 在出现的面板中选择“浏览”,然后搜索“全息远程处理”
  4. 选择“Microsoft.Holographic.Remoting.OpenXr”,确保已选择最新版本 2.x.x,然后选择“安装”
  5. 如果出现了“预览”对话框,请选择“确定”
  6. 弹出许可协议对话框时,请选择“我接受”
  7. 针对以下 NuGet 包重复步骤 3 - 6:OpenXR.Headers、OpenXR.Loader

注意

“1.xx” 版本的 NuGet 包仍然可供要将 HoloLens 1 作为目标的开发人员使用。 有关详细信息,请参阅添加 Holographic Remoting(HoloLens(第一代))

选择全息远程处理 OpenXR 运行时

在远程应用中需要执行的第一步是选择全息远程处理 OpenXR 运行时,它是 Microsoft.Holographic.Remoting.OpenXr NuGet 包的一部分。 为此,可以将 XR_RUNTIME_JSON 环境变量设置为应用中 RemotingXR.json 文件的路径。 OpenXR 加载程序使用此环境变量来避免使用系统默认的 OpenXR 运行时,而是重定向到全息远程处理 OpenXR 运行时。 使用 Microsoft.Holographic.Remoting.OpenXr NuGet 包时,RemotingXR.json 文件会在编译期间自动复制到输出文件夹,OpenXR 运行时选择通常如下所示。

bool EnableRemotingXR() {
    wchar_t executablePath[MAX_PATH];
    if (GetModuleFileNameW(NULL, executablePath, ARRAYSIZE(executablePath)) == 0) {
        return false;
    }
    
    std::filesystem::path filename(executablePath);
    filename = filename.replace_filename("RemotingXR.json");

    if (std::filesystem::exists(filename)) {
        SetEnvironmentVariableW(L"XR_RUNTIME_JSON", filename.c_str());
            return true;
        }

    return false;
}

使用全息远程处理扩展创建 XrInstance

典型的 OpenXR 应用应执行的第一个操作是选择 OpenXR 扩展并创建 XrInstance。 OpenXR 核心规范不提供任何特定于远程处理的 API。 为此,全息远程处理引入了其自己的名为 XR_MSFT_holographic_remoting 的 OpenXR 扩展。 确保 XR_MSFT_HOLOGRAPHIC_REMOTING_EXTENSION_NAME 包含在 xrCreateInstance 调用的 XrInstanceCreateInfo 中。

提示

默认情况下,应用的呈现内容仅流式传输给在 HoloLens 2 或 Windows Mixed Reality 头戴显示设备上运行的全息远程处理播放器。 为了在远程电脑上同样显示呈现的内容,例如,通过窗口的交换链,全息远程处理提供名为 XR_MSFT_holographic_remoting_frame_mirroring 的第二个 OpenXR 扩展。 如果要使用该功能,务必还要使用 XR_MSFT_HOLOGRAPHIC_REMOTING_FRAME_MIRRORING_EXTENSION_NAME 启用此扩展。

重要

若要了解全息远程处理 OpenXR 扩展 API,请查看可在全息远程处理示例 github 存储库中找到的规范

连接到设备

远程应用创建 XrInstance 并通过 xrGetSystem 查询 XrSystemId 后,即可建立到播放器设备的连接。

警告

全息远程处理 OpenXR 运行时只有在建立连接后才能提供设备特定的数据,例如视图配置或环境混合模式。 xrEnumerateViewConfigurationsxrEnumerateViewConfigurationViewsxrGetViewConfigurationPropertiesxrEnumerateEnvironmentBlendModesxrGetSystemProperties 将提供默认值,与你在完全连接之前连接到运行在 HoloLens 2 上的播放器时通常会获得的值匹配。 强烈建议在建立连接之前不要调用这些方法。 建议在成功创建 XrSession 并且会话状态至少为 XR_SESSION_STATE_READY 之后再使用这些方法。

可通过 xrRemotingSetContextPropertiesMSFT 配置常规属性,例如最大比特率、启用的音频、视频编解码器或深度缓冲区流分辨率,如下所示。

XrRemotingRemoteContextPropertiesMSFT contextProperties;
contextProperties = XrRemotingRemoteContextPropertiesMSFT{static_cast<XrStructureType>(XR_TYPE_REMOTING_REMOTE_CONTEXT_PROPERTIES_MSFT)};
contextProperties.enableAudio = false;
contextProperties.maxBitrateKbps = 20000;
contextProperties.videoCodec = XR_REMOTING_VIDEO_CODEC_H265_MSFT;
contextProperties.depthBufferStreamResolution = XR_REMOTING_DEPTH_BUFFER_STREAM_RESOLUTION_HALF_MSFT;
xrRemotingSetContextPropertiesMSFT(m_instance.Get(), m_systemId, &contextProperties);

可通过以下两种方式之一进行连接。

  1. 远程应用连接到设备上运行的播放器。
  2. 在设备上运行的播放器连接到远程应用。

若要建立从远程应用到播放器设备的连接,请通过 XrRemotingConnectInfoMSFT 结构指定主机名和端口以调用 xrRemotingConnectMSFT 方法。 Holographic Remoting 播放器使用的端口是 8265

XrRemotingConnectInfoMSFT connectInfo{static_cast<XrStructureType>(XR_TYPE_REMOTING_CONNECT_INFO_MSFT)};
connectInfo.remoteHostName = "192.168.x.x";
connectInfo.remotePort = 8265;
connectInfo.secureConnection = false;
xrRemotingConnectMSFT(m_instance.Get(), m_systemId, &connectInfo);

可以通过调用 xrRemotingListenMSFT 方法来侦听远程应用上的传入连接。 可以通过 XrRemotingListenInfoMSFT 结构指定握手端口和传输端口。 握手端口用于初始握手。 然后通过传输端口发送数据。 默认使用 8265 和 8266

XrRemotingListenInfoMSFT listenInfo{static_cast<XrStructureType>(XR_TYPE_REMOTING_LISTEN_INFO_MSFT)};
listenInfo.listenInterface = "0.0.0.0";
listenInfo.handshakeListenPort = 8265;
listenInfo.transportListenPort = 8266;
listenInfo.secureConnection = false;
xrRemotingListenMSFT(m_instance.Get(), m_systemId, &listenInfo);

调用 xrRemotingConnectMSFTxrRemotingListenMSFT 时,连接状态必须为断开连接。 创建 XrInstance 并通过 xrRemotingGetConnectionStateMSFT 查询 XrSystemId 后,可以随时获取连接状态。

XrRemotingConnectionStateMSFT connectionState;
xrRemotingGetConnectionStateMSFT(m_instance.Get(), m_systemId, &connectionState, nullptr);

可用的连接状态包括:

  • XR_REMOTING_CONNECTION_STATE_DISCONNECTED_MSFT
  • XR_REMOTING_CONNECTION_STATE_CONNECTING_MSFT
  • XR_REMOTING_CONNECTION_STATE_CONNECTED_MSFT

重要

在尝试通过 xrCreateSession 创建 XrSession 之前,必须调用 xrRemotingConnectMSFTxrRemotingListenMSFT。 如果尝试在连接状态为 XR_REMOTING_CONNECTION_STATE_DISCONNECTED_MSFT 时创建 XrSession,会话创建将成功,但会话状态会立即转换为 XR_SESSION_STATE_LOSS_PENDING。

全息远程处理的 xrCreateSession 实现支持等待建立连接。 可以在调用 xrRemotingConnectMSFTxrRemotingListenMSFT 后立即调用 xrCreateSession,这将阻止并等待建立连接。 xrRemotingConnectMSFT 的超时固定为 10 秒,而 xrRemotingListenMSFT 的超时则不受限制。 如果可以在此时段内建立连接,XrSession 创建将成功,并且会话状态将转换为 XR_SESSION_STATE_READY。 如果不能建立连接,会话创建也会成功,但状态会立即转换为 XR_SESSION_STATE_LOSS_PENDING。

一般来说,连接状态与 XrSession 状态是成对的。 对连接状态的任何更改也会影响会话状态。 例如,如果连接状态从 XR_REMOTING_CONNECTION_STATE_CONNECTED_MSFT 切换到 XR_REMOTING_CONNECTION_STATE_DISCONNECTED_MSFT,会话状态也将转换为 XR_SESSION_STATE_LOSS_PENDING。

处理远程处理特定事件

全息远程处理 OpenXR 运行时公开三个事件,这三个事件对于监视连接状态非常重要。

  1. XR_TYPE_REMOTING_EVENT_DATA_CONNECTED_MSFT:在与设备成功建立连接时触发。
  2. XR_TYPE_REMOTING_EVENT_DATA_DISCONNECTED_MSFT:在建立的连接关闭或无法建立连接时触发。
  3. XR_TYPE_REMOTING_EVENT_DATA_LISTENING_MSFT:在开始侦听传入连接时触发。

这些事件被放在一个队列中,远程应用必须通过 xrPollEvent 定期从队列中读取。

auto pollEvent = [&](XrEventDataBuffer& eventData) -> bool {
	eventData.type = XR_TYPE_EVENT_DATA_BUFFER;
	eventData.next = nullptr;
	return CHECK_XRCMD(xrPollEvent(m_instance.Get(), &eventData)) == XR_SUCCESS;
};

XrEventDataBuffer eventData{};
while (pollEvent(eventData)) {
	switch (eventData.type) {
	
	...
	
	case XR_TYPE_REMOTING_EVENT_DATA_LISTENING_MSFT: {
		DEBUG_PRINT("Holographic Remoting: Listening on port %d",
					reinterpret_cast<const XrRemotingEventDataListeningMSFT*>(&eventData)->listeningPort);
		break;
	}
	case XR_TYPE_REMOTING_EVENT_DATA_CONNECTED_MSFT: {
		DEBUG_PRINT("Holographic Remoting: Connected.");
		break;
	}
	case XR_TYPE_REMOTING_EVENT_DATA_DISCONNECTED_MSFT: {
		DEBUG_PRINT("Holographic Remoting: Disconnected - Reason: %d",
					reinterpret_cast<const XrRemotingEventDataDisconnectedMSFT*>(&eventData)->disconnectReason);
		break;
	}
}

在本地预览流式传输内容

若要在远程应用中显示发送到设备的相同内容,可以使用 XR_MSFT_holographic_remoting_frame_mirroring 扩展。 通过此扩展,可以使用未链接至 XrFrameEndInfo 的 XrRemotingFrameMirrorImageInfoMSFT 将纹理提交到 xrEndFrame,如下所示。

XrFrameEndInfo frameEndInfo{XR_TYPE_FRAME_END_INFO};
...

XrRemotingFrameMirrorImageD3D11MSFT mirrorImageD3D11{
    static_cast<XrStructureType>(XR_TYPE_REMOTING_FRAME_MIRROR_IMAGE_D3D11_MSFT)};
mirrorImageD3D11.texture = m_window->GetNextSwapchainTexture();

XrRemotingFrameMirrorImageInfoMSFT mirrorImageEndInfo{
    static_cast<XrStructureType>(XR_TYPE_REMOTING_FRAME_MIRROR_IMAGE_INFO_MSFT)};
mirrorImageEndInfo.image = reinterpret_cast<const XrRemotingFrameMirrorImageBaseHeaderMSFT*>(&mirrorImageD3D11);

frameEndInfo.next = &mirrorImageEndInfo;

xrEndFrame(m_session.Get(), &frameEndInfo);

m_window->PresentSwapchain();

上面的示例使用 DX11 交换链纹理,在调用 xrEndFrame 后立即显示窗口。 此用法并不限于交换链纹理。 此外,无需其他 GPU 同步。 有关用法和约束的详细信息,请查看扩展规范。 如果远程应用使用的是 DX12,请使用 XrRemotingFrameMirrorImageD3D12MSFT 而不是 XrRemotingFrameMirrorImageD3D11MSFT。

可选:自定义数据通道

从版本 2.5.0 开始,自定义数据通道可以与 OpenXR API 一起使用,以通过已建立的远程处理连接发送用户数据。 有关详细信息,请参阅使用 OpenXR API 自定义数据通道

可选:语音

从版本 2.6.0 开始,XR_MSFT_holographic_remoting_speech 扩展允许远程应用使用 OpenXR API 响应播放器应用检测到的语音命令。

[!重要说明] 可在全息远程处理示例 github 存储库中找到详细的规范

若要在播放器应用上初始化语音识别器,远程应用可以调用 xrInitializeRemotingSpeechMSFT。 此调用将语音初始化参数(由一种语言、短语字典和语法文件内容组成)传输到播放器应用。

注意

在版本 2.6.1 之前,每个 XrSession 只能初始化语音识别器一次。

如果语音识别器创建成功,(如 XR_TYPE_EVENT_DATA_REMOTING_SPEECH_RECOGNIZER_STATE_CHANGED_MSFT 事件所示),当播放器应用上生成了语音识别结果时,远程应用将收到通知。 当播放器端上的语音识别器状态发生变化时,XrEventDataRemotingSpeechRecognizerStateChangedMSFT 事件结构会置于事件队列中。

XrRemotingSpeechRecognizerStateMSFT 定义播放器端上的语音识别器的所有可能状态,如果播放器端上的语音识别器具有已识别的短语,则 XrEventDataRemotingSpeechRecognizedMSFT 事件结构会置于事件队列中。 在远程应用收到有关已识别短语的通知后,它可以通过调用 xrRetrieveRemotingSpeechRecognizedTextMSFT 来检索已识别的短语。

注意

XrRemotingSpeechRecognitionConfidenceMSFT 是 Windows 语音识别 API 随语音识别结果一起返回的 SpeechRecognitionConfidence 枚举的一个直接映射。

可选:坐标系统同步

从版本 2.7.0 开始,坐标系统同步可用于对齐播放器与远程应用之间的空间数据。 有关详细信息,请参阅 Holographic Remoting 概述中的“坐标系同步”

另请参阅