共用方式為


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;
});
}

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

特殊考慮

TryExportAnchorsAsync API 可讓多個SpatialAnchors匯出至相同的不透明二進位 Blob。 不過,Blob 將包含的資料有細微的差異,視單一 SpatialAnchor 或多個 SpatialAnchors 是否在單一呼叫中匯出而定。

匯出單一 SpatialAnchor

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

匯出單一 SpatialAnchor

匯出多個 SpatialAnchors

如同單一 SpatialAnchor 的匯出,Blob 包含所有指定 SpatialAnchors 附近環境的標記法。 此外,如果包含的 SpatialAnchors 位於相同的實體空間,Blob 也會包含內含 SpatialAnchors 之間的連線相關資訊。 這表示如果匯入兩個鄰近的 SpatialAnchors,則附加至 第二 個 SpatialAnchor 的全像投影將會是可理解的,即使裝置只辨識 第一個 SpatialAnchor 周圍的環境,因為足以計算兩個 SpatialAnchors 之間的轉換資料已包含在 Blob 中。 如果兩個 SpatialAnchors 個別匯出, (兩個不同的 TryExportSpatialAnchors 呼叫) ,則連結至第二個 SpatialAnchor 的全像投影在找到第一個 SpatialAnchor 時,可能沒有足夠的資料包含在 Blob 中。

使用單一 TryExportAnchorsAsync 呼叫匯出的多個錨點使用個別的 TryExportAnchorsAsync 呼叫匯出的每個錨點

範例:使用 Windows::Networking::StreamSocket 傳送錨點資料

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

WinRT StreamSocket 類別使用 PPL 工作程式庫。 如果是網路錯誤,則會使用重新擲回的例外狀況,將錯誤傳回至鏈結中的下一個工作。 例外狀況包含 HRESULT,指出錯誤狀態。

搭配 TCP 使用 Windows::Networking::StreamSocketListener 來傳送匯出的錨點資料

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

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 裝置,以試用。

另請參閱