チュートリアル用サンプルアプリケーション¶
Genvid SDK を使用するには、アプリケーションの様々なセクションに統合する必要があります。このセクションでは、チュートリアルサンプルを使って、SDK の統合方法を紹介します。コードのサンプルや行番号などを参考に、チュートリアルアプリケーションを操作してください。
はじめに¶
ローカル環境にチュートリアルサンプルをセットアップすれば、このセクションでの統合手順の理解にとても役立ちます。詳細については、チュートリアル用サンプル を参照してください。
Classes¶
Tutorial.cpp | このファイルには、ウィンドウ初期化プロセス、DirectX レンダリング、ゲーム双方向操作、Genvid SDK 統合に関するすべてが含まれています。 |
Cube.h | ゲームで表示するキューブのヘッダーファイル。 |
Cube.cpp | キューブの変形機能をすべて含む class。 |
DDSTextureLoader.h | DDS テクスチャの読み込みと、Direct3D 11 ランタイムリソースの作成用ヘッダーファイル。 |
DDSTextureLoader.cpp | DDS テクスチャの読み込みと、Direct3D 11 ランタイムリソースの作成用のすべての関数を含む class。 |
JsonConversion.h | さまざまなデータを JSON 形式に変換し、ストリームで送信するためのヘッダーファイル。 |
JsonConversion.cpp | さまざまな形式のデータを JSON 形式に変換し、ストリームで送信するための class。 |
ヘッダーと宣言¶
初めに、Genvid Native SDK ヘッダーファイルをインクルードし、静的変数を宣言します。
#include <genvid.h>
static const std::string sStream_Audio = "Audio";
static const std::string sStream_Video = "Video";
static const std::string sStream_GameData = "GameData";
static const std::string sStream_Popularity = "Popularity";
static const std::string sStream_ColorChanged = "ColorChanged";
static const std::string sStream_GameCopyright = "GameCopyright";
static const std::string sEvent_changeColor = "changeColor";
static const std::string sEvent_reset = "reset";
static const std::string sEvent_cheer = "cheer";
static const std::string sCommand_speed = "speed";
static const std::string sCommand_direction = "direction";
static const std::string sCommand_reset = "reset";
static std::string oldPopularityJSON;
static void GenvidSubscriptionCallback(const GenvidEventSummary * summary, void * userData);
static void GenvidSubscriptionCommandCallback(const GenvidCommandResult * summary, void * userData);
GenvidTimecode gCurrentTc = -1;
GenvidTimecode gPrevTc = -1;
GenvidTimecode gFirstFrameTc = -1;
system_clock::time_point gStartTime = system_clock::now();
double gWorldTime = 0.0;
float gGenvidFramerate = 30.0f; // uses default value
float gGameFramerate = 30.0f; // uses default value
bool gEnableVSync = true;
bool gSilent = false; // Global flag to kill all sounds.
XMVECTORF32 gBackgroundColor = Colors::Blue;
XMVECTORF32 gBackgroundColorArray[] = {Colors::Blue, Colors::Green, Colors::Red, Colors::Orange, Colors::Bisque, Colors::CadetBlue, Colors::Indigo, Colors::Yellow};
XMFLOAT4 gColors[] = {
{0.6f, 0.1f, 0.4f, 1.0f},
{0.2f, 0.9f, 0.1f, 1.0f},
{0.7f, 0.7f, 0.7f, 1.0f},
{0.9f, 0.9f, 0.1f, 1.0f},
{0.1f, 0.2f, 0.3f, 1.0f},
{0.3f, 0.3f, 0.3f, 1.0f},
{0.2f, 0.4f, 0.6f, 1.0f},
{0.5f, 0.3f, 0.2f, 1.0f},
{0.1f, 0.5f, 0.9f, 1.0f},
{0.4f, 0.2f, 0.4f, 1.0f},
};
std::map<std::string, XMFLOAT4> sNameToColor = {
{"rose", gColors[0]},
{"green", gColors[1]},
{"white", gColors[2]},
{"yellow", gColors[3]},
{"dark blue", gColors[4]},
{"gray", gColors[5]},
{"light blue", gColors[6]},
{"orange", gColors[7]},
{"blue", gColors[8]},
{"purple", gColors[9]},
};
初期化¶
InitDevice()
に DirectX をセットアップ後、InitGenvid()
を呼び出すと、次のような動作をします。
- Genvid ライブラリを初期化します。
- 必要なすべてのストリームを定義します。
- すべてのイベントとコマンドサブスクリプションを設定します。
このサンプルには、ビデオストリームとオーディオストリームが 1 つずつしかありません。
Genvid_SetParameterPointer()
で、ビデオ IDXGISwapChain を送信します。
ゲームデータをサーバーに送信するストリームを作成していきます。1 つは popularity の変更用、もう 1 つは色の変更用です。
最後に、Web ページからのイベントやコマンドを受け取るために必要なすべてのサブスクリプションを作成します。
ここでは、Web ページで発生する 3 つのアクションを受け取ります。
- 色の変更
- 位置のリセット
- popularity の変更
また、Web ページで発生する 3 つのアクションをコマンドで受け取ります。
- スピードの変更
- 方向の変更
- 位置のリセット
関数 GenvidSubscriptionCallback()
は、後に表示するイベント受付部を制御します。ページから 3 種類のイベントを送信するため、Genvid_Subscribe()
を適切な名称で 3 回呼び出す必要があります。
注釈
Genvid_Subscribe()
の呼び出しに使用する名前は、Web ページや map-reduce で使用しているものと一致している必要があります。
HRESULT InitGenvid()
{
GenvidStatus gs;
// Initialize the Genvid Native SDK.
gs = Genvid_Initialize();
if (GENVID_FAILED(gs))
return E_FAIL;
// Create the audio stream.
gs = Genvid_CreateStream(sStream_Audio.c_str());
if (GENVID_FAILED(gs))
return E_FAIL;
// Specify auto-capture video source.
gs = Genvid_SetParameterInt(sStream_Audio.c_str(), "Audio.Source.WASAPI", 1);
if (GENVID_FAILED(gs))
return E_FAIL;
// Create the video stream.
gs = Genvid_CreateStream(sStream_Video.c_str());
if (GENVID_FAILED(gs))
return E_FAIL;
if (gGenvidFramerate > 0)
{
gs = Genvid_SetParameterFloat(sStream_Video.c_str(), "framerate", gGenvidFramerate);
if (GENVID_FAILED(gs))
return E_FAIL;
}
#if GENVID_USE_DXGISWAPCHAIN
// Specify auto-capture video source.
gs = Genvid_SetParameterPointer(sStream_Video.c_str(), "Video.Source.IDXGISwapChain", g_pSwapChain);
if (GENVID_FAILED(gs))
return E_FAIL;
#endif
// Create stream for game data.
gs = Genvid_CreateStream(sStream_GameData.c_str());
if (GENVID_FAILED(gs))
return E_FAIL;
gs = Genvid_CreateStream(sStream_GameCopyright.c_str());
if (GENVID_FAILED(gs))
return E_FAIL;
// this frame is receive one time only by each viewer when genvid client is connected
std::string copyright = "Copyright Genvid Technologies 2019";
gs = Genvid_SubmitGameData(-1, sStream_GameCopyright.c_str(), copyright.c_str(), (int)copyright.size());
if (GENVID_FAILED(gs))
return E_FAIL;
gs = Genvid_CreateStream(sStream_Popularity.c_str());
if (GENVID_FAILED(gs))
return E_FAIL;
gs = Genvid_CreateStream(sStream_ColorChanged.c_str());
if (GENVID_FAILED(gs))
return E_FAIL;
// Subscribe to events.
gs = Genvid_Subscribe(sEvent_changeColor.c_str(), &GenvidSubscriptionCallback, nullptr);
if (GENVID_FAILED(gs))
return E_FAIL;
gs = Genvid_Subscribe(sEvent_reset.c_str(), &GenvidSubscriptionCallback, nullptr);
if (GENVID_FAILED(gs))
return E_FAIL;
gs = Genvid_Subscribe(sEvent_cheer.c_str(), &GenvidSubscriptionCallback, nullptr);
if (GENVID_FAILED(gs))
return E_FAIL;
// Subscribe to commands.
gs = Genvid_SubscribeCommand(sCommand_speed.c_str(), &GenvidSubscriptionCommandCallback, nullptr);
if (GENVID_FAILED(gs))
return E_FAIL;
gs = Genvid_SubscribeCommand(sCommand_direction.c_str(), &GenvidSubscriptionCommandCallback, nullptr);
if (GENVID_FAILED(gs))
return E_FAIL;
gs = Genvid_SubscribeCommand(sCommand_reset.c_str(), &GenvidSubscriptionCommandCallback, nullptr);
if (GENVID_FAILED(gs))
return E_FAIL;
return S_OK;
}
ストリーミング¶
バックバッファを表示する直前に、(あらかじめ指定した IDXGISwapChain を使用して) ビデオフレームを自動キャプチャして Genvid に送信します。
次に、以下のゲームデータを収集します。
- キューブの位置。
- 向き。
- カメラマトリクス。
ゲームデータを Web サイトで使用する JSON 形式に変換します。
ユーティリティルーチン GetGameDataJSON()
を使用してビデオデータと同じタイムコードで、ゲームデータを送信します。
次に popularity データを収集し、変化の有無を検証します。変更がある場合、 更新した popularity データを Genvid_SubmitNotification()
で JSON に変換します。
最後に Genvid_CheckForEvents()
を呼び出して、Web サイトからイベントを受信したか否か確認します。
GenvidStatus gs = GenvidStatus_Success;
GenvidTimecode tc = Genvid_GetCurrentTimecode();
std::string gameData = GetGameDataJSON();
gs = Genvid_SubmitGameData(tc, sStream_GameData.c_str(), gameData.data(), (int)gameData.size());
assert(!GENVID_FAILED(gs));
// Send the popularity as a notification
std::string popularityJSON = GetPopularityJSON();
if (popularityJSON != oldPopularityJSON)
{
gs = Genvid_SubmitNotification(sStream_Popularity.c_str(), popularityJSON.data(), (int)popularityJSON.size());
assert(!GENVID_FAILED(gs) || gs == GenvidStatus_Disconnected);
}
oldPopularityJSON = popularityJSON;
gs = Genvid_CheckForEvents();
assert(!GENVID_FAILED(gs));
GenvidSubscriptionCallback¶
Genvid_CheckForEvents()
を呼び出すことで、GenvidSubscriptionCallback()
関数が実行される場合があります。この関数は、Web ページからゲームに送信されるイベントを受信します。呼び出す際には、
- summary ID 値を検証し、どのイベントが送信されたかを確認し、
- 生成したキーフィールドをパラメータとしてイベントに変換します。
//--------------------------------------------------------------------------------------
// Callback invoked when Genvid Events are received.
//--------------------------------------------------------------------------------------
void GenvidSubscriptionCallback(const GenvidEventSummary * summary, void * /*userData*/)
{
if (summary->id == sEvent_changeColor)
{
// Change the color of the cube.
const char * cubeName = summary->results[0].key.fields[0];
const char * cubeColor = summary->results[0].key.fields[1];
// Finding the cube and change the color
for (int i = 0; i < g_cubeTotal; ++i)
{
if (g_cubeList[i].GetName() == cubeName)
{
std::map<std::string, XMFLOAT4>::iterator colorToFind = sNameToColor.find(cubeColor);
if (colorToFind != sNameToColor.end())
{
g_cubeList[i].SetColor(colorToFind->second);
}
}
}
OnUpdateColor();
}
else if (summary->id == sEvent_reset)
{
// Reset the cube position.
const char * cubeName = summary->results[0].key.fields[0];
for (int i = 0; i < g_cubeTotal; ++i)
{
if (g_cubeList[i].GetName() == cubeName)
{
g_cubeList[i].ResetPosition();
return;
}
}
}
else if (summary->id == sEvent_cheer)
{
// Handle cube cheering.
const char * cubeName = summary->results[0].key.fields[0];
for (int i = 0; i < g_cubeTotal; ++i)
{
if (g_cubeList[i].GetName() == cubeName)
{
// GENVID - Get cheer min value.
g_cubeList[i].Cheer(float(summary->results[0].values[0].value));
// GENVID - Cheer value set.
return;
}
}
}
}
サマリーの構造は、config/events.json
でアプリケーションと一緒に提供される map-reduce の定義と一貫しています。
{
"version": "1.7.0",
"event": {
"game": {
"maps": [
{
"id": "changeColor",
"source": "userinput",
"where": {"key": ["changeColor", "<name>"], "name": "<color>", "type": "string"},
"key": ["changeColor", "<name>", "<color>"], "value": 1
},
{
"id": "reset",
"source": "userinput",
"where": {"key": ["reset"], "name": "<name>", "type": "string"},
"key": ["reset", "<name>"], "value": 1
},
{
"id": "cheer",
"source": "userinput",
"where": {"key": ["cheer"], "name": "<name>", "type": "string"},
"key": ["cheer", "<name>"], "value": 1
}
],
"reductions": [
{
"id": "changeColor",
"where": {"key": ["changeColor", "<name>", "<color>"]},
"key": ["<name>", "<color>"],
"value": ["$count"],
"period": 250
},
{
"id": "reset",
"where": {"key": ["reset", "<name>"]},
"key": ["<name>"],
"value": ["$count"],
"period": 250
},
{
"id": "cheer",
"where": {"key": ["cheer", "<name>"]},
"key": ["<name>"],
"value": ["$sum"],
"period": 250
}
]
}
}
}
GenvidSubscriptionCommandCallback¶
また、 Genvid_CheckForEvents()
を呼び出すことで、 GenvidSubscriptionCommandCallback()
関数が実行される場合があります。これにより、コマンドの解釈が行われます。
コマンドの結果には、ID と値が含まれています。値を解析し、メソッドで使用する文字列に変換します。
void GenvidSubscriptionCommandCallback(const GenvidCommandResult * result, void * /*userData*/)
{
std::string id(result->id);
std::string value(result->value);
if (id == "direction")
{
std::vector<std::string> elems;
split(value, ':', elems);
auto cubeName = elems[0];
for (int i = 0; i < g_cubeTotal; ++i)
{
if (g_cubeList[i].GetName() == cubeName)
{
float x = (float)std::atof(elems[1].c_str());
float y = (float)std::atof(elems[2].c_str());
float z = (float)std::atof(elems[3].c_str());
g_cubeList[i].ChangeDirection(x, y, z);
return;
}
}
}
else if (id == "speed")
{
std::vector<std::string> elems;
split(value, ':', elems);
auto cubeName = elems[0];
for (int i = 0; i < g_cubeTotal; ++i)
{
if (g_cubeList[i].GetName() == cubeName)
{
float speed = (float)std::atof(elems[1].c_str());
auto velocity = g_cubeList[i].GetLinearVelocity() * speed;
g_cubeList[i].SetLinearVelocity(velocity);
return;
}
}
}
else if (id == "reset")
{
auto cubeName = value;
for (int i = 0; i < g_cubeTotal; ++i)
{
if (g_cubeList[i].GetName() == cubeName)
{
auto velocity = g_cubeList[i].GetInitialLinearVelocity();
g_cubeList[i].ResetPosition();
g_cubeList[i].ResetOrientation();
g_cubeList[i].SetLinearVelocity(velocity);
return;
}
}
}
}
ビデオストリーミング¶
チュートリアルでは、ビデオストリーミングの 2 つのアプローチを紹介しています。自動キャプチャとマニュアルキャプチャです。自動キャプチャがデフォルトに設定されています。
キャプチャを行うには、null パラメータで Genvid_SubmitVideoData()
関数を呼び出します。
gs = Genvid_SubmitVideoData(tc, sStream_Video.c_str(), nullptr, 0);
assert(!GENVID_FAILED(gs));
状況によっては、マニュアルキャプチャ使用する必要がある場合があります。DirectX11 を使用していない場合や、レンダリングスレッドを使用しないでデータを Genvid に渡したい場合などです。
マニュアルキャプチャを使用するには、正しいパラメータを使用してビデオフレームを設定する必要があります。
assert(description.Format == DXGI_FORMAT_R8G8B8A8_UNORM);
gs = Genvid_SetParameterInt(sStream_Video.c_str(), "video.pixel_format", GenvidPixelFormat_R8G8B8A8);
assert(gs == GenvidStatus_Success);
gs = Genvid_SetParameterInt(sStream_Video.c_str(), "video.width", description.Width);
assert(gs == GenvidStatus_Success);
gs = Genvid_SetParameterInt(sStream_Video.c_str(), "video.height", description.Height);
assert(gs == GenvidStatus_Success);
バッファをキャプチャして、Genvid に渡します。
// Figure out what we need on the first run.
if (g_pVideoBuffer == nullptr)
{
// Hardcoded for DXGI_FORMAT_R8G8B8A8_UNORM
g_NumBytes = description.Height * description.Width * 4;
g_pVideoBuffer = new uint8_t[g_NumBytes];
}
// On the following runs, check just in case.
else
{
// Hardcoded for DXGI_FORMAT_R8G8B8A8_UNORM
uint32_t TempNumBytes = description.Height * description.Width * 4;
if (TempNumBytes != g_NumBytes)
{
delete[] g_pVideoBuffer;
g_pVideoBuffer = new uint8_t[TempNumBytes];
g_NumBytes = TempNumBytes;
}
}
uint8_t * pDestination = g_pVideoBuffer;
uint8_t * pSource = (uint8_t *)resource.pData;
uint32_t NumSourceBytes = resource.RowPitch;
// Hardcoded for DXGI_FORMAT_R8G8B8A8_UNORM
uint32_t NumDestinationBytes = description.Width * 4;
uint32_t NumSourceLines = description.Height;
for (uint32_t i = 0; i < NumSourceLines; ++i)
{
memcpy(pDestination, pSource, NumDestinationBytes);
pSource += NumSourceBytes;
pDestination += NumDestinationBytes;
}
gs = Genvid_SubmitVideoData(tc, sStream_Video.c_str(), g_pVideoBuffer, g_NumBytes);
assert(!GENVID_FAILED(gs));
g_pImmediateContext->Unmap(pNewTexture, subresource);
終了¶
TermGenvid()
関数で、Genvid SDK を適切にクリーンアップするために必要なすべてのアクションを実行します。
ストリーム、イベント、コマンドのサブスクリプションをすべて解除する必要があります。その後 Genvid_Terminate()
で、すべてのストリームを破棄して終了します。
void TermGenvid()
{
// Cancel command subscriptions.
Genvid_UnsubscribeCommand(sCommand_reset.c_str(), &GenvidSubscriptionCommandCallback, nullptr);
Genvid_UnsubscribeCommand(sCommand_direction.c_str(), &GenvidSubscriptionCommandCallback, nullptr);
Genvid_UnsubscribeCommand(sCommand_speed.c_str(), &GenvidSubscriptionCommandCallback, nullptr);
// Cancel event subscriptions.
Genvid_Unsubscribe(sEvent_cheer.c_str(), &GenvidSubscriptionCallback, nullptr);
Genvid_Unsubscribe(sEvent_reset.c_str(), &GenvidSubscriptionCallback, nullptr);
Genvid_Unsubscribe(sEvent_changeColor.c_str(), &GenvidSubscriptionCallback, nullptr);
// Destroy the streams.
Genvid_DestroyStream(sStream_ColorChanged.c_str());
Genvid_DestroyStream(sStream_Popularity.c_str());
Genvid_DestroyStream(sStream_GameData.c_str());
Genvid_DestroyStream(sStream_GameCopyright.c_str());
Genvid_DestroyStream(sStream_Video.c_str());
Genvid_DestroyStream(sStream_Audio.c_str());
// Terminate the Genvid Native SDK.
Genvid_Terminate();
}