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:
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:
- GenvidStatus Genvid_SetParameterInt(const char *streamID, const char *paramKey, int *paramValue)
- GenvidStatus Genvid_SetParameterFloat(const char *streamID, const char *paramKey, float *paramValue)
- 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.
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:
- GenvidStatus Genvid_GetParameterInt(const char *streamID, const char *paramKey, int *paramValue)
- GenvidStatus Genvid_GetParameterFloat(const char *streamID, const char *paramKey, float *paramValue)
- GenvidStatus Genvid_GetParameterPointer(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:
- GenvidStatus Genvid_Unsubscribe(const char *id, GenvidEventSummaryCallback callback, void *userData)
- 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.