Unity Sample Integration

Warning

Please note that if you deploy your sample to the cloud that you must be compliant with Unity’s Terms of Service related to server streaming. Please contact Unity for more information. Genvid is not responsible for your licensing terms with Unity.

Integrating the Genvid SDK into your Unity project is a simple process using 3 DLL files. In this page, we will use our own sample to explain how to integrate the API directly into your project and how to use it. The Unity sample is similar to our D3D11 tutorial sample and exposes the same functionalities. We currently use two methods for our sample:

  • Genvid SDK management via scripts and making calls directly to the Genvid SDK C Sharp wrapper.
  • A prefab that manages all the calls made to the Genvid SDK C Sharp wrapper.

This page will cover the details about the Genvid SDK management via scripts and making calls to the Genvid SDK C Sharp wrapper, but if you want more details about the prefab, please proceed to this page.

Genvid SDK activation

We added an option to disable the Genvid SDK inside the editor in the event you would need to test in the editor without the stack running. Simply select the GameManager object and click on the Genvid SDK Active checkbox to disable it. Also, you will need to deactivate the prefab elements by clicking on GenvidManagerCube and GenvidManagerCallback and change the value of Genvid SDK Active to not selected. Make sure to proceed to the second scene to deactivate any Genvid SDK objects if needed.

  1. How to run the sample
  2. Files needed
  3. Additional script files
  4. Prefab files
  5. Include and declaration
  6. Initialization
  7. Callbacks
  8. Update
  9. Video capture
  10. Termination

How to run the sample

This guide assumes that your system is already properly configured to run the Genvid tutorial sample (see Tutorial), i.e., that your config file is properly set up with a working YouTube account (see Genvid Configuration) and that you can successfully run the Genvid services (see Local Environment Usage).

Before explaining about the files and methods used, let us explain how to run the Unity sample.

  1. Clean the cluster config and load the unity sample:
genvid-sdk clean-config
genvid-sdk load-config-sdk
py unity.py load
  1. Build the project and the website:
py unity.py build

Note that there are now several build options, so you can use:

  • -b: Build in 32 bit
  • -d: Build in debug
  1. Launch the game with all of the required Genvid services:
genvid-sdk start

You can see the website by doing genvid-sdk open web.

In the event you would want to stop the stack: genvid-sdk stop.

Files needed

Three files are currently required to be integrated into your project to fully use our SDK without any issues. If you want to run the current sample, you don’t need to copy the files in the sample. By using py unity.py build, you can build the project and run it without having to manually copy any file.

Genvid.dll

This is the file created by our Native SDK and it is used to make all the calls related to Genvid. The Genvid SDK includes both a 32-bit and 64-bit version of the library for the Windows platform. Make sure to use the proper one related to the compilation of your application.

This file needs to be placed inside the Plugins folder of your project. For the current sample, we placed the files inside an x64 and x86 folder depending of the library version. The command script of the project handles this for you when you run local.py run-script build.

As a developer, you will not need to call any function from this DLL directly since we created a C# wrapper integrating all the functions needed from it to simplify the process.

GenvidPlugin.dll

This is a file created by the GenvidPlugin project integrated in the Unity sample project folder. This file is taking care of making calls to do the video capture.

This file needs to be placed inside the Plugins folder of your project. For the current sample, we placed the files inside an x64 and x86 folder depending of the library version. The command script of the project handles this for you when you run local.py run-script build.

GenvidSDKCSharp.dll

This is a file that is used to make all the calls to the Genvid.dll. This file is a C# wrapper of the Genvid C SDK, so all the proper conversions are handled by this dll.

This file needs to be placed inside the Plugins folder of your project.

Additional script files

Some additional script files are used for the Unity sample, so we made up a list for each and what are their purpose.

GenvidActivation.cs

This is a file that is used to create a dynamic object that is carried from one scene to another. This object is used to determine if the Genvid SDK initialization process has been done when proceeding to another scene.

This file is placed inside the Assets folder of the sample project.

GameManagement.cs

This is a file that is used to handle the Genvid SDK in our sample project. This script also coordinates various actions happening on the scene.

This file is placed inside the Assets folder of the sample project.

MovementCube.cs

This is a file that is used to move the objects on the scene. It is also used to update popularity and colors of the associated game object.

This file is placed inside the Assets folder of the sample project.

Build.cs

This is a file that is used to build the unity sample project via the python script in various configurations.

This file is placed inside the Assets folder of the sample project.

Prefab files

Several files are located in the folder GenvidPrefab for the Unity samples. The current Unity sample runs with the prefab activated. If you want more information about the prefab, proceed to this page, otherwise this page will cover information on how the Genvid SDK C Sharp wrapper is used in the sample.

Include and declaration

In the include section, we need to have using GenvidSDKCSharp to be able to use all the calls from the Genvid SDK. Afterwards, you can call any content from the Genvid SDK by writting GenvidSDK.theVariableOrFunctionNeeded.

In the declaration section of the script, we need to have various dll import to be able to use the GenvidPlugin properly for the video capture.

    [DllImport("GenvidPlugin", EntryPoint = "getVideoInitStatus")]
    [return: MarshalAs(UnmanagedType.I4)]
    private static extern GenvidSDK.Status GetVideoInitStatus();

    [DllImport("GenvidPlugin", EntryPoint = "getVideoSubmitDataStatus")]
    [return: MarshalAs(UnmanagedType.I4)]
    private static extern GenvidSDK.Status GetVideoSubmitDataStatus();

    [DllImport("GenvidPlugin", EntryPoint = "setupVideoChannel")]
    public static extern void SetupVideoChannel(string streamID);

    [DllImport("GenvidPlugin", EntryPoint = "cleanUp")]
    public static extern void CleanUp();

    [DllImport("GenvidPlugin")]
    public static extern IntPtr GetRenderEventFunc();
  1. GenvidSDK.Status GetVideoInitStatus() is used to get the Status of the video initialization process.
  2. GenvidSDK.Status GetVideoSubmitDataStatus() is used to get the Status of the submit video data process.
  3. SetupVideoChannel(string streamID) is used to indicate which stream is the one used for the video.
  4. CleanUp() is used to clean up pointers used in GenvidPlugin.dll when done with the video capture.
  5. IntPtr GetRenderEventFunc() is a function that we call to proceed with the video streaming (either starting the video capture or submitting video data).

Initialization

The initialization process is performed during the Start() function of our sample inside the GameManagement.cs script.

    // Use this for initialization
    IEnumerator Start()
    {
        // Find all the camera available on the scene
        m_sceneCamera = FindObjectsOfType(typeof(Camera)) as Camera[];
        m_sceneCameraTexture = new IntPtr[m_sceneCamera.Length];
        m_cameraTexture = new RenderTexture[m_sceneCamera.Length];
        commandBuffer = new CommandBuffer[m_sceneCamera.Length];
        secondVideoStream = GameObject.Find("VideoStream");

        if (m_sceneCamera != null)
        {
            for (int x = 0; x < m_sceneCamera.Length; x++)
            {
                if (x == 0)
                {
                    m_sceneCamera[x].enabled = true;
                }
                else
                {
                    m_sceneCamera[x].enabled = false;
                }
            }
        }

        m_timecodeText = GameObject.Find("timecode_text").GetComponent<Text>();
        m_getParamIntText = GameObject.Find("getParamInt_text").GetComponent<Text>();
        m_getUTF8Text = GameObject.Find("getUTF8_text").GetComponent<Text>();
        secondGenvidManager = GameObject.Find("GenvidManagerCube");
        callbackGenvidManager = GameObject.Find("GenvidManagerCallback");

        if(secondGenvidManager != null)
        {
            secondGenvidSDKActive = secondGenvidManager.GetComponent<GenvidManagement>().m_genvidSDKActive;
        }
        else
        {
            secondGenvidSDKActive = false;
        }
        

        m_activeObjectsList = GameObject.FindGameObjectsWithTag("ActiveObjects");
        m_GenvidActivation = GameObject.Find("GenvidActivation");
        bool initializationGenvid;

        if (m_GenvidActivation == null)
        {
            m_GenvidActivation = new GameObject();
            m_GenvidActivation.name = "GenvidActivation";
            m_GenvidActivation.AddComponent<GenvidActivation>();
            m_GenvidActivation.GetComponent<GenvidActivation>().activateObject(GenvidSDKActive);
            GC.KeepAlive(m_GenvidActivation);
            initializationGenvid = true;
        }
        else
        {
            GenvidSDKActive = m_GenvidActivation.GetComponent<GenvidActivation>().getGenvidActive();
            initializationGenvid = false;
        }

        if (GenvidSDKActive)
        {
            GenvidSDK.Status gvStatus;

            if (initializationGenvid)
            {
                // Genvid Initialize
                gvStatus = GenvidSDK.Initialize();
                if (gvStatus != GenvidSDK.Status.Success)
                {
                    Debug.LogError("Error while Genvid Initialize : " + GenvidSDK.StatusToString(gvStatus));
                }

                // Initialize all Genvid streams
                foreach (string stream in Enum.GetNames(typeof(Stream)))
                {
                    var status = GenvidSDK.CreateStream(stream);
                    if (status != GenvidSDK.Status.Success)
                    {
                        Debug.LogError("Error while creating the " + stream + " stream: " + GenvidSDK.StatusToString(status));
                    }
                }

                gvStatus = GenvidSDK.SetParameter(Stream.Audio, "Audio.Source.WASAPI", 1);
                if (gvStatus != GenvidSDK.Status.Success)
                {
                    Debug.LogError("Error while setting the parameter for the audio : " + GenvidSDK.StatusToString(gvStatus));
                }

                // Setting up the video channel
                SetupVideoChannel(Stream.Video.ToString());
            }

            // Setting up the callback for the events with their subscribe
            m_eventCallback = new GenvidSDK.EventSummaryCallback(this.eventCallbackFunction);
            m_operationCompleteCallback = new GenvidSDK.EventSummaryCallback(this.operationCompleteCallbackFunction);

            // Initialize all Genvid Events
            int counter = 0;
            foreach (string eventSummary in Enum.GetNames(typeof(EventSummary)))
            {
                listUserData.Add(new IntPtr(counter));

                var status = GenvidSDK.Subscribe(eventSummary, m_eventCallback, listUserData[counter]);
                if (status != GenvidSDK.Status.Success)
                {
                    Debug.LogError("Error while doing the subscribe for " + eventSummary + " for m_eventCallback : " + GenvidSDK.StatusToString(status));
                }

                status = GenvidSDK.Subscribe(eventSummary, m_operationCompleteCallback, listUserData[counter]);
                if (status != GenvidSDK.Status.Success)
                {
                    Debug.LogError("Error while doing the subscribe for " + eventSummary + " for m_operationCompleteCallback : " + GenvidSDK.StatusToString(status));
                }
                counter++;
            }

            // Setting up the callback for the commands with their subscribe
            m_commandCallback = new GenvidSDK.CommandCallback(this.commandCallbackFunction);

            // Initialize all Genvid Commands
            foreach (string command in Enum.GetNames(typeof(Command)))
            {
                listUserData.Add(new IntPtr(counter));

                var status = GenvidSDK.SubscribeCommand(command, m_commandCallback, listUserData[counter]);
                if (status != GenvidSDK.Status.Success)
                {
                    Debug.LogError("Error while doing the subscribe command for " + command + ": " + GenvidSDK.StatusToString(status));
                }
                counter++;
            }
        }

        // GENVID - On video begin
        if (GenvidSDKActive)
        {
            yield return StartCoroutine(CallPluginAtEndOfFrames());
        }
        // GENVID - On video end
    }

At the start, we are finding all the cameras available on the scene and activate the first one. We are also setting up various variables according to the number of cameras for a second type of video capture. We are also finding our text objects that will be modified later on and we are also finding all the active objects via the tag we created ActivateObjects on the scene (cube or sphere depending on the scene).

We also verify if the prefab is activated by getting the m_genvidSDKActive public variable from the object placed on the scene.

Afterwards, we proceed to verify if the GenvidActivation object exists. If the object doesn’t exist, it means that we need to do the proper initialization process.

We start by creating an empty game object, give it a name, add the script GenvidActivation to it and we proceed to activate. The activateObject(bool GenvidSDKActive) method gets the number of scenes available in this project along with the current scene number plus it saves the GenvidSDKActive value to know if the Genvid SDK will be active (useful when testing in the editor). We make sure that the GenvidActivation object will not be deleted by the garbage collector and we set a bool variable to true to perform the Genvid SDK initialization later.

In the case the object exist, we simply get the GenvidSDKActive value from the object and we set the bool variable to false.

Afterwards if the Genvid SDK is active and if it is the first time that we are booting the project, we proceed to initialize the Genvid SDK with Initialize. Each GenvidSDK function return a Status which we validate to know if there was an error with the SDK interaction.

We then proceed to create the various streams needed with CreateStream(String).

As for the audio stream, we also proceed to do a SetParameter(Object,String,Int32). to activate the sound capture with the proper parameters.

For the video stream, the stream creation process is a bit different. We are still creating the stream as usual, but we also need to call setupVideoChannel(string streamID) from the GenvidPlugin.dll.

Outside the initialization condition, we proceed to subscribe for events and commands. We use Subscribe(String,EventSummaryCallback,IntPtr) and SubscribeCommand(String,CommandCallback,IntPtr) respectively.

Callbacks

When an event is called from the website, the appropriate callback that was subscribed to the event StreamID is triggered.

    public void eventCallbackFunction(IntPtr summaryData, IntPtr userData)
    {
        var summary = GenvidSDK.GetSummary(summaryData);

        if (summary.id == EventSummary.changeColor.ToString()) 
        {
            string cubeName = summary.results[0].key.fields[0];
            string cubeColor = summary.results[0].key.fields[1];

            for (int i = 0; i < m_activeObjectsList.Length; ++i)
            {
                if (m_activeObjectsList[i].name == cubeName)
                {
                    for (int x = 0; x < m_NameToColor.Length; x++) 
                    {
                        if (m_NameToColor[x] == cubeColor) 
                        {
                            changeColorCube(i, m_gColors[x]);
                        }
                    }
                }
            }
        }
        else if (summary.id == EventSummary.cheer.ToString())
        {
            // Handle cube cheering.
            string cubeName = summary.results[0].key.fields[0];
            for (int i = 0; i < m_activeObjectsList.Length; ++i)
            {
                if (m_activeObjectsList[i].name == cubeName)
                {
                    m_activeObjectsList[i].GetComponent<MovementCube>().cheer((float)(summary.results[0].values[0].value));
                }
            }
        }
        else if (summary.id == EventSummary.reset.ToString())
        {
            // Handle cube reset
            string cubeName = summary.results[0].key.fields[0];
            for (int i = 0; i < m_activeObjectsList.Length; ++i)
            {
                if (m_activeObjectsList[i].name == cubeName)
                {
                    m_activeObjectsList[i].GetComponent<MovementCube>().transform.position = m_activeObjectsList[i].GetComponent<MovementCube>().oldPosition;
                }
            }
        }
    }

The event callbacks need to be formatted as indicated on this EventSummaryCallback page.

In the sample, we are sending the IntPtr into GetSummary(IntPtr) which provide the callback with a EventSummary converted properly in C#.

Once we have the EventSummary, we can access the data received and handle the event appropriately. In the sample, we verify the id of the structure to know which event was triggered and we use the result to interact with the objects of the sample.

One of the event callback trigger the method changeColorCube which we use to send an annotation to the stream. If the Genvid SDK is active for the script management, we perform a SubmitAnnotation(Int64,String,String).

    //Change the color of a cube
    void changeColorCube(int location, Vector4 colorChange)
    {
        m_activeObjectsList[location].transform.Find(m_activeObjectsList[location].name + "_child").GetComponent<Renderer>().material.SetColor("_EmissionColor", colorChange);
        m_activeObjectsList[location].GetComponent<MovementCube>().cubeColor = colorChange;
        m_activeObjectsList[location].GetComponent<MovementCube>().cubeChangedColor = true;

        if(GenvidSDKActive)
        {
            long tc = GenvidSDK.GetCurrentTimecode();
            var status = GenvidSDK.SubmitAnnotation(tc, Stream.ColorChanged.ToString(), getColorJSON());
            if (status != GenvidSDK.Status.Success)
            {
                Debug.LogError("Annotation fail with error " + GenvidSDK.StatusToString(status));
            }
        }
        else if(secondGenvidSDKActive)
        {
            GameObject.Find("AnnotationSend_Color").GetComponent<AnnotationManagement>().submitAnnotation();
        }
    }

When a command is called from the website, the appropriate callback that was subscribed to the command StreamID is triggered.

    public void commandCallbackFunction(GenvidSDK.CommandResult commandResult, IntPtr uniqueID)
    {
        if (commandResult.id == Command.direction.ToString())
        {
            Char delimiter = ':';
            String[] substrings = commandResult.value.Split(delimiter);
            for (int i = 0; i < m_activeObjectsList.Length; ++i)
            {
                if (m_activeObjectsList[i].name == substrings[0])
                {
                    m_activeObjectsList[i].GetComponent<MovementCube>().changeDirection(int.Parse(substrings[1]), int.Parse(substrings[3]));
                    return;
                }
            }
        }
        else if (commandResult.id == Command.speed.ToString())
        {
            Char delimiter = ':';
            String[] substrings = commandResult.value.Split(delimiter);
            for (int i = 0; i < m_activeObjectsList.Length; ++i)
            {
                if (m_activeObjectsList[i].name == substrings[0])
                {
                    if (float.Parse(substrings[1]) > 1.0f) 
                    {
                        m_activeObjectsList [i].GetComponent<MovementCube>().changeSpeed (float.Parse (substrings [1]));
                    } 
                    else 
                    {
                        m_activeObjectsList [i].GetComponent<MovementCube>().changeSpeed (float.Parse (substrings [1]) * -1.0f);
                    }
                    return;
                }
            }
        }
        else if (commandResult.id == Command.reset.ToString())
        {
            for (int i = 0; i < m_activeObjectsList.Length; ++i)
            {
                if (m_activeObjectsList[i].name == commandResult.value)
                {
                    m_activeObjectsList[i].GetComponent<MovementCube>().transform.position = m_activeObjectsList[i].GetComponent<MovementCube>().oldPosition;
                    return;
                }
            }
        }
        else if (commandResult.id == Command.camera.ToString())
        {
            changeCamera();
        }
        else if (commandResult.id == Command.scene.ToString())
        {
            changeScene();
        }
    }

The command callbacks need to be formatted as indicated on this CommandCallback page.

In the sample, we use the CommandResult to access the data received and perform the action related to the command that was sent.

It is also possible to assign a second callback to the same stream ID. In the sample, we assign a second callback for each event subscribed to display the event unique int associated with it that was sent as userData.

    public void operationCompleteCallbackFunction(IntPtr summaryData, IntPtr userData)
    {
        int uniqueId = userData.ToInt32();
        m_getUTF8Text.text = "Event completed with uniqueID: " + uniqueId;
    }

Also, in the OnDisable function, we are removing the callback. When we are proceeding to another scene, we need to register the callback to a new callback instead. This prevents the SDK from calling a callback that does not exist in the new scene.

    void OnDisable()
    {
        if (GenvidSDKActive && m_unsubDone == false)
        {
            int counter = 0;
            // Unsubscribe all the events
            foreach (string eventSummary in Enum.GetNames(typeof(EventSummary)))
            {
                var status = GenvidSDK.Unsubscribe(eventSummary, m_eventCallback, listUserData[counter]);
                if (status != GenvidSDK.Status.Success)
                {
                    Debug.LogError("Error while unsubscribe process for " + eventSummary + " on the m_eventCallback : " + GenvidSDK.StatusToString(status));
                }

                status = GenvidSDK.Unsubscribe(eventSummary, m_operationCompleteCallback, listUserData[counter]);
                if (status != GenvidSDK.Status.Success)
                {
                    Debug.LogError("Error while unsubscribe process for " + eventSummary + " on the  m_operationCompleteCallback : " + GenvidSDK.StatusToString(status));
                }
                counter++;
            }

            // Unsubscribe all the commands
            foreach (string command in Enum.GetNames(typeof(Command)))
            {
                var status = GenvidSDK.UnsubscribeCommand(command, m_commandCallback, listUserData[counter]);
                if (status != GenvidSDK.Status.Success)
                {
                    Debug.LogError("Error while doing the unsubscribe command for " + command + ": " + GenvidSDK.StatusToString(status));
                }
                counter++;
            }

            m_unsubDone = true;

        }
    }

We are using Unsubscribe(String,EventSummaryCallback,IntPtr) to unsubscribe the events and UnsubscribeCommand(String,CommandCallback,IntPtr) to unsubscribe the commands.

Update

During the FixedUpdate() process, we perform some actions related to the Genvid SDK.

    // Update is called once per frame - Used to submit the game data mostly
    void FixedUpdate ()
    {
        ProjView = m_sceneCamera[m_currentActiveCamera].projectionMatrix * m_sceneCamera[m_currentActiveCamera].worldToCameraMatrix;

        if (secondGenvidSDKActive)
        {
            for (int i = 0; i < m_activeObjectsList.Length; ++i)
            {
                if(m_activeObjectsList[i].name.Equals("Porthos"))
                {
                    PorthosLocalMatrix = m_activeObjectsList[i].transform.localToWorldMatrix;
                }
                else if (m_activeObjectsList[i].name.Equals("Athos"))
                {
                    AthosLocalMatrix = m_activeObjectsList[i].transform.localToWorldMatrix;
                }
                else if (m_activeObjectsList[i].name.Equals("Aramis"))
                {
                    AramisLocalMatrix = m_activeObjectsList[i].transform.localToWorldMatrix;
                }
            }
        }
        if (GenvidSDKActive)
        {
            //Getting the cube information and send it into the game data stream
            long tc = GenvidSDK.GetCurrentTimecode();

            string jsonCubesArray = "";
            jsonCubesArray += '[';
            for (int i = 0; i < m_activeObjectsList.Length; ++i)
            {
                jsonCubesArray += toJSONObject(m_activeObjectsList[i]);
                if ((i + 1) < m_activeObjectsList.Length)
                {
                    jsonCubesArray += ','; // Exclude last one.
                }
            }
            jsonCubesArray += ']';

            string jsonData = "";
            jsonData += '{';
            jsonData += toJSON("MatProjView", toJSON(ProjView));
            jsonData += ',';
            jsonData += toJSON("cubes", jsonCubesArray);
            jsonData += ',';
            jsonData += toJSON("camera", m_cameraCounter.ToString("F2"));
            jsonData += ',';
            jsonData += toJSON("scene", m_changeSceneCounter.ToString("F2"));
            jsonData += '}';

            var gvStatus = GenvidSDK.SubmitGameData(tc, Stream.GameData.ToString(), jsonData);
            if (gvStatus != GenvidSDK.Status.Success)
            {
                Debug.LogError("Error while doing the submit game data : " + GenvidSDK.StatusToString(gvStatus));
            }

            //Get the popularity JSON send a notification if it was changed
            string popularityJSON = getPopularityJSON();
            if (!popularityJSON.Equals(m_oldPopularityJSON))
            {
                gvStatus = GenvidSDK.SubmitNotification(Stream.Popularity.ToString(), popularityJSON);
                if (gvStatus != GenvidSDK.Status.Success)
                {
                    Debug.LogError("Error while doing submit notification for popularity : " + GenvidSDK.StatusToString(gvStatus));
                }
            }
            m_oldPopularityJSON = popularityJSON;

            gvStatus = GenvidSDK.CheckForEvents();
            if (gvStatus != GenvidSDK.Status.Success && gvStatus != GenvidSDK.Status.ConnectionTimeout)
            {
                Debug.LogError("Error while doing the CheckForEvents : " + GenvidSDK.StatusToString(gvStatus));
            }
        }

        //To calculate the popularity decay
        for (int i = 0; i < m_activeObjectsList.Length; ++i)
        {
            m_activeObjectsList[i].GetComponent<MovementCube>().cheer(-Time.deltaTime);
        }

        //camera change
        m_cameraCounter = m_cameraCounter - Time.deltaTime;

         if (m_cameraCounter <= 0)
         {
             changeCamera();
         }

         m_changeSceneCounter = m_changeSceneCounter - Time.deltaTime;
 
        if (m_changeSceneCounter <= 0)
         {
             changeScene();
         }

        if ((GenvidSDKActive || secondGenvidSDKActive) && m_textFill == false)
        {
            GenvidSDK.Status gvStatus;

            int paramReceived = 0;

            if (GenvidSDKActive)
            {
                gvStatus = GenvidSDK.GetParameter(Stream.Audio.ToString(), "Audio.Source.WASAPI", ref paramReceived);
            }
            else
            {
                gvStatus = GenvidSDK.GetParameter(GameObject.Find("AudioStream").GetComponent<AudioStream>().m_streamName, "Audio.Source.WASAPI", ref paramReceived);
            }


            if (gvStatus != GenvidSDK.Status.Success)
            {
                Debug.LogError("Error while trying to get the int parameter : " + GenvidSDK.StatusToString(gvStatus));
            }

            int major = 0;
            int minor = 0;
            int version = 0;
            int build = 0;
            m_getParamIntText.text = "getParamInt: " + paramReceived + " version: " + GenvidSDK.GetVersion(ref major, ref minor, ref version, ref build);

            string paramSearch = "";
            gvStatus = GenvidSDK.GetParameterUTF8("genvid.kv", "genvid/encode/input/height", ref paramSearch, 256);

            if (gvStatus != GenvidSDK.Status.Success)
            {
                Debug.LogError("Error while trying to get the UTF8 parameter : " + GenvidSDK.StatusToString(gvStatus));
            }

            if (GenvidSDKActive)
            {
                gvStatus = GenvidSDK.SetParameter(Stream.Video, "framerate", 30.0f);
            }
            else
            {
                gvStatus = GenvidSDK.SetParameter(GameObject.Find("VideoStream").GetComponent<VideoStream>().m_streamName, "framerate", 30.0f);
            }
            
            if (gvStatus != GenvidSDK.Status.Success)
            {
                Debug.LogError("Error while setting the float parameter for the video : " + GenvidSDK.StatusToString(gvStatus));
            }

            float floatParamReceived = 0.0f;
            if (GenvidSDKActive)
            {
                gvStatus = GenvidSDK.GetParameter(Stream.Video, "framerate", ref floatParamReceived);
            }
            else
            {
                gvStatus = GenvidSDK.GetParameter(GameObject.Find("VideoStream").GetComponent<VideoStream>().m_streamName, "framerate", ref floatParamReceived);
            }

            if (gvStatus != GenvidSDK.Status.Success)
            {
                Debug.LogError("Error while trying to get the float parameter : " + GenvidSDK.StatusToString(gvStatus));
            }

            m_getUTF8Text.text = "get float param : " + floatParamReceived + " get UTF8 : " + paramSearch;

            m_textFill = true;
        }

        if (GenvidSDKActive || secondGenvidSDKActive)
        {
            m_timecodeText.text = "Previous timecode: " + GenvidSDK.GetPreviousTimecode();
        }
            
    }

First, we update the matrix for the camera as a public variable and we are updating variables for the matrix of each cubes. This data will be used when using the prefab.

Otherwise, we get the GetCurrentTimecode that will be sent via any data during the FixedUpdate(). In the sample, we collect the cube matrices, camera matrice, camera timer and scene timer that are formatted in JSON and sent using SubmitGameData(Int64,String,String). We provide a toJSON() function to help format your content, but you are free to do it yourself via other means.

In the sample, we are also doing an example on how to do a notification via the SubmitNotification(String,String). In this case, we are simply comparing the popularity to notice if there is a difference with the most recent one, and proceed to submit the notification if that is the case.

Before finishing FixedUpdate(), we do a CheckForEvents that is very important to be able to trigger the callbacks properly.

Outside of the condition for the Genvid SDK activation, we are also proceeding with a simple timer to change the camera every 10 seconds and scene every 60 seconds. We also modify the popularity of each cube which decays over time.

Also, we are filling some text fields with data via the GetParameter(Object,String,Int32@), the GetVersion(Int32@,Int32@,Int32@,Int32@), the GetParameterUTF8(String,String,String@,Int32) and the GetParameter(Object,String,Float@).

Video capture

First, at the end of the Start() method, if the Genvid SDK is active, we need to create a specific loop at the appropriate time which is the end of the frame to be able to capture the video.

        if (GenvidSDKActive)
        {
            yield return StartCoroutine(CallPluginAtEndOfFrames());
        }

This method CallPluginAtEndOfFrames() is then called as a coroutine.

    private IEnumerator CallPluginAtEndOfFrames()
    {
        // GetRenderEventFunc param
        System.IntPtr renderingFunction = GetRenderEventFunc();
        var waitForEndOfFrame = new WaitForEndOfFrame();
        GenvidSDK.Status status = GenvidSDK.Status.Success;

        while (true) 
        {
            // Wait until all frame rendering is done
            yield return waitForEndOfFrame;

            if (m_quitProcess == false) 
            {
                if (m_processComplete) 
                {
                    
                    m_processComplete = false;

                    if(cameraCapture)
                    {
                        for (int x = 0; x < m_sceneCamera.Length; x++)
                        {
                            m_cameraTexture[x] = new RenderTexture(m_sceneCamera[x].pixelWidth, m_sceneCamera[x].pixelHeight, 24, RenderTextureFormat.ARGB32);
                            m_cameraTexture[x].Create();

                            m_sceneCameraTexture[x] = m_cameraTexture[x].GetNativeTexturePtr();
                            commandBuffer[x] = new CommandBuffer();
                            commandBuffer[x].name = "MultipleCapture";
                            m_sceneCamera[x].AddCommandBuffer(CameraEvent.AfterEverything, commandBuffer[x]);
                            commandBuffer[x].Blit(BuiltinRenderTextureType.CurrentActive, m_cameraTexture[x]);
                        }
                        GenvidSDK.SetParameterPointer(Stream.Video.ToString(), "video.source.id3d11texture2d", m_sceneCameraTexture[m_currentActiveCamera]);
                    }
                    else
                    {
                        GL.IssuePluginEvent(renderingFunction, 0);
                        status = GetVideoInitStatus();
                        if (status != GenvidSDK.Status.Success)
                        {
                            Debug.LogError("Error while starting the video stream : " + GenvidSDK.StatusToString(status));
                        }
                    }
                } 
                else 
                {
                    GL.IssuePluginEvent(renderingFunction, 1);
                    status = GetVideoSubmitDataStatus();
                    if (status != GenvidSDK.Status.Success)
                    {
                        Debug.LogError("Error while sending video data : " + GenvidSDK.StatusToString(status));
                    }

                    if (m_changeCamera)
                    {
                        GenvidSDK.SetParameterPointer(Stream.Video.ToString(), "video.source.id3d11texture2d", m_sceneCameraTexture[m_currentActiveCamera]);
                        m_changeCamera = false;
                    }
                }
            }
            else
            {
                m_coRoutineEnd = true;
                break;
            }
        }
    }

At the start of the method, we are assigning the GenvidPlugin GetRenderEventFunc() to a System.IntPtr and we are creating a new variable for a new WaitForEndOFrame().

We are then using a while loop with a yield return waitForEndOfFrame to wait until all the rendering is completed to proceed. In the event that the video capture was not started yet, we are validating if we are doing a camera capture or a global capture. In the event that a camera capture is selected, we are creating a renderTexture for each camera and we get the native texture pointer for each. We proceed to create a command buffer and add the command buffer for each camera to perform on CameraEvent.AfterEverything which consist of a Blit on the camera itself with the Texture2d. Afterwards, we sent the Texture2d pointer to the SDK for the video capture.

Otherwise, we are doing a global capture with the Dxswapchain and a GL.IssuePluginEvent(renderingFunction, 0) is done with 0 as an argument to initialize the video capture. Afterwards, we can verify if the process was completed properly by verifying the Status with GetVideoInitStatus().

If the video capture initialization process was already completed, it is performing a GL.IssuePluginEvent(renderingFunction, 1) with 1 as an argument to do a normal video capture (submitting the video data). Afterwards, we can verify if the process was completed properly by verifying the Status with GetVideoSubmitDataStatus(). If the camera was changed, we are changing the pointer for the video capture (note that this variable doesn’t change unless the camera video capture is selected).

Termination

When the application is terminated, we need to perform various actions to terminate the Genvid SDK. In the current sample, we perform these operations inside the OnApplicationQuit() process.

    void OnApplicationQuit()
    {
        if (GenvidSDKActive)
        {
            OnDisable();

            m_quitProcess = true;

            new WaitUntil(() => m_coRoutineEnd == true);

            // Destroy all Genvid streams
            foreach (string stream in Enum.GetNames(typeof(Stream)))
            {
                var status = GenvidSDK.DestroyStream(stream);
                if (status != GenvidSDK.Status.Success)
                {
                    Debug.LogError("Error while creating the " + stream + " stream: " + GenvidSDK.StatusToString(status));
                }
            }

            var gvStatus = GenvidSDK.Terminate();
            if (gvStatus != GenvidSDK.Status.Success)
            {
                Debug.LogError("Error while doing the terminate process : " + GenvidSDK.StatusToString(gvStatus));
            }

            
            CleanUp();
        }
    }

We first proceed with the unsubscribe process for all the events and commands that is available inside the OnDisable().

Afterwards, we destroy all the streams that were created during the initialization process via the DestroyStream(String). We then call Terminate to clear all the data that was allocated by the Genvid SDK. Finally, we need to call cleanUp() to clear all the data that was allocated by the GenvidPlugin.