共用方式為


DirectX 中的本機錨點傳輸

在您無法使用 Azure Spatial Anchors 的情況下,本機錨點傳輸可讓一部 HoloLens 裝置匯出要由第二個 HoloLens 裝置匯入的錨點。

注意

本機錨點傳輸提供比 Azure Spatial Anchors 更強固的錨點召回率,而此方法不支援 iOS 和 Android 裝置。

注意

本文中的代碼段目前示範如何使用 C++/CX,而不是C++17 相容C++/WinRT,如C++全像攝影專案範本所示。 概念相當於C++/WinRT 項目,不過您必須翻譯程序代碼。

傳輸空間錨點

您可以使用 SpatialAnchorTransferManager,在 Windows Mixed Reality 裝置之間傳輸空間錨點。 此 API 可讓您將錨點與尋找世界上確切位置所需的所有支援感測器數據組合在一起,然後在另一個裝置上匯入該配套。 第二個裝置上的應用程式匯入該錨點之後,每個應用程式都可以使用該共用空間錨點的座標系統來轉譯全像投影,然後會出現在真實世界中的相同位置。

請注意,空間錨點無法在不同的裝置類型之間傳輸,例如,HoloLens 空間錨點可能無法使用沉浸式頭戴式裝置來擷取。 傳輸的錨點也與iOS或Android裝置不相容。

設定您的應用程式以使用 spatialPerception 功能

您的應用程式必須先獲得使用 SpatialPerception 功能的許可權,才能使用 SpatialAnchorTransferManager。 這是必要的,因為傳輸空間錨點牽涉到共用在該錨點附近一段時間收集的感測器影像,這可能包括敏感性資訊。

在 app 的 package.appxmanifest 檔案中宣告這項功能。 以下是範例:

<Capabilities>
  <uap2:Capability Name="spatialPerception" />
</Capabilities>

此功能來自 uap2 命名空間。 若要取得指令清單中此命名空間的存取權,請在 Package> 元素中包含<它作為 xlmns 屬性。 以下是範例:

<Package
    xmlns="https://schemas.microsoft.com/appx/manifest/foundation/windows10"
    xmlns:mp="https://schemas.microsoft.com/appx/2014/phone/manifest"
    xmlns:uap="https://schemas.microsoft.com/appx/manifest/uap/windows10"
    xmlns:uap2="https://schemas.microsoft.com/appx/manifest/uap/windows10/2"
    IgnorableNamespaces="uap mp"
    >

注意: 您的應用程式必須在運行時間要求功能,才能存取 SpatialAnchor 匯出/匯入 API。 請參閱 下列範例中的 RequestAccessAsync

使用 SpatialAnchorTransferManager 導出錨點數據來串行化錨點數據

協助程式函式包含在程式碼範例中,以匯出(串行化) SpatialAnchor 數據。 此匯出 API 會將索引鍵/值組集合中的所有錨點串行化,使字串與錨點產生關聯。

// ExportAnchorDataAsync: Exports a byte buffer containing all of the anchors in the given collection.
//
// This function will place data in a buffer using a std::vector<byte>. The ata buffer contains one or more
// Anchors if one or more Anchors were successfully imported; otherwise, it is ot modified.
//
task<bool> SpatialAnchorImportExportHelper::ExportAnchorDataAsync(
    vector<byte>* anchorByteDataOut,
    IMap<String^, SpatialAnchor^>^ anchorsToExport
    )
{

首先,我們需要設定數據流。 這將允許我們 1. 使用 TryExportAnchorsAsync 將資料放入應用程式擁有的緩衝區,以及 2。 從導出的位元組緩衝區數據流讀取數據, 這是 WinRT 數據流 - 到我們自己的記憶體緩衝區,這是 std::vector<位元組>。

// Create a random access stream to process the anchor byte data.
InMemoryRandomAccessStream^ stream = ref new InMemoryRandomAccessStream();
// Get an output stream for the anchor byte stream.
IOutputStream^ outputStream = stream->GetOutputStreamAt(0);

我們需要要求存取空間數據的許可權,包括系統導出的錨點。

// Request access to spatial data.
auto accessRequestedTask = create_taskSpatialAnchorTransferManager::RequestAccessAsync()).then([anchorsToExport, utputStream](SpatialPerceptionAccessStatus status)
{
    if (status == SpatialPerceptionAccessStatus::Allowed)
    {
        // Access is allowed.
        // Export the indicated set of anchors.
        return create_task(SpatialAnchorTransferManager::TryExportAnchorsAsync(
            anchorsToExport,
            outputStream
            ));
    }
    else
    {
        // Access is denied.
        return task_from_result<bool>(false);
    }
});

如果我們確實取得許可權並匯出錨點,我們可以讀取數據流。 在這裡,我們也會示範如何建立我們將用來讀取數據的 DataReader 和 InputStream。

// Get the input stream for the anchor byte stream.
IInputStream^ inputStream = stream->GetInputStreamAt(0);
// Create a DataReader, to get bytes from the anchor byte stream.
DataReader^ reader = ref new DataReader(inputStream);
return accessRequestedTask.then([anchorByteDataOut, stream, reader](bool nchorsExported)
{
    if (anchorsExported)
    {
        // Get the size of the exported anchor byte stream.
        size_t bufferSize = static_cast<size_t>(stream->Size);
        // Resize the output buffer to accept the data from the stream.
        anchorByteDataOut->reserve(bufferSize);
        anchorByteDataOut->resize(bufferSize);
        // Read the exported anchor store into the stream.
        return create_task(reader->LoadAsync(bufferSize));
    }
    else
    {
        return task_from_result<size_t>(0);
    }

從數據流讀取位元組之後,我們可以將其儲存到自己的數據緩衝區,如下所示。

}).then([anchorByteDataOut, reader](size_t bytesRead)
{
    if (bytesRead > 0)
    {
        // Read the bytes from the stream, into our data output buffer.
        reader->ReadBytes(Platform::ArrayReference<byte>(&(*anchorByteDataOut)[0], bytesRead));
        return true;
    }
    else
    {
        return false;
    }
});
};

使用 SpatialAnchorTransferManager 將錨點數據匯入系統,以還原串行化錨點數據

協助程式函式包含在程式代碼範例中,以載入先前匯出的數據。 這個還原串行化函式會提供索引鍵/值組的集合,類似於 SpatialAnchorStore 所提供的集合,但除了我們從另一個來源取得此數據,例如網路套接字。 您可以在離線儲存資料、使用應用程式記憶體,或(如果適用的話)應用程式 SpatialAnchorStore 之前處理和推理此數據。

// ImportAnchorDataAsync: Imports anchors from a byte buffer that was previously exported.
//
// This function will import all anchors from a data buffer into an in-memory ollection of key, value
// pairs that maps String objects to SpatialAnchor objects. The Spatial nchorStore is not affected by
// this function unless you provide it as the target collection for import.
//
task<bool> SpatialAnchorImportExportHelper::ImportAnchorDataAsync(
    std::vector<byte>& anchorByteDataIn,
    IMap<String^, SpatialAnchor^>^ anchorMapOut
    )
{

首先,我們需要建立數據流物件來存取錨點數據。 我們會將數據從緩衝區寫入至系統緩衝區,因此我們將建立 DataWriter,以寫入記憶體內部數據流,以完成從位元組緩衝區取得錨點的目標,以 SpatialAnchors 的形式進入系統。

// Create a random access stream for the anchor data.
InMemoryRandomAccessStream^ stream = ref new InMemoryRandomAccessStream();
// Get an output stream for the anchor data.
IOutputStream^ outputStream = stream->GetOutputStreamAt(0);
// Create a writer, to put the bytes in the stream.
DataWriter^ writer = ref new DataWriter(outputStream);

我們再次需要確保應用程式具有導出空間錨點數據的許可權,這可能包含用戶環境的私人資訊。

// Request access to transfer spatial anchors.
return create_task(SpatialAnchorTransferManager::RequestAccessAsync()).then(
    [&anchorByteDataIn, writer](SpatialPerceptionAccessStatus status)
{
    if (status == SpatialPerceptionAccessStatus::Allowed)
    {
        // Access is allowed.

如果允許存取,我們可以將位元組從緩衝區寫入系統數據流。

// Write the bytes to the stream.
        byte* anchorDataFirst = &anchorByteDataIn[0];
        size_t anchorDataSize = anchorByteDataIn.size();
        writer->WriteBytes(Platform::ArrayReference<byte>(anchorDataFirst, anchorDataSize));
        // Store the stream.
        return create_task(writer->StoreAsync());
    }
    else
    {
        // Access is denied.
        return task_from_result<size_t>(0);
    }

如果我們成功將位元組儲存在數據流中,我們可以嘗試使用 SpatialAnchorTransferManager 匯入該數據。

}).then([writer, stream](unsigned int bytesWritten)
{
    if (bytesWritten > 0)
    {
        // Try to import anchors from the byte stream.
        return create_task(writer->FlushAsync())
            .then([stream](bool dataWasFlushed)
        {
            if (dataWasFlushed)
            {
                // Get the input stream for the anchor data.
                IInputStream^ inputStream = stream->GetInputStreamAt(0);
                return create_task(SpatialAnchorTransferManager::TryImportAnchorsAsync(inputStream));
            }
            else
            {
                return task_from_result<IMapView<String^, SpatialAnchor^>^>(nullptr);
            }
        });
    }
    else
    {
        return task_from_result<IMapView<String^, SpatialAnchor^>^>(nullptr);
    }

如果數據能夠匯入,我們會取得索引鍵/值組的對應檢視,將字串與錨點產生關聯。 我們可以將此載入至自己的記憶體內部數據收集,並使用該集合來尋找我們感興趣的錨點。

}).then([anchorMapOut](task<Windows::Foundation::Collections::IMapView<String^, SpatialAnchor^>^>  previousTask)
{
    try
    {
        auto importedAnchorsMap = previousTask.get();
        // If the operation was successful, we get a set of imported anchors.
        if (importedAnchorsMap != nullptr)
        {
            for each (auto& pair in importedAnchorsMap)
            {
                // Note that you could look for specific anchors here, if you know their key values.
                auto const& id = pair->Key;
                auto const& anchor = pair->Value;
                // Append "Remote" to the end of the anchor name for disambiguation.
                std::wstring idRemote(id->Data());
                idRemote += L"Remote";
                String^ idRemoteConst = ref new String (idRemote.c_str());
                // Store the anchor in the current in-memory anchor map.
                anchorMapOut->Insert(idRemoteConst, anchor);
            }
            return true;
        }
    }
    catch (Exception^ exception)
    {
        OutputDebugString(L"Error: Unable to import the anchor data buffer bytes into the in-memory anchor collection.\n");
    }
    return false;
});
}

注意: 因為您可以匯入錨點,不一定表示您可以立即使用它。 錨點可能位於不同的房間,或完全位於另一個實體位置;在收到錨點的裝置有足夠的視覺資訊建立錨點之前,錨點將無法存取,以還原錨點相對於已知目前環境的位置。 用戶端實作應該先嘗試尋找相對於本機座標系統或參考框架的錨點,然後再繼續嘗試將它用於實時內容。 例如,請嘗試定期尋找相對於目前座標系統的錨點,直到錨點開始成為可擷取為止。

特殊考量

TryExportAnchorsAsync API 可讓多個 SpatialAnchors 導出至相同的不透明二進位 Blob。 不過,根據單一呼叫中導出單一 SpatialAnchor 或多個 SpatialAnchors,Blob 將包含哪些數據有細微的差異。

匯出單一 SpatialAnchor

Blob 包含 SpatialAnchor 附近環境表示法,以便可在匯入 SpatialAnchor 的裝置上辨識環境。 匯入完成後,新的 SpatialAnchor 將可供裝置使用。 假設使用者最近在錨點附近,它將會是可擷取的,且附加至 SpatialAnchor 的全像投影可以轉譯。 這些全像投影會顯示在導出 SpatialAnchor 的原始裝置上相同的實體位置。

匯出單一 SpatialAnchor

匯出多個 SpatialAnchors

如同單一 SpatialAnchor 的導出,Blob 包含所有指定 SpatialAnchors 附近環境表示法。 此外,如果 Blob 位於相同的實體空間,則 Blob 會包含內含 SpatialAnchors 之間連線的相關信息。 這表示如果匯入了兩個附近的 SpatialAnchors,則即使裝置只辨識第一個 SpatialAnchor 周圍的環境,仍可擷取連結至第二個 SpatialAnchor 的全像投影,因為足以計算 Blob 中包含兩個 SpatialAnchors 之間的轉換數據。 如果個別匯出這兩個 SpatialAnchors (對 TryExportSpatialAnchors 的兩個個別呼叫),則當第一個找到第一個 SpatialAnchor 時,連結至第二個 SpatialAnchor 的全像投影可能沒有足夠的數據。

使用單一 TryExportAnchorsAsync 呼叫導出的多個錨點 針對每個錨點使用不同的 TryExportAnchorsAsync 呼叫導出的多個錨點

範例:使用 Windows::Networking::StreamSocket 傳送錨點數據

在這裡,我們會提供如何透過 TCP 網路傳送導出錨點數據的範例。 這是來自 HolographicSpatialAnchorTransferSample。

WinRT StreamSocket 類別會使用 PPL 工作連結庫。 在發生網路錯誤時,錯誤會使用重新擲回的例外狀況,傳回鏈結中的下一個工作。 例外狀況包含指出錯誤狀態的 HRESULT。

使用 Windows::Networking::StreamSocketListener 搭配 TCP 來傳送導出的錨點數據

建立接聽連線的伺服器實例。

void SampleAnchorTcpServer::ListenForConnection()
{
    // Make a local copy to avoid races with Closed events.
    StreamSocketListener^ streamSocketListener = m_socketServer;
    if (streamSocketListener == nullptr)
    {
        OutputDebugString(L"Server listening for client.\n");
        // Create the web socket connection.
        streamSocketListener = ref new StreamSocketListener();
        streamSocketListener->Control->KeepAlive = true;
        streamSocketListener->BindEndpointAsync(
            SampleAnchorTcpCommon::m_serverHost,
            SampleAnchorTcpCommon::m_tcpPort
            );
        streamSocketListener->ConnectionReceived +=
            ref new Windows::Foundation::TypedEventHandler<StreamSocketListener^, StreamSocketListenerConnectionReceivedEventArgs^>(
                std::bind(&SampleAnchorTcpServer::OnConnectionReceived, this, _1, _2)
                );
        m_socketServer = streamSocketListener;
    }
    else
    {
        OutputDebugString(L"Error: Stream socket listener not created.\n");
    }
}

收到連線時,請使用用戶端套接字連線來傳送錨點數據。

void SampleAnchorTcpServer::OnConnectionReceived(StreamSocketListener^ listener, StreamSocketListenerConnectionReceivedEventArgs^ args)
{
    m_socketForClient = args->Socket;
    if (m_socketForClient != nullptr)
    {
        // In this example, when the client first connects, we catch it up to the current state of our anchor set.
        OutputToClientSocket(m_spatialAnchorHelper->GetAnchorMap());
    }
}

現在,我們可以開始傳送包含導出錨點數據的數據流。

void SampleAnchorTcpServer::OutputToClientSocket(IMap<String^, SpatialAnchor^>^ anchorsToSend)
{
    m_anchorTcpSocketStreamWriter = ref new DataWriter(m_socketForClient->OutputStream);
    OutputDebugString(L"Sending stream to client.\n");
    SendAnchorDataStream(anchorsToSend).then([this](task<bool> previousTask)
    {
        try
        {
            bool success = previousTask.get();
            if (success)
            {
                OutputDebugString(L"Anchor data sent!\n");
            }
            else
            {
                OutputDebugString(L"Error: Anchor data not sent.\n");
            }
        }
        catch (Exception^ exception)
        {
            HandleException(exception);
            OutputDebugString(L"Error: Anchor data was not sent.\n");
        }
    });
}

我們必須先傳送標頭封包,才能傳送數據流本身。 此標頭封包必須具有固定長度,而且也必須指出錨點數據流之位元組的可變數組長度;在此範例中,我們沒有其他標頭數據要傳送,因此我們的標頭長度為 4 個字節,且包含 32 位無符號整數。

Concurrency::task<bool> SampleAnchorTcpServer::SendAnchorDataLengthMessage(size_t dataStreamLength)
{
    unsigned int arrayLength = dataStreamLength;
    byte* data = reinterpret_cast<byte*>(&arrayLength);
    m_anchorTcpSocketStreamWriter->WriteBytes(Platform::ArrayReference<byte>(data, SampleAnchorTcpCommon::c_streamHeaderByteArrayLength));
    return create_task(m_anchorTcpSocketStreamWriter->StoreAsync()).then([this](unsigned int bytesStored)
    {
        if (bytesStored > 0)
        {
            OutputDebugString(L"Anchor data length stored in stream; Flushing stream.\n");
            return create_task(m_anchorTcpSocketStreamWriter->FlushAsync());
        }
        else
        {
            OutputDebugString(L"Error: Anchor data length not stored in stream.\n");
            return task_from_result<bool>(false);
        }
    });
}
Concurrency::task<bool> SampleAnchorTcpServer::SendAnchorDataStreamIMap<String^, SpatialAnchor^>^ anchorsToSend)
{
    return SpatialAnchorImportExportHelper::ExportAnchorDataAsync(
        &m_exportedAnchorStoreBytes,
        anchorsToSend
        ).then([this](bool anchorDataExported)
    {
        if (anchorDataExported)
        {
            const size_t arrayLength = m_exportedAnchorStoreBytes.size();
            if (arrayLength > 0)
            {
                OutputDebugString(L"Anchor data was exported; sending data stream length message.\n");
                return SendAnchorDataLengthMessage(arrayLength);
            }
        }
        OutputDebugString(L"Error: Anchor data was not exported.\n");
        // No data to send.
        return task_from_result<bool>(false);

一旦數據流長度以位元組為單位傳送至客戶端,我們可以繼續將數據流本身寫入套接字數據流。 這會導致錨點存放區位元組傳送至用戶端。

}).then([this](bool dataLengthSent)
    {
        if (dataLengthSent)
        {
            OutputDebugString(L"Data stream length message sent; writing exported anchor store bytes to stream.\n");
            m_anchorTcpSocketStreamWriter->WriteBytes(Platform::ArrayReference<byte>(&m_exportedAnchorStoreBytes[0], m_exportedAnchorStoreBytes.size()));
            return create_task(m_anchorTcpSocketStreamWriter->StoreAsync());
        }
        else
        {
            OutputDebugString(L"Error: Data stream length message not sent.\n");
            return task_from_result<size_t>(0);
        }
    }).then([this](unsigned int bytesStored)
    {
        if (bytesStored > 0)
        {
            PrintWstringToDebugConsole(
                std::to_wstring(bytesStored) +
                L" bytes of anchor data written and stored to stream; flushing stream.\n"
                );
        }
        else
        {
            OutputDebugString(L"Error: No anchor data bytes were written to the stream.\n");
        }
        return task_from_result<bool>(false);
    });
}

如本主題稍早所述,我們必須準備好處理包含網路錯誤狀態消息的例外狀況。 對於非預期的錯誤,我們可以將例外狀況資訊寫入偵錯控制台,如下所示。 如果程式代碼範例無法完成連線,或無法完成傳送錨點數據,這會讓我們知道發生什麼情況。

void SampleAnchorTcpServer::HandleException(Exception^ exception)
{
    PrintWstringToDebugConsole(
        std::wstring(L"Connection error: ") +
        exception->ToString()->Data() +
        L"\n"
        );
}

使用 Windows::Networking::StreamSocket 搭配 TCP 來接收導出的錨點數據

首先,我們必須連線到伺服器。 此程式代碼範例示範如何建立及設定 StreamSocket,以及建立 DataReader,讓您可用來使用套接字連線來取得網路數據。

注意: 如果您執行此範例程式代碼,請務必先設定並啟動伺服器,再啟動用戶端。

task<bool> SampleAnchorTcpClient::ConnectToServer()
{
    // Make a local copy to avoid races with Closed events.
    StreamSocket^ streamSocket = m_socketClient;
    // Have we connected yet?
    if (m_socketClient == nullptr)
    {
        OutputDebugString(L"Client is attempting to connect to server.\n");
        EndpointPair^ endpointPair = ref new EndpointPair(
            SampleAnchorTcpCommon::m_clientHost,
            SampleAnchorTcpCommon::m_tcpPort,
            SampleAnchorTcpCommon::m_serverHost,
            SampleAnchorTcpCommon::m_tcpPort
            );
        // Create the web socket connection.
        m_socketClient = ref new StreamSocket();
        // The client connects to the server.
        return create_task(m_socketClient->ConnectAsync(endpointPair, SocketProtectionLevel::PlainSocket)).then([this](task<void> previousTask)
        {
            try
            {
                // Try getting all exceptions from the continuation chain above this point.
                previousTask.get();
                m_anchorTcpSocketStreamReader = ref new DataReader(m_socketClient->InputStream);
                OutputDebugString(L"Client connected!\n");
                m_anchorTcpSocketStreamReader->InputStreamOptions = InputStreamOptions::ReadAhead;
                WaitForAnchorDataStream();
                return true;
            }
            catch (Exception^ exception)
            {
                if (exception->HResult == 0x80072741)
                {
                    // This code sample includes a very simple implementation of client/server
                    // endpoint detection: if the current instance tries to connect to itself,
                    // it is determined to be the server.
                    OutputDebugString(L"Starting up the server instance.\n");
                    // When we return false, we'll start up the server instead.
                    return false;
                }
                else if ((exception->HResult == 0x8007274c) || // connection timed out
                    (exception->HResult == 0x80072740)) // connection maxed at server end
                {
                    // If the connection timed out, try again.
                    ConnectToServer();
                }
                else if (exception->HResult == 0x80072741)
                {
                    // No connection is possible.
                }
                HandleException(exception);
                return true;
            }
        });
    }
    else
    {
        OutputDebugString(L"A StreamSocket connection to a server already exists.\n");
        return task_from_result<bool>(true);
    }
}

線上之後,我們可以等候伺服器傳送數據。 我們會在數據流數據讀取器上呼叫 LoadAsync 來執行此動作。

我們收到的第一組位元組應該一律是標頭封包,這表示錨點數據流位元組長度,如上一節所述。

void SampleAnchorTcpClient::WaitForAnchorDataStream()
{
    if (m_anchorTcpSocketStreamReader == nullptr)
    {
        // We have not connected yet.
        return;
    }
    OutputDebugString(L"Waiting for server message.\n");
    // Wait for the first message, which specifies the byte length of the string data.
    create_task(m_anchorTcpSocketStreamReader->LoadAsync(SampleAnchorTcpCommon::c_streamHeaderByteArrayLength)).then([this](unsigned int numberOfBytes)
    {
        if (numberOfBytes > 0)
        {
            OutputDebugString(L"Server message incoming.\n");
            return ReceiveAnchorDataLengthMessage();
        }
        else
        {
            OutputDebugString(L"0-byte async task received, awaiting server message again.\n");
            WaitForAnchorDataStream();
            return task_from_result<size_t>(0);
        }

...

task<size_t> SampleAnchorTcpClient::ReceiveAnchorDataLengthMessage()
{
    byte data[4];
    m_anchorTcpSocketStreamReader->ReadBytes(Platform::ArrayReference<byte>(data, SampleAnchorTcpCommon::c_streamHeaderByteArrayLength));
    unsigned int lengthMessageSize = *reinterpret_cast<unsigned int*>(data);
    if (lengthMessageSize > 0)
    {
        OutputDebugString(L"One or more anchors to be received.\n");
        return task_from_result<size_t>(lengthMessageSize);
    }
    else
    {
        OutputDebugString(L"No anchors to be received.\n");
        ConnectToServer();
    }
    return task_from_result<size_t>(0);
}

收到標頭封包之後,我們知道應該預期多少個錨點數據位元組。 我們可以繼續從數據流讀取這些位元組。

}).then([this](size_t dataStreamLength)
    {
        if (dataStreamLength > 0)
        {
            std::wstring debugMessage = std::to_wstring(dataStreamLength);
            debugMessage += L" bytes of anchor data incoming.\n";
            OutputDebugString(debugMessage.c_str());
            // Prepare to receive the data stream in one or more pieces.
            m_anchorStreamLength = dataStreamLength;
            m_exportedAnchorStoreBytes.clear();
            m_exportedAnchorStoreBytes.resize(m_anchorStreamLength);
            OutputDebugString(L"Loading byte stream.\n");
            return ReceiveAnchorDataStream();
        }
        else
        {
            OutputDebugString(L"Error: Anchor data size not received.\n");
            ConnectToServer();
            return task_from_result<bool>(false);
        }
    });
}

以下是接收錨點數據流的程序代碼。 同樣地,我們會先從數據流載入位元組;此作業可能需要一些時間才能完成,因為 StreamSocket 會等候從網路接收該數量的位元組。

載入作業完成時,我們可以讀取該位元組數目。 如果我們收到我們預期錨點數據流的位元元組數目,我們可以繼續匯入錨點數據;如果沒有,就必須發生某種錯誤。 例如,當伺服器實例在完成傳送數據流之前終止,或網路在用戶端可以接收整個數據流之前關閉時,就會發生這種情況。

task<bool> SampleAnchorTcpClient::ReceiveAnchorDataStream()
{
    if (m_anchorStreamLength > 0)
    {
        // First, we load the bytes from the network socket.
        return create_task(m_anchorTcpSocketStreamReader->LoadAsync(m_anchorStreamLength)).then([this](size_t bytesLoadedByStreamReader)
        {
            if (bytesLoadedByStreamReader > 0)
            {
                // Once the bytes are loaded, we can read them from the stream.
                m_anchorTcpSocketStreamReader->ReadBytes(Platform::ArrayReference<byte>(&m_exportedAnchorStoreBytes[0],
                    bytesLoadedByStreamReader));
                // Check status.
                if (bytesLoadedByStreamReader == m_anchorStreamLength)
                {
                    // The whole stream has arrived. We can process the data.
                    // Informational message of progress complete.
                    std::wstring infoMessage = std::to_wstring(bytesLoadedByStreamReader);
                    infoMessage += L" bytes read out of ";
                    infoMessage += std::to_wstring(m_anchorStreamLength);
                    infoMessage += L" total bytes; importing the data.\n";
                    OutputDebugStringW(infoMessage.c_str());
                    // Kick off a thread to wait for a new message indicating another incoming anchor data stream.
                    WaitForAnchorDataStream();
                    // Process the data for the stream we just received.
                    return SpatialAnchorImportExportHelper::ImportAnchorDataAsync(m_exportedAnchorStoreBytes, m_spatialAnchorHelper->GetAnchorMap());
                }
                else
                {
                    OutputDebugString(L"Error: Fewer than expected anchor data bytes were received.\n");
                }
            }
            else
            {
                OutputDebugString(L"Error: No anchor bytes were received.\n");
            }
            return task_from_result<bool>(false);
        });
    }
    else
    {
        OutputDebugString(L"Warning: A zero-length data buffer was sent.\n");
        return task_from_result<bool>(false);
    }
}

同樣地,我們必須準備好處理未知的網路錯誤。

void SampleAnchorTcpClient::HandleException(Exception^ exception)
{
    std::wstring error = L"Connection error: ";
    error += exception->ToString()->Data();
    error += L"\n";
    OutputDebugString(error.c_str());
}

介紹完畢 現在,您應該有足夠的資訊來嘗試尋找透過網路收到的錨點。 同樣地,請注意,客戶端必須有足夠的視覺追蹤數據,空間才能成功找到錨點;如果它不能馬上工作,請嘗試四處走動一會兒。 如果仍然無法運作,請讓伺服器傳送更多錨點,並使用網路通訊來就適用於用戶端的網路通訊達成一致。 您可以下載 HolographicSpatialAnchorTransferSample、設定您的用戶端和伺服器 IP,並將其部署至用戶端和伺服器 HoloLens 裝置,以試用此功能。

另請參閱