TeamSpeak 3 SDK Client Audio

Hello Audio World

Introduction

This article will guide you through handling basic audio related topics. This application will expand upon our Hello World application and additionally offer several audio related features.

Changing the capture audio stream state

The natural modus operandi of an audio stream is for it to stream continuously. However, in voice communication we don't want to stream when no one is talking, both to save bandwidth as well as to improve the receiving party's audio experience.

Our first approach will be to pause streaming when not required. This can be done manually, e.g. by determining whether a key is being pressed down ("Push-To-Talk") or automatically by analyzing the audio stream for the presence of a voice ("Voice Activity Detection").

In the TeamSpeak SDK, all variants are supported:

  • Continuous Transmission
  • Manually setting the audio stream status
  • Voice Activity Detection
  • Combining the two aforementioned modes

If you enable both VAD, as well as set the input state manually, audio will be send when both the stream is identified as voice as well as the state is set to be sending, e.g. while a key is being pressed.

Let's code

In the following chapters we'll create functions to achieve the variants above. Once again, we've included error checking in the full example and omitted it for readability here.

Voice Activity Detection (VAD)

Let's take our Hello world application and add a function that is going to tell us if a Digital Signal Processing unit is enabled: int is_dsp_enabled(uint64 connection_id, const char* dsp) {}.

You can configure the Voice Activity Detection via the API function pair get-/setPreProcessorConfigValue, now we'll be using the getter:
unsigned int ts3client_getPreProcessorConfigValue(uint64 connection_id, const char* ident, char** result);. The 'ident' parameter will be our dsp parameter:

char* enabled;
ts3client_getPreProcessorConfigValue(connection_id, dsp, &enabled);

The 'enabled' variable now contains either "true" or "false", let's make that a boolean:

const bool val = strcmp("true", enabled) == 0;

Now that we no longer need the 'enabled' variable, we can free it by calling ts3client.freeMemory(enabled);

Finally, we return 'val' and our function is done:

bool is_dsp_enabled(uint64 connection_id, const char* dsp)
{
    char* enabled;
    ts3client_getPreProcessorConfigValue(connection_id, dsp, &enabled);
    const bool val = strcmp("true", enabled) == 0;
    ts3client.freeMemory(enabled);
    return val;
}

Calling it with "vad" as the 'dsp' parameter will tell us if Voice Activity Detection is enabled.

Now, we create a function to toggle VAD: void toggle_dsp(uint64 connection_id, const char* dsp) {}. We'll use the function we created to get the current status and change it:

void toggle_dsp(uint64 connection_id, const char* dsp)
{
    const bool target_enable = !is_preprocessor_enabled(connection_id, dsp);
    ts3client_setPreProcessorConfigValue(connection_id, dsp, target_enable ? "true" : "false");
    printf("\nToggled %s %s.\n\n", dsp, target_enable ? "on" : "off");
}

In addition to that, you can set the minimum volume for the voice analyzation processor for fine grain control like so:
ts3client_setPreProcessorConfigValue(connection_id, "voiceactivation_level", "-12"));, the value being in decibel.

Manually setting the stream state (e.g. for Push-To-Talk)

Let's create a function to toggle the capture stream on and off manually: void toggle_input_status(uint64 connection_id) {}. We can do this by setting the 'Client Variable' CLIENT_INPUT_DEACTIVATED. We'll first get the current variable, and then set it to the opposite:

void toggle_input_status(uint64 connection_id)
{
    int status;
    ts3client_getClientSelfVariableAsInt(connection_id, CLIENT_INPUT_DEACTIVATED, &status);
    status = (status == INPUT_ACTIVE) ? INPUT_DEACTIVATED : INPUT_ACTIVE;
    ts3client_setClientSelfVariableAsInt(connection_id, CLIENT_INPUT_DEACTIVATED, status);
    ts3client_flushClientSelfUpdates(connection_id, NULL);
}

Continuous Transmission

The client will stream continuous, if the input isn't manually deactivated and VAD is disabled. A function to enter this modus would look like:

void stream_continuously(uint64 connection_id)
{
    ts3client_setPreProcessorConfigValue(connection_id, "vad", "false");
    ts3client_setClientSelfVariableAsInt(connection_id, CLIENT_INPUT_DEACTIVATED, INPUT_ACTIVE);
    ts3client_flushClientSelfUpdates(connection_id, NULL);
}

Digital Signal Processing

We've created the generic getter and toggle functions is_dsp_enabled and toggle_dsp earlier. We'll reuse them to toggle other DSP units.

Automatic Gain Control

The AGC unit adjusts the input level internally to compensate for non-ideal user system configuration. Using "agc" as a parameter for is_dsp_enabled and toggle_dsp we can query and toggle the state of the AGC unit.

Denoise

The Denoiser analyzes the audio stream for background noise and reduces it. Using "denoise" as a parameter for is_dsp_enabled and toggle_dsp we can query and toggle the state of the Denoiser.

Echo Cancellation

If the device doesn't have built-in echo cancellation, voice communication can cause echoes when using speakers. A client should turn the Echo Cancellation unit on in that case. Using "echo_canceling" as a parameter for is_dsp_enabled and toggle_dsp we can query and toggle the state of the Echo Cancellation unit.

Individual Client Volume

A client can modify the volume of individual speakers locally by calling
unsigned int ts3client_setClientVolumeModifier(uint64 connection_id, anyID client_id, float value);.
The 'value' parameter is in decibel. You can also change the volume of a connection, i.e. all streams that belong to it, via
ts3client_setPlaybackConfigValue(uint64 connection_id, "volume_modifier", const char* decibel)),
however in most cases (e.g. when the app doesn't connect to multiple servers at once) it's recommended to let the user change the application volume in the OS. The volumes of both functions are additive, with a connection volume of -15dB, and an individual client volume of 10dB, the resulting volume modification for the client would be -5dB.

Muting clients locally

Muting oneself

Remember when we toggled our capture audio stream earlier with the CLIENT_INPUT_DEACTIVATED client property? Muting yourself will be similar.

The key difference here is that CLIENT_INPUT_DEACTIVATED is just one of the conditions that determine if a stream is sent to the server. We're going to use another condition, CLIENT_INPUT_MUTED, which is a property other clients can know about.

void toggle_input_muted(uint64 connection_id)
{
    int status;
    ts3client_getClientSelfVariableAsInt(connection_id, CLIENT_INPUT_MUTED, &status);
    status = (status == MUTEINPUT_MUTED) ? MUTEINPUT_NONE : MUTEINPUT_MUTED;
    ts3client_setClientSelfVariableAsInt(connection_id, CLIENT_INPUT_MUTED, status);
    ts3client_flushClientSelfUpdates(connection_id, NULL);
}

You can mute your output similarly, of course.

Muting other clients

You can also mute other clients with the TeamSpeak SDK. This works a little bit differently, since we don't want to just stop audio streams in the client, we want them not to be sent from the server in the first place. Please note that the server won't snitch this to other clients.

That's why we'll be using one of our request_### functions. This naming convention points out that the function call will send something to the server and receive a reply asynchronously. In this example we're assuming that the server will comply with our request though. Specifically we'll be using
unsigned int ts3client_requestMuteClients(uint64 connection_id, const anyID* client_ids, const char* return_code); and
unsigned int ts3client_requestUnmuteClients(uint64 connection_id, const anyID* clients, const char* return_code);, respectively.

The first thing to notice here is that the functions take an array of client IDs, not just one. We don't want to spam the server with several requests just to mute a couple of clients.

Secondly, there's an optional return code. We can use this to tie the asynchronous answer to our request.

Let's, for the sake of demonstration, create a function that toggles all clients' mute status: void toggle_mute_all(uint64 connection_id) {}. First, we get the clients that are currently visible to us:

anyID* visible_clients;
ts3client_getClientList(serverConnectionHandlerID, &visible_clients)

Once we have a list, we can check the first client's mute status exemplarily to determine the target status and then set it for all clients:

void toggle_mute_all(uint64 connection_id)
{
    anyID* visible_clients;
    if (ts3client_getClientList(connection_id, &visible_clients) == ERROR_ok)
    {
        // base the target status on the status of the first client in the array
        if (visible_clients[0] != 0)
        {
            int status = 0;
            CHECK_ERROR(ts3client_getClientVariableAsInt(connection_id, first_client, CLIENT_IS_MUTED, &status));
            if (status == 0)
            {
                CHECK_ERROR(ts3client_requestMuteClients(connection_id, visible_clients, NULL));
            }
            else
            {
                CHECK_ERROR(ts3client_requestUnmuteClients(connection_id, visible_clients, NULL));
            }
        }
        ts3client_freeMemory(visible_clients);
    }
}

Congratulations!

You have extended your Hello World application with functions for modifying the voice stream using the TeamSpeak SDK.