使用 HolographicSpace API 撰寫全像攝影遠端應用程式
如果您不熟悉全像攝影遠端,建議您 閱讀我們的概觀。
重要事項
本文件說明如何使用 HolographicSpace API 為 HoloLens 2 建立遠端應用程式。 HoloLens (第 1 代) 的遠端應用程式必須使用 NuGet 套件 1.x.x 版。這表示針對 HoloLens 2 撰寫的遠端應用程式與 HoloLens 1 不相容,反之亦然。 您可以 在這裡找到 HoloLens 1 的檔案。
取代注意事項:2.9.x 版本行將是支援 Windows 全像攝影 API 進行應用程式開發的最後一行。 即將推出的版本將僅支援 OpenXR 進行應用程式開發。 與此無關,我們建議您在應用程式中使用 OpenXR 來進行所有新的應用程式開發。 使用 2.9 或更舊版本的現有應用程式將會繼續運作,不受即將推出的變更影響。
全像攝影遠端應用程式可以將遠端轉譯的內容串流至 HoloLens 2,並 Windows Mixed Reality 沉浸式頭戴裝置。 您也可以存取更多系統資源,並將遠端 沉浸式檢視 整合到現有的桌面計算機軟體中。 遠端應用程式會從 HoloLens 2 接收輸入數據流、在虛擬沉浸式檢視中轉譯內容,並將內容畫面串流回 HoloLens 2。 聯機是使用標準 Wi-Fi 所建立。 全像攝影遠端功能會透過 NuGet 封包新增至桌面或 UWP 應用程式。 需要額外的程式代碼,以處理連線,並在沉浸式檢視中呈現。 一般遠端連線的延遲將低至 50 毫秒。 播放機應用程式可以即時報告延遲。
您可以在 全像攝影遠端處理範例 Github 存放庫中找到此頁面上的所有程式代碼和工作專案。
必要條件
良好的起點是以 Windows Mixed Reality API 為目標的可運作 DirectX 型桌面或 UWP 應用程式。 如需詳細資訊,請 參閱 DirectX 開發概觀。 C++全像攝影項目範本是不錯的起點。
重要事項
任何使用全像攝影遠端處理的應用程式都應該撰寫成使用 多線程 Apartment。 支援使用單個 線程 Apartment,但會導致效能不佳,而且在播放期間可能會斷斷續續。 使用 C++/WinRT winrt::init_apartment 多線程 Apartment 是預設值。
取得全像攝影遠端 NuGet 套件
需要執行下列步驟,才能將 NuGet 套件新增至 Visual Studio 中的專案。
- 在 Visual Studio 中開啟專案。
- 以滑鼠右鍵按兩下項目節點,然後選 取 [管理 NuGet 套件...]
- 在出現的面板中,選取 [ 流覽 ],然後搜尋 [全像攝影遠端]。
- 選 取 [Microsoft.Holographic.Remoting],確定挑選最新的 2.x.x 版本,然後選取 [ 安裝]。
- 如果 [ 預覽] 對話框出現,請選取 [ 確定]。
- 在 [許可協定] 對話框快顯時,選取 [ 我接受 ]。
注意事項
NuGet 套件 1.x.x 版仍可供想要以 HoloLens 1 為目標的開發人員使用。 如需詳細資訊,請 參閱新增 HoloLens (HoloLens (第 1 代) ) 。
建立遠端內容
在第一個步驟中,應用程式應該建立遠程內容。
// 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);
警告
全像攝影遠端處理的運作方式是以遠端特定運行時間取代屬於 Windows 的 Windows Mixed Reality 執行時間。 這會在建立遠端內容期間完成。 因此,在建立遠端內容之前,任何 Windows Mixed Reality API 上的任何呼叫都可能導致非預期的行為。 建議的方法是儘早建立遠端內容,再與任何 Mixed Reality API 互動。 在呼叫 CreateRemoteContext 之前,請勿混合透過任何 Windows Mixed Reality API 建立或擷取的物件,以及之後建立或擷取的物件。
接下來必須建立全像攝影空間。 不需要指定 CoreWindow。 沒有 CoreWindow 的傳統型應用程式可以直接傳遞 nullptr
。
m_holographicSpace = winrt::Windows::Graphics::Holographic::HolographicSpace::CreateForCoreWindow(nullptr);
線上到裝置
當遠端應用程式準備好轉譯內容時,可以建立與播放機裝置的連線。
線上可以透過兩種方式之一來完成。
- 遠端應用程式會連線到在裝置上執行的播放程式。
- 在裝置上執行的播放機會連線到遠端應用程式。
若要建立從遠端應用程式到播放機裝置的連線,請在指定主機名和埠的遠端內容上呼叫 Connect
方法。 全像攝影遠端播放機使用的埠是 8265。
try
{
m_remoteContext.Connect(m_hostname, m_port);
}
catch(winrt::hresult_error& e)
{
DebugLog(L"Connect failed with hr = 0x%08X", e.code());
}
重要事項
如同任何C++/WinRT API Connect
可能會擲回需要處理的 winrt::hresult_error。
提示
若要避免使用 C++/WinRT 語言投影,可以包含全像攝影遠端 NuGet 套件內的檔案 build\native\include\<windows sdk version>\abi\Microsoft.Holographic.AppRemoting.h
。 它包含基礎 COM 介面的宣告。 不過,建議使用 C++/WinRT。
您可以呼叫 Listen
方法來接聽遠端應用程式上的連入連線。 在此呼叫期間,可以指定交握埠和傳輸埠。 交握埠用於初始交握。 然後,數據會透過傳輸埠傳送。 預設會使用 8265 和 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());
}
重要事項
build\native\include\HolographicAppRemoting\Microsoft.Holographic.AppRemoting.idl
NuGet 套件內的 包含全像攝影遠端所公開之 API 的詳細檔。
處理遠端處理特定事件
遠端內容會公開三個事件,這對於監視連線的狀態很重要。
- OnConnected:成功建立裝置連線時觸發。
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
}
});
- OnDisconnected:如果已建立的連線已關閉或無法建立連線,則觸發。
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.");
}
}
});
- OnListening:接聽傳入連線時啟動。
m_onListeningEventRevoker = m_remoteContext.OnListening(winrt::auto_revoke, [this, remoteContextWeakRef]() {
if (auto remoteContext = remoteContextWeakRef.get())
{
// Update UI state
}
});
此外,您可以使用 ConnectionState
遠端內容上的 屬性來查詢連線狀態。
auto connectionState = m_remoteContext.ConnectionState();
處理語音事件
使用遠端語音介面,您可以向 HoloLens 2 註冊語音觸發程式,並將它們遠端到遠端應用程式。
追蹤遠端語音的狀態需要下列額外的成員:
winrt::Microsoft::Holographic::AppRemoting::IRemoteSpeech::OnRecognizedSpeech_revoker m_onRecognizedSpeechRevoker;
首先,擷取遠端語音介面。
if (auto remoteSpeech = m_remoteContext.GetRemoteSpeech())
{
InitializeSpeechAsync(remoteSpeech, m_onRecognizedSpeechRevoker, weak_from_this());
}
使用異步協助程式方法,您可以接著初始化遠端語音。 這應該以異步方式完成,因為初始化可能需要相當長的時間。 C++/WinRT 的並行和異步作 說明如何使用 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);
}
有兩種方式可以指定要辨識的片語。
- 語音文法 xml 檔案內的規格。 如需詳細資訊,請參閱 如何建立基本 XML 文法 。
- 在字典向量內將它們傳遞至 來指定
ApplyParameters
。
在 OnRecognizedSpeech 回呼內,接著可以處理語音事件:
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")
{
...
}
...
}
在本機預覽串流內容
若要在傳送至裝置的遠端應用程式中顯示相同的內容, OnSendFrame
可以使用遠端內容的事件。
OnSendFrame
每次全像攝影遠端連結庫將目前的畫面傳送至遠端裝置時,就會觸發事件。 這是將內容放入桌面或 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.
});
深度重新投影
從 2.1.0 版開始,全像攝影遠端支援 深度重新投影。 這需要將色彩緩衝區和深度緩衝區從遠端應用程式串流至 HoloLens 2。 根據預設,深度緩衝區串流會啟用並設定為使用色彩緩衝區的一半解析度。 這可以變更,如下所示:
// class implementation
#include <HolographicAppRemoting\Streamer.h>
...
CreateRemoteContext(m_remoteContext, 20000, false, PreferredVideoCodec::Default);
// Configure for half-resolution depth.
m_remoteContext.ConfigureDepthVideoStream(DepthBufferStreamResolution::Half_Resolution);
請注意,如果不應該使用ConfigureDepthVideoStream
預設值,則必須在建立與 HoloLens 2 的連線之前呼叫 。 最佳位置是在您建立遠端內容之後。 DepthBufferStreamResolution 的可能值為:
- Full_Resolution
- Half_Resolution
- Quarter_Resolution
- 已停用 (2.1.3 版新增,若未使用,則不會建立其他深度視訊串流)
請記住,使用完整解析度深度緩衝區也會影響頻寬需求,而且必須考慮您提供給 CreateRemoteContext
的最大頻寬值。
除了設定解決方案之外,您也必須透過 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);
}
});
}
});
}
若要確認深度重新投影是否在 HoloLens 2 上正常運作,您可以透過裝置入口網站啟用深度可視化檢視。 如需詳細資訊,請參閱 確認深度已正確設定 。
選擇性:自定義數據通道
自訂數據通道可用來透過已建立的遠端連線傳送用戶數據。 如需詳細資訊,請參閱 自定義數據通道。
選擇性:座標系統同步處理
從 2.7.0 版開始,座標系統同步處理可用來對齊播放機與遠端應用程式之間的空間數據。 如需詳細資訊,請參閱 使用全像攝影遠端處理的座標系統同步處理概觀。