Engine Integration

Integrating the Genvid SDK should be rather simple. The API itself is contained in a single header file, genvid.h, which is written in C so that it can be integrated in as many development environments as possible.

You will need to add genvid.h to your include path. Please refer to your compilation environment for how to do this.

You will then need to link with the proper version of the shared library. The Genvid SDK includes both a 32-bit and 64-bit version of the library for the Windows platform. Again, refer to your compilation environment (or to our tutorial sample) for details.

Because the native Genvid SDK is a shared library, you will need to deploy the corresponding DLL alongside your game. This typically consists of simply copying Genvid.dll next to your game executable.

Note that all the methods used in the Genvid SDK return a enum GenvidStatus which can be used to determine if the operation was done properly. You can also use const char *Genvid_StatusToString(const GenvidStatus status) to convert the enum value into a const char* value.

Using the Genvid SDK program flow can be summarized into the following steps:

  1. Initialization
  2. Configuration
  3. Streaming
  4. Termination
Genvid program flow

Fig. 9 Genvid program flow

Initialization

Before calling any other routine, you must first initialize the library. This is done by calling GenvidStatus Genvid_Initialize():

GenvidStatus gs = Genvid_Initialize();
if(gs != GenvidStatus_Success)
{
   // Something bad happened; you can't use Genvid.
   return false;
}

Calling any Genvid SDK routine without properly initializing the Genvid library will result in a GenvidStatus_InvalidState error.

Configuration

Once the library is properly initialized, you then need to create a stream, using any name that you want that does not contain genvid which will be the streamID of your stream.

The stream is the context by which Genvid associates audio, video, as well as game data. Its streamID is used to reference it across all relevant calling locations.

Streams are created by calling GenvidStatus Genvid_CreateStream(const char *streamID):

GenvidStatus gs;
gs = Genvid_CreateStream("audio");
if(gs != GenvidStatus_Success)
{
   return false;
}
gs = Genvid_CreateStream("video");
if(gs != GenvidStatus_Success)
{
   return false;
}
gs = Genvid_CreateStream("game.players");
if(gs != GenvidStatus_Success)
{
   return false;
}
gs = Genvid_CreateStream("game.camera");
if(gs != GenvidStatus_Success)
{
   return false;
}

Before sending data to that stream, you need to set a few parameters.

All of the parameters are assigned using:

  1. GenvidStatus Genvid_GetParameterInt(const char *streamID, const char *paramKey, int *paramValue)
  2. GenvidStatus Genvid_GetParameterFloat(const char *streamID, const char *paramKey, float *paramValue)
  3. GenvidStatus Genvid_SetParameterPointer(const char *streamID,const char *paramKey, void *paramValue)

All of these are are variations of a routine specifying the parameter in a specific format (specified as integer, floating-pointer, or pointer values, respectively).

Every Genvid_SetParameter() routine takes the streamID as its first parameter.

It then requires a parameter key, which is effectively the name of the parameter to change. The key is case insensitive.

Finally, it takes a parameter value, which expects a specific type depending on the variant used.

Calling a Genvid_SetParameter() function without a proper parameter key will result in a GenvidStatus_InvalidParameter error.

Common parameters

The first parameter to set corresponds to the frame rate of the stream. This informs Genvid of the expected frequency at which it will receive data. It is specified using either an integer or a floating-point number:

GenvidStatus gs;
// Sending video 30 times a second.
gs = Genvid_SetParameterInt("video", "framerate", 30);
if(gs != GenvidStatus_Success)
{
   return false;
}
// Sending slow data once every 10 seconds.
gs = Genvid_SetParameterFloat("slowdata", "framerate", 0.1f);
if(gs != GenvidStatus_Success)
{
   return false;
}

Once the framerate is defined, any timecode will round to the nearest value. The default framerate for new streams is 30 Hz.

Another common parameter to set relates to the video source, which specifies where to grab the game’s rendered frame. Since the Genvid SDK currently only supports D3D11 sources, it currently only accepts either a DXGI swap chain or a 2D texture.

To specify the IDXGISwapChain, use Video.Source.IDXGISwapChain parameter key with a pointer:

IDXGISwapChain* mySwapChain = nullptr;
// Some code assigning mySwapChain to a valid value...
GenvidStatus gs = Genvid_SetParameterPointer("video", "Video.Source.IDXGISwapChain", mySwapChain);
if(gs != GenvidStatus_Success)
{
   return false;
}

For a 2D texture resource handle, use Video.Source.ID3D11Texture2D instead:

ID3D11Texture2D* myTexture = nullptr;
// Some code assigning myTexture to a valid value...
GenvidStatus gs = Genvid_SetParameterPointer("video", "Video.Source.ID3D11Texture2D", myTexture);
if(gs != GenvidStatus_Success)
{
   return false;
}

This will inform the Genvid SDK where to grab the video data from when streaming. Specifying any of the video source parameters will activate automatic frame grabbing. Developers are still required to inform Genvid that the frame is ready (see Video Streaming).

Just like for video, the Genvid SDK has the ability to auto-capture the audio stream. This is enabled by setting the Audio.Source.WASAPI parameter to 1:

GenvidStatus gs = Genvid_SetParameterInt("audio", "Audio.Source.WASAPI", 1);
if(gs != GenvidStatus_Success)
{
   return false;
}

Unlike what is required for video, developers do not need to inform the system when data is ready. The Genvid SDK will handle everything automatically.

It is recommended to set the same framerate as the associated video source (i.e. 30 Hz by default).

When audio auto-capture is active, the default mixing format and sample rate are used. By default, Windows uses stereo floating-point samples at 48 kHz.

Below is a table summarizing the most important stream parameters. Note that parameter keys are case-insensitive.

Table 1 List of the main stream parameters.
Key Type Description Default
framerate float Specifies the framerate. 30
video.source.idxgiswapchain pointer Specifies the D3D11 swap chain to use for video source. This activates auto-video-capture. N/A
video.source.id3d11texture2d pointer Specifies the D3D11 texture to use for video source. This activates auto-video-capture. N/A
audio.source.wasapi integer Turns WASAPI audio capture on or off (with 1 or 0). This activates auto-audio-capture. 0
audio.rate integer Change the sample rate of the audio. N/A

Parameter Retrieval

The current state of the Genvid SDK can be retrieved using the inverse functions:

  1. GenvidStatus Genvid_GetParameterInt(const char *streamID, const char *paramKey, int *paramValue)
  2. GenvidStatus Genvid_GetParameterFloat(const char *streamID, const char *paramKey, float *paramValue)
  3. GenvidStatus Genvid_SetParameterPointer(const char *streamID, const char *paramKey, void *paramValue)

These functions take a pointer to integer, floating-point, and pointer, respectively.

Genvid_GetParameterInt(), Genvid_GetParameterFloat() and GenvidStatus Genvid_GetParameterUTF8(const char *id, const char *paramKey, char *dstBuffer, size_t dstBufferSize) can be used also to get key value from Consul.

For Genvid_GetParameterInt() and GenvidGetParameterFloat(), you need to use "genvid.kv" as the stream id to be able to access the key value, the paramKey as the key name and paramValue pointer as the destination for the value.

int score = 0;
GenvidStatus gs = Genvid_GetParameterInt("genvid.kv", "genvid/game/score", &score);

float time = 0.0f;
GenvidStatus gs = Genvid_GetParameterFloat("genvid.kv", "genvid/game/time", &time);

For Genvid_GetParameterUTF8(), you can use any value as the stream id, the paramKey is the key name, the dstBuffer is the container that will receive the value and dstBufferSize is the buffer size. In the event that the key received is smaller than the allocation size available, a GenvidStatus_Incomplete warning is returned.

char playerName[10];
GenvidStatus gs = Genvid_GetParameterUTF8("genvid.getStringKv", "genvid/game/playername", playerName, 10);

Subscriptions

Streams creation and configuration only allow for pushing data to the Genvid Services. In order to get any information back, we need to subscribe to them. There are 2 types of subscriptions: one for events, and one for commands.

The event subscriptions are done using GenvidStatus Genvid_Subscribe (const char *id, GenvidEventSummaryCallback callback, void *userData), and must specify a callback, as well as some optional data pointer which will be sent to the callback (user nullptr if you don’t need any):

GenvidStatus gs = Genvid_Subscribe("some_event_id", &SomeEventCallback, nullptr);

In the above example, the event callback would have the following signature:

void SomeEventCallback(const GenvidEventSummary* summary, void* userData*)
{
    // summary->id would be equal to "some_event_id".
    // summary->results[] would contain the results of the MapReduce.
    // userData would contain nullptr (but could be anything the user specifies).
    // ...
}

Please refer to the typedef void (*GenvidEventSummaryCallback) (const GenvidEventSummary *summary, void *userData) page for details on the callback, struct GenvidEventSummary page for details on the structure returned and to the Events Overview documentation for details on how to define MapReduce operations.

It is possible to register the same callback using different event ids and/or user data pointers.

In addition to event subscriptions, we can also subscribe to commands. Commands are special immediate messages that can be used to send very succinct directives to the game process. These are immediate but non-scalable, and should be used with care.

Commands subscription uses a slightly different function called GenvidStatus Genvid_SubscribeCommand(const char *id, GenvidCommandCallback callback, void *userData):

GenvidStatus gs = Genvid_SubscribeCommand("some_command_id", &SomeCommandCallback, nullptr);

and the associated callback is slightly simpler to use due to the fact that GenvidCommandResult is a simpler structure containing just an id and a value (both of them const char*):

void SomeCommandCallback(const GenvidCommandResult* result, void* userData)
{
    // result->id would be equal to "some_event_id".
    // result->value would contain whatever string was sent.
    // userData would contain nullptr (but could be anything the user specifies).
    // ...
}

Please refer to the typedef void (*GenvidCommandCallback) (const GenvidCommandResult *result, void *userData) page for details on the callback, struct GenvidCommandResult page for details on the structure returned and to the Command service API documentation for details on how to the command service API function.

Streaming

Every time you have data that you want to stream, you need to inform Genvid to proceed.

You also need to associate a timecode to every data chunk, so that Genvid can properly place it inside the stream.

Providing an out-of-order timecode (i.e. a timecode prior to the last one used) is a programming error and has undefined results.

Timecode

The Genvid SDK provides a routine to retrieve the current timecode: GenvidTimecode Genvid_GetCurrentTimecode(). It returns a special typedef int64_t GenvidTimecode value, which is 64-bit value defined to be accurate to the millisecond. We may, in the future, modify the internal representation of GenvidTimecode depending on our needs.

Using a timecode of -1 is equivalent to calling Genvid_GetCurrentTimecode() and using that result.

If you want multiple data chunks to share the same timecode, you should call Genvid_GetCurrentTimecode() only once, and reuse the result in the subsequent Genvid_Submit*Data() calls (see below).

The previous value returned by Genvid_GetCurrentTimecode() can always be retrieved by calling GenvidTimecode Genvid_GetPreviousTimecode() instead. This can prove useful when you want multiple code locations to all use the same timecode, but where carrying the GenvidTimecode yourself can prove problematic.

Audio Streaming

We currently only support auto-capture for audio. Nothing is required other than activating it (see Common parameters for details).

We have reserved the function GenvidStatus Genvid_SubmitAudioData(const GenvidTimecode timecode, const char *streamID, const void *audioData, const int audioDataSize) for future uses in which developers might want to send audio samples themselves.

Video Streaming

When your rendered frame is ready, you need to inform Genvid that it can capture and encode the frame.

To do that, you need to call GenvidStatus Genvid_SubmitVideoData(const GenvidTimecode timecode, const char *streamID, const void *videoData, const int videoDataSize). Because you have set up video source previously (see here), you do not need to send actual data; only the timecode and streamID are necessary, which is why we send a null pointer and 0 as the last two parameters in the example (note that the interface is designed to allow specifying image data in the future).

A typical usage would be:

GenvidTimecode tc = Genvid_GetCurrentTimecode();
GenvidStatus gs = Genvid_SubmitVideoData(tc, "video", nullptr, 0);
if(gs != GenvidStatus_Success)
{
   return false;
}

When this routine is called, the Genvid SDK will capture the video source, encode it, then stream it. All of this is done in an asynchronous way in order to be as efficient as possible.

Game Data Streaming

Similar to audio and video data, every game data chunk must specify a timecode and a streamID in addition to a byte array (data and size) containing the actual game data.

We currently make no assumption on that data, and simply carry raw bytes along the way. We are currently investigating placing stricter requirements on game data format as this allows considerable bandwidth savings.

Here is a sample code using GenvidStatus Genvid_SubmitGameData (const GenvidTimecode timecode, const char *streamID, const void *gameData, const int gameDataSize) to show how to stream both camera and player data onto separate streams using the same timecode:

// Fake runtime data.
std::string playerData = R"({ "P1": {"position": [0, 0, 0]}, "P2": {"position": [1, 0, 0]} })";
std::string cameraData = R"({ "matrix": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] })";

GenvidStatus gs;
GenvidTimecode tc = Genvid_GetCurrentTimecode();
gs = Genvid_SubmitGameData(tc, "game.player", playerData.data(), (int)playerData.size());
if(gs != GenvidStatus_Success)
{
    return false;
}
gs = Genvid_SubmitGameData(tc, "game.camera", cameraData.data(), (int)cameraData.size());
if(gs != GenvidStatus_Success)
{
    return false;
}

Game Annotation streaming

The annotations are similar to data streaming. The main difference is how the system and javascript handles the information.

Here is a sample code using GenvidStatus Genvid_SubmitAnnotation (const GenvidTimecode timecode, const char *streamID, const void *annotationData, const int annotationDataSize) to show how to send annotation about the confirm kills.

// Fake runtime data.
std::string confirmKills = R"({ "kills": ["Player1", "Player2"] })";

GenvidStatus gs;
GenvidTimecode tc = Genvid_GetCurrentTimecode();
gs = Genvid_SubmitAnnotation(tc, "game.confirmKills", playerData.data(), (int)playerData.size());
if(gs != GenvidStatus_Success)
{
    return false;
}

Game Notification

A notification is used to send information as fast as possible to the web site. This can be used to send realtime update to the system. The notifications are not persistant. A notification is not associated with a time code.

Here is a sample code using GenvidStatus Genvid_SubmitNotification (const char* notificationID, const void* notificationData, const int notificationDataSize) to show how to send notification about the player’s popularity.

// Fake runtime data.
std::string popularityData = R"({ "players": [{"name":"player1", "popularity": 2},{"name":"player2", "popularity": 10}] })";

GenvidStatus gs;
gs = Genvid_SubmitNotification("game.popularity", popularityData.data(), (int)popularityData.size());
if(gs != GenvidStatus_Success)
{
    return false;
}

Should a game need to create a new stream, it must first terminate the Genvid Cluster, then create and configure the new stream, and restart the cluster. This current limitation is due to the fact that the cluster must reserve resources in order to be able to properly handle all of the possible streams.

Callback verification

While the streaming is occuring, if you have any callback for any events or commands, you need to do a GenvidStatus Genvid_CheckForEvents() to allow the callback to be called when new results are available. It is recommended to call it on a regular basis to make sure the callback are called as soon as possible. For our sample, we decided to do a Genvid_CheckForEvents() at the same time that we are sending data.

In the event that you would be catching the GenvidStatus returned, note that in the case that events are available, a GenvidStatus_Success status is returned, but in the case that there are no event, a GenvidStatus_ConnectionTimeout status is returned instead.

Termination

Should you no longer need a stream, you can deallocate it using GenvidStatus Genvid_DestroyStream(const char *streamID) with the proper streamID:

Genvid_DestroyStream("game.player");
Genvid_DestroyStream("came.camera");
Genvid_DestroyStream("video");

This should release internal storage related to the specified stream, e.g., any temporary buffer required for streaming or encoding.

Using the streamID of a destroyed stream will result in a GenvidStatus_InvalidState error.

Both subscription routines have an equivalent one to unsubscribe:

  1. GenvidStatus Genvid_Unsubscribe(const char *id, GenvidEventSummaryCallback callback, void *userData)
  2. GenvidStatus Genvid_UnsubscribeCommand(const char *id, GenvidCommandCallback callback, void *userData)
Genvid_Unsubscribe("some_event_id", &SomeEventCallback, nullptr);
Genvid_UnsubscribeCommand("some_command_id", &SomeCommandCallback, nullptr);

When unsubscribing, sending a nullptr for any argument will match any existing callback for that callback, so calling:

Genvid_Unsubscribe(nullptr, &SomeEventCallback, nullptr);

would unsubscribe to all event callbacks registered to call the function SomeEventCallback(), whereas calling:

Genvid_UnsubscribeCommand(nullptr, nullptr, nullptr);

would unsubscribe to all of the existing command callbacks.

Once you are done using the Genvid SDK, you should call GenvidStatus Genvid_Terminate():

GenvidStatus gs = Genvid_Terminate();
if(gs != GenvidStatus_Success)
{
    return false;
}

Once the API is terminated, no other API call should be made until it is initialized again.