使用 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 包添加到某个项目,需要执行以下步骤。
- 在 Visual Studio 中打开项目。
- 右键单击项目节点,然后选择“管理 NuGet 包...”
- 在出现的面板中选择“浏览”,然后搜索“全息远程处理”。
- 选择“Microsoft.Holographic.Remoting.OpenXr”,确保已选择最新版本 2.x.x,然后选择“安装”。
- 如果出现了“预览”对话框,请选择“确定”。
- 弹出许可协议对话框时,请选择“我接受”。
- 针对以下 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 运行时只有在建立连接后才能提供设备特定的数据,例如视图配置或环境混合模式。 xrEnumerateViewConfigurations
、xrEnumerateViewConfigurationViews
、xrGetViewConfigurationProperties
、xrEnumerateEnvironmentBlendModes
和 xrGetSystemProperties
将提供默认值,与你在完全连接之前连接到运行在 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);
可通过以下两种方式之一进行连接。
- 远程应用连接到设备上运行的播放器。
- 在设备上运行的播放器连接到远程应用。
若要建立从远程应用到播放器设备的连接,请通过 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);
调用 xrRemotingConnectMSFT
或 xrRemotingListenMSFT
时,连接状态必须为断开连接。 创建 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 之前,必须调用 xrRemotingConnectMSFT
或 xrRemotingListenMSFT
。 如果尝试在连接状态为 XR_REMOTING_CONNECTION_STATE_DISCONNECTED_MSFT
时创建 XrSession,会话创建将成功,但会话状态会立即转换为 XR_SESSION_STATE_LOSS_PENDING。
全息远程处理的 xrCreateSession
实现支持等待建立连接。 可以在调用 xrRemotingConnectMSFT
或 xrRemotingListenMSFT
后立即调用 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 运行时公开三个事件,这三个事件对于监视连接状态非常重要。
XR_TYPE_REMOTING_EVENT_DATA_CONNECTED_MSFT
:在与设备成功建立连接时触发。XR_TYPE_REMOTING_EVENT_DATA_DISCONNECTED_MSFT
:在建立的连接关闭或无法建立连接时触发。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 概述中的“坐标系同步”。