DirectX でのローカル アンカー転送
Azure Spatial Anchors を使用できない状況では、ローカルアンカー転送により、1 つの HoloLens デバイスがアンカーをエクスポートして 2 番目の HoloLens デバイスによってインポートできるようになります。
Note
このアプローチによるアンカー呼び戻しは Azure Spatial Anchors ほど安定したものではなく、iOS と Android デバイスはサポートされません。
Note
この記事のコード スニペットは、現在、C++ ホログラフィック プロジェクト テンプレートで使用されている C++17 準拠の C++/WinRT ではなく、C++/CX の使用を示しています。 概念は C++/WinRT プロジェクトに相当しますが、コードを変換する必要があります。
空間アンカーの転送
Windows Mixed Reality デバイス間での空間アンカーの転送は、SpatialAnchorTransferManager を使用して実行できます。 このAPIを使用すると、アンカーを、世界のその正確な場所を見つけるために必要なすべてのサポートセンサーデータとバンドルして、そのバンドルを別のデバイスにインポートできます。 2 番目のデバイスのアプリがそのアンカーをインポートすると、各アプリはその共有空間アンカーの座標系を使用してホログラムをレンダリングできます。これは、現実世界の同じ場所に表示されます。
空間アンカーは、異なるデバイスタイプ間で転送できないことに注意してください。たとえば、HoloLens 空間アンカーは、イマーシブ ヘッドセットを使用して配置できない場合があります。 転送されたアンカーは、iOS デバイスや Android デバイスとは互換性がありません。
spatialPerception 機能を使用するアプリを設定する
アプリでSpatialAnchorTransferManager を使用する前に、SpatialPerception機能を使用する権限をアプリに付与する必要があります。 これが必要なのは、空間アンカーの転送には、そのアンカーの近くで時間の経過とともに収集されたセンサー画像の共有が含まれるためです。これには機密情報が含まれる場合があります。
アプリの 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<byte> である独自のメモリバッファにデータを読み取ります。
// 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
)
{
まず、アンカー データにアクセスするストリーム オブジェクトを作成する必要があります。 バッファからシステムバッファにデータを書き込むので、バイトバッファから SpatialAnchors としてシステムにアンカーを取得するという目標を達成するために、メモリ内のデータストリームに書き込む DataWriter を作成します。
// 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 にエクスポートできます。 ただし、1 つの SpatialAnchor または複数の SpatialAnchor が 1 つの呼び出しでエクスポートされるかどうかによって、BLOB に含まれるデータには微妙な違いがあります。
1 つの SpatialAnchor のエクスポート
BLOB には、SpatialAnchor の近くにある環境の表現が含められます。これにより、SpatialAnchor をインポートしたデバイスで、環境を認識できます。 インポートが完了したら、新しい SpatialAnchor をデバイスで使用できるようになります。 ユーザーが最近アンカーの近くにいると仮定すると、アンカーは位置を特定でき、SpatialAnchor に取り付けられたホログラムをレンダリングできます。 これらのホログラムは、SpatialAnchor のエクスポート元のデバイスと同じ物理的位置に表示されます。
複数の SpatialAnchors のエクスポート
1 つの SpatialAnchor のエクスポートと同様に、BLOB には、指定された SpatialAnchors の近くにある環境の表現が含まれています。 さらに、BLOB には、含まれている SpatialAnchors が同じ物理空間にある場合、それらの間の接続に関する情報が含まれています。 つまり、近くにある 2 つの SpatialAnchors がインポートすると、デバイスが 1 つ目の SpatialAnchor の周囲の環境しか認識できない場合でも、2 つの SpatialAnchor 間の変換を計算するのに十分なデータが BLOB に含まれているので、2 つ目の SpatialAnchor にアタッチされたホログラムの位置を特定することが可能になります。 2 つの SpatialAnchors が個別にエクスポートされた場合は、 (TryExportSpatialAnchors に対する 2 つの個別の呼び出し)、最初の SpatialAnchor が配置されたときに、2番目の SpatialAnchor にアタッチ状態でホログラムを配置するのに十分なデータがBLOBに含まれていない可能性があります。
例: 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"
);
}
エクスポートされたアンカーデータを受信するには、WindowsNetworking: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 デバイスに展開してください。