C/C++ Core SDK

This is the core client API implemented in Rust and exported to native C libraries. These libraries are the building blocks for our high level SDKs like Unity SDK or Unreal SDK.

Please check out our public Github repository for more information, source-code and a cool CLI application to play around with ODIN right from your command line.

C-API Header
/* Copyright (c) 4Players GmbH. All rights reserved. */

#pragma once

#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>

#define ODIN_VERSION "0.5.0"

/**
 * Supported channel layouts in audio functions.
 */
typedef enum OdinChannelLayout {
    /**
     * Samples are sequential
     */
    OdinChannelLayout_Mono,
    /**
     * Channels are interleaved
     */
    OdinChannelLayout_Stereo,
} OdinChannelLayout;

/**
 * Known types of a media stream.
 *
 * Note: Video streams are not supported yet.
 */
typedef enum OdinMediaStreamType {
    /**
     * Media stream is of type audio
     */
    OdinMediaStreamType_Audio,
    /**
     * Media stream is of type video
     */
    OdinMediaStreamType_Video,
    /**
     * Media stream is invalid
     */
    OdinMediaStreamType_Invalid,
} OdinMediaStreamType;

/**
 * Valid levels for aggressiveness of the noise suppression. A higher level will reduce the noise
 * level at the expense of a higher speech distortion.
 */
typedef enum OdinNoiseSuppsressionLevel {
    /**
     * Noise suppression is disabled
     */
    OdinNoiseSuppsressionLevel_None,
    /**
     * Use low suppression (6 dB)
     */
    OdinNoiseSuppsressionLevel_Low,
    /**
     * Use moderate suppression (12 dB)
     */
    OdinNoiseSuppsressionLevel_Moderate,
    /**
     * Use high suppression (18 dB)
     */
    OdinNoiseSuppsressionLevel_High,
    /**
     * Use very high suppression (21 dB)
     */
    OdinNoiseSuppsressionLevel_VeryHigh,
} OdinNoiseSuppsressionLevel;

/**
 * All valid connection states for an ODIN room.
 */
typedef enum OdinRoomConnectionState {
    /**
     * Connection is being established
     */
    OdinRoomConnectionState_Connecting,
    /**
     * Connection is established
     */
    OdinRoomConnectionState_Connected,
    /**
     * Connection is closed
     */
    OdinRoomConnectionState_Disconnected,
} OdinRoomConnectionState;

/**
 * Posible reasons for connection state changes of an ODIN room.
 */
typedef enum OdinRoomConnectionStateChangeReason {
    /**
     * Connection state change was initiated by the user
     */
    OdinRoomConnectionStateChangeReason_ClientRequested,
    /**
     * Connection state change was initiated by the server (e.g. peer was kicked)
     */
    OdinRoomConnectionStateChangeReason_ServerRequested,
    /**
     * Connection state change was caused by a timeout
     */
    OdinRoomConnectionStateChangeReason_ConnectionLost,
} OdinRoomConnectionStateChangeReason;

/**
 * Valid audiences for ODIN room tokens.
 */
typedef enum OdinTokenAudience {
    /**
     * JWT has no audience
     */
    OdinTokenAudience_None,
    /**
     * JWt is accepted the ODIN gateway
     */
    OdinTokenAudience_Gateway,
    /**
     * JWt is accepted by the ODIN server
     */
    OdinTokenAudience_Sfu,
} OdinTokenAudience;

/**
 * Supported targets for user data updates.
 */
typedef enum OdinUserDataTarget {
    /**
     * Individual user data for your own peer
     */
    OdinUserDataTarget_Peer,
    /**
     * Global user data for the room
     */
    OdinUserDataTarget_Room,
} OdinUserDataTarget;

/**
 * A pointer to an individual ODIN audio/video stream used to send/receive data.
 */
typedef struct OdinMediaStream OdinMediaStream;

/**
 * A pointer to an ODIN room to interact with.
 */
typedef struct OdinRoom OdinRoom;

/**
 * A pointer to a local ODIN token generator used to generate signed room tokens based based on an
 * access key. Please note, that access keys are your the unique authentication keys to be used to
 * generate room tokens for accessing the ODIN server network. For your own security, we strongly
 * recommend that you _NEVER_ put an access key in your client code and generate room tokens on a
 * server.
 */
typedef struct OdinTokenGenerator OdinTokenGenerator;

/**
 * A numeric code returned by ODIN function calls. Use `odin_is_error` to determine whether the
 * code represents an error or an actual result value.
 *
 * Note: Use `odin_error_format` to get a human readable string to represent error codes.
 */
typedef int32_t OdinReturnCode;

/**
 * All the different events emitted from an ODIN room.
 */
typedef enum OdinEventTag {
    /**
     * Emitted after joining once initial room information was processed
     */
    OdinEvent_Joined,
    /**
     * Emitted when other peers joined the room
     */
    OdinEvent_PeerJoined,
    /**
     * Emitted when other peers left the room
     */
    OdinEvent_PeerLeft,
    /**
     * Emitted when other peers updated their user data
     */
    OdinEvent_PeerUserDataChanged,
    /**
     * Emitted when other peers started a media stream
     */
    OdinEvent_MediaAdded,
    /**
     * Emitted when other peers stopped a media stream
     */
    OdinEvent_MediaRemoved,
    /**
     * Emitted whenever data is sent/received over any known media
     */
    OdinEvent_MediaActiveStateChanged,
    /**
     * Emitted when other peers changed the global user data of the room itself
     */
    OdinEvent_RoomUserDataChanged,
    /**
     * Emitted when the internal room connection state of the ODIN client changed
     */
    OdinEvent_RoomConnectionStateChanged,
    /**
     * Emitted when others peers sent arbitrary data
     */
    OdinEvent_MessageReceived,
    OdinEvent_None,
} OdinEventTag;

typedef struct OdinEvent_JoinedData {
    /**
     * Name of the joined room (null-terminated)
     */
    const char *room_id;
    /**
     * Length of the room name
     */
    size_t room_id_len;
    /**
     * Byte array with arbitrary user data of the room
     */
    const uint8_t *room_user_data;
    /**
     * Length of the room user data array
     */
    size_t room_user_data_len;
    /**
     * Identifier of the customer the room is assigned to (nul-terminated)
     */
    const char *customer;
    /**
     * Length of the customer identifier
     */
    size_t customer_len;
    /**
     * Own peer ID assigned by the server
     */
    uint64_t own_peer_id;
} OdinEvent_JoinedData;

typedef struct OdinEvent_PeerJoinedData {
    /**
     * ID of the peer
     */
    uint64_t peer_id;
    /**
     * Byte array with arbitrary user data of the peer
     */
    const uint8_t *peer_user_data;
    /**
     * Length of the room user data array
     */
    size_t peer_user_data_len;
    /**
     * User identifier of the peer specified during authentication (null-terminated)
     */
    const char *user_id;
    /**
     * Length of the user identifier
     */
    size_t user_id_len;
} OdinEvent_PeerJoinedData;

typedef struct OdinEvent_PeerLeftData {
    /**
     * ID of the peer
     */
    uint64_t peer_id;
} OdinEvent_PeerLeftData;

typedef struct OdinEvent_PeerUserDataChangedData {
    /**
     * ID of the peer
     */
    uint64_t peer_id;
    /**
     * Byte array with arbitrary user data of the peer
     */
    const uint8_t *peer_user_data;
    /**
     * Length of the room user data array
     */
    size_t peer_user_data_len;
} OdinEvent_PeerUserDataChangedData;

typedef struct OdinEvent_MediaAddedData {
    /**
     * ID of the media
     */
    uint16_t media_id;
    /**
     * ID of the peer this media belongs to
     */
    uint64_t peer_id;
    /**
     * Pointer to the new audio/video stream
     */
    struct OdinMediaStream *stream;
} OdinEvent_MediaAddedData;

typedef struct OdinEvent_MediaRemovedData {
    /**
     * ID of the media
     */
    uint16_t media_id;
    /**
     * ID of the peer this media belongs to
     */
    uint64_t peer_id;
} OdinEvent_MediaRemovedData;

typedef struct OdinEvent_MediaActiveStateChangedData {
    /**
     * ID of the media
     */
    uint16_t media_id;
    /**
     * ID of the peer this media belongs to
     */
    uint64_t peer_id;
    /**
     * Indicator for whether or not the media is sending/receiving data
     */
    bool active;
} OdinEvent_MediaActiveStateChangedData;

typedef struct OdinEvent_RoomUserDataChangedData {
    /**
     * Byte array with arbitrary user data of the room
     */
    const uint8_t *room_user_data;
    /**
     * Length of the room user data array
     */
    size_t room_user_data_len;
} OdinEvent_RoomUserDataChangedData;

typedef struct OdinEvent_RoomConnectionStateChangedData {
    /**
     * Status of the room connection
     */
    enum OdinRoomConnectionState state;
    /**
     * Reason for this update
     */
    enum OdinRoomConnectionStateChangeReason reason;
} OdinEvent_RoomConnectionStateChangedData;

typedef struct OdinEvent_MessageReceivedData {
    /**
     * ID of the peer who sent this message
     */
    uint64_t peer_id;
    /**
     * Byte array with arbitrary data received
     */
    const uint8_t *data;
    /**
     * Lengh of the data array
     */
    size_t data_len;
} OdinEvent_MessageReceivedData;

typedef struct OdinEvent {
    OdinEventTag tag;
    union {
        OdinEvent_JoinedData joined;
        OdinEvent_PeerJoinedData peer_joined;
        OdinEvent_PeerLeftData peer_left;
        OdinEvent_PeerUserDataChangedData peer_user_data_changed;
        OdinEvent_MediaAddedData media_added;
        OdinEvent_MediaRemovedData media_removed;
        OdinEvent_MediaActiveStateChangedData media_active_state_changed;
        OdinEvent_RoomUserDataChangedData room_user_data_changed;
        OdinEvent_RoomConnectionStateChangedData room_connection_state_changed;
        OdinEvent_MessageReceivedData message_received;
    };
} OdinEvent;

/**
 * Per-room configuration of the ODIN audio processing module which provides a variety of smart
 * enhancement algorithms.
 */
typedef struct OdinApmConfig {
    /**
     * Enables or disables voice activity detection
     */
    bool vad_enable;
    /**
     * Enable or disable echo cancellation
     */
    bool echo_canceller;
    /**
     * Enable or disable high pass filtering
     */
    bool high_pass_filter;
    /**
     * Enable or disable the pre amplifier
     */
    bool pre_amplifier;
    /**
     * Set the aggressiveness of the suppression
     */
    enum OdinNoiseSuppsressionLevel noise_suppression_level;
    /**
     * Enable or disable the transient suppressor
     */
    bool transient_suppressor;
} OdinApmConfig;

/**
 * Audio stream configuration.
 */
typedef struct OdinAudioStreamConfig {
    /**
     * The number of samples per second in hertz (between 8000 and 192000)
     */
    uint32_t sample_rate;
    /**
     * The number of channels for the new audio stream (between 1 and 2)
     */
    uint8_t channel_count;
} OdinAudioStreamConfig;

/**
 * Options for ODIN room tokens.
 */
typedef struct OdinTokenOptions {
    /**
     * Customer identifier (you should _NOT_ set this unless connecting directly to an ODIN server)
     */
    const char *customer;
    /**
     * Audience of the token
     */
    enum OdinTokenAudience audience;
    /**
     * Token lifetime in seconds
     */
    uint64_t lifetime;
} OdinTokenOptions;

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

/**
 * Formats an ODIN return code into a human readable string representation for use in logging and
 * diagnostics. If `buf` is `NULL` this functions simply returns the required buffer length to
 * store the output buffer.
 */
size_t odin_error_format(OdinReturnCode error, char *buf, size_t buf_len);

/**
 * Checks whether the code returned from ODIN function calls represents an error or an actual
 * result. This is used to easier work with certain functions that might return an error or a
 * valid result like `odin_audio_data_len`.
 */
bool odin_is_error(OdinReturnCode code);

/**
 * Creates a new ODIN room in an unconnected state. This function will return `NULL` when the
 * internal ODIN client runtime is not initialized using `odin_startup` or has already been
 * terminated using `odin_shutdown`.
 */
struct OdinRoom *odin_room_create(void);

/**
 * Destroys the specified ODIN room, thus making our own peer leave the room on the ODIN server
 * and closing the connection if needed.
 */
void odin_room_destroy(struct OdinRoom *room);

/**
 * Sets the event callback on the the specified `OdinRoom`. When a callback has already been set
 * previously, this will call the new callback with new medias and all previously recevied medias
 * will be dropped and stop receiving any data. Generally this should be called _once_ before
 * joining a room.
 */
OdinReturnCode odin_room_set_event_callback(struct OdinRoom *room,
                                            void (*callback)(struct OdinRoom *room, const struct OdinEvent *event, void *extra_data),
                                            void *extra_data);

/**
 * Sets the scaling used for all coordinates passed to `odin_room_update_position`. This allows
 * adapting to the individual needs of your game coorinate system if necessary. Only peers within
 * a unit circle with a radius of `1.0` are able to 'see' each other. When changing the position
 * of a peer, the position must be scaled such as that the maximum distance is one or less. The
 * scaling can be done either manually or by setting the multiplicative scale here.
 *
 * Note: Please make sure that all of your client apps use the same scaling.
 */
OdinReturnCode odin_room_set_position_scale(struct OdinRoom *room, float scale);

/**
 * Joins a room on an ODIN server. This function takes an URL to an ODIN gateway and a signed room
 * token obtained externally that authorizes the client to establish the connection. Unless you're
 * hosting your own servers, always use gateway running at `https://gateway.odin.4players.io`.
 */
OdinReturnCode odin_room_join(struct OdinRoom *room, const char *url, const char *token);

/**
 * Updates the custom user data for either your own peer or the specified `OdinRoom` itself. This
 * data is synced automatically, which allows storing of arbitrary information for each individual
 * peer and even globally for the room if needed.
 *
 * Note: Use this before calling `odin_room_join` to set initial peer user data upon connect.
 */
OdinReturnCode odin_room_update_user_data(struct OdinRoom *room,
                                          enum OdinUserDataTarget target,
                                          const uint8_t *user_data,
                                          size_t user_data_length);

/**
 * Updates the two-dimensional position of your own peer in the given `OdinRoom`. The server will
 * use the specified coordinates for each peer in the same room to apply automatic culling based
 * on unit circles with a radius of `1.0`. This is ideal for any scenario, where you want to put
 * a very large number of peers into the same room and make them only 'see' each other while being
 * in proximity. Additionally, you can use `odin_room_set_position_scale` to adjust the distance
 * multiplier for position updates if needed.
 *
 * Note: Use this before calling `odin_room_join` to set the initial peer position upon connect.
 */
OdinReturnCode odin_room_update_position(struct OdinRoom *room, float x, float y);

/**
 * Adds a specified `OdinMediaStream` to the room. Please note, that this can only be done _once_
 * on a given media. Trying to do it more than once will return an error on subsequent calls to
 * this function.
 */
OdinReturnCode odin_room_add_media(struct OdinRoom *room, struct OdinMediaStream *media);

/**
 * Sends arbitrary data to a list of target peers over the ODIN server. If `NULL` is specified, the
 * message will be sent to all other peers in the same room.
 */
OdinReturnCode odin_room_send_message(struct OdinRoom *room,
                                      const uint64_t *peer_id_list,
                                      size_t peer_id_list_size,
                                      const uint8_t *data,
                                      size_t data_length);

/**
 * Configures the ODIN audio processing module on the room with the specified config.
 */
OdinReturnCode odin_room_configure_apm(struct OdinRoom *room, struct OdinApmConfig config);

/**
 * Creates a new audio stream, which can be added to a room and send data over it.
 */
struct OdinMediaStream *odin_audio_stream_create(struct OdinAudioStreamConfig config);

/**
 * Creates a new video stream, which can be added to a room and send data over it.
 *
 * Note: Video streams are not supported yet.
 */
struct OdinMediaStream *odin_video_stream_create(void);

/**
 * Destroys the specified `OdinMediaStream`, after which you will no longer be able to receive
 * or send any data over it. If the media is currently 'attached' to a room it will be removed.
 */
OdinReturnCode odin_media_stream_destroy(struct OdinMediaStream *stream);

/**
 * Retreives the media ID of the specified `OdinMediaStream`.
 */
OdinReturnCode odin_media_stream_media_id(struct OdinMediaStream *stream, uint16_t *out_media_id);

/**
 * Retreives the peer ID of the specified `OdinMediaStream`.
 */
OdinReturnCode odin_media_stream_peer_id(struct OdinMediaStream *stream, uint64_t *out_peer_id);

/**
 * Returns the type of the specified media stream.
 *
 * Note: This function will always return `OdinMediaStreamType_Audio` at the moment.
 */
enum OdinMediaStreamType odin_media_stream_type(struct OdinMediaStream *stream);

/**
 * Sends data to the audio stream. The data has to be interleaved [-1, 1] float data.
 */
OdinReturnCode odin_audio_push_data(struct OdinMediaStream *stream,
                                    const float *buf,
                                    size_t buf_len);

/**
 * Returns the number of available sample available in the audio buffer of the of the specified
 * `OdinMediaStream`.
 */
OdinReturnCode odin_audio_data_len(struct OdinMediaStream *stream);

/**
 * Reads audio data from the specified `OdinMediaStream`. This will return audio data in 48kHz
 * interleaved.
 *
 * Note: `out_channel_layout` is reserved for future use.
 */
OdinReturnCode odin_audio_read_data(struct OdinMediaStream *stream,
                                    float *out_buffer,
                                    size_t out_buffer_len,
                                    enum OdinChannelLayout out_channel_layout);

/**
 * Reads up to `out_buffer_len` samples from the given streams and mixes them into the `out_buffer`.
 * All audio streams will be read based on a 48khz sample rate so make sure to allocate the buffer
 * accordingly. After the call the `out_buffer_len` will contain the amount of samples that have
 * actually been read and mixed into `out_buffer`.
 *
 * `out_channel_layout` specifies the target channel layout of the `out_buffer`. This is either:
 *  - Mono, all samples are sequential
 *  - Stereo, channels are interleaved
 *
 * If enabled this will also apply any audio processing to the output stream and feed back required
 * data to the internal audio processing pipeline which requires a final mix.
 */
OdinReturnCode odin_audio_mix_streams(struct OdinRoom *room,
                                      struct OdinMediaStream *const *streams,
                                      size_t stream_count,
                                      float *out_buffer,
                                      size_t *out_buffer_len,
                                      enum OdinChannelLayout out_channel_layout);

/**
 * Processes the reverse audio stream, also known as the loopback data to be used in the ODIN echo
 * canceller. This should only be done if you are _NOT_ using `odin_audio_mix_streams`.
 */
OdinReturnCode odin_audio_process_reverse(struct OdinRoom *room,
                                          float *buffer,
                                          size_t buffer_len,
                                          enum OdinChannelLayout out_channel_layout);

/**
 * Starts the internal ODIN client runtime and verifies thatthe correct API header file is used.
 * This is ref-counted so you need matching calls of startup and shutdown in your application.
 * A lot of the functions in the API require a running ODIN runtime. With the only exception being
 * the `access_key` and `token_generator` related functions.
 *
 * Note: Use `ODIN_VERSION` to pass the `version` argument.
 */
bool odin_startup(const char *version);

/**
 * Terminates the internal ODIN runtime. This function _should_ be called before shutting down
 * the application. After calling this function all `odin_*` methods will fail immediately.
 * (Given the internal ref-count reached zero. See `odin_startup` for more information)
 */
void odin_shutdown(void);

/**
 * Creates a new access key required to access the ODIN network. An access key is a 44 character
 * long Base64-String, which consists of a version, random bytes and a checksum.
 */
OdinReturnCode odin_access_key_generate(char *buf, size_t buf_len);

/**
 * Retreives the key ID from a specified access key. The key ID is included in room tokens,
 * making it possible to identify which public key must be used for verification.
 */
OdinReturnCode odin_access_key_id(const char *access_key, char *out_key_id, size_t out_key_id_len);

/**
 * Retreives the public key from a specified access key. The public key is based on the Ed25519
 * curve and must be submitted to _4Players_ so that a generated room token can be verified.
 */
OdinReturnCode odin_access_key_public_key(const char *access_key,
                                          char *out_public_key,
                                          size_t out_public_key_len);

/**
 * Retreives the secret key from a specified access key. The secret key is based on the Ed25519
 * curve and used to sign a generated room token to access the ODIN network.
 */
OdinReturnCode odin_access_key_secret_key(const char *access_key,
                                          char *out_secret_key,
                                          size_t out_secret_key_len);

/**
 * Creates a new token generator instance.
 */
struct OdinTokenGenerator *odin_token_generator_create(const char *access_key);

/**
 * Destroys an existing token generator instance.
 */
void odin_token_generator_destroy(struct OdinTokenGenerator *generator);

/**
 * Generates a signed JWT, which can be used by an ODIN client to join a room.
 */
OdinReturnCode odin_token_generator_create_token(struct OdinTokenGenerator *generator,
                                                 const char *room_id,
                                                 const char *user_id,
                                                 char *out_token,
                                                 size_t out_token_len);

/**
 * Generates a signed JWT such as `odin_token_generator_create_token` and allows passing a custom
 * set of `OdinTokenOptions` for advanced use-cases.
 */
OdinReturnCode odin_token_generator_create_token_ex(struct OdinTokenGenerator *generator,
                                                    const char *room_id,
                                                    const char *user_id,
                                                    const struct OdinTokenOptions *options,
                                                    char *out_token,
                                                    size_t out_token_len);

#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus