Skip to main content

Minimal Command Line Client

We provide a comprehensive C++ example that demonstrates how to build a fully functional Voice Chat client using the ODIN Core SDK. This example handles:

  • Connection Pooling and Room Management
  • Microphone Capture and Audio Playback (via miniaudio)
  • RPC Event Handling (Room/Peer updates)
  • Media Signaling (Start/Stop Media)

The source code is located in the test directory of the ODIN SDK repository.

Building the Sample

Prerequisites

  • CMake (3.10+)
  • C++17 compatible compiler
  • Git (with LFS support)

Steps

  1. Clone the repository with LFS support:

    git clone https://github.com/4Players/odin-sdk.git
    cd odin-sdk
    git lfs install
    git lfs pull
  2. Navigate to the test directory and build:

    cd test
    mkdir build && cd build
    cmake ..
    cmake --build .

Code Overview

The sample is structured around a State class that manages the ODIN room, audio devices, and active media streams.

1. Initialization & Connection Pool

The application starts by initializing the ODIN SDK and creating a OdinConnectionPool. This pool manages the underlying network socket and dispatches events.

OdinConnectionPoolSettings settings = {
.on_datagram = on_datagram, // Handle incoming audio packets
.on_rpc = on_rpc, // Handle signaling/events
.user_data = &state // Pass our state object to callbacks
};
odin_connection_pool_create(settings, &pool);

2. Handling RPC Events

The on_rpc callback is the central hub for all signaling. It deserializes incoming MessagePack data into JSON and dispatches it to the appropriate handler in the State class.

void on_rpc(uint64_t room_ref, const uint8_t *bytes, uint32_t bytes_length, void *user_data) {
auto state = reinterpret_cast<State *>(user_data);
// Deserialize MessagePack to JSON
nlohmann::json rpc = nlohmann::json::from_msgpack(bytes, bytes + bytes_length);

// Convert to typed Event variant and visit
auto event = rpc.get<api::server::Event>();
std::visit(api::visitor{
[state](const api::server::RoomUpdated &u) { state->handle_room_updates(u); },
[state](const api::server::PeerUpdated &u) { state->handle_peer_updates(u); },
// ... handle other events
}, event);
}

3. Media Signaling

When a peer joins or starts talking, the client receives PeerUpdateMediaStarted events. The sample responds by creating a decoder for that stream. Conversely, when the local user joins, the sample sends a StartMedia RPC to announce its own audio stream.

// Local User Joining: Start Media
this->send_rpc(api::client::StartMedia{media_id, {{"kind", "audio"}}});

// Remote Peer Started Media: Configure Decoder
void State::on_media_started(const api::Media media, const uint64_t peer_id) {
this->configure_decoder(peer_id, media.id);
}

4. Audio Processing Loop

The miniaudio callback handles the actual audio data flow.

  • Capture: Reads from microphone -> pushes to OdinEncoder -> specific media ID.
  • Playback: Reads from OdinDecoder -> mixes into output buffer.
void handle_audio_data(ma_device *device, void *output, const void *input, ma_uint32 frame_count) {
// ... Push input to encoder ...
odin_encoder_push(encoder, input_buffer, input_count);

// ... Pop from encoder and send to network ...
odin_encoder_pop(encoder, &media_id, ...);
odin_room_send_datagram(room, datagram, len);
}

Running the Client

Run the client with your Room ID and Gateway info:

./odin_minimal -r <room_id> -s <server_url>

Use --help to see all available options including input/output device selection and APM configuration.